利用gin-frame做一个钉钉智能提醒
之前又出过gin-frame的文章,今天就利用gin-frame做一个钉钉智能提醒功能吧。
需求
工作生活中,琐事太多,就容易忘,希望有这么个智能提醒功能,能利用钉钉准时推送提醒我!
比如:
每周五18:00提醒我发周报
每周三14:00提醒我发车买奶茶
10分钟提醒我去会议室开会
2020-08-20提醒我纪念日到了
…诸如此类
钉钉接入
钉钉有机器人功能,之前已有应用到项目告警提醒等功能中,效果不错,另外Outgoing机制也已经开放使用,我们就可以利用它进行对话了。
按照文档在群里新建机器人即可。我开启的是webhook自定义机器人,outgoing提送地址就是项目接收信息地址,比如:http://cron.13sai.com/dingdingPost。
解析内容
钉钉文档的outgoing说明不全,或者是藏在哪里我没找到,可以使用@机器人接收信息打印看一下。
{
"conversationId":"xxx",
"atUsers":[
{
"dingtalkId":"xxx"
}],
"chatbotUserId":"xxx",
"msgId":"xxx",
"senderNick":"sai0556",
"isAdmin":false,
"sessionWebhookExpiredTime":1594978626787,
"createAt":1594973226742,
"conversationType":"2",
"senderId":"xxx",
"conversationTitle":"智能备忘录",
"isInAtList":true,
"sessionWebhook":"xxx",
"text":{
"content":" hello gin-frame"
},
"msgtype":"text"
}
//关注senderId发送人id,text发送内容,senderNick发送人昵称即可
定义一个struct,接收消息
type DingDingMsgContent struct {
SenderNick string `json:"senderNick"`
SenderId string `json:"senderId"`
Text struct {
Content string `json:"content"`
} `json:"text"`
}
func DingDing(c *gin.Context) {
data, _ := ioutil.ReadAll(c.Request.Body)
form := DingDingMsgContent{}
err := json.Unmarshal([]byte(data), &form)
// err := c.ShouldBindJSON(&form)
if err != nil {
fmt.Println(err)
return
}
....
}
发送钉钉消息
func SendDD(msg string) {
// 打印出来看看是个啥
fmt.Println(msg)
tips := make(map[string]interface{})
content := make(map[string]interface{})
tips["msgtype"] = "text"
// @ 是用来提醒群里对应的人
arr := strings.Split(msg, "@")
// [提醒]是机器人关键字,个人建议设置机器人限制ip或使用token,比较靠谱
content["content"] = fmt.Sprintf("%s[提醒]", arr[0])
tips["text"] = content
if len(arr) > 1 {
mobile := make([]string, 0)
at := make(map[string]interface{})
mobile = append(mobile, arr[1])
at["atMobiles"] = mobile
tips["at"] = at
}
bytesData, err := json.Marshal(tips)
if err != nil {
fmt.Println(err.Error() )
return
}
reader := bytes.NewReader(bytesData)
url := viper.GetString("dingding_url")
request, err := http.NewRequest("POST", url, reader)
if err != nil {
return
}
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
client := http.Client{}
_, err = client.Do(request)
if err != nil {
fmt.Println(err.Error())
return
}
// 返回可自行处理,可重试,偷个懒不处理了
}
关键字
// util/common.go
// 就列了一些常见的,可自行扩展
func UpdateKeywords() {
redis := model.RedisClient.Pipeline()
key := KeyWords
redis.HSet(model.Ctx, key, "分钟后", "1|60")
redis.HSet(model.Ctx, key, "时后", "1|3600")
redis.HSet(model.Ctx, key, "天后", "1|86400")
redis.HSet(model.Ctx, key, "每天", "-1|1")
redis.HSet(model.Ctx, key, "每周一", "2|0")
redis.HSet(model.Ctx, key, "每周二", "2|1")
redis.HSet(model.Ctx, key, "每周三", "2|2")
redis.HSet(model.Ctx, key, "每周四", "2|3")
redis.HSet(model.Ctx, key, "每周五", "2|4")
redis.HSet(model.Ctx, key, "每周六", "2|5")
redis.HSet(model.Ctx, key, "每周日", "2|6")
redis.HSet(model.Ctx, key, "周一", "3|0")
redis.HSet(model.Ctx, key, "周二", "3|1")
redis.HSet(model.Ctx, key, "周三", "3|2")
redis.HSet(model.Ctx, key, "周四", "3|3")
redis.HSet(model.Ctx, key, "周五", "3|4")
redis.HSet(model.Ctx, key, "周六", "3|5")
redis.HSet(model.Ctx, key, "周日", "3|6")
redis.HSet(model.Ctx, key, "下周一", "3|7")
redis.HSet(model.Ctx, key, "下周二", "3|8")
redis.HSet(model.Ctx, key, "下周三", "3|9")
redis.HSet(model.Ctx, key, "下周四", "3|10")
redis.HSet(model.Ctx, key, "下周五", "3|11")
redis.HSet(model.Ctx, key, "下周六", "3|12")
redis.HSet(model.Ctx, key, "下周日", "3|13")
redis.HSet(model.Ctx, key, "下星期一", "3|7")
redis.HSet(model.Ctx, key, "下星期二", "3|8")
redis.HSet(model.Ctx, key, "下星期三", "3|9")
redis.HSet(model.Ctx, key, "下星期四", "3|10")
redis.HSet(model.Ctx, key, "下星期五", "3|11")
redis.HSet(model.Ctx, key, "下星期六", "3|12")
redis.HSet(model.Ctx, key, "下星期日", "3|13")
redis.HSet(model.Ctx, key, "今天", "4|0")
redis.HSet(model.Ctx, key, "明天", "4|1")
redis.HSet(model.Ctx, key, "后天", "4|2")
redis.HSet(model.Ctx, key, "取消", "0|0")
redis.Exec(model.Ctx)
}
解析内容
func parseContent(form DingDingMsgContent) (err error) {
str := form.Text.Content
redis := model.RedisClient
// 要先绑定哟,不然无法@到对应的人
index := strings.Index(str, "手机")
if index > -1 {
reg := regexp.MustCompile("1[0-9]{10}")
res := reg.FindAllString(str, 1)
if len(res) < 1 || res[0] == "" {
err = errors.New("手机格式不正确")
return
}
redis.HSet(model.Ctx, util.KeyDingDingID, form.SenderId, res[0])
err = errors.New("绑定成功")
return
}
hExist := redis.HExists(model.Ctx, util.KeyDingDingID, form.SenderId)
if !hExist.Val() {
err = errors.New("绑定手机号才能精确提醒哦,发送--手机 13456567878--@我即可")
return
}
index = strings.Index(util.StrSub(str, 0, 4), "取消")
if index > -1 {
reg := regexp.MustCompile("[a-z0-9]{32}")
res := reg.FindAllString(str, 1)
if len(res) < 1 {
err = errors.New("任务id不正确")
return
}
if er := util.CancelQueue(res[0], form.SenderId); er != nil {
err = er
return
}
err = errors.New("取消成功")
return
}
return tips(form)
}
// 提醒内容
func tips(form DingDingMsgContent) (err error) {
rd := model.RedisClient
str := form.Text.Content
mobile := rd.HGet(model.Ctx, util.KeyDingDingID, form.SenderId).Val()
key := util.KeyWords
list, _ := rd.HGetAll(model.Ctx, key).Result()
now := time.Now().Unix()
tipsType := 1
k := ""
v := ""
fmt.Println("str", str)
index := 0
for key, value := range list {
index = util.UnicodeIndex(str, key)
if index > -1 && util.StrLen(key) > util.StrLen(k) {
fmt.Println("index", index, str, key, value)
k = key
v = value
}
}
msg := ""
var score int64
if k != "" {
kLen := util.StrLen(k)
msg = util.StrSub(str, index+kLen)
val := strings.Split(v, "|")
unit := val[1]
units,_ := strconv.Atoi(unit)
switch val[0] {
// 多少时间后
case "1":
reg := regexp.MustCompile("[0-9]{1,2}")
res := reg.FindAllString(str, 1)
minute, _ := strconv.Atoi(res[0])
score = now + int64(units*minute)
// 每周
case "2":
reg := regexp.MustCompile("[0-9]{1,2}")
res := reg.FindAllString(util.StrSub(msg, 0, 7), -1)
hour := 9
minute := 0
if len(res) > 0 {
hour, _ = strconv.Atoi(res[0])
}
if len(res) > 1 {
minute, _ = strconv.Atoi(res[1])
}
now = util.GetWeekTS(int64(units))
score = now + int64(60*minute + 3600*hour)
tipsType = 2
// 下周
case "3":
reg := regexp.MustCompile("[0-9]{1,2}")
res := reg.FindAllString(util.StrSub(msg, 0, 7), -1)
hour := 9
minute := 0
if len(res) > 0 {
hour, _ = strconv.Atoi(res[0])
}
if len(res) > 1 {
minute, _ = strconv.Atoi(res[1])
}
now = util.TodayTS()
score = now + int64(60*minute + 3600*hour + units*86400)
case "4":
reg := regexp.MustCompile("[0-9]{1,2}")
res := reg.FindAllString(util.StrSub(msg, 0, 7), -1)
hour := 9
minute := 0
if len(res) > 0 {
hour, _ = strconv.Atoi(res[0])
}
if len(res) > 1 {
minute, _ = strconv.Atoi(res[1])
}
now = util.GetWeekTS(int64(units))
score = now + int64(60*minute + 3600*hour)
case "-1":
reg := regexp.MustCompile("[0-9]{1,10}")
res := reg.FindAllString(util.StrSub(msg, 0, 7), -1)
fmt.Println("res", res)
hour := 9
minute := 0
if len(res) > 0 {
hour, _ = strconv.Atoi(res[0])
}
if len(res) > 1 {
minute, _ = strconv.Atoi(res[1])
}
now = util.TodayTS() + 86400
score = now + int64(60*minute + 3600*hour)
fmt.Println(now, score, minute, hour)
tipsType = 3
default:
}
} else {
reg := regexp.MustCompile("(([0-9]{4})[-|/|年])?([0-9]{1,2})[-|/|月]([0-9]{1,2})日?")
pi := reg.FindAllStringSubmatch(str, -1)
if (len(pi) > 0 ) {
date := pi[0]
if date[2] == "" {
date[2] = "2020"
}
location, _ := time.LoadLocation("Asia/Shanghai")
tm2, _ := time.ParseInLocation("2006/01/02", fmt.Sprintf("%s/%s/%s", date[2], date[3], date[4]), location)
score = util.GetZeroTime(tm2).Unix()
msg = reg.ReplaceAllString(str, "")
fmt.Println(msg)
} else {
msg = str
score = util.TodayTS()
}
reg = regexp.MustCompile("[0-9]{1,10}")
res := reg.FindAllString(util.StrSub(msg, 0, 7), -1)
fmt.Println("res", res)
hour := 9
minute := 0
if len(res) >= 1 {
hour, _ = strconv.Atoi(res[0])
fmt.Println("hour", hour, minute)
}
if len(res) > 1 {
minute, _ = strconv.Atoi(res[1])
}
score += int64(60*minute + 3600*hour)
}
if msg == "" {
err = errors.New("你说啥")
return
}
index = util.UnicodeIndex(msg, "提醒我")
if index < 0 {
err = errors.New("大哥,要我提醒你干啥呢?请发送--下周一13点提醒我写作业")
return
}
msg = util.StrSub(msg, index+3)
fmt.Println(msg, mobile)
msg = util.StrCombine(msg, "@", mobile)
fmt.Println(score, msg, tipsType, err)
if err != nil {
util.SendDD(err.Error())
return
}
member := util.StrCombine(strconv.Itoa(tipsType), msg)
rd.ZAdd(model.Ctx, util.KeyCrontab, &redis.Z{
Score: float64(score),
Member: member,
})
uniqueKey := util.Md5(member)
rd.HSet(model.Ctx, util.StrCombine(util.KeyUserCron, form.SenderId), uniqueKey, member)
util.SendDD(fmt.Sprintf("设置成功(取消请回复:取消任务%s)--%s提醒您%s", uniqueKey, time.Unix(score, 0).Format("2006/01/02 15:04:05"), msg))
return
}
定时发送
上面的代码能看出来,使用的是redis的有序集合,我们每分钟去取过期的集合内容就ok了。
func Cron() {
c := cron.New()
spec := "*/10 * * * * ?"
c.AddJob(spec, Queue{})
c.Start()
}
type Queue struct {
}
func (q Queue) Run() {
now := time.Now().Unix()
rd := model.RedisClient
op := &redis.ZRangeBy{
Min: "0",
Max: strconv.FormatInt(now, 10),
}
ret, err := rd.ZRangeByScoreWithScores(model.Ctx, KeyCrontab, op).Result()
if err != nil {
fmt.Printf("zrangebyscore failed, err:%v\n", err)
return
}
for _, z := range ret {
fmt.Println(z.Member.(string), z.Score)
QueueDo(z.Member.(string), z.Score)
}
}
func QueueDo(msg string, score float64) {
msgType := msg[0:1]
SendDD(msg[1:])
rd := model.RedisClient
rd.ZRem(model.Ctx, KeyCrontab, msg)
switch msgType {
case "2":
rd.ZAdd(model.Ctx, KeyCrontab, &redis.Z{
Score: score + 7*86400,
Member: msg,
})
case "3":
rd.ZAdd(model.Ctx, KeyCrontab, &redis.Z{
Score: score + 86400,
Member: msg,
})
default:
rd.ZRem(model.Ctx, KeyCrontab, msg)
}
}
func CancelQueue(uniqueKey string, SenderId string) (err error) {
rd := model.RedisClient
member := rd.HGet(model.Ctx, StrCombine(KeyUserCron, SenderId), uniqueKey).Val()
if member == "" {
fmt.Println(StrCombine(KeyUserCron, SenderId), uniqueKey)
err = errors.New("没有此任务")
return
}
fmt.Println(member, "member")
rd.ZRem(model.Ctx, KeyCrontab, member)
rd.HDel(model.Ctx, StrCombine(KeyUserCron, SenderId), uniqueKey)
err = errors.New("取消成功")
return
}
启动任务:
util.UpdateKeywords()
util.Cron()
代码中有用到很多函数,不理解的可下载代码查看。
这只是个基础的提醒功能,有bug,也有需要改进的点,欢迎讨论交流。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: