自学内容网 自学内容网

Go语言Gin框架调用企业微信接口根据手机号获取userid

这篇文章主要是讲一下在Go语言Gin框架中对接企业微信获取数据的一个代码示例,主要涉及到Redis的基本用法、HTTP的GET和POST请求、对接企业微信接口获取数据并返回JSON。

获取企业微信用户id

拿到需求,首先第一感觉就是查找企业微信(以下简称“企微”)的文档,文档链接如下:https://developer.work.weixin.qq.com/document/path/95402

请求方式:POST(HTTPS)
请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=ACCESS_TOKEN
请求包体:
{
   "mobile": "13430388888"
}

OK, 拿到了接口文档,那么下一步就是要发送一个HTTP请求了。

发送HTTP的GET和POST请求的方法

我们先来封装一个发送HTTP的GET和POST请求的方法,这类方法网上一查就能搜到一大堆,也不需要死记硬背,实在不行问一下AI也行:

//utils/funcUtils/http.go

package funcUtils

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)

// HttpGet 发送GET请求
// url:请求地址
func HttpGet(url string) (map[string]interface{}, error) {
client := &http.Client{Timeout: 5 * time.Second}
fmt.Println("HttpGet请求三方接口信息:", url)
resp, err := client.Get(url)
if err != nil {
fmt.Println("HttpGet调用三方接口出现了错误:", err)
return nil, err
}
res, err := ioutil.ReadAll(resp.Body)
fmt.Println("HttpGet三方接口返回信息:", bytes.NewBuffer(res), err)

// 将 JSON 字符串转换为 map[string]interface{}
var dataMap map[string]interface{}
if err := json.Unmarshal([]byte(res), &dataMap); err != nil {
fmt.Printf("Failed to unmarshal JSON: %v\n", err)
return nil, err
}

return dataMap, nil
}

// HttpPost 发送POST请求
// url:         请求地址
// data:        POST请求提交的数据
// contentType: 请求体格式,如:application/json
func HttpPost(url string, data interface{}, contentType string) (map[string]interface{}, error) {
//创建调用API接口的client
client := &http.Client{Timeout: 5 * time.Second}
jsonStr, _ := json.Marshal(data)
fmt.Println("HttpPost请求三方接口信息:", url, bytes.NewBuffer(jsonStr))
resp, err := client.Post(url, contentType, bytes.NewBuffer(jsonStr))
if err != nil {
fmt.Println("HttpPost调用三方接口出现了错误:", err)
return nil, err
}
res, err := ioutil.ReadAll(resp.Body)
fmt.Println("HttpPost三方接口返回信息:", bytes.NewBuffer(res), err)

// 将 JSON 字符串转换为 map[string]interface{}
var dataMap map[string]interface{}
if err := json.Unmarshal([]byte(res), &dataMap); err != nil {
fmt.Printf("Failed to unmarshal JSON: %v\n", err)
return nil, err
}
return dataMap, err
}

实现方法和调试

接下来,新增一个controller文件,开始写实现方法:

// controllers/userController/user.go

// 获取企业微信用户详情
func GetWorkWechatUserInfo(c *gin.Context) {
var weworkRequestParams = map[string]string{
"mobile": "132xxxxx", //传入一个手机号
}

workWechatAccessTokenVal := "xxxx" //企微accessToken,可以先写死
url := "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=" + workWechatAccessTokenVal
data, err := funcUtils.HttpPost(url, weworkRequestParams, "application/json")
fmt.Println(data)
fmt.Println(err)
}

上面的代码逻辑很简单,定义好需要发送POST请求的URL和参数body体,其中 access_token 先不管,先随便传一个字符串试试POST请求能不能通。

然后,定义一个路由:

// router/user.go

package router

func UserRouter(e *gin.Engine) {
user := e.Group("/user")
{
user.POST("/workWechatUserInfo", userController.GetWorkWechatUserInfo)
}
}

运行一下,启动后用 postman 发送 POST请求访问 http://127.0.0.1:8080/user/workWechatUserInfo 看看控制台输出:
在这里插入图片描述

企微返回了一个json数据,说明我们的POST请求发送成功了。接下来,按照企微返回的提示信息,我们来获取一下 access_token。

获取企业微信access_token

同样的,找到企微的文档如下:https://developer.work.weixin.qq.com/document/path/91039

请求方式: GET(HTTPS)
请求地址: https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET

这次是个 GET请求,刚好上面我们已经把GET请求的方法封装好了,直接调用就行。这里需要传两个参数:corpidcorpsecret ,这两个参数登录企业微信开发者后台就可以获取到。接下来,直接写实现的方法:

// 获取企业微信的access_token
func GetWorkWechatAccessToken() string {
corpId := "test" //替换成你自己的数据,下同
secret := "test"
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%v&corpsecret=%v", corpId, secret)
data, err := funcUtils.HttpGet(url)
fmt.Println(data)
fmt.Println(err)
}

然后在上面的 GetWorkWechatUserInfo() 方法中来调用这个方法:
在这里插入图片描述
可以看到,已经成功获取到企微返回的 access_token 了。但是这里需要仔细阅读企微的文档:注意:不能频繁调用gettoken接口,否则会受到频率拦截。
在这里插入图片描述
也就是说,这个接口返回的 access_token 在 7200秒之内是不变的,因此不能每次都去请求企微接口获取 access_token,需要放到缓存里。那么,最简单的,就是直接用 Redis给它存起来。

使用 Redis 将 access_token缓存起来

使用Redis也很简单,安装扩展 go get github.com/go-redis/redis/v8 ,然后如下配置:

// utils/redisUtil/goRedis.go

package redisUtil

import (
"context"
"github.com/go-redis/redis/v8"
"log"
)

var ctx = context.Background()
var GoRdb *redis.Client

func init() {
// 初始化 Redis 客户端
GoRdb = redis.NewClient(&redis.Options{
Addr:     "localhost:6379", // Redis 服务器地址
Password: "",               // 密码(如果没有密码则为空)
DB:       0,                // 使用的数据库编号
})

// 测试连接
_, err := GoRdb.Ping(ctx).Result()
if err != nil {
log.Fatalf("Failed to connect to Redis: %v", err)
}
log.Println("Connected to Redis successfully")
}

GetWorkWechatUserInfo() 这个方法调用企微接口获取 access_token 之前先查一下有没有缓存,并且把整体代码结构也优化一下,该写配置文件的就写到配置文件。代码如下:

// conf/application.yml

workWechat:
  requestUrl: https://qyapi.weixin.qq.com/cgi-bin
  corpId: xxx
  secret: xxx
// controllers/userController/user.go

var weworkConfig map[string]string

func init() {
common.InitConfig()
weworkConfig = make(map[string]string)
weworkConfig["requestUrl"] = viper.GetString("workWechat.requestUrl")
weworkConfig["corpId"] = viper.GetString("workWechat.corpId")
weworkConfig["secret"] = viper.GetString("workWechat.secret")
}

// 获取企业微信的access_token
func GetWorkWechatAccessToken() string {
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%v&corpsecret=%v", weworkConfig["corpId"], weworkConfig["secret"])
data, err := funcUtils.HttpGet(url)
if err != nil {
fmt.Println("GetWorkWechatAccessToken调用三方接口出现了错误:", err)
return ""
}
if data["access_token"].(string) == "" {
fmt.Println("GetWorkWechatAccessToken获取到的access_token为空")
return ""
}
return data["access_token"].(string)
}

// 获取企业微信用户详情
func GetWorkWechatUserInfo(c *gin.Context) {
var ctx = context.Background()
workWechatAccessTokenKey := "workWechatAccessTokenKey"
workWechatAccessTokenVal := redisUtil.GoRdb.Get(ctx, workWechatAccessTokenKey).Val()
if workWechatAccessTokenVal == "" {
workWechatAccessTokenVal = GetWorkWechatAccessToken()
if workWechatAccessTokenVal == "" {
controllers.ReturnError(c, 400, fmt.Sprintf("获取企业微信access_token失败"))
}
expiration, _ := time.ParseDuration("7000s") //7200-200
redisUtil.GoRdb.Set(ctx, workWechatAccessTokenKey, workWechatAccessTokenVal, expiration)
}

var weworkRequestParams = map[string]string{
"mobile": "132xxxx",
}

url := "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=" + workWechatAccessTokenVal
data, err := funcUtils.HttpPost(url, weworkRequestParams, "application/json")
if err != nil {
fmt.Println("GetWorkWechatUserInfo调用企微接口出现了错误:", err)
controllers.ReturnError(c, 400, "获取失败(-1)")
}
if data["userid"].(string) == "" {
fmt.Println("GetWorkWechatUserInfo获取到的userid为空")
controllers.ReturnError(c, 400, "获取失败(-2)")
}

result := map[string]string{
"work_wechat_user_id": data["userid"].(string),
}
controllers.ReturnSuccess(c, 200, "获取成功", result, 1)
}

再次运行一下:

在这里插入图片描述
在这里插入图片描述
至此,已经成功获取到了企业微信的用户id。

代码优化

实现了基本功能之后,回过头来,我们再来阅读一下整体的代码,发现还有很大的优化空间。

代码逻辑拆分service层和controller层

上面代码中的 GetWorkWechatUserInfo()方法 把Gin框架的入口逻辑和实现的业务逻辑混在了一起,不方便代码的维护和复用。因此,可以拆分出来一层 service 层,或者说是逻辑层:

// service/work_wechat/workWechatUser.go

package work_wechat

var weworkConfig map[string]string

type WorkWechatUser struct {

}

func NewWorkWechatUser() *WorkWechatUser {
return &WorkWechatUser{}
}

func (we *WorkWechatUser) init() {
common.InitConfig()
weworkConfig = make(map[string]string)
weworkConfig["requestUrl"] = viper.GetString("workWechat.requestUrl")
weworkConfig["corpId"] = viper.GetString("workWechat.corpId")
weworkConfig["secret"] = viper.GetString("workWechat.secret")
}

