七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s

This commit is contained in:
2026-07-25 17:06:58 +08:00
parent ad9f613dd6
commit 73f5125d3d
249 changed files with 17137 additions and 877 deletions

View 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)