要实现功能:
- 企微机器人提醒
- 机器人数量不一定
- 机器人提醒企微有限制 一分钟不能超过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实例的情况;