基于 GoLang 快速开发赋能微信公众号

admin 2023年8月16日19:41:25评论34 views字数 5751阅读19分10秒阅读模式

基于 GoLang 快速开发赋能微信公众号

0x00 背景

   平时,周围人甚至包括自己都遇到使用idea、pycharm等工具时没有激活码,所以只能求助Goole或者别人的情况。后来,入门学习了一段Go语言之后,我发现基于Gin框架,可以非常快地实现公众号自动回复信息的开发者功能动态获取激活码,遇到的网络请求,并发FUZZ等问题,Go处理起来也还可以。出于记录下自己的实践过程目的,故作本文以分享。

0x01 功能需求定位

需求实现效果如下:

1)用户通过关注公众号才能实现对话(微信自实现)

2)用户输入非 <idea> 关键字内容时,公众号需要回复 [其他类型信息] xxx

3)用户输入含 <idea>关键字的内容,公众号需要回复 [激活码获取] xxx

0x02 初步平台调研

1)第一步当然是申请开通个人公众号的过程,过程并不复杂,故不在此赘述。

2)第二步是查看微信公众号的开发者功能支持,打开 左侧菜单栏 -> 打开设置与开发 -> 选择[接口权限],显示支持自动回复文本信息,这基本满足我的需求。

基于 GoLang 快速开发赋能微信公众号
image-20230816084955919

0x03 微信开发者配置

由于目标实现并不复杂,流程只需遵循开发者入门指引走如下几步即可.

1)基本配置:选用token时为了安全性和方便性,可以考虑md5进行加密:  echo -en "iamtokenhaha"|md5 & 明文模式

基于 GoLang 快速开发赋能微信公众号
image-20230814161231292

2)服务器需要通过验证才能完成基本配置, 分享一些调试的技巧,服务器可以先 nc 监听查看一下微信服务器发过来的请求格式

基于 GoLang 快速开发赋能微信公众号
image-20230814171053537

相关核心代码

1)请求参数结构体,

1type CheckSignatureRequest struct {
2    Signature string `form:"signature"`
3    Timestamp string `form:"timestamp"`
4    Nonce     string `form:"nonce"`
5    Echostr   string `form:"echostr"`
6}

2)sha1 加密函数实现

 1func makeSignature(token, timestamp, nonce string) string { //本地计算signature
2    si := []string{token, timestamp, nonce}
3    sort.Strings(si)            //字典序排序
4    str := strings.Join(si, ""//组合字符串
5    s := sha1.New()             //返回一个新的使用SHA1校验的hash.Hash接口
6    _, err := io.WriteString(s, str)
7    if err != nil {
8        log.Printf("Error, makeSignature: %s", err)
9        return ""
10    } //WriteString函数将字符串数组str中的内容写入到s中
11    return fmt.Sprintf("%x", s.Sum(nil))
12}

3)验证接口实现, 实现的内容就是将 之前开发者配置的token 和 解析传入的timestamp nonce 进行字典序排序,然后使用 sha1 进行加密,与传入的signature 比较,确保通信双方都是已知Token的人,最终输出传入的echostr参数,代表检验成功完成验证。

基于 GoLang 快速开发赋能微信公众号
image-20230816101551601

0x04 信息接收与回复

首先需要了解微信的自动回复机制, 可以简单处理为两个步骤,先接收信息,然后回复信息,定义相关请求结构体

相关资料:  接收普通消息

当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。

1type WXTextMsg struct {
2    ToUserName   string
3    FromUserName string
4    CreateTime   int64
5    MsgType      string
6    Content      string
7    MsgId        int64
8}

相关资料:  被动回复用户消息

当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

1type WXRepTextMsg struct {
2    ToUserName   string
3    FromUserName string
4    CreateTime   int64
5    MsgType      string
6    Content      string
7    // 若不标记XMLName, 则解析后的xml名为该结构体的名称, 因为需要返回<xml>xxxx</xml>的格式,所以需要标记根节点名称
8    XMLName xml.Name `xml:"xml"`
9}

相关接口代码简化实现如下:

1)处理信息接收

 1func wxMsgHandler(context *gin.Context) {
2    var textMsg WXTextMsg
3 // 解析请求内容
4    err := context.ShouldBindXML(&textMsg)
5    if err != nil {
6        log.Printf("[消息接收] - XML数据包解析失败: %vn", err)
7        return
8    }
9    log.Printf("[消息接收] - 收到消息, 消息类型为: %s, 消息内容为: %sn", textMsg.MsgType, textMsg.Content)
10    WXMsgReply(context, textMsg.ToUserName, textMsg.FromUserName, "信息类型""我是信息内容")
11}

