123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- 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
- }
|