Skip to content

Commit

Permalink
Merge branch 'release/1.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
xen0n committed Jun 5, 2023
2 parents 1831db9 + 37986bf commit 567c67e
Show file tree
Hide file tree
Showing 18 changed files with 427 additions and 295 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ jobs:
run: go build -v ./...

- name: Lint
uses: golangci/golangci-lint-action@v3.4.0
uses: golangci/golangci-lint-action@v3.5.0
with:
version: v1.52
version: v1.53

- name: Test
run: go test -v ./...
Expand Down
4 changes: 1 addition & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ linters:
- staticcheck
- unused
- gosimple
- structcheck
- varcheck
- ineffassign
- deadcode
- typecheck
# project additions
# keep this list alphabetically sorted
Expand All @@ -26,6 +23,7 @@ linters:
- nakedret
- nolintlint
- prealloc
- revive
- unconvert

linters-settings:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ CI 会在 `go1.17` 和 Go 的当前稳定版本、上一个稳定版本上跑测
* [x] 消息推送
- [x] 创建企业群发
- [ ] 获取企业的全部群发记录
- [ ] 发送新客户欢迎语
- [x] 发送新客户欢迎语
- [ ] 入群欢迎语素材管理

</details>
Expand Down
14 changes: 14 additions & 0 deletions apis.md.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 45 additions & 36 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"mime/multipart"
"net/url"
"sync"
)

// Workwx 企业微信客户端
Expand All @@ -18,13 +17,16 @@ type Workwx struct {
}

// WorkwxApp 企业微信客户端(分应用)
//
//nolint:revive // The (stuttering) name is part of public API, so cannot be fixed without a v2 bump
type WorkwxApp struct {
*Workwx

// CorpSecret 应用的凭证密钥,必填
CorpSecret string
// AgentID 应用 ID,必填
AgentID int64
AgentID int64

accessToken *token
jsapiTicket *token
jsapiTicketAgentConfig *token
Expand Down Expand Up @@ -52,18 +54,16 @@ func (c *Workwx) WithApp(corpSecret string, agentID int64) *WorkwxApp {

CorpSecret: corpSecret,
AgentID: agentID,

accessToken: &token{mutex: &sync.RWMutex{}},
jsapiTicket: &token{mutex: &sync.RWMutex{}},
jsapiTicketAgentConfig: &token{mutex: &sync.RWMutex{}},
}
app.accessToken.setGetTokenFunc(app.getAccessToken)
app.jsapiTicket.setGetTokenFunc(app.getJSAPITicket)
app.jsapiTicketAgentConfig.setGetTokenFunc(app.getJSAPITicketAgentConfig)

app.accessToken = newToken(c.opts.AccessTokenProvider, app.getAccessToken)
app.jsapiTicket = newToken(c.opts.JSAPITicketProvider, app.getJSAPITicket)
app.jsapiTicketAgentConfig = newToken(c.opts.JSAPITicketAgentConfigProvider, app.getJSAPITicketAgentConfig)

return &app
}

