All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
201 lines
6.8 KiB
Go
201 lines
6.8 KiB
Go
package wecom
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/bytedance/sonic"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
approvalquery "github.com/break/junhong_cmp_fiber/internal/query/approval"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// ApprovalApproverProjection 是企微审批节点成员到系统账号的可选映射。
|
|
type ApprovalApproverProjection struct {
|
|
WeComUserID string `json:"wecom_userid"`
|
|
AccountID *uint `json:"account_id,omitempty"`
|
|
AccountName string `json:"account_name"`
|
|
}
|
|
|
|
// ApprovalChannelProjection 是通用审批 Query 返回的企微本地只读扩展。
|
|
type ApprovalChannelProjection struct {
|
|
SPNo string `json:"sp_no"`
|
|
SPStatus int `json:"sp_status"`
|
|
Approvers []ApprovalApproverProjection `json:"approvers"`
|
|
}
|
|
|
|
// ApprovalProjectionResolver 从已同步详情快照批量投影审批人,不调用企业微信接口。
|
|
type ApprovalProjectionResolver struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewApprovalProjectionResolver 创建企业微信审批读取投影 Resolver。
|
|
func NewApprovalProjectionResolver(db *gorm.DB) *ApprovalProjectionResolver {
|
|
return &ApprovalProjectionResolver{db: db}
|
|
}
|
|
|
|
// Resolve 按当前页审批实例批量读取快照,并批量映射同企业下的系统账号。
|
|
func (r *ApprovalProjectionResolver) Resolve(
|
|
ctx context.Context,
|
|
_ approvalquery.Viewer,
|
|
references []approvalquery.ExtensionReference,
|
|
) (map[uint]any, error) {
|
|
result := make(map[uint]any)
|
|
if len(references) == 0 {
|
|
return result, nil
|
|
}
|
|
if r == nil || r.db == nil {
|
|
return nil, errors.New(errors.CodeInternalError, "企业微信审批读取投影未配置")
|
|
}
|
|
instanceIDs := weComExtensionInstanceIDs(references)
|
|
if len(instanceIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
var contexts []model.WeComApprovalContext
|
|
if err := r.db.WithContext(ctx).Where("approval_instance_id IN ?", instanceIDs).Find(&contexts).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询企业微信审批详情快照失败")
|
|
}
|
|
applicationIDs := make([]uint, 0, len(contexts))
|
|
for _, channelContext := range contexts {
|
|
applicationIDs = append(applicationIDs, channelContext.ApplicationID)
|
|
}
|
|
corpByApplication, err := r.loadApplicationCorps(ctx, applicationIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userIDsByCorp := make(map[string][]string)
|
|
approversByInstance := make(map[uint][]string, len(contexts))
|
|
for _, channelContext := range contexts {
|
|
userIDs := approvalNodeUserIDs(channelContext.LatestDetailSnapshot)
|
|
approversByInstance[channelContext.ApprovalInstanceID] = userIDs
|
|
corpID := corpByApplication[channelContext.ApplicationID]
|
|
userIDsByCorp[corpID] = append(userIDsByCorp[corpID], userIDs...)
|
|
}
|
|
accounts, err := r.loadBoundAccounts(ctx, userIDsByCorp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, channelContext := range contexts {
|
|
corpID := corpByApplication[channelContext.ApplicationID]
|
|
approvers := make([]ApprovalApproverProjection, 0, len(approversByInstance[channelContext.ApprovalInstanceID]))
|
|
for _, userID := range approversByInstance[channelContext.ApprovalInstanceID] {
|
|
item := ApprovalApproverProjection{WeComUserID: userID}
|
|
if account, exists := accounts[weComAccountKey(corpID, userID)]; exists {
|
|
accountID := account.ID
|
|
item.AccountID = &accountID
|
|
item.AccountName = account.Username
|
|
}
|
|
approvers = append(approvers, item)
|
|
}
|
|
result[channelContext.ApprovalInstanceID] = ApprovalChannelProjection{
|
|
SPNo: channelContext.SPNo, SPStatus: channelContext.LatestSPStatus, Approvers: approvers,
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func weComExtensionInstanceIDs(references []approvalquery.ExtensionReference) []uint {
|
|
seen := make(map[uint]struct{}, len(references))
|
|
ids := make([]uint, 0, len(references))
|
|
for _, reference := range references {
|
|
if reference.InstanceID == 0 || reference.Provider != constants.IntegrationProviderWeCom {
|
|
continue
|
|
}
|
|
if _, exists := seen[reference.InstanceID]; exists {
|
|
continue
|
|
}
|
|
seen[reference.InstanceID] = struct{}{}
|
|
ids = append(ids, reference.InstanceID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (r *ApprovalProjectionResolver) loadApplicationCorps(ctx context.Context, applicationIDs []uint) (map[uint]string, error) {
|
|
result := make(map[uint]string)
|
|
if len(applicationIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
var applications []model.WeComApplication
|
|
if err := r.db.WithContext(ctx).Select("id", "corp_id").Where("id IN ?", applicationIDs).Find(&applications).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询企业微信应用企业标识失败")
|
|
}
|
|
for _, application := range applications {
|
|
result[application.ID] = application.CorpID
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *ApprovalProjectionResolver) loadBoundAccounts(ctx context.Context, userIDsByCorp map[string][]string) (map[string]model.Account, error) {
|
|
result := make(map[string]model.Account)
|
|
query := r.db.WithContext(ctx).Model(&model.Account{}).Where("1 = 0")
|
|
hasCondition := false
|
|
for corpID, userIDs := range userIDsByCorp {
|
|
corpID = strings.TrimSpace(corpID)
|
|
userIDs = uniqueStrings(userIDs)
|
|
if corpID == "" || len(userIDs) == 0 {
|
|
continue
|
|
}
|
|
query = query.Or("wecom_corp_id = ? AND wecom_userid IN ? AND status = ?", corpID, userIDs, constants.StatusEnabled)
|
|
hasCondition = true
|
|
}
|
|
if !hasCondition {
|
|
return result, nil
|
|
}
|
|
var accounts []model.Account
|
|
if err := query.Select("id", "username", "wecom_corp_id", "wecom_userid").Find(&accounts).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量映射企业微信审批人账号失败")
|
|
}
|
|
for _, account := range accounts {
|
|
result[weComAccountKey(account.WeComCorpID, account.WeComUserID)] = account
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func approvalNodeUserIDs(snapshot []byte) []string {
|
|
var detail struct {
|
|
SPRecords []struct {
|
|
Details []struct {
|
|
Approver struct {
|
|
UserID string `json:"userid"`
|
|
} `json:"approver"`
|
|
} `json:"details"`
|
|
} `json:"sp_record"`
|
|
}
|
|
if sonic.Unmarshal(snapshot, &detail) != nil {
|
|
return []string{}
|
|
}
|
|
values := make([]string, 0)
|
|
for _, record := range detail.SPRecords {
|
|
for _, node := range record.Details {
|
|
values = append(values, node.Approver.UserID)
|
|
}
|
|
}
|
|
return uniqueStrings(values)
|
|
}
|
|
|
|
func uniqueStrings(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
|
|
}
|
|
|
|
func weComAccountKey(corpID, userID string) string {
|
|
return strings.TrimSpace(corpID) + "\x00" + strings.TrimSpace(userID)
|
|
}
|
|
|
|
var _ approvalquery.ChannelExtensionResolver = (*ApprovalProjectionResolver)(nil)
|