利用gin-frame做一個釘釘智慧提醒

sai0556發表於2020-08-02

之前又出過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 協議》,轉載必須註明作者和本文連結

收藏前不妨點個贊試試!!!
分享開發知識,歡迎交流。qq交流群:965666112,公眾號:愛好歷史的程式設計師。
點選直達個人部落格

相關文章