func (c *WorkwxApp) composeQyapiURL(path string, req interface{}) *url.URL {
func (c *WorkwxApp) composeQyapiURL(path string, req interface{}) (*url.URL, error) {
values := url.Values{}
if valuer, ok := req.(urlValuer); ok {
values = valuer.intoURLValues()
Expand All @@ -72,73 +72,81 @@ func (c *WorkwxApp) composeQyapiURL(path string, req interface{}) *url.URL {
// TODO: refactor
base, err := url.Parse(c.opts.QYAPIHost)
if err != nil {
// TODO: error_chain
panic(fmt.Sprintf("qyapiHost invalid: host=%s err=%+v", c.opts.QYAPIHost, err))
return nil, fmt.Errorf("qyapiHost invalid: host=%s err=%w", c.opts.QYAPIHost, err)
}

base.Path = path
base.RawQuery = values.Encode()

return base
return base, nil
}

func (c *WorkwxApp) composeQyapiURLWithToken(path string, req interface{}, withAccessToken bool) *url.URL {
url := c.composeQyapiURL(path, req)
func (c *WorkwxApp) composeQyapiURLWithToken(path string, req interface{}, withAccessToken bool) (*url.URL, error) {
url, err := c.composeQyapiURL(path, req)
if err != nil {
return nil, err
}

if !withAccessToken {
return url
return url, nil
}

tok, err := c.accessToken.getToken()
if err != nil {
return nil, err
}

q := url.Query()
q.Set("access_token", c.accessToken.getToken())
q.Set("access_token", tok)
url.RawQuery = q.Encode()

return url
return url, nil
}

func (c *WorkwxApp) executeQyapiGet(path string, req urlValuer, respObj interface{}, withAccessToken bool) error {
url := c.composeQyapiURLWithToken(path, req, withAccessToken)
url, err := c.composeQyapiURLWithToken(path, req, withAccessToken)
if err != nil {
return err
}
urlStr := url.String()

resp, err := c.opts.HTTP.Get(urlStr)
if err != nil {
// TODO: error_chain
return err
return makeRequestErr(err)
}
defer resp.Body.Close()

decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(respObj)
if err != nil {
// TODO: error_chain
return err
return makeRespUnmarshalErr(err)
}

return nil
}

func (c *WorkwxApp) executeQyapiJSONPost(path string, req bodyer, respObj interface{}, withAccessToken bool) error {
url := c.composeQyapiURLWithToken(path, req, withAccessToken)
url, err := c.composeQyapiURLWithToken(path, req, withAccessToken)
if err != nil {
return err
}
urlStr := url.String()

body, err := req.intoBody()
if err != nil {
// TODO: error_chain
return err
return makeReqMarshalErr(err)
}

resp, err := c.opts.HTTP.Post(urlStr, "application/json", bytes.NewReader(body))
if err != nil {
// TODO: error_chain
return err
return makeRequestErr(err)
}
defer resp.Body.Close()

decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(respObj)
if err != nil {
// TODO: error_chain
return err
return makeRespUnmarshalErr(err)
}

return nil
Expand All @@ -150,7 +158,10 @@ func (c *WorkwxApp) executeQyapiMediaUpload(
respObj interface{},
withAccessToken bool,
) error {
url := c.composeQyapiURLWithToken(path, req, withAccessToken)
url, err := c.composeQyapiURLWithToken(path, req, withAccessToken)
if err != nil {
return err
}
urlStr := url.String()

m := req.getMedia()
Expand All @@ -159,7 +170,7 @@ func (c *WorkwxApp) executeQyapiMediaUpload(
buf := bytes.Buffer{}
mw := multipart.NewWriter(&buf)

err := m.writeTo(mw)
err = m.writeTo(mw)
if err != nil {
return err
}
Expand All @@ -171,16 +182,14 @@ func (c *WorkwxApp) executeQyapiMediaUpload(

resp, err := c.opts.HTTP.Post(urlStr, mw.FormDataContentType(), &buf)
if err != nil {
// TODO: error_chain
return err
return makeRequestErr(err)
}
defer resp.Body.Close()

decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(respObj)
if err != nil {
// TODO: error_chain
return err
return makeRespUnmarshalErr(err)
}

return nil
Expand Down
68 changes: 64 additions & 4 deletions client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
const DefaultQYAPIHost = "https://qyapi.weixin.qq.com"

type options struct {
QYAPIHost string
HTTP *http.Client
QYAPIHost string
HTTP *http.Client
AccessTokenProvider ITokenProvider
JSAPITicketProvider ITokenProvider
JSAPITicketAgentConfigProvider ITokenProvider
}

// CtorOption 客户端对象构造参数
Expand All @@ -20,8 +23,11 @@ type CtorOption interface {
// impl Default for options
func defaultOptions() options {
return options{
QYAPIHost: DefaultQYAPIHost,
HTTP: &http.Client{},
QYAPIHost: DefaultQYAPIHost,
HTTP: &http.Client{},
AccessTokenProvider: nil,
JSAPITicketProvider: nil,
JSAPITicketAgentConfigProvider: nil,
}
}

Expand Down Expand Up @@ -62,3 +68,57 @@ var _ CtorOption = (*withHTTPClient)(nil)
func (x *withHTTPClient) applyTo(y *options) {
y.HTTP = x.x
}

//
//
//

type withAccessTokenProvider struct {
x ITokenProvider
}

func WithAccessTokenProvider(provider ITokenProvider) CtorOption {
return &withAccessTokenProvider{x: provider}
}

var _ CtorOption = (*withAccessTokenProvider)(nil)

func (x *withAccessTokenProvider) applyTo(y *options) {
y.AccessTokenProvider = x.x
}

//
//
//

type withJSAPITicketProvider struct {
x ITokenProvider
}

func WithJSAPITicketProvider(provider ITokenProvider) CtorOption {
return &withJSAPITicketProvider{x: provider}
}

var _ CtorOption = (*withJSAPITicketProvider)(nil)

func (x *withJSAPITicketProvider) applyTo(y *options) {
y.JSAPITicketProvider = x.x
}

//
//
//

type withJSAPITicketAgentConfigProvider struct {
x ITokenProvider
}

func WithJSAPITicketAgentConfigProvider(provider ITokenProvider) CtorOption {
return &withJSAPITicketAgentConfigProvider{x: provider}
}

var _ CtorOption = (*withJSAPITicketAgentConfigProvider)(nil)

func (x *withJSAPITicketAgentConfigProvider) applyTo(y *options) {
y.JSAPITicketAgentConfigProvider = x.x
}
1 change: 1 addition & 0 deletions docs/apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,4 @@ Name|Request Type|Response Type|Access Token|URL|Doc
Name|Request Type|Response Type|Access Token|URL|Doc
:---|------------|-------------|------------|:--|:--
`execAddMsgTemplate`|`reqAddMsgTemplateExternalContact`|`respAddMsgTemplateExternalContact`|+|`POST /cgi-bin/externalcontact/add_msg_template`|[创建企业群发](https://developer.work.weixin.qq.com/document/path/92135)
`execSendWelcomeMsg`|`reqSendWelcomeMsgExternalContact`|`respSendWelcomeMsgExternalContact`|+|`POST /cgi-bin/externalcontact/send_welcome_msg`|[发送新客户欢迎语](https://developer.work.weixin.qq.com/document/path/92137)
8 changes: 8 additions & 0 deletions docs/external_contact.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ Name|JSON|Type|Doc
`Text`|`text`|`Text`| 消息文本,最多4000个字节
`Attachments`|`attachments`|`[]Attachments`| 附件,最多支持添加9个附件

### `SendWelcomeMsgExternalContact` 发送新客户欢迎语请求参数

Name|JSON| Type |Doc
:---|:---|:----------------|:--
`WelcomeCode`|`welcome_code`| `string` | 通过添加外部联系人事件推送给企业的发送欢迎语的凭证,有效期为20秒
`Text`|`text`| `Text` | 消息文本,最多4000个字节
`Attachments`|`attachments`| `[]Attachments` | 附件,最多支持添加9个附件

### `Attachments` 附件

Name|JSON|Type|Doc
Expand Down
Loading

0 comments on commit 567c67e

Please sign in to comment.