// 获取企业微信的access_token
func (ctx *WorkWechatUser) GetWorkWechatAccessToken() (string, error) {
//... 忽略部分代码
return data["access_token"].(string), nil
}

// 获取企业微信用户详情
func (ctx *WorkWechatUser) GetWorkWechatUserInfo() (map[string]string, error) {
//... 忽略部分代码
//返回结果
result := map[string]string{
"work_wechat_user_id": data["userid"].(string),
}
return result, nil
}

可以看的出来,在 service 中,不涉及任何 Gin 框架的逻辑,这样也方便我们这部分的业务逻辑代码的复用。接下来定义Gin框架的controller层,用来调用上面的service层:

//  controllers/userController/user.go

package userController

func GetWorkWechatUserInfo(c *gin.Context) {
service := work_wechat.NewWorkWechatUser()
data, err := service.GetWorkWechatUserInfo()
if err != nil {
controllers.ReturnError(c, 400, fmt.Sprintf("获取失败:%v", err))
} else {
controllers.ReturnSuccess(c, 200, "获取成功", data, 1)
}
}

这样一来,代码看起来就简洁清晰了很多。

参数改为由客户端传递

上面在实现功能的时候,直接把请求参数的手机号写死在了代码里,这显然不太合理,因此需要把手机号作为参数从客户端传递。代码优化如下:

//  controllers/userController/user.go
func GetWorkWechatUserInfo(c *gin.Context) {
// 定义一个 map 来存储请求体中的数据
var requestData map[string]interface{}

// 绑定请求体中的 JSON 数据到 map
if err := c.ShouldBindJSON(&requestData); err != nil {
log.Printf("Failed to bind JSON: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to bind JSON"})
return
}

if requestData["mobile"] == "" {
controllers.ReturnError(c, 400, fmt.Sprintf("缺少参数mobile"))
return
}

//.... 后续逻辑...
}

在这里插入图片描述

企微配置文件初始化

在最开始的时候,我把企微相关的配置信息写在了一个 map 里面:

var weworkConfig map[string]string

func init() {
common.InitConfig()
weworkConfig = make(map[string]string)
weworkConfig["requestUrl"] = viper.GetString("workWechat.requestUrl")
weworkConfig["corpId"] = viper.GetString("workWechat.corpId")
weworkConfig["secret"] = viper.GetString("workWechat.secret")
}

但实际上,如果把配置信息放在 service 初始化的结构体里面可能会更优雅一些,因此,继续优化 service 层的代码如下:

// service/work_wechat/workWechatUser.go

type WorkWechatConfig struct {
requestUrl string
corpId     string
secret     string
}

func NewWorkWechatConfig() *WorkWechatConfig {
return &WorkWechatConfig{
requestUrl: viper.GetString("workWechat.requestUrl"),
corpId:     viper.GetString("workWechat.corpId"),
secret:     viper.GetString("workWechat.secret"),
}
}

func (ctx *WorkWechatConfig) GetWorkWechatAccessToken() (string, error) {
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%v&corpsecret=%v", ctx.corpId, ctx.secret)
data, err := funcUtils.HttpGet(url)
//.... 后续逻辑
}

这样,对于 controller 层:

service := work_wechat.NewWorkWechatConfig() //初始化配置信息
data, err := service.GetWorkWechatUserInfo(requestData["mobile"].(string))

这样的写法, 直接就会在调用的时候初始化企微相关的配置信息。

另外,记得把Redis的配置信息也放到配置文件里面:

// conf/application.yml

redis:
  host: localhost
  port: 6379
  password: ""
  db: 1
// utils/redisUtil/goRedis.go

var ctx = context.Background()
var GoRdb *redis.Client

func InitRedis() {
// 初始化 Redis 客户端
GoRdb = redis.NewClient(&redis.Options{
Addr:     fmt.Sprintf("%s:%s", viper.GetString("redis.host"), viper.GetString("redis.port")), //Redis服务器地址,格式:"localhost:6379"
Password: viper.GetString("redis.password"),                                                  // 密码(如果没有密码则为空)
DB:       viper.GetInt("redis.db"),                                                           // 使用的数据库编号
})
}

个人感悟:很多逻辑看似简单,但不能只看不练,只有多加练习,才能学以致用,融会贯通。纸上得来终觉浅,绝知此事要躬行。刚开始做一件事情,比如学习一门新的编程语言或者做一个小功能,先不要想着怎么把它做的最好,而是要先做出来v0.1版本,先别管好不好,先能运行起来,后面再慢慢优化。很多人并不是有多少过人之处,只不过比其他人更加刻苦的去练习而已,练习的次数多了,自然就熟练了。
正所谓:“无他,唯手熟尔。”

完整源代码链接:https://gitee.com/rxbook/go-demo-2025


原文地址:https://blog.csdn.net/rxbook/article/details/143004503

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!