golang 实现不定数量企微群机器人消息通知 顺序必须一致 并不超过群机器人消息每分钟提醒上限20条

要实现功能:

  1. 企微机器人提醒
  2. 机器人数量不一定
  3. 机器人提醒企微有限制 一分钟不能超过20条

准备好发送markdown消息的方法

type RobotRsp struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

type RobotMsg struct {
	Msgtype string `json:"msgtype"`
	Text RobotContent `json:"text"`
	Markdown RobotContent `json:"markdown"`
}

type RobotContent struct {
	Content string `json:"content"`
}

func RobotMarkdownMsg(content string, url string)  {
	var rspInfo = rsp.RobotRsp{}
	var robotMsg = req.RobotMsg{
		Msgtype: "markdown",
		Markdown: req.RobotContent{
			Content:content,
		},
	}
	_ = ApiPost(url, &rspInfo, robotMsg, time.Second * 3)
	if rspInfo.ErrCode != 0 {
		fmt.Printf("RobotMsg markdown信息发送失败:%#v \n", rspInfo)
	}
}

将发送消息的Service加好

type RobotMsgService struct {
	ImpService
	UrlsMap       map[string]*RobotTimer
	mtx           sync.Mutex
	MaxContentLen int
}

type RobotTimer struct {
	mtx       sync.Mutex // 需要将消息发送条数增加加锁
	Timer     time.Time  // 一分钟内发送第一条的最早时间
	SendCount int        // 一分钟内 发送条数
}

// redisKey的头部 这样进行队列区分
const RedisRobotSendListPre = "{2022}robot:"

func (r *RobotMsgService) init() {
	r.logger = r.GetLogger()
	r.SetLogger(providers.Logger)
	r.MaxContentLen = 20
	r.UrlsMap = make(map[string]*RobotTimer)
}

// 直接new 是无法进行结构体内部属性实例化init也不会执行
func NewRobotMsgService() *RobotMsgService {
	r := new(RobotMsgService)
	r.init()
	ticker := time.NewTicker(time.Second * 20)
	// 这里进行的不仅仅发送 先看到达20条上限制没有,如果到了那么我们就要进行处理了
	go func() {
		for {
			select {
			case <-ticker.C:
				r.RefreshTicket()
			}
		}
	}()
	return r
}

/**
 * @note: 将消息发送到对应的通道内
**/
func (r *RobotMsgService) AddRobotMsg(msg string, url string) {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	robotTimer, ok := r.UrlsMap[url]
	if !ok || robotTimer == nil {
		r.UrlsMap[url] = new(RobotTimer)
		// 初始化一个时间
		r.UrlsMap[url].Timer = time.Now()
	}
	// 将消息放到队列中
	providers.Redis.RPush(RedisRobotSendListPre+url, msg)
	// 发送消息方法执行
	r.SendRobotMsg(url)
}

/**
 * @note: 触发 发送多个机器人url
**/
func (r *RobotMsgService) AddRobotMsgUrls(msg string, urls []string) {
	for _, sendUrl := range urls {
		r.AddRobotMsg(msg, sendUrl)
	}
}

/**
 * @note: 发送markdown消息
**/
func (r *RobotMsgService) SendRobotMsg(url string) {
	robotTime, ok := r.UrlsMap[url]
	if !ok {
		fmt.Printf("想要发送消息,但是发现UrlsMap中没有值 \n")
		return
	}
	// 单个发送消息也需要加锁
	robotTime.mtx.Lock()
	defer robotTime.mtx.Unlock()
	// 时间允许范围内 可以发送的数据条数
	canSentCount := 0
	if robotTime.SendCount < r.MaxContentLen {
		canSentCount = r.MaxContentLen - robotTime.SendCount
	} else {
		// 如果发送条数 >= 20条那么我们不需要执行后面的代码 因为不能发送消息
		return
	}
	redisListKey := RedisRobotSendListPre + url
	// 队列中数据长度
	// listLen := providers.Redis.LLen(redisListKey).Val()
	// 进行redis缓存中数据消费
	for i := 0; i < canSentCount; i++ {
		sendMsg := providers.Redis.LPop(redisListKey).Val()
		// 拉取列表中数据  如果为空直接跳过
		if sendMsg == "" {
			continue
		}
		common.RobotMarkdownMsg(sendMsg, url)
		// 上面表示发送了一条 那么我们要增加一
		robotTime.SendCount ++
	}
	// 更新最近一次发送时间 如果最近发了20条了 那么最后一条 一分钟后才能重新开始发
	robotTime.Timer = time.Now()
}

/**
 * @note: 刷新时钟 对发送条数进行处理
**/
func (r *RobotMsgService) RefreshTicket() {
	// fmt.Printf("时间:%s, 刷新时钟Ticket \n", time.Now().Format("2006-01-02 15:04:05"))
	if len(r.UrlsMap) == 0 {
		return
	}
	fmt.Printf("map长度:%d, map信息:%#v \n", len(r.UrlsMap), r.UrlsMap)
	// 查看每个struct中数据是否 发送到了20条 如果没有超过可以继续发 当然是必须有数据的前提下
	for url, robotTime := range r.UrlsMap {
		// 对计数器 > 0的数据查看时钟 进行 重置发送条数
		if robotTime.SendCount > 0 && time.Now().Sub(robotTime.Timer) > time.Minute * 1 {
			// 对计数器进行加锁
			robotTime.mtx.Lock()
			robotTime.SendCount = 0
			robotTime.mtx.Unlock()
			r.SendRobotMsg(url)
		}
	}
}

我们初始化的时候要给 *RobotMsgService类型 加一个时钟,这样没过20秒检查一下RobotTimer中有没有超过一分钟,需要发送消息的数据;

我们如果收到消息之后,就立马将消息放到以url作为key的redis hashMap中 这样需要发送消息的时候只要找到有这个redis队列缓存就行,你重启服务也不会因为服务中断导致队列中数据消失;

如果触发了发送立马去检查UrlsMap[url] 查看这个struct中已经发送的数量如果大于等于20 我们直接结束,如果小于20条,我们发送20-x = 剩余条数

如果还有好多没有发完,我们的ticket会20秒定时去检查最近发送时间有没有超过一分钟,如果超过 我们就开始发送数据,并发完之后写入发送条数和最新的发送时间(加锁);

// 只有queue才能触发这个变量 (防止循环引用)
var NewRobotMsgService = service.NewRobotMsgService()

我们在一个单独的文件中将Service初始化,之后直接使用变量就行,这样就不会重复触发时钟,和多个NewRobotMsgService实例的情况;

你可能感兴趣的:(go,map,机器人,企业微信,golang,redis)