feat: ClientRealnameHandler 集成异步触发实名检查
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
@@ -11,10 +13,13 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetService "github.com/break/junhong_cmp_fiber/internal/service/asset"
|
||||
pollingSvc "github.com/break/junhong_cmp_fiber/internal/service/polling"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
pkgMiddleware "github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
@@ -30,6 +35,7 @@ type ClientRealnameHandler struct {
|
||||
carrierStore *postgres.CarrierStore
|
||||
gatewayClient *gateway.Client
|
||||
logger *zap.Logger
|
||||
manualTriggerSvc *pollingSvc.ManualTriggerService // 手动触发服务(可为nil,nil时跳过自动触发)
|
||||
}
|
||||
|
||||
// NewClientRealnameHandler 创建 C 端实名认证处理器
|
||||
@@ -41,6 +47,7 @@ func NewClientRealnameHandler(
|
||||
carrierStore *postgres.CarrierStore,
|
||||
gatewayClient *gateway.Client,
|
||||
logger *zap.Logger,
|
||||
manualTriggerSvc *pollingSvc.ManualTriggerService, // 可为nil
|
||||
) *ClientRealnameHandler {
|
||||
return &ClientRealnameHandler{
|
||||
assetService: assetSvc,
|
||||
@@ -50,6 +57,7 @@ func NewClientRealnameHandler(
|
||||
carrierStore: carrierStore,
|
||||
gatewayClient: gatewayClient,
|
||||
logger: logger,
|
||||
manualTriggerSvc: manualTriggerSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,34 +102,9 @@ func (h *ClientRealnameHandler) GetRealnameLink(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 5. 定位目标卡(3 条路径)
|
||||
var targetCard *model.IotCard
|
||||
switch {
|
||||
case asset.AssetType == "card":
|
||||
// 路径 1:资产本身就是卡,直接使用
|
||||
card, cardErr := h.iotCardStore.GetByID(ctx, asset.AssetID)
|
||||
if cardErr != nil {
|
||||
return errors.New(errors.CodeIotCardNotFound, "卡信息查询失败")
|
||||
}
|
||||
targetCard = card
|
||||
|
||||
case asset.AssetType == "device" && req.ICCID != "":
|
||||
// 路径 2:资产是设备,指定了 ICCID,从设备绑定中查找该卡
|
||||
card, cardErr := h.findCardInDeviceBindings(c, asset.AssetID, req.ICCID)
|
||||
if cardErr != nil {
|
||||
return cardErr
|
||||
}
|
||||
targetCard = card
|
||||
|
||||
case asset.AssetType == "device":
|
||||
// 路径 3:资产是设备,未指定 ICCID,取第一张绑定卡(按插槽位置排序)
|
||||
card, cardErr := h.findFirstBoundCard(c, asset.AssetID)
|
||||
if cardErr != nil {
|
||||
return cardErr
|
||||
}
|
||||
targetCard = card
|
||||
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidParam, "不支持的资产类型")
|
||||
targetCard, err := h.resolveTargetCard(c, asset, req.ICCID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 6. 检查实名状态
|
||||
@@ -138,50 +121,83 @@ func (h *ClientRealnameHandler) GetRealnameLink(c *fiber.Ctx) error {
|
||||
return errors.New(errors.CodeCarrierNotFound, "运营商信息查询失败")
|
||||
}
|
||||
|
||||
resp := &dto.RealnimeLinkResponse{
|
||||
CardInfo: dto.CardInfoBrief{
|
||||
ICCID: targetCard.ICCID,
|
||||
MSISDN: targetCard.MSISDN,
|
||||
VirtualNo: targetCard.VirtualNo,
|
||||
},
|
||||
resp, err := h.buildRealnameResponse(ctx, targetCard, carrier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 异步触发实名检查,提升检测优先级;失败不影响主流程
|
||||
if h.manualTriggerSvc != nil && config.Get().PollingAutoTrigger.EnableAutoTrigger {
|
||||
systemUserID := uint(config.Get().PollingAutoTrigger.AutoTriggerSystemUserID)
|
||||
go h.triggerRealnameCheck(targetCard.ID, customerID, targetCard.ICCID, systemUserID)
|
||||
}
|
||||
|
||||
return response.Success(c, resp)
|
||||
}
|
||||
|
||||
// resolveTargetCard 根据资产类型和ICCID定位目标卡
|
||||
// 支持三条路径:直接卡资产、设备+指定ICCID、设备取第一张绑定卡
|
||||
func (h *ClientRealnameHandler) resolveTargetCard(c *fiber.Ctx, asset *dto.AssetResolveResponse, iccid string) (*model.IotCard, error) {
|
||||
switch {
|
||||
case asset.AssetType == "card":
|
||||
// 路径 1:资产本身就是卡,直接使用
|
||||
card, cardErr := h.iotCardStore.GetByID(c.UserContext(), asset.AssetID)
|
||||
if cardErr != nil {
|
||||
return nil, errors.New(errors.CodeIotCardNotFound, "卡信息查询失败")
|
||||
}
|
||||
return card, nil
|
||||
case asset.AssetType == "device" && iccid != "":
|
||||
// 路径 2:资产是设备,指定了 ICCID,从设备绑定中查找该卡
|
||||
return h.findCardInDeviceBindings(c, asset.AssetID, iccid)
|
||||
case asset.AssetType == "device":
|
||||
// 路径 3:资产是设备,未指定 ICCID,取第一张绑定卡(按插槽位置排序)
|
||||
return h.findFirstBoundCard(c, asset.AssetID)
|
||||
default:
|
||||
return nil, errors.New(errors.CodeInvalidParam, "不支持的资产类型")
|
||||
}
|
||||
}
|
||||
|
||||
// buildRealnameResponse 根据运营商实名链接类型构建响应
|
||||
func (h *ClientRealnameHandler) buildRealnameResponse(ctx context.Context, card *model.IotCard, carrier *model.Carrier) (*dto.RealnimeLinkResponse, error) {
|
||||
resp := &dto.RealnimeLinkResponse{
|
||||
CardInfo: dto.CardInfoBrief{
|
||||
ICCID: card.ICCID,
|
||||
MSISDN: card.MSISDN,
|
||||
VirtualNo: card.VirtualNo,
|
||||
},
|
||||
}
|
||||
switch carrier.RealnameLinkType {
|
||||
case constants.RealnameLinkTypeNone:
|
||||
// 该运营商不支持在线实名
|
||||
return errors.New(errors.CodeInvalidStatus, "该运营商暂不支持在线实名")
|
||||
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "该运营商暂不支持在线实名")
|
||||
case constants.RealnameLinkTypeTemplate:
|
||||
// 模板模式:替换占位符生成实名链接
|
||||
url := carrier.RealnameLinkTemplate
|
||||
url = strings.ReplaceAll(url, "{iccid}", targetCard.ICCID)
|
||||
url = strings.ReplaceAll(url, "{msisdn}", targetCard.MSISDN)
|
||||
url = strings.ReplaceAll(url, "{virtual_no}", targetCard.VirtualNo)
|
||||
url = strings.ReplaceAll(url, "{iccid}", card.ICCID)
|
||||
url = strings.ReplaceAll(url, "{msisdn}", card.MSISDN)
|
||||
url = strings.ReplaceAll(url, "{virtual_no}", card.VirtualNo)
|
||||
resp.RealnameMode = constants.RealnameLinkTypeTemplate
|
||||
resp.RealnameURL = url
|
||||
|
||||
case constants.RealnameLinkTypeGateway:
|
||||
// 网关模式:调用 Gateway 接口获取实名链接
|
||||
linkResp, gwErr := h.gatewayClient.GetRealnameLink(ctx, &gateway.CardStatusReq{
|
||||
CardNo: targetCard.ICCID,
|
||||
CardNo: card.ICCID,
|
||||
})
|
||||
if gwErr != nil {
|
||||
logger.GetAppLogger().Error("Gateway 获取实名链接失败",
|
||||
zap.String("iccid", targetCard.ICCID),
|
||||
zap.String("iccid", card.ICCID),
|
||||
zap.Error(gwErr))
|
||||
return errors.Wrap(errors.CodeGatewayError, gwErr, "获取实名链接失败")
|
||||
return nil, errors.Wrap(errors.CodeGatewayError, gwErr, "获取实名链接失败")
|
||||
}
|
||||
resp.RealnameMode = constants.RealnameLinkTypeGateway
|
||||
resp.RealnameURL = linkResp.URL
|
||||
|
||||
default:
|
||||
logger.GetAppLogger().Warn("未知的实名链接类型",
|
||||
zap.Uint("carrier_id", carrier.ID),
|
||||
zap.String("realname_link_type", carrier.RealnameLinkType))
|
||||
return errors.New(errors.CodeInvalidStatus, "该运营商暂不支持在线实名")
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "该运营商暂不支持在线实名")
|
||||
}
|
||||
|
||||
return response.Success(c, resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// findCardInDeviceBindings 在设备绑定中查找指定 ICCID 的卡
|
||||
@@ -247,3 +263,33 @@ func (h *ClientRealnameHandler) findFirstBoundCard(c *fiber.Ctx, deviceID uint)
|
||||
|
||||
return card, nil
|
||||
}
|
||||
|
||||
// triggerRealnameCheck 异步触发单卡实名检查
|
||||
// 在独立 goroutine 中调用,使用独立 context 避免 Fiber 请求 context 失效问题
|
||||
// 参数全部为值类型,不捕获请求相关指针
|
||||
func (h *ClientRealnameHandler) triggerRealnameCheck(cardID, customerID uint, iccid string, systemUserID uint) {
|
||||
// 必须使用独立 context,禁止复用 Fiber 请求 context(请求返回后即失效)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 使用平台用户身份构建 context,绕过卡归属权限检查
|
||||
// 注意:使用 UserTypePlatform 而非 UserTypeSuperAdmin,SuperAdmin 不受日限制约束
|
||||
sysCtx := pkgMiddleware.SetUserContext(ctx, &pkgMiddleware.UserContextInfo{
|
||||
UserID: systemUserID,
|
||||
UserType: constants.UserTypePlatform,
|
||||
})
|
||||
|
||||
err := h.manualTriggerSvc.TriggerSingle(sysCtx, cardID, constants.TaskTypePollingRealname, systemUserID)
|
||||
if err != nil {
|
||||
h.logger.Warn("自动触发实名检查失败",
|
||||
zap.Uint("customer_id", customerID),
|
||||
zap.String("iccid", iccid),
|
||||
zap.Uint("card_id", cardID),
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
h.logger.Info("自动触发实名检查成功",
|
||||
zap.Uint("customer_id", customerID),
|
||||
zap.String("iccid", iccid),
|
||||
zap.Uint("card_id", cardID))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user