mindoc/utils/workweixin/workweixin.go

359 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package workweixin
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"net/http"
"time"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
"github.com/mindoc-org/mindoc/cache"
"github.com/mindoc-org/mindoc/conf"
)
// doc
// - 全局错误码: https://work.weixin.qq.com/api/doc/90000/90139/90313
const (
AccessTokenCacheKey = "access-token-cache-key"
// ContactAccessTokenCacheKey = "contact-access-token-cache-key"
)
// 获取访问凭据-请求响应结构
type AccessTokenResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"` // 获取到的凭证,最长为512字节
ExpiresIn int `json:"expires_in"` // 凭证的有效时间(秒)
}
// 获取用户Id-请求响应结构
type UserIdResponse struct {
// 接口文档: https://developer.work.weixin.qq.com/document/path/91023
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"userid"` // 企业成员UserID
UserTicket string `json:"user_ticket"` // 用于获取敏感信息
OpenId string `json:"openid"` // 非企业成员的标识,对当前企业唯一
ExternalUserId string `json:"external_userid"` // 外部联系人ID
}
// 获取成员ID列表-请求响应结构
type UserListIdResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
NextCursor string `json:"next_cursor"` // 分页游标,下次请求时填写以获取之后分页的记录
DeptUser []WorkWeixinDeptUserInfo `json:"dept_user"` // 用户-部门关系列表
}
// 获取用户信息-请求响应结构
type UserInfoResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"UserId"` // 企业成员UserID
Name string `json:"name"` // 成员名称
HideMobile int `json:"hide_mobile"` // 是否隐藏了手机号码
Mobile string `json:"mobile"` // 手机号码
Department []int `json:"department"` // 成员所属部门id列表
Email string `json:"email"` // 邮箱
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
Avatar string `json:"avatar"` // 头像url
Alias string `json:"alias"` // 别名
Status int `json:"status"` // 激活状态: 1=已激活2=已禁用4=未激活5=退出企业
MainDepartment int `json:"main_department"` // 主部门
}
type UserPrivateInfoResponse struct {
// 文档地址: https://developer.work.weixin.qq.com/document/path/95833
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"userid"` // 企业成员userid
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 访问凭据缓存-结构
type AccessTokenCache struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
UpdateTime time.Time `json:"update_time"`
}
// 企业微信用户敏感信息-结构
type WorkWeixinUserPrivateInfo struct {
UserId string `json:"userid"` // 企业成员userid
Name string `json:"name"` // 姓名
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 企业微信用户信息-结构
type WorkWeixinDeptUserInfo struct {
UserId string `json:"UserId"` // 企业成员UserID
Department int `json:"department"` // 成员所属部门id列表
}
// 企业微信用户信息-结构
type WorkWeixinUserInfo struct {
UserId string `json:"UserId"` // 企业成员UserID
Name string `json:"name"` // 成员名称
HideMobile int `json:"hide_mobile"` // 是否隐藏了手机号码
Mobile string `json:"mobile"` // 手机号码
Department []int `json:"department"` // 成员所属部门id列表
Email string `json:"email"` // 邮箱
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
Avatar string `json:"avatar"` // 头像url
Alias string `json:"alias"` // 别名
Status int `json:"status"` // 激活状态: 1=已激活2=已禁用4=未激活5=退出企业
MainDepartment int `json:"main_department"` // 主部门
}
func httpFilter(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
r := req.GetRequest()
logs.Info("filter-url: ", r.URL)
// Never forget invoke this. Or the request will not be sent
return next(ctx, req)
}
}
// 获取访问凭据-请求
func RequestAccessToken(corpid string, secret string) (cache_token AccessTokenCache, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
req := httplib.Get(url)
req.Param("corpid", corpid) // 企业ID
req.Param("corpsecret", secret) // 应用的凭证密钥
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
resp, err := req.Response()
_ = resp
var token AccessTokenCache
if err != nil {
logs.Error(err)
return token, false
}
var atr AccessTokenResponse
err = req.ToJSON(&atr)
if err != nil {
logs.Error(err)
return token, false
}
token = AccessTokenCache{
AccessToken: atr.AccessToken,
ExpiresIn: atr.ExpiresIn,
UpdateTime: time.Now(),
}
return token, true
}
// 获取访问凭据
func GetAccessToken() (access_token string, ok bool) {
var cache_token AccessTokenCache
cache_key := AccessTokenCacheKey
err := cache.Get(cache_key, &cache_token)
if err == nil {
logs.Info("AccessToken从缓存读取成功")
// TODO: access_token有效期判断, 刷新
return cache_token.AccessToken, true
} else {
logs.Warning(err)
workweixinConfig := conf.GetWorkWeixinConfig()
logs.Debug("corp_id: ", workweixinConfig.CorpId)
logs.Debug("agent_id: ", workweixinConfig.AgentId)
logs.Debug("secret: ", workweixinConfig.Secret)
secret := workweixinConfig.Secret
new_token, ok := RequestAccessToken(workweixinConfig.CorpId, secret)
if ok {
logs.Debug(new_token)
if err = cache.Put(cache_key, new_token, time.Second*time.Duration(new_token.ExpiresIn)); err == nil {
logs.Info("AccessToken缓存写入成功")
return new_token.AccessToken, true
}
logs.Warning("AccessToken缓存写入失败")
return "", false
}
logs.Warning("AccessToken请求失败")
return "", false
}
}
// 获取用户id-请求
func RequestUserId(access_token string, code string) (user_id string, ticket string, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo"
req := httplib.Get(url)
req.Param("access_token", access_token) // 应用调用接口凭证
req.Param("code", code) // 通过成员授权获取到的code
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
resp, err := req.Response()
_ = resp
if err != nil {
logs.Error(err)
return "", "", false
}
var uir UserIdResponse
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return "", "", false
}
return uir.UserId, uir.UserTicket, uir.UserId != ""
}
func RequestUserPrivateInfo(access_token, userid, ticket string) (WorkWeixinUserPrivateInfo, error) {
url := "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=" + access_token
req := httplib.Post(url)
body := map[string]string{
"user_ticket": ticket,
}
b, _ := json.Marshal(body)
req.Body(b)
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
resp, err := req.Response()
_ = resp
var uir UserPrivateInfoResponse
var info WorkWeixinUserPrivateInfo
if err != nil {
logs.Error(err)
return info, err
}
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return info, err
}
if uir.ErrCode != 0 {
return info, errors.New(uir.ErrMsg)
}
user_info, err, _ := RequestUserInfo(access_token, userid)
if err != nil {
return info, err
}
info = WorkWeixinUserPrivateInfo{
UserId: userid,
Name: user_info.Name,
Gender: uir.Gender,
Avatar: uir.Avatar,
QrCode: uir.QrCode,
Mobile: uir.Mobile,
Mail: uir.Mail,
BizMail: uir.BizMail,
Address: uir.Address,
}
return info, nil
}
/*
获取用户详细信息-请求
从2022年8月15日10点开始“企业管理后台 - 管理工具 - 通讯录同步”的新增IP将不能再调用此接口
url:https://developer.work.weixin.qq.com/document/path/96079
*/
func RequestUserInfo(contact_access_token string, userid string) (user_info WorkWeixinUserInfo, error_msg error, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/user/get"
req := httplib.Get(url)
req.Param("access_token", contact_access_token) // 通讯录应用调用接口凭证
req.Param("userid", userid) // 成员UserID
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
resp_str, err := req.String()
_ = resp_str
var info WorkWeixinUserInfo
if err != nil {
logs.Error(err)
return info, err, false
} else {
logs.Debug(resp_str)
}
var uir UserInfoResponse
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return info, err, false
}
if uir.ErrCode != 0 {
return info, errors.New(uir.ErrMsg), false
}
info = WorkWeixinUserInfo{
UserId: uir.UserId,
Name: uir.Name,
HideMobile: uir.HideMobile,
Mobile: uir.Mobile,
Department: uir.Department,
Email: uir.Email,
IsLeaderInDept: uir.IsLeaderInDept,
IsLeader: uir.IsLeader,
Avatar: uir.Avatar,
Alias: uir.Alias,
Status: uir.Status,
MainDepartment: uir.MainDepartment,
}
return info, nil, true
}
/*
获取成员ID列表
*/
func GetUserListId(contact_access_token string, userid string) (user_info WorkWeixinDeptUserInfo, error_msg string, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/user/list_id"
req := httplib.Get(url)
req.Param("access_token", contact_access_token) // 通讯录应用调用接口凭证
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
respStr, err := req.String()
_ = respStr
var info WorkWeixinDeptUserInfo
if err != nil {
logs.Error(err)
return info, "请求失败", false
} else {
logs.Debug(respStr)
}
// 返回响应
var uir UserListIdResponse
//获取用户信息失败: 请求数据结果错误
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return info, "请求数据结果错误", false
}
if uir.ErrCode != 0 {
return info, uir.ErrMsg, false
}
// 判断userid 中是否还有当前用户id
for i := 0; i < len(uir.DeptUser); i++ {
if uir.DeptUser[i].UserId == userid {
info = WorkWeixinDeptUserInfo{
UserId: uir.DeptUser[i].UserId,
}
return info, "", true
}
}
return info, uir.ErrMsg, false
}