304 lines
8.8 KiB
Go
304 lines
8.8 KiB
Go
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
|
||
}
|