Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions api_generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import (
"bytes"
"flag"
"fmt"
"github.com/ChimeraCoder/gojson"
"github.com/PuerkitoBio/goquery"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"strings"
"text/template"

"github.com/ChimeraCoder/gojson"
"github.com/PuerkitoBio/goquery"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
)

// 生成企微api代码
Expand Down Expand Up @@ -95,7 +97,7 @@ func main() {
}
fmt.Printf("开始抓取和生成API代码,文档地址:%s,代码保存路径:%s\n", docURL, savePath)

rawHtml, err := doc.Find(".frame_cntHtml").Html()
rawHtml, err := doc.Find(".ep-layout-cnt").Html()
if err != nil {
die("failed to get html: %+v\n", err)
}
Expand Down Expand Up @@ -298,6 +300,10 @@ func main() {
if err != nil {
die("ioutil.WriteFile failed: %+v\n", err)
}
err = exec.Command("gofmt", "-w", savePath).Run()
if err != nil {
die("exec gofmt failed: %+v\n", err)
}
fmt.Printf("保存文件成功:%s\n", savePath)
}

Expand Down
6 changes: 3 additions & 3 deletions apis/OA审批-提交审批申请.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type DateValue struct { // 日期/日期+时间控件(control参数为Date)
STimestamp string `json:"s_timestamp"` // 时间戳-字符串类型,在此填写日期/日期+时间控件的选择值,以此为准
}
type SelectorValue struct { // 单选/多选控件(control参数为Selector)
Type string `json:"type"` // 选择方式:single-单选;multi-多选
Type string `json:"type"` // 选择方式:single-单选;multi-多选
Options []struct { // 多选选项,多选属性的选择控件允许输入多个
Key string `json:"key"` // 选项key,可通过“获取审批模板详情”接口获得
} `json:"options"`
Expand Down Expand Up @@ -43,9 +43,9 @@ type TableValueList struct { // 子明细列表,在此填写子明细的所有
}
type VacationValue struct { // 假勤组件-请假组件(control参数为Vacation)
Selector struct { // 请假类型,所选选项与假期管理关联,为假期管理中的假期类型
Type string `json:"type"` // 选择方式:single-单选;multi-多选,在假勤控件中固定为单选
Type string `json:"type"` // 选择方式:single-单选;multi-多选,在假勤控件中固定为单选
Options []struct { // 用户所选选项
Key string `json:"key"` // 选项key,选项的唯一id,可通过“获取审批模板详情”接口获得vacation_list中item的id值
Key string `json:"key"` // 选项key,选项的唯一id,可通过“获取审批模板详情”接口获得vacation_list中item的id值
Value []struct { // 选项值,若配置了多语言则会包含中英文的选项值
Text string `json:"text"`
Lang string `json:"lang"`
Expand Down
4 changes: 2 additions & 2 deletions apis/OA审批-获取审批模板详情.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func (x ReqGetTemplateDetail) intoBody() ([]byte, error) {
// RespGetTemplateDetail 获取审批模板详情
// 文档:https://developer.work.weixin.qq.com/document/path/92631#获取审批模板详情
type Property struct { // 模板控件属性,包含了模板内控件的各种属性信息
Control string `json:"control"` // 控件类型:Text-文本;Textarea-多行文本;Number-数字;Money-金额;Date-日期/日期+时间;Selector-单选/多选;Contact-成员/部门;Tips-说明文字;File-附件;Table-明细;Attendance-假勤控件;Vacation-请假控件
Id string `json:"id"` // 控件id
Control string `json:"control"` // 控件类型:Text-文本;Textarea-多行文本;Number-数字;Money-金额;Date-日期/日期+时间;Selector-单选/多选;Contact-成员/部门;Tips-说明文字;File-附件;Table-明细;Attendance-假勤控件;Vacation-请假控件
Id string `json:"id"` // 控件id
Title []struct { // 控件名称,若配置了多语言则会包含中英文的控件名称,默认为zh_CN中文
Text string `json:"text"`
Lang string `json:"lang"`
Expand Down
45 changes: 40 additions & 5 deletions apis/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/valyala/fasthttp"
"io"
"mime/multipart"
"net/http"
"net/url"
"sync"
"time"

"github.com/valyala/fasthttp"
)

// 标识
const (
Provider = "provider" // 第三方服务商
ThirdApp = "third_app" // 第三方应用
CustomizedApp = "customized_app" // 自建应用代开发
SelfApp = "self_app" // 自建应用
)

// 分布式app_suite_ticket:获取和设置suite_ticket的值,自行实现该接口的具体逻辑,比如使用redis方案【企微服务器每十分钟推送一次suite_ticket】
Expand Down Expand Up @@ -49,7 +51,8 @@ type ApiClient struct {
dcsSuiteTicketCacheKey string // suite_ticket 缓存key,企微每十分钟更新一次
dcsAppSuiteTicket DcsAppSuiteTicket // 分布式app_suite_ticket

ThirdAppClient *ApiClient // 第三方应用client,用于授权企业API客户端获取suite_access_token,目前用于第三方应用获取企业凭证接口
ThirdAppClient *ApiClient // 第三方应用client,用于授权企业API客户端获取suite_access_token,目前用于第三方应用获取企业凭证接口
FasthttpClient *fasthttp.Client // 支持当前Client调用企业微信接口的自定义fasthttp.Client

logger Logger
}
Expand Down Expand Up @@ -199,6 +202,31 @@ func NewCustomizedAuthCorpApiClient(corpId, companyPermanentCode string, AgentId
return &c
}

// 自建应用API客户端初始化
func NewSelfApiClient(corpId string, agentId int, secret string, opts Options) *ApiClient {
accessTokenName := "access_token"
c := ApiClient{
CorpId: corpId,
CorpProviderSecret: secret,
AgentId: agentId,
accessTokenName: accessTokenName,
accessToken: &token{
mutex: &sync.RWMutex{},
dcsToken: opts.DcsToken,
tokenCacheKey: fmt.Sprintf("%s#%s#%s#%d", SelfApp, accessTokenName, corpId, agentId),
},
logger: opts.Logger,
}

if c.logger == nil {
c.logger = loggerPrint{}
}

c.accessToken.setGetTokenFunc(c.getCorpToken)

return &c
}

func (c *ApiClient) composeWXApiURL(path string, req interface{}) *url.URL {
values := url.Values{}
if valuer, ok := req.(urlValuer); ok {
Expand Down Expand Up @@ -247,7 +275,7 @@ func (c *ApiClient) executeWXApiGet(path string, req urlValuer, objResp interfac
httpReq.SetRequestURI(urlStr)
httpReq.Header.SetMethod(http.MethodGet)

if err := FastClient.DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
if err := c.fasthttpClient().DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
return err
}

Expand Down Expand Up @@ -289,7 +317,7 @@ func (c *ApiClient) executeWXApiPost(path string, req bodyer, objResp interface{
httpReq.SetBody(reqBody)
httpReq.Header.SetMethod(http.MethodPost)

if err := FastClient.DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
if err := c.fasthttpClient().DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
return err
}

Expand Down Expand Up @@ -348,7 +376,7 @@ func (c *ApiClient) executeWXApiMediaUpload(path string, req mediaUploader, objR
httpReq.SetBody(bodyBufer.Bytes())
httpReq.Header.SetMethod(http.MethodPost)

if err := FastClient.DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
if err := c.fasthttpClient().DoTimeout(httpReq, httpResp, HttpTTL); err != nil {
return err
}

Expand All @@ -369,3 +397,10 @@ func (c *ApiClient) executeWXApiMediaUpload(path string, req mediaUploader, objR

return json.Unmarshal(respBody, &objResp)
}

func (c *ApiClient) fasthttpClient() *fasthttp.Client {
if c.FasthttpClient != nil {
return c.FasthttpClient
}
return FastClient
}
7 changes: 4 additions & 3 deletions apis/api_client_option.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package apis

import (
"github.com/valyala/fasthttp"
"log"
"net"
"time"

"github.com/valyala/fasthttp"
)

// DefaultQYAPIHost 默认企业微信 API Host
Expand All @@ -14,10 +15,10 @@ const HttpTTL = 1 * time.Minute

var FastClient = CreateFastHttpClient()

func CreateFastHttpClient() fasthttp.Client {
func CreateFastHttpClient() *fasthttp.Client {
var defaultDialer = &fasthttp.TCPDialer{Concurrency: 300}

return fasthttp.Client{
return &fasthttp.Client{
Dial: func(addr string) (net.Conn, error) {
idx := 3 // 重试三次
for {
Expand Down
5 changes: 3 additions & 2 deletions apis/api_media.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package apis

import (
"bytes"
"github.com/google/uuid"
"io"
"mime/multipart"
"net/url"
"os"
"path"

"github.com/google/uuid"
)

const mediaFieldName = "media"
Expand Down Expand Up @@ -85,7 +86,7 @@ func (c *ApiClient) UploadTempMedia(req UploadMediaReq) (UploadMediaResult, erro
var result UploadMediaResult

// 下载文件
_, body, err := FastClient.Get(nil, req.URL)
_, body, err := c.fasthttpClient().Get(nil, req.URL)
if err != nil {
return result, err
}
Expand Down
27 changes: 26 additions & 1 deletion apis/api_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"encoding/json"
"errors"
"github.com/cenkalti/backoff/v4"
"log"
"sync"
"time"

"github.com/cenkalti/backoff/v4"
)

// 分布式access_token:获取和设置access_token的值,自行实现该接口的具体逻辑,比如使用redis方案
Expand Down Expand Up @@ -193,6 +194,30 @@ func (c *ApiClient) getCustomizedAuthCorpToken() (TokenInfo, error) {
return TokenInfo{Token: get.AccessToken, ExpiresIn: time.Duration(get.ExpiresIn) * time.Second}, nil
}

// 获取自建应用access_token
func (c *ApiClient) getCorpToken() (TokenInfo, error) {
if c.CorpProviderSecret == "" {
return TokenInfo{}, errors.New("企业应用secret不存在,corp_id:" + c.CorpId)
}
get, err := c.ExecGetCustomizedCorpTokenService(ReqGetCustomizedCorpTokenService{
Corpid: c.CorpId,
Corpsecret: c.CorpProviderSecret,
})
if err != nil {
apiError, ok := err.(*ClientError)
if ok {
if apiError.Code == ErrCode2000002 || apiError.Code == ErrCode301007 || apiError.Code == ErrCode40084 { // 企业已注销,但要等15天后才会收到企业取消授权事件
return TokenInfo{}, nil
}
c.logger.Errorf("customized_corp_access_token1: corp_id=%s, permanent_code=%s, err=%+v\n", c.CorpId, c.CompanyPermanentCode, apiError)
} else {
c.logger.Errorf("customized_corp_access_token2: corp_id=%s, permanent_code=%s, err=%+v\n", c.CorpId, c.CompanyPermanentCode, err)
}
return TokenInfo{}, err
}
return TokenInfo{Token: get.AccessToken, ExpiresIn: time.Duration(get.ExpiresIn) * time.Second}, nil
}

// getJSAPITicket 获取 JSAPI_ticket
func (c *ApiClient) getJSAPITicket() (TokenInfo, error) {
get, err := c.ExecGetJSAPITicket(JsAPITicketReq{})
Expand Down
49 changes: 49 additions & 0 deletions apis/企业微信帐号ID安全性全面升级.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,52 @@ func (c *ApiClient) ExternalcontactToServiceExternalUserid(req ReqExternalcontac
}
return resp, nil
}

// 3.3 转换群成员external_userid
// 文档:https://developer.work.weixin.qq.com/document/path/95327
type ReqGetNewGroupchatExternalUseridExternalcontact struct {
// 客户群ID,必填
ChatID string `json:"chat_id"`
// 旧外部联系人id列表,最多不超过1000个,必填
ExternalUseridList []string `json:"external_userid_list"`
}

var _ bodyer = ReqGetNewGroupchatExternalUseridExternalcontact{}

func (x ReqGetNewGroupchatExternalUseridExternalcontact) intoBody() ([]byte, error) {
result, err := json.Marshal(x)
if err != nil {
return nil, err
}
return result, nil
}

type RespGetNewGroupchatExternalUseridExternalcontact struct {
CommonResp
Items []struct {
ExternalUserid string `json:"external_userid"`
NewExternalUserid string `json:"new_external_userid"`
} `json:"items"`
}

var _ bodyer = RespGetNewGroupchatExternalUseridExternalcontact{}

func (x RespGetNewGroupchatExternalUseridExternalcontact) intoBody() ([]byte, error) {
result, err := json.Marshal(x)
if err != nil {
return nil, err
}
return result, nil
}

func (c *ApiClient) ExecGetNewGroupchatExternalUseridExternalcontact(req ReqGetNewGroupchatExternalUseridExternalcontact) (RespGetNewGroupchatExternalUseridExternalcontact, error) {
var resp RespGetNewGroupchatExternalUseridExternalcontact
err := c.executeWXApiPost("/cgi-bin/externalcontact/groupchat/get_new_external_userid", req, &resp, true)
if err != nil {
return RespGetNewGroupchatExternalUseridExternalcontact{}, err
}
if bizErr := resp.TryIntoErr(); bizErr != nil {
return RespGetNewGroupchatExternalUseridExternalcontact{}, bizErr
}
return resp, nil
}
57 changes: 57 additions & 0 deletions apis/会话存档-获取会话内容存档开启成员列表.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package apis

import (
"encoding/json"
)

// 自动生成的文件, 生成方式: make api doc=微信文档地址url
// 可自行修改生成的文件,以满足开发需求

// ReqGetPermitUserListMsgaudit 获取会话内容存档开启成员列表请求
// 文档:https://developer.work.weixin.qq.com/document/path/91614#获取会话内容存档开启成员列表
type ReqGetPermitUserListMsgaudit struct {
// Type 拉取对应版本的开启成员列表。1表示办公版;2表示服务版;3表示企业版。非必填,不填写的时候返回全量成员列表。
Type int `json:"type,omitempty"`
}

var _ bodyer = ReqGetPermitUserListMsgaudit{}

func (x ReqGetPermitUserListMsgaudit) intoBody() ([]byte, error) {
result, err := json.Marshal(x)
if err != nil {
return nil, err
}
return result, nil
}

// RespGetPermitUserListMsgaudit 获取会话内容存档开启成员列表响应
// 文档:https://developer.work.weixin.qq.com/document/path/91614#获取会话内容存档开启成员列表
type RespGetPermitUserListMsgaudit struct {
CommonResp
// Ids 设置在开启范围内的成员的userid列表
Ids []string `json:"ids"`
}

var _ bodyer = RespGetPermitUserListMsgaudit{}

func (x RespGetPermitUserListMsgaudit) intoBody() ([]byte, error) {
result, err := json.Marshal(x)
if err != nil {
return nil, err
}
return result, nil
}

// ExecGetPermitUserListMsgaudit 获取会话内容存档开启成员列表
// 文档:https://developer.work.weixin.qq.com/document/path/91614#获取会话内容存档开启成员列表
func (c *ApiClient) ExecGetPermitUserListMsgaudit(req ReqGetPermitUserListMsgaudit) (RespGetPermitUserListMsgaudit, error) {
var resp RespGetPermitUserListMsgaudit
err := c.executeWXApiPost("/cgi-bin/msgaudit/get_permit_user_list", req, &resp, true)
if err != nil {
return RespGetPermitUserListMsgaudit{}, err
}
if bizErr := resp.TryIntoErr(); bizErr != nil {
return RespGetPermitUserListMsgaudit{}, bizErr
}
return resp, nil
}
Loading