七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
195
internal/infrastructure/wecom/approval_info_client.go
Normal file
195
internal/infrastructure/wecom/approval_info_client.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package wecom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// ApprovalInfoQuery 描述按审批单提交时间查询审批单号的过滤条件。
|
||||
type ApprovalInfoQuery struct {
|
||||
ApplicationID uint
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
TemplateID string
|
||||
CreatorUserID string
|
||||
Cursor string
|
||||
Size int
|
||||
}
|
||||
|
||||
// ApprovalInfoPage 是企业微信批量审批单号接口的一页结果。
|
||||
type ApprovalInfoPage struct {
|
||||
SPNos []string
|
||||
NextCursor string
|
||||
}
|
||||
|
||||
// ApprovalInfoClient 按提交时间窗批量获取企业微信审批单号。
|
||||
type ApprovalInfoClient struct {
|
||||
tokens DirectoryTokenProvider
|
||||
integration TokenIntegrationLog
|
||||
httpClient *http.Client
|
||||
baseURL string
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewApprovalInfoClient 创建企业微信批量审批单号客户端。
|
||||
func NewApprovalInfoClient(tokens DirectoryTokenProvider, integration TokenIntegrationLog, baseURL string, timeout time.Duration) *ApprovalInfoClient {
|
||||
if timeout <= 0 {
|
||||
timeout = constants.WeComDefaultHTTPTimeout
|
||||
}
|
||||
return &ApprovalInfoClient{
|
||||
tokens: tokens, integration: integration, httpClient: &http.Client{Timeout: timeout},
|
||||
baseURL: strings.TrimRight(baseURL, "/"), now: time.Now,
|
||||
}
|
||||
}
|
||||
|
||||
// List 获取一页审批单号,并为每次真实外呼写入 Integration Log。
|
||||
func (c *ApprovalInfoClient) List(ctx context.Context, input ApprovalInfoQuery) (ApprovalInfoPage, error) {
|
||||
if c == nil || c.tokens == nil || c.integration == nil || c.httpClient == nil {
|
||||
return ApprovalInfoPage{}, errors.New(errors.CodeServiceUnavailable, "企业微信批量审批单号客户端未配置")
|
||||
}
|
||||
if err := validateApprovalInfoQuery(input); err != nil {
|
||||
return ApprovalInfoPage{}, err
|
||||
}
|
||||
token, err := c.tokens.GetAccessToken(ctx, input.ApplicationID)
|
||||
if err != nil {
|
||||
return ApprovalInfoPage{}, err
|
||||
}
|
||||
request, err := c.newRequest(ctx, token, input)
|
||||
if err != nil {
|
||||
return ApprovalInfoPage{}, err
|
||||
}
|
||||
resourceID := strconv.FormatUint(uint64(input.ApplicationID), 10)
|
||||
attempt, err := c.integration.Start(ctx, integrationlog.Attempt{
|
||||
Provider: constants.IntegrationProviderWeCom, Direction: constants.IntegrationDirectionOutbound,
|
||||
Operation: constants.IntegrationOperationWeComApprovalInfo, ResourceType: constants.WeComApprovalInstanceResourceType,
|
||||
ResourceID: &resourceID, RequestSummary: map[string]any{
|
||||
"application_id": input.ApplicationID, "starttime": input.StartTime.Unix(), "endtime": input.EndTime.Unix(),
|
||||
"template_id": input.TemplateID, "creator_userid": input.CreatorUserID, "size": input.Size,
|
||||
"cursor_present": strings.TrimSpace(input.Cursor) != "",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return ApprovalInfoPage{}, err
|
||||
}
|
||||
startedAt := c.now()
|
||||
response, err := c.httpClient.Do(request)
|
||||
if err != nil {
|
||||
return ApprovalInfoPage{}, c.completeFailure(ctx, attempt.IntegrationID, 0, "request_failed", "企业微信批量审批单号请求失败", startedAt)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := io.ReadAll(io.LimitReader(response.Body, constants.WeComMaxResponseBodyBytes+1))
|
||||
if err != nil || int64(len(body)) > constants.WeComMaxResponseBodyBytes {
|
||||
return ApprovalInfoPage{}, c.completeFailure(ctx, attempt.IntegrationID, response.StatusCode, "invalid_response", "企业微信批量审批单号响应无效", startedAt)
|
||||
}
|
||||
var result struct {
|
||||
ErrCode int64 `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
SPNoList []string `json:"sp_no_list"`
|
||||
NewNextCursor string `json:"new_next_cursor"`
|
||||
}
|
||||
if err := sonic.Unmarshal(body, &result); err != nil {
|
||||
return ApprovalInfoPage{}, c.completeFailure(ctx, attempt.IntegrationID, response.StatusCode, "invalid_response", "企业微信批量审批单号响应无效", startedAt)
|
||||
}
|
||||
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices || result.ErrCode != 0 {
|
||||
return ApprovalInfoPage{}, c.completeFailure(ctx, attempt.IntegrationID, response.StatusCode, strconv.FormatInt(result.ErrCode, 10), result.ErrMsg, startedAt)
|
||||
}
|
||||
spNos := normalizeSPNos(result.SPNoList)
|
||||
_, err = c.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultSuccess, HTTPStatus: response.StatusCode,
|
||||
ProviderCode: strconv.FormatInt(result.ErrCode, 10), ProviderMessage: result.ErrMsg,
|
||||
ResponseSummary: map[string]any{
|
||||
"sp_no_count": len(spNos), "next_cursor_present": strings.TrimSpace(result.NewNextCursor) != "",
|
||||
},
|
||||
DurationMS: c.now().Sub(startedAt).Milliseconds(),
|
||||
})
|
||||
return ApprovalInfoPage{SPNos: spNos, NextCursor: strings.TrimSpace(result.NewNextCursor)}, err
|
||||
}
|
||||
|
||||
func validateApprovalInfoQuery(input ApprovalInfoQuery) error {
|
||||
if input.ApplicationID == 0 || input.StartTime.IsZero() || input.EndTime.IsZero() || !input.StartTime.Before(input.EndTime) {
|
||||
return errors.New(errors.CodeInvalidParam, "企业微信批量审批单号查询参数无效")
|
||||
}
|
||||
if input.EndTime.Sub(input.StartTime) > constants.WeComApprovalInfoMaxWindow {
|
||||
return errors.New(errors.CodeInvalidParam, "企业微信批量审批单号查询时间跨度不能超过 31 天")
|
||||
}
|
||||
if input.Size <= 0 || input.Size > constants.WeComApprovalInfoMaxPageSize {
|
||||
return errors.New(errors.CodeInvalidParam, "企业微信批量审批单号查询每页数量无效")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newRequest 组装企业微信批量审批单号请求,筛选值仅来自本地稳定提交快照。
|
||||
func (c *ApprovalInfoClient) newRequest(ctx context.Context, token string, input ApprovalInfoQuery) (*http.Request, error) {
|
||||
endpoint, err := url.Parse(c.baseURL + "/cgi-bin/oa/getapprovalinfo")
|
||||
if err != nil || endpoint.Scheme == "" || endpoint.Host == "" {
|
||||
return nil, errors.New(errors.CodeWeComCredentialInvalid, "企业微信 API 地址配置无效")
|
||||
}
|
||||
query := endpoint.Query()
|
||||
query.Set("access_token", token)
|
||||
endpoint.RawQuery = query.Encode()
|
||||
filters := make([]map[string]string, 0, 2)
|
||||
if value := strings.TrimSpace(input.TemplateID); value != "" {
|
||||
filters = append(filters, map[string]string{"key": "template_id", "value": value})
|
||||
}
|
||||
if value := strings.TrimSpace(input.CreatorUserID); value != "" {
|
||||
filters = append(filters, map[string]string{"key": "creator", "value": value})
|
||||
}
|
||||
payload := map[string]any{
|
||||
"starttime": input.StartTime.Unix(), "endtime": input.EndTime.Unix(),
|
||||
"new_cursor": strings.TrimSpace(input.Cursor), "size": input.Size, "filters": filters,
|
||||
}
|
||||
body, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "编码企业微信批量审批单号请求失败")
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建企业微信批量审批单号请求失败")
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (c *ApprovalInfoClient) completeFailure(ctx context.Context, integrationID string, status int, providerCode, message string, startedAt time.Time) error {
|
||||
if strings.TrimSpace(message) == "" {
|
||||
message = "企业微信批量审批单号接口返回失败"
|
||||
}
|
||||
_, err := c.integration.Complete(ctx, integrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultFailed, HTTPStatus: status, ProviderCode: providerCode,
|
||||
ProviderMessage: message, ResponseSummary: map[string]any{"success": false},
|
||||
DurationMS: c.now().Sub(startedAt).Milliseconds(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(errors.CodeServiceUnavailable, "获取企业微信审批单号失败")
|
||||
}
|
||||
|
||||
func normalizeSPNos(values []string) []string {
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[value]; exists {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user