在服务中添加简单业务

举报
码乐 发表于 2024/02/17 09:39:57 2024/02/17
【摘要】 2.3 承载业务直入主题。无论那个类型的业务都可以加入,为了方便理解,现在假设我们现在有一些濒临倒闭的动物园,我们设计一个系统方便推广,帮助他们提升收入。如果这些动物园有三种类型动物: 亚洲猫头鹰 asia-owl,非洲老虎 tiger,非洲大象 elephant他们的信息分别如下: { {"name": "asia_owl_01", "title": "asia_owl_01", "i...

2.3 承载业务

直入主题。

无论那个类型的业务都可以加入,为了方便理解,现在假设我们现在有一些濒临倒闭的动物园,我们设计一个系统方便推广,帮助他们提升收入。

如果这些动物园有三种类型动物: 亚洲猫头鹰 asia-owl,非洲老虎 tiger,非洲大象 elephant

他们的信息分别如下:

  {
	{"name": "asia_owl_01", "title": "asia_owl_01", "id": "en00029", "founded": 1672729697872, "price": 10, "times": "180min", "url": "zoo.asdaliyun.com/oss/owl"},
            
	{"name": "africa_tiger_001", "title": "africa_tiger_001", "id": "en00028", "founded": 1672729697872, "price": 15, "times": "170min", "url": "zoo.asdaliyun.com/oss/tiger"},
            
	{"name": "africa_elephants_001", "title": "africa_elephants_001", "id": "en00030", "founded": 1672729697872, "price": 20, "times": "170min", "url": "zoo.asdaliyun.com/oss/ele"},
}

我们如何存数据呢。

2.3.1 数据的结构设计

我们项目不大,使用缓存保持数据是可行的。

使用缓存时,不推荐使用太大的key,我们将其分别拆分,为总数字段,信息字段,我们将它与预知的信息对应。

   type ZoosInfo struct {
		ID           string `db:"id" json:"id" form:"id"`
		Title        string `db:"title" json:"title" form:"title"`
		Url          string `db:"url" json:"url" form:"url"`
		Introduction string `db:"introduction"  json:"introduction"   form:"introduction"`
		CreatedAt    int64  `db:"created_at" json:"created_at" form:"created_at"`
		Founded      int64  `db:"founded" json:"founded"  form:"founded"`
		Price        int64  `db:"price" json:"price"  form:"price"`
		Visits       int64  `db:"visits json:"visits" form:"visits"`
	}

在缓存中,我们使用有序集 zset:{zoos:id} 存放主键信息,使用{zoos:id} 计算总预定数。然后创建管道,一次将一个对象信息存入 并返回保存结果:

    func PipelinedCreate(priKeys, zsetKeys string, price int64, ex map[string]string, dest []string, options ...Option) ([]any, error)

     ...
	 rdb := c.GetConn() 
	 pipe := rdb.Pipeline()
     ...

     setrst = pipe.Set(Ctx, k, val, 0)
	 setrsts = append(setrsts, setrst)
	 ...

接口实现

正如上一节所讲,我们在api也就是 control实现接口的参数接收和校验,在views层实现数据的处理封装,并在此处使用model层的预定。
其过程涉及数据的拆分,和组合,不过有现成的连接库可以使用,比如go-redis的v8,当然也可以自己实现。

在返回前,我们对其进行处理,如果失败则返回 没有内容的错误

 		helpers.JSONs(ctx, http.StatusNoContent, gin.H{"message": "failed", "data": err})

如果成功则返回 200

   	helpers.JSONs(ctx, http.StatusOK, datas)

完成后我们的接口实际返回信息如下,这将在下一节的示例程序中实际看到。

查询详细信息 search/en00029

   {"code":200,
   "data":
      {"Visits":0,
      "created_at":1672818977504,
      "founded":1672729697872,
      "id":"en00029",
      "introduction":"new one to visitor.",
      "price":10,
      "title":"asia_owl_01",
      "url":"zoo.asdaliyun.com/oss/owl"},
    "message":"Success"}

预定票据 booked 的动作

    1672818978.270390 [0 192.168.30.1:11457] "exists" "{zoo:id}"
	1672818978.271737 [0 192.168.30.1:11457] "incr" "{zoo:id}"
	1672818978.274423 [0 192.168.30.1:11457] "zadd" "zset:{zoo:id}" "nx" "ch" "1" "{zoo:id}:1"
	1672818978.275343 [0 192.168.30.1:11457] "set" "{zoo:id}:1:name" "asia_owl_01"
	1672818978.275360 [0 192.168.30.1:11457] "set" "{zoo:id}:1:spec" ""
	1672818978.275366 [0 192.168.30.1:11457] "set" "{zoo:id}:1:command" ""

预定信息返回

    {"code":200,
    "data":
        {  
        "command":"1672818977509",
        "id":1,
        "name":"asia_owl_01",
        "pri_key":"id",
        "spec":"zoo.asdaliyun.com/oss/owl",
        "table_name":"zoo"},
    "message":"Success"}

同时我们使用订阅系统,以保证实时接收用户订阅信息。 此次共返回了3条消息

第一条返回消息类型消息类型,长度为7,

第二条长度为17,频道字符长度,

第三条长度为28,具体消息信息

   Message{Id:"M:1672912846090", Text:"*3"} //此次共返回了3条消息
   Message{Id:"M:1672912846091", Text:"$7"}  //第一条长度为7
   Message{Id:"M:1672912846092", Text:"message"}  //消息类型
   Message{Id:"M:1672912846092", Text:"$17"}    //第二条长度为17,频道字符长度
   Message{Id:"M:1672912846093", Text:"boards:zoo:visits"}   //频道名称
   Message{Id:"M:1672912846093", Text:"$28"}  //第三条长度为28,具体消息信息
   Message{Id:"M:1672912846093", Text:"1672912845088:33:385:en00028"}

每次有用户新增订阅时,我们的后台管理程序就可以实时地观测到新增的订阅信息,以及现在的总售卖数额:33:385,表示售出:33票,总价 $385 元。

   	   Message{Id:"M:1672912846093", Text:"1672912845088:33:385:en00028"}

2.3.2 优雅地服务

当监控服务于主服务在一起时,我们可以通过分别定义,在一个管理器中启动,并且实现优雅的停止,避免误操作关闭。

子服务:

	   /*
	子服务入口,集成启动函数.
	*/
	func SubSericeRun() error {

		ser := Sers.Start()

		infos := make(chan service.Cmds)
		go Sers.TcpServer(infos)
        ...
		return fmt.Errorf("Sers Running is false: %v", Sers.Running)

	}

主服务入口:

创建侦听来自操作系统的中断信号的上下文,阻止第一次 的误操作退出

            func MainService() error {
        
        ...

监听一次中断信号

		ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
		defer stop()
		go func() {
			fmt.Printf("Server start Port at:%#v \n", configs.ServicePorts)
			logger.Fatalf(Server.ListenAndServe().Error())

		}()

		<-ctx.Done()
		stop()

再次检测到信号将退出

		fmt.Println("Press Ctrl+C again to force,shutting down gracefully")

		quit := make(chan os.Signal)
		signal.Notify(quit, os.Interrupt)
		<-quit

	}

然后在 errorGroup中监听管理

    Gep  = errgroup.Group{}
    idelClosed := make(chan struct{})

启动服务

    Gep.Go(func() error {
	   return MainService()
	})

	Gep.Go(func() error {
		return SubSericeRun()
	})

关闭服务

	<-idelClosed

	if err := Gep.Wait(); err != nil {
		logger.Fatalf("Listener Shutdown:", err)
	}

2.3.3 程序C/S结构

综合服务和客户端,我们的最终结构是这样的。 下一节,我们完成客户端程序和监控程序,并执行演示。

下一节我们模拟成群的客户去搜索和预定。

3 小结

我们现在只使用基本的接口服务,并没有任何队列实现流量管控,或授权或认证等。

基于缓存数据的服务让我们可以较快地响应客户。

下一节我们假设有客户群预定,并实时的显示用户预定信息。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。