2)处理回复信息,主要获取调换一些发信人和收信人,即 ToUserName &  FromUserName

 1func WXMsgReply(c *gin.Context, fromUser, toUser string, mType string, content string) {
2    repTextMsg := WXRepTextMsg{
3        ToUserName:   toUser,
4        FromUserName: fromUser,
5        CreateTime:   time.Now().Unix(),
6        MsgType:      "text",
7        Content:      fmt.Sprintf("[%s] - %s n%s", mType, time.Now().Format("2006-01-02"), content),
8    }
9
10    msg, err := xml.Marshal(&repTextMsg)
11    if err != nil {
12        log.Printf("[消息回复] - 将对象进行XML编码出错: %vn", err)
13        return
14    }
15    _, _ = c.Writer.Write(msg)
16}

0x05 激活码实时获取

动态实时获取激活码, 是本程序最核心的一个功能,实现方面稍微走了点弯路,原因是回复信息的内容有长度限制,所以不能直接返回激活码,故需要利用一个中转的接口,以网页的形式展示出来,这里涉及到一个动态路由及其路由有效时间的简易实现。

如果不设置为动态路由,那么用户可以通过直接访问你的固定接口,就可以跳过公众号渠道,直接获取到邀请码

设计实现较为简单,相关代码如下所示:

动态路由通过var uuidRouteList = make(map[string]int) map映射类型存储, 过期时间通过go-cache缓存库实现路由失效

Bug: 路由过期的时候并不会删除,因为并没有实现定时任务每3分钟删除一次,而是尝试生成路由的时候,检查一次缓存的方式来刷新过期路由

1)程序通过定时器,每3小时调用一次激活码动态获取函数, updateActivationCodes函数提供了两种方式自动获取激活码,一旦成功获取到激活码,就会将这个值进行缓存,因为激活码有效期和支持激活的用户是多个的,通过缓存的方式可以提供程序返回信息的速度,减少请求压力

 1func RunPeriodically(c *cache.Cache, runChan chan struct{}) {
2    // 设置定时器, 每三小时执行一次, 首先需要执行第一次
3    cdKey, err := getOfficialCode()
4    if err != nil {
5        updateActivationCodes(c, "")
6    } else {
7        updateActivationCodes(c, cdKey)
8    }
9    ticker := time.NewTicker(time.Hour * 3)
10    defer ticker.Stop()
11    for {
12        select {
13        case <-ticker.C:
14            cdKey, err = getOfficialCode()
15            if err != nil {
16                updateActivationCodes(c, "")
17            } else {
18                updateActivationCodes(c, cdKey)
19            }
20        // 控制定时器结束
21        case <-runChan:
22            return
23        }
24    }
25}

2)动态路由实现代码, 每次用户发起请求的时候,会自动生成一个uuid的路由存储在 uuidRouteList路由列表里面,有效的话,则成功返回激活码

1r.GET("/code/:uuid", showCodeHandler)
 1func showCodeHandler(context *gin.Context) {
2    checkUUid := context.Param("uuid")
3    if _, exist := uuidRouteList[checkUUid]; exist {
4        if code, found := caching.Get("code"); found {
5            successCode := code.(string)
6            context.JSON(http.StatusOK, gin.H{
7                "code"0,
8                "data": successCode,
9                "msg":  "success",
10            })
11        } else {
12            context.JSON(http.StatusOK, gin.H{
13                "code"-1,
14                "data"nil,
15                "msg":  "not found code in the cache, come back later for few minutes",
16            })
17        }
18    } else {
19        context.JSON(http.StatusForbidden, gin.H{
20            "code"-1,
21            "msg":  "Don't Hack me, thanks!",
22        })
23    }
24}

0x06 完整效果展示

1)公众号回复 "idea" 关键字即可获取到一个动态路由链接,访问即可获取到data字段里面的邀请码,邀请码针对JetBrainsIntellij全家桶来说都是通用的,不单单是idea

基于 GoLang 快速开发赋能微信公众号
image-20230816111902293

2) 浏览器访问打开,即可获取到邀请码

基于 GoLang 快速开发赋能微信公众号
image-20230816112238711

3)当超过三分钟之后,还有人触发动态路由生成,会对过期的路由进行删除处理,从而限制用户复用URL来获得激活码.

基于 GoLang 快速开发赋能微信公众号
image-20230816112626799

0x07 结语

  在实现这些简单的功能过程中,其实自己不仅更为熟悉应用Go语法, 而且对于一些实际场景的实现也做了一些自己理解上面的实现。简单来说,Go满足了一个脚本小子绝大部分的期待吧,它表现优异的网络性能、跨平台开箱即用的特性,以及到一开始我觉得对于Py而言有点反人类的语法,但现在觉得GO的语法特点应该定义为简洁明确的人来说,我没有理由不去更多地去尝试它,这仅仅是一个起步,未来,Go应该会成为自己主力的一门语言之一。

原文始发于微信公众号(黑客真酷):基于 GoLang 快速开发赋能微信公众号

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月16日19:41:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   基于 GoLang 快速开发赋能微信公众号https://cn-sec.com/archives/1959746.html

发表评论

匿名网友 填写信息