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也行:

go 复制代码
//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文件,开始写实现方法:

go 复制代码
// 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请求能不能通。

然后,定义一个路由:

go 复制代码
// 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 ,这两个参数登录企业微信开发者后台就可以获取到。接下来,直接写实现的方法:

go 复制代码
// 获取企业微信的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 ,然后如下配置:

go 复制代码
// 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 之前先查一下有没有缓存,并且把整体代码结构也优化一下,该写配置文件的就写到配置文件。代码如下:

yaml 复制代码
// conf/application.yml

workWechat:
  requestUrl: https://qyapi.weixin.qq.com/cgi-bin
  corpId: xxx
  secret: xxx
go 复制代码
// 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 层,或者说是逻辑层:

go 复制代码
// 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层:

go 复制代码
//  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)
	}
}

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

参数改为由客户端传递

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

go 复制代码
//  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 里面:

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")
}

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

go 复制代码
// 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 层:

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

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

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

yaml 复制代码
// conf/application.yml

redis:
  host: localhost
  port: 6379
  password: ""
  db: 1
go 复制代码
// 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

相关推荐
hkNaruto10 小时前
【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试
golang·udp·p2p
入 梦皆星河10 小时前
go中常用的处理json的库
golang
桃园码工12 小时前
2-Gin 框架中的路由 --[Gin 框架入门精讲与实战案例]
gin·路由·实战案例
海绵波波10712 小时前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
海绵波波10712 小时前
Gin-vue-admin(4):项目创建前端一级页面和二级页面
前端·vue.js·gin
每天写点bug12 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang
一个不秃头的 程序员12 小时前
代码加入SFTP Go ---(小白篇5)
开发语言·后端·golang
基哥的奋斗历程13 小时前
初识Go语言
开发语言·后端·golang
ZVAyIVqt0UFji19 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
唐墨1231 天前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang