利用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,也有需要改进的点,欢迎讨论交流。

go
本作品采用《CC 协议》,转载必须注明作者和本文链接
分享开发知识,欢迎交流。公众号:开源到
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!