七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
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:
200
internal/infrastructure/wecom/approval_projection.go
Normal file
200
internal/infrastructure/wecom/approval_projection.go
Normal file
@@ -0,0 +1,200 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user