• 利用go制作微信机器人


    这些天在学习Go,也写了几篇关于阅读Gin后端项目代码的博客。但编程这种,一定要实际上手练习,要不然都是纸上谈兵。于是就想上手自己实际写一些代码来练练手。思来想去,不知道能写些什么来练手。后来突然想到,之前写过用Python做微信聊天机器人(博客传送门),当时代码没有放到git上,后来重置了服务器导致代码全部没了。现在正好苦于不知道做什么项目练手,可以用Go也实现一套微信聊天机器人。

    说干就干,照着之前自己写的博客,看了下当时Python的代码。转而用Go优化了下并实现。

    1. 回顾流程
      根据之前Python写的自动发消息的机器人可知,要想发消息就需要三个参数:company_id、secret、angent_id。 对于这三个参数如何获取,可参考文章开头的传送门。整个发送消息过程就是 首先通过company_id和secret来调用接口获取token,再通过token和angent_id来给对应接口发送post请求,就可以把post请求体中的信息发送到微信上。

    2. 项目基础配置
      由于目前对Go的项目布局学习的还不是特别熟练,而且对于项目基础部分如果从头开始做的话,需要耗费大量时间。因此我使用了基于开源gin项目进行二次开发的方法,实现这个机器人。

    前几天在学习Gin时,发现了一位老哥封装了个Gin脚手架,可以达到开箱即用目的。项目地址: github传送门。 里边把读取配置文件,编写路由,连接数据库等多个操作均进行了实现。因此可以基于这个项目来进行二次开发,做微信机器人。

    在把项目clone下来后,可以先看下整个项目的布局,主要的业务核心代码都放在了internal 下面。如果我们要实现一个主动给微信发消息的功能,那么多说了就是写一个发送消息的方法,让后端调用这个方法即可。

    要想基于此项目来开发微信机器人,首先就要将三个参数配置上。项目中,对于各种参数均在config.yaml中配置,因为可以在这个配置文件中增加这三个参数的配置:

    然后在代码的config/autoload目录下新增一个weCaht.go 文件,接收配置文件中的配置。

    package autoload

    type WeChatConfig struct {
    AgentId string ini:"wechat" yaml:"agent_id"
    Secret string ini:"wechat" yaml:"secret"
    CompanyId string ini:"wechat" yaml:"company_id"
    }

    var WeChat = WeChatConfig{}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    并且,将此配置加入到项目的配置集合中。在config/config.go中添加如下代码:

    这样操作,就可以通过代码来读取配置文件了。在其他包中,可以通过如下方式来访问对应的值

    config.Config.WeChat.CompanyId //yaml中的company_id字段
    1
    2. Redis封装
    因为要给微信发送消息,首先要获取到token,而官方介绍此token的有效时长为2小时。在之前Python的项目中,是直接将token写到了文件中,通过文件来读取。在此项目中,我想直接使用redis来存储。因为使用redis来存储的话,可以设置key值时长,过了这个时长就自动清除,这样就方便了许多。

    而我们基于这个gin-layout项目中,已经对redis做了一层封装,具体代码可查看data/redis.go,主要是通过对外暴露一个Rdb的结构体,来操作redis

    而目前我们这边使用redis,只会用到对应的set和get方法。因此我对这个项目中的redis又做了一层封装。只对外暴露set,get,del方法。
    首先将Rdb变量名改为小写,这样就代表不对外暴露,然后在此文件中添加如下代码

    func SetRedis(key string, value string, t int64) bool {
    expire := time.Duration(t) * time.Second
    if err := rdb.Set(ctx, key, value, expire).Err(); err != nil {
    return false
    }
    return true
    }

    func GetRedis(key string) string {
    result, err := rdb.Get(ctx, key).Result()
    if err != nil {
    return “”
    }
    return result
    }

    func DelRedis(key string) bool {
    _, err := rdb.Del(ctx, key).Result()
    if err != nil {
    return false
    }
    return true
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    这样,后续使用redis时候,只需要调用data.SetRedis(xxx) 即可。
    然后就是修改配置文件,启用redis,这里根据实际的redis配置来写即可。

    3.消息体封装
    在最终给微信服务器发送post请求时,对应的请求体格式如下:

    {
    “touser”: “@all”,
    “msgtype”: “text”,
    “agentid”: “xxxxx”,
    “text”: {“content”: “xxxx”}
    }
    1
    2
    3
    4
    5
    6
    因此,接下来可以对这个结构体做一个封装。在model包下,新建一个send_msg.go文件

    package model

    type wcSendcontent struct {
    Content string json:"content"
    }

    type WcSendMsg struct {
    ToUser string json:"touser"
    MsgType string json:"msgtype"
    AgentId string json:"agentid"
    Text wcSendcontent json:"text"
    }

    func (t *WcSendMsg) SetMessage(message string) {
    t.Text.Content = message
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    这里针对message信息,专门对外暴露了一个方法来进行设置。

    1. 核心代码
      在设置好redis,消息体封装后,就可以编写核心的代码了。主要就是通过发送http请求,获取token,再通过token发送post请求来发送消息。我们可以在service包下新建一个weChat.go的文件,里边新建一个SendWeChat方法来进行消息发送操作。

    package service

    import (
    “bytes”
    “encoding/json”
    “errors”
    “fmt”

    c "github.com/wannanbigpig/gin-layout/config"
    "github.com/wannanbigpig/gin-layout/data"
    "github.com/wannanbigpig/gin-layout/internal/model"
    log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
    "github.com/wannanbigpig/gin-layout/pkg/utils"
    "go.uber.org/zap"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    )

    /**

    • @description: 给企微发送消息
    • @param {string} message 消息内容
    • @param {string} msgType 消息类型
    • @return {*}
      */
      func SendWeChat(message string, msgType string) error {
      redis_key := “access_token”
      // 尝试从redis中读取token
      accessToken := data.GetRedis(redis_key)
      http := &utils.HttpRequest{}
      // 若redis中的token已过期,则重新请求api获取token
      if accessToken == “” {
      log.Logger.Info(“access token is null, will recall”)
      getTokenUrl := fmt.Sprintf(“https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s”,
      c.Config.WeChat.CompanyId, c.Config.WeChat.Secret)
      log.Logger.Info(“token_url”, zap.String(“url”, getTokenUrl))
      http.Request(“GET”, getTokenUrl, nil)
      ret := make(map[string]interface{})
      if err := http.ParseJson(&ret); err != nil {
      return err
      }
      marshal, _ := json.Marshal(ret)
      log.Logger.Info(string(marshal))
      accessToken = fmt.Sprintf("%v", ret[“access_token”])
      // 写入redis 有效期2小时
      data.SetRedis(redis_key, accessToken, 7200)
      }
      msg := &model.WcSendMsg{
      ToUser: “@all”,
      MsgType: msgType,
      AgentId: c.Config.WeChat.AgentId,
      }
      msg.SetMessage(message)
      sendMsgUrl := fmt.Sprintf(“https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%v”, accessToken)
      log.Logger.Info(“sendMsgUrl = " + string(sendMsgUrl))
      header := map[string]string{“Content-Type”: “application/json”}
      bytesData, _ := json.Marshal(msg)
      http.Request(“POST”, sendMsgUrl, bytes.NewReader(bytesData), header)
      log.Logger.Info(“bytes data = " + string(bytesData))
      ret := make(map[string]interface{})
      err := http.ParseJson(&ret)
      if err != nil {
      return err
      }
      if ret[“errcode”].(float64) != 0 {
      errmsg := fmt.Sprintf(”%v”, ret[“errmsg”])
      return errors.New(errmsg)
      }
      return nil
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      从上面代码中可以看出,首先是通过redis来获取token,若没有则请求api获取token,并将其写入到redis中,有效期为2小时。然后生成一个之前封装的消息的结构体,将AgentId和message进行填充后,通过发送post请求,已达到发消息的目的。

    5.本地测试
    若想验证这个方法,可以通过对外提供一个接口,访问此接口后调用发送消息的方法。
    可以在controller目录下新建一个weChat.go,在里边实现一个get请求的方法,获取请求中的msg参数,然后调用刚才实现的发送企微的方法。

    package wechat

    import (
    “github.com/gin-gonic/gin”
    “github.com/wannanbigpig/gin-layout/internal/pkg/error_code”
    log “github.com/wannanbigpig/gin-layout/internal/pkg/logger”
    r “github.com/wannanbigpig/gin-layout/internal/pkg/response”
    “github.com/wannanbigpig/gin-layout/internal/service”
    )

    func SendMsg(c *gin.Context) {
    msg, ok := c.GetQuery(“msg”)
    if !ok {
    msg = “please input message”
    }
    log.Logger.Info("send wechat message: " + msg)
    err := service.SendWeChat(msg, “text”)
    if err != nil {
    r.Resp().FailCode(c, error_code.FAILURE, err.Error())
    return
    }
    r.Success(c, “success”)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    写好后,将此方法绑定到路由上。在routers包下新建一个weChatRouter.go文件

    package routers

    import (
    “github.com/gin-gonic/gin”
    w “github.com/wannanbigpig/gin-layout/internal/controller/wechat”
    )

    func setWeChatRouter(r *gin.Engine) {
    // version 1
    v1 := r.Group(“wechat”)
    {
    v1.GET("/send", w.SendMsg)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    这样,后续可以通过wechat/send的url来请求这个接口。最后就是调用此绑定路由的方法,在routers/router.go中添加一行代码即可

    接下来启动项目,比如发送一个msg=Hello,Golang 的请求

    curl --location --request GET “http:// I P : {IP}: IP:{PORT}/wechat/send?msg=Hello,Golang”
    1
    执行这个命令,就可以得到本文开头的截图。

    当然,这个api接口主要是为了让我们验证,实际项目运行时,建议不要这么搞。因为这接口没有任何鉴权的措施,如果对外暴露了出去,那么别人也可以肆意的调用这个接口给你的企微发送消息。

  • 相关阅读:
    HT5010 音频转换器工作原理
    关于promethus+grafana的监控exporter访问/为errorpage404(notfound)重写exporter
    数据库篇--八股文学习第十八天| MySQL和Redis的区别是什么;Redis有什么优缺点?为什么用Redis查询会比较快
    芯片科普 |ATE测试如何入门?ATE测试的工作内容和要求?
    【LeetCode】【Java】 最小公倍数为 K 的子数组数目
    vue3中如何掉用子組件的方法
    计算机考研 | 2016年 | 计算机组成原理真题
    一开始,我以为是跨域问题,原来是413
    CAS:1260586-88-6_生物素-C5-叠氮_Biotin-C5-Azide
    pandas学习(三) grouping
  • 原文地址:https://blog.csdn.net/m0_71905144/article/details/126074244