Files
ycc-proxy-server/app/main/api/internal/service/querywhitelistsync.go
2026-06-20 15:13:58 +08:00

304 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"database/sql"
"encoding/json"
"regexp"
"strings"
"ycc-server/app/main/api/internal/config"
tianyuanapi "ycc-server/app/main/api/internal/service/tianyuanapi_sdk"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
var queryWhitelistIdCardPattern = regexp.MustCompile(`^\d{17}[\dXx]$`)
const (
tianyuanQueryWhitelistSuccessCode = 0
tianyuanQueryWhitelistExistsCode = 1013
tianyuanQueryWhitelistNotFoundCode = 1014
tianyuanQueryWhitelistLocalFailCode = -1
)
// QueryWhitelistSyncService 天远查询白名单同步服务
type QueryWhitelistSyncService struct {
config config.Config
client *tianyuanapi.Client
opLogModel model.QueryWhitelistOpLogModel
}
func NewQueryWhitelistSyncService(
c config.Config,
client *tianyuanapi.Client,
opLogModel model.QueryWhitelistOpLogModel,
) *QueryWhitelistSyncService {
return &QueryWhitelistSyncService{
config: c,
client: client,
opLogModel: opLogModel,
}
}
// TrySync 尽力同步天远查询白名单,失败仅记操作日志,不影响主流程
func (s *QueryWhitelistSyncService) TrySync(
ctx context.Context,
name, idCard string,
apiCodes []string,
remark string,
) {
if err := s.Sync(ctx, name, idCard, apiCodes, remark); err != nil {
logx.WithContext(ctx).Errorf("天远查询白名单同步失败(不影响主流程): %v", err)
}
}
// TrySyncWhitelistOrder 尽力同步白名单订单中的模块到天远
func (s *QueryWhitelistSyncService) TrySyncWhitelistOrder(
ctx context.Context,
whitelistOrder *model.WhitelistOrder,
items []*model.WhitelistOrderItem,
remark string,
) {
if whitelistOrder == nil || len(items) == 0 {
return
}
s.TrySync(ctx, "*", whitelistOrder.IdCard, collectWhitelistOrderApiCodes(items), remark)
}
// Sync 将产品编码同步到天远查询白名单(先追加,规则不存在则创建)
func (s *QueryWhitelistSyncService) Sync(
ctx context.Context,
name, idCard string,
apiCodes []string,
remark string,
) error {
if err := validateQueryWhitelistSyncReq(idCard, apiCodes); err != nil {
s.recordOpLogFailure(ctx, "sync", name, idCard, apiCodes, remark, err.Error(), tianyuanQueryWhitelistLocalFailCode)
return err
}
appendResp, err := s.callMgmt(
"/api/v1/query-whitelist/entries/append",
name,
idCard,
apiCodes,
remark,
)
if err != nil {
s.recordOpLogFailure(ctx, "append", name, idCard, apiCodes, remark, err.Error(), tianyuanQueryWhitelistLocalFailCode)
return err
}
if isTianyuanQueryWhitelistSuccess(appendResp.Code) {
s.recordOpLog(ctx, "append", name, idCard, apiCodes, remark, appendResp)
return nil
}
if appendResp.Code != tianyuanQueryWhitelistNotFoundCode {
s.recordOpLog(ctx, "append", name, idCard, apiCodes, remark, appendResp)
return errors.Wrapf(xerr.NewErrMsg(appendResp.Message), "天远查询白名单同步失败(code=%d)", appendResp.Code)
}
createResp, err := s.callMgmt(
"/api/v1/query-whitelist/entries",
name,
idCard,
apiCodes,
remark,
)
if err != nil {
s.recordOpLogFailure(ctx, "create", name, idCard, apiCodes, remark, err.Error(), tianyuanQueryWhitelistLocalFailCode)
return err
}
if isTianyuanQueryWhitelistSuccess(createResp.Code) {
s.recordOpLog(ctx, "create", name, idCard, apiCodes, remark, createResp)
return nil
}
if createResp.Code == tianyuanQueryWhitelistExistsCode {
retryResp, retryErr := s.callMgmt(
"/api/v1/query-whitelist/entries/append",
name,
idCard,
apiCodes,
remark,
)
if retryErr != nil {
s.recordOpLogFailure(ctx, "append", name, idCard, apiCodes, remark, retryErr.Error(), tianyuanQueryWhitelistLocalFailCode)
return retryErr
}
s.recordOpLog(ctx, "append", name, idCard, apiCodes, remark, retryResp)
if isTianyuanQueryWhitelistSuccess(retryResp.Code) {
return nil
}
return errors.Wrapf(xerr.NewErrMsg(retryResp.Message), "天远查询白名单同步失败(code=%d)", retryResp.Code)
}
s.recordOpLog(ctx, "create", name, idCard, apiCodes, remark, createResp)
return errors.Wrapf(xerr.NewErrMsg(createResp.Message), "天远查询白名单同步失败(code=%d)", createResp.Code)
}
func collectWhitelistOrderApiCodes(items []*model.WhitelistOrderItem) []string {
codes := make([]string, 0, len(items))
seen := make(map[string]bool, len(items))
for _, item := range items {
if item == nil || item.FeatureApiId == "" || seen[item.FeatureApiId] {
continue
}
seen[item.FeatureApiId] = true
codes = append(codes, item.FeatureApiId)
}
return codes
}
func validateQueryWhitelistSyncReq(idCard string, apiCodes []string) error {
if strings.TrimSpace(idCard) == "" {
return errors.Wrapf(xerr.NewErrMsg("身份证号不能为空"), "")
}
if !queryWhitelistIdCardPattern.MatchString(strings.TrimSpace(idCard)) {
return errors.Wrapf(xerr.NewErrMsg("身份证号格式不正确需为18位中国大陆身份证号"), "")
}
if len(apiCodes) == 0 {
return errors.Wrapf(xerr.NewErrMsg("请至少选择一个产品编码"), "")
}
for _, code := range apiCodes {
if strings.TrimSpace(code) == "" {
return errors.Wrapf(xerr.NewErrMsg("产品编码不能为空"), "")
}
if code == "*" {
return errors.Wrapf(xerr.NewErrMsg("产品编码不支持通配符 *"), "")
}
}
return nil
}
func (s *QueryWhitelistSyncService) callMgmt(
apiPath string,
name, idCard string,
apiCodes []string,
remark string,
) (*tianyuanapi.QueryWhitelistMgmtResponse, error) {
mgmtKey := s.config.Tianyuanapi.WhitelistMgmtKey
if mgmtKey == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("查询白名单管理密钥未配置,请在服务端配置 Tianyuanapi.WhitelistMgmtKey"), "")
}
if s.client == nil {
return nil, errors.Wrapf(xerr.NewErrMsg("天远 API 客户端未初始化"), "")
}
if strings.TrimSpace(name) == "" {
name = "*"
}
payload := map[string]interface{}{
"name": name,
"id_card": strings.TrimSpace(idCard),
"api_codes": apiCodes,
}
if strings.TrimSpace(remark) != "" {
payload["remark"] = strings.TrimSpace(remark)
}
return s.client.CallQueryWhitelistMgmt(apiPath, payload, mgmtKey)
}
func (s *QueryWhitelistSyncService) recordOpLogFailure(
ctx context.Context,
action, name, idCard string,
apiCodes []string,
remark, message string,
code int64,
) {
s.insertOpLog(ctx, action, name, idCard, apiCodes, remark, code, message, "", nil)
}
func (s *QueryWhitelistSyncService) recordOpLog(
ctx context.Context,
action, name, idCard string,
apiCodes []string,
remark string,
apiResp *tianyuanapi.QueryWhitelistMgmtResponse,
) {
if apiResp == nil {
return
}
s.insertOpLog(ctx, action, name, idCard, apiCodes, remark, int64(apiResp.Code), apiResp.Message, apiResp.TransactionID, apiResp.Entry)
}
func (s *QueryWhitelistSyncService) insertOpLog(
ctx context.Context,
action, name, idCard string,
apiCodes []string,
remark string,
tianyuanCode int64,
tianyuanMessage, transactionID string,
entry *tianyuanapi.QueryWhitelistEntry,
) {
if s.opLogModel == nil {
return
}
operatorUserId, err := ctxdata.GetUidFromCtx(ctx)
if err != nil {
logx.WithContext(ctx).Errorf("记录查询白名单操作日志失败: 获取操作人ID失败, %v", err)
operatorUserId = ""
}
apiCodesJSON, marshalErr := json.Marshal(apiCodes)
if marshalErr != nil {
logx.WithContext(ctx).Errorf("记录查询白名单操作日志失败: 序列化 api_codes 失败, %v", marshalErr)
return
}
log := &model.QueryWhitelistOpLog{
AdminUserId: operatorUserId,
Action: action,
Name: resolveQueryWhitelistName(name),
IdCard: strings.TrimSpace(idCard),
ApiCodes: string(apiCodesJSON),
TianyuanCode: tianyuanCode,
}
if strings.TrimSpace(remark) != "" {
log.Remark = sql.NullString{String: strings.TrimSpace(remark), Valid: true}
}
if tianyuanMessage != "" {
log.TianyuanMessage = sql.NullString{String: tianyuanMessage, Valid: true}
}
if transactionID != "" {
log.TransactionId = sql.NullString{String: transactionID, Valid: true}
}
if entry != nil {
if entry.IdCardMasked != "" {
log.IdCardMasked = sql.NullString{String: entry.IdCardMasked, Valid: true}
}
if entry.ID != "" {
log.EntryId = sql.NullString{String: entry.ID, Valid: true}
}
if entry.Status != "" {
log.EntryStatus = sql.NullString{String: entry.Status, Valid: true}
}
if len(entry.ApiCodes) > 0 {
entryCodesJSON, _ := json.Marshal(entry.ApiCodes)
log.EntryApiCodes = sql.NullString{String: string(entryCodesJSON), Valid: true}
}
}
if _, insertErr := s.opLogModel.Insert(ctx, nil, log); insertErr != nil {
logx.WithContext(ctx).Errorf("记录查询白名单操作日志失败: %v", insertErr)
}
}
func resolveQueryWhitelistName(name string) string {
if strings.TrimSpace(name) == "" {
return "*"
}
return strings.TrimSpace(name)
}
func isTianyuanQueryWhitelistSuccess(code int) bool {
return code == tianyuanQueryWhitelistSuccessCode
}