Files
ycc-proxy-server/app/main/api/internal/service/querywhitelistsync.go

304 lines
8.8 KiB
Go
Raw Normal View History

2026-06-20 15:13:58 +08:00
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
}