This commit is contained in:
2026-01-12 16:43:08 +08:00
parent dc747139c9
commit 3c6e2683f5
110 changed files with 9630 additions and 481 deletions

View File

@@ -630,18 +630,18 @@ func (s *AgentService) getRebateConfigFloat(ctx context.Context, configKey strin
// giveRebate 发放返佣
func (s *AgentService) giveRebate(ctx context.Context, session sqlx.Session, agentId, sourceAgentId, orderId, productId string, amount float64, levelBonus int64, rebateType int64) error {
// 1. 创建返佣记录
rebate := &model.AgentRebate{
Id: uuid.NewString(),
AgentId: agentId,
SourceAgentId: sourceAgentId,
OrderId: orderId,
ProductId: productId,
RebateType: rebateType,
LevelBonus: float64(levelBonus), // 等级加成金额
RebateAmount: amount,
Status: 1, // 已发放
}
// 1. 创建返佣记录
rebate := &model.AgentRebate{
Id: uuid.NewString(),
AgentId: agentId,
SourceAgentId: sourceAgentId,
OrderId: orderId,
ProductId: productId,
RebateType: rebateType,
LevelBonus: float64(levelBonus), // 等级加成金额
RebateAmount: amount,
Status: 1, // 已发放
}
if _, err := s.AgentRebateModel.Insert(ctx, session, rebate); err != nil {
return errors.Wrapf(err, "创建返佣记录失败")
}
@@ -971,12 +971,12 @@ func (s *AgentService) reconnectToGrandparent(ctx context.Context, session sqlx.
}
// 创建新的关系连接到原上级的上级
relation := &model.AgentRelation{
Id: uuid.NewString(),
ParentId: grandparent.Id,
ChildId: agentId,
RelationType: 1, // 直接关系
}
relation := &model.AgentRelation{
Id: uuid.NewString(),
ParentId: grandparent.Id,
ChildId: agentId,
RelationType: 1, // 直接关系
}
if _, err := s.AgentRelationModel.Insert(ctx, session, relation); err != nil {
return errors.Wrapf(err, "创建新关系失败")
}
@@ -1073,3 +1073,201 @@ func (s *AgentService) GetUpgradeRebate(ctx context.Context, fromLevel, toLevel
}
return 0, nil
}
// CancelAgentCommission 撤销代理佣金和返佣(订单退款时调用)
// 功能:收回已发放的代理佣金和返佣,处理冻结任务
// 余额不足时允许余额为负数(欠款),通过提现限制控制
func (s *AgentService) CancelAgentCommission(ctx context.Context, orderId string) error {
// 1. 查找代理订单
agentOrder, err := s.AgentOrderModel.FindOneByOrderId(ctx, orderId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 不是代理订单,直接返回
return nil
}
return errors.Wrapf(err, "查询代理订单失败, orderId: %s", orderId)
}
// 2. 检查是否已处理(只有已处理的订单才需要收回)
if agentOrder.ProcessStatus != 1 {
logx.Infof("代理订单未处理,无需撤销佣金, orderId: %s, processStatus: %d", orderId, agentOrder.ProcessStatus)
return nil
}
// 3. 查找所有佣金记录
builder := s.AgentCommissionModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
commissions, err := s.AgentCommissionModel.FindAll(ctx, builder, "")
if err != nil {
return errors.Wrapf(err, "查询佣金记录失败, orderId: %s", orderId)
}
// 4. 查找所有返佣记录
rebateBuilder := s.AgentRebateModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
rebates, err := s.AgentRebateModel.FindAll(ctx, rebateBuilder, "")
if err != nil {
return errors.Wrapf(err, "查询返佣记录失败, orderId: %s", orderId)
}
// 5. 查找相关冻结任务
freezeBuilder := s.AgentFreezeTaskModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
freezeTasks, err := s.AgentFreezeTaskModel.FindAll(ctx, freezeBuilder, "")
if err != nil {
return errors.Wrapf(err, "查询冻结任务失败, orderId: %s", orderId)
}
// 6. 使用事务处理收回逻辑
return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
// 6.1 收回代理佣金
for _, commission := range commissions {
// 只处理已发放的佣金Status=1
if commission.Status != 1 {
logx.Infof("佣金记录状态不是已发放,跳过, commissionId: %s, status: %d", commission.Id, commission.Status)
continue
}
// 查找冻结任务(如果有)
var freezeTask *model.AgentFreezeTask
for _, task := range freezeTasks {
if task.CommissionId == commission.Id && task.Status == 1 {
freezeTask = task
break
}
}
// 扣除代理钱包余额
if err := s.deductCommissionFromWallet(transCtx, session, commission.AgentId, commission.Amount, freezeTask); err != nil {
return errors.Wrapf(err, "扣除代理佣金失败, agentId: %s, amount: %.2f", commission.AgentId, commission.Amount)
}
// 更新佣金记录状态为已撤销
commission.Status = 3 // 3=已取消
if err := s.AgentCommissionModel.UpdateWithVersion(transCtx, session, commission); err != nil {
return errors.Wrapf(err, "更新佣金记录状态失败, commissionId: %s", commission.Id)
}
// 如果有冻结任务,更新为已撤销
if freezeTask != nil {
freezeTask.Status = 3 // 3=已取消
if err := s.AgentFreezeTaskModel.UpdateWithVersion(transCtx, session, freezeTask); err != nil {
return errors.Wrapf(err, "更新冻结任务状态失败, freezeTaskId: %s", freezeTask.Id)
}
}
}
// 6.2 收回返佣
for _, rebate := range rebates {
// 只处理已发放的返佣Status=1
if rebate.Status != 1 {
logx.Infof("返佣记录状态不是已发放,跳过, rebateId: %s, status: %d", rebate.Id, rebate.Status)
continue
}
// 扣除上级代理钱包余额
if err := s.deductRebateFromWallet(transCtx, session, rebate.AgentId, rebate.RebateAmount); err != nil {
return errors.Wrapf(err, "扣除返佣失败, agentId: %s, amount: %.2f", rebate.AgentId, rebate.RebateAmount)
}
// 更新返佣记录状态为已撤销
rebate.Status = 3 // 3=已取消
if err := s.AgentRebateModel.UpdateWithVersion(transCtx, session, rebate); err != nil {
return errors.Wrapf(err, "更新返佣记录状态失败, rebateId: %s", rebate.Id)
}
}
// 6.3 更新代理订单备注(可选,记录撤销信息)
agentOrder.ProcessRemark = sql.NullString{
String: "订单退款,佣金和返佣已撤销",
Valid: true,
}
if err := s.AgentOrderModel.UpdateWithVersion(transCtx, session, agentOrder); err != nil {
logx.Errorf("更新代理订单备注失败, orderId: %s, err: %v", orderId, err)
// 不阻断流程,只记录日志
}
return nil
})
}
// deductCommissionFromWallet 从钱包扣除佣金(支持余额不足,允许负数)
func (s *AgentService) deductCommissionFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64, freezeTask *model.AgentFreezeTask) error {
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId)
}
// 计算可用余额
availableBalance := wallet.Balance + wallet.FrozenBalance
// 如果有冻结任务,需要先解冻
if freezeTask != nil && freezeTask.Status == 1 {
freezeAmount := freezeTask.FreezeAmount
// 解冻:从冻结余额转回可用余额
if wallet.FrozenBalance >= freezeAmount {
wallet.FrozenBalance -= freezeAmount
wallet.Balance += freezeAmount
} else {
// 冻结余额不足(异常情况),记录日志
logx.Errorf("冻结余额不足,异常情况, agentId: %s, frozenBalance: %.2f, freezeAmount: %.2f", agentId, wallet.FrozenBalance, freezeAmount)
wallet.Balance += wallet.FrozenBalance
wallet.FrozenBalance = 0
}
}
// 扣除佣金金额
if availableBalance >= amount {
// 余额充足:优先扣除冻结余额,再扣除可用余额
if wallet.FrozenBalance >= amount {
wallet.FrozenBalance -= amount
} else {
remaining := amount - wallet.FrozenBalance
wallet.FrozenBalance = 0
wallet.Balance -= remaining
}
} else {
// 余额不足:扣除所有可用余额,允许负数
wallet.Balance = wallet.Balance - (amount - wallet.FrozenBalance)
wallet.FrozenBalance = 0
// 记录日志:欠款金额
logx.Errorf("代理余额不足,产生欠款: agentId=%s, orderId=%s, debt=%.2f, balance=%.2f",
agentId, "", -wallet.Balance, wallet.Balance)
}
// TotalEarnings 始终减少(即使余额为负数)
wallet.TotalEarnings -= amount
if wallet.TotalEarnings < 0 {
wallet.TotalEarnings = 0 // 累计收益不能为负数
}
return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
}
// deductRebateFromWallet 从钱包扣除返佣(支持余额不足,允许负数)
func (s *AgentService) deductRebateFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64) error {
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId)
}
// 扣除返佣金额
if wallet.Balance >= amount {
// 余额充足:正常扣除
wallet.Balance -= amount
} else {
// 余额不足:扣除所有余额,允许负数
wallet.Balance -= amount
// 记录日志:欠款金额
logx.Errorf("代理返佣余额不足,产生欠款: agentId=%s, debt=%.2f, balance=%.2f",
agentId, -wallet.Balance, wallet.Balance)
}
// TotalEarnings 始终减少(即使余额为负数)
wallet.TotalEarnings -= amount
if wallet.TotalEarnings < 0 {
wallet.TotalEarnings = 0 // 累计收益不能为负数
}
return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
}

View File

@@ -0,0 +1,541 @@
package service
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"ycc-server/app/main/api/internal/config"
"ycc-server/app/main/model"
"ycc-server/pkg/lzkit/lzUtils"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/smartwalle/alipay/v3"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
// AlipayComplaintService 支付宝投诉服务,集中处理投诉相关的业务逻辑
type AlipayComplaintService struct {
config config.Config
AlipayClient *alipay.Client
ComplaintMainModel model.ComplaintMainModel
ComplaintAlipayModel model.ComplaintAlipayModel
ComplaintAlipayTradeModel model.ComplaintAlipayTradeModel
OrderModel model.OrderModel // 用于订单关联
}
// NewAlipayComplaintService 创建支付宝投诉服务
func NewAlipayComplaintService(
c config.Config,
alipayClient *alipay.Client,
complaintMainModel model.ComplaintMainModel,
complaintAlipayModel model.ComplaintAlipayModel,
complaintAlipayTradeModel model.ComplaintAlipayTradeModel,
orderModel model.OrderModel,
) *AlipayComplaintService {
return &AlipayComplaintService{
config: c,
AlipayClient: alipayClient,
ComplaintMainModel: complaintMainModel,
ComplaintAlipayModel: complaintAlipayModel,
ComplaintAlipayTradeModel: complaintAlipayTradeModel,
OrderModel: orderModel,
}
}
// QueryComplaintDetail 查询投诉详情
func (s *AlipayComplaintService) QueryComplaintDetail(ctx context.Context, complainId int64) (*alipay.SecurityRiskComplaintInfo, error) {
req := alipay.SecurityRiskComplaintInfoQueryReq{
ComplainId: complainId,
}
resp, err := s.AlipayClient.SecurityRiskComplaintInfoQuery(ctx, req)
if err != nil {
return nil, errors.Wrapf(err, "查询投诉详情失败, complain_id: %d", complainId)
}
if !resp.IsSuccess() {
return nil, errors.Errorf("支付宝返回错误: %s-%s", resp.Code, resp.Msg)
}
return &resp.SecurityRiskComplaintInfo, nil
}
// QueryComplaintByTaskId 根据 task_id 查询投诉详情并更新记录
func (s *AlipayComplaintService) QueryComplaintByTaskId(ctx context.Context, taskId string) error {
// 1. 通过 task_id 查找数据库中的投诉记录
complaint, err := s.ComplaintAlipayModel.FindOneByTaskId(ctx, taskId)
if err != nil {
if err == model.ErrNotFound {
logx.Infof("投诉记录不存在, task_id: %s可能是新投诉将在定时任务中同步", taskId)
return nil // 新投诉会在定时任务中同步,这里不处理
}
return errors.Wrapf(err, "查找投诉记录失败, task_id: %s", taskId)
}
// 2. 使用 alipay_id 查询投诉详情
detail, err := s.QueryComplaintDetail(ctx, complaint.AlipayId)
if err != nil {
return errors.Wrapf(err, "查询投诉详情失败, task_id: %s, alipay_id: %d", taskId, complaint.AlipayId)
}
logx.Infof("查询投诉详情成功, task_id: %s, alipay_id: %d, status: %s", taskId, complaint.AlipayId, detail.Status)
// 3. 更新投诉记录
return s.UpdateComplaintFromDetail(ctx, complaint, detail)
}
// UpdateComplaintFromDetail 根据查询详情更新投诉记录
func (s *AlipayComplaintService) UpdateComplaintFromDetail(ctx context.Context, complaint *model.ComplaintAlipay, detail *alipay.SecurityRiskComplaintInfo) error {
return s.ComplaintAlipayModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
// 1. 更新或创建投诉主表记录
var complaintMain *model.ComplaintMain
var err error
if complaint.ComplaintId != "" {
// 查找主表记录
complaintMain, err = s.ComplaintMainModel.FindOne(transCtx, complaint.ComplaintId)
if err != nil {
return errors.Wrapf(err, "查找投诉主表失败, complaint_id: %s", complaint.ComplaintId)
}
// 更新主表信息
complaintMain.Name = lzUtils.StringToNullString(detail.OppositeName) // 使用被投诉方名称作为投诉人姓名
complaintMain.Contact = lzUtils.StringToNullString(detail.Contact)
complaintMain.Content = lzUtils.StringToNullString(detail.ComplainContent)
complaintMain.Status = lzUtils.StringToNullString(detail.Status)
complaintMain.StatusDescription = lzUtils.StringToNullString(detail.StatusDescription)
// 如果主表没有订单ID尝试关联订单
if !complaintMain.OrderId.Valid {
if detail.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, detail.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
}
}
// 尝试从交易信息中查找
if !complaintMain.OrderId.Valid && len(detail.ComplaintTradeInfoList) > 0 {
for _, tradeInfo := range detail.ComplaintTradeInfoList {
if tradeInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
break
}
}
if tradeInfo.OutNo != "" {
order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo)
if err == nil && order != nil {
complaintMain.OrderId = lzUtils.StringToNullString(order.Id)
break
}
}
}
}
}
if err := s.ComplaintMainModel.UpdateWithVersion(transCtx, session, complaintMain); err != nil {
return errors.Wrapf(err, "更新投诉主表失败, complaint_id: %s", complaint.ComplaintId)
}
} else {
// 创建新的主表记录
complaintMain = &model.ComplaintMain{
Id: uuid.NewString(),
Type: "alipay",
Name: lzUtils.StringToNullString(detail.OppositeName),
Contact: lzUtils.StringToNullString(detail.Contact),
Content: lzUtils.StringToNullString(detail.ComplainContent),
Status: lzUtils.StringToNullString(detail.Status),
StatusDescription: lzUtils.StringToNullString(detail.StatusDescription),
}
// 如果有交易单号,尝试关联订单
if detail.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, detail.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
}
}
// 如果主表没有订单ID尝试从交易信息中查找
if !complaintMain.OrderId.Valid && len(detail.ComplaintTradeInfoList) > 0 {
for _, tradeInfo := range detail.ComplaintTradeInfoList {
if tradeInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
break
}
}
// 也可以尝试通过 out_no商家订单号查找
if tradeInfo.OutNo != "" {
order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo)
if err == nil && order != nil {
complaintMain.OrderId = lzUtils.StringToNullString(order.Id)
break
}
}
}
}
if _, err := s.ComplaintMainModel.Insert(transCtx, session, complaintMain); err != nil {
return errors.Wrapf(err, "创建投诉主表失败, task_id: %s", complaint.TaskId)
}
complaint.ComplaintId = complaintMain.Id
}
// 2. 更新支付宝投诉表信息
complaint.OppositePid = lzUtils.StringToNullString(detail.OppositePid)
complaint.OppositeName = lzUtils.StringToNullString(detail.OppositeName)
if detail.ComplainAmount != "" {
amount, err := s.parseDecimal(detail.ComplainAmount)
if err == nil {
complaint.ComplainAmount = lzUtils.Float64ToNullFloat64(amount)
}
}
complaint.GmtComplain = s.parseTime(detail.GmtComplain)
complaint.GmtProcess = s.parseTime(detail.GmtProcess)
complaint.GmtOverdue = s.parseTime(detail.GmtOverdue)
complaint.ComplainContent = lzUtils.StringToNullString(detail.ComplainContent)
complaint.TradeNo = lzUtils.StringToNullString(detail.TradeNo)
complaint.Status = lzUtils.StringToNullString(detail.Status)
complaint.StatusDescription = lzUtils.StringToNullString(detail.StatusDescription)
complaint.ProcessCode = lzUtils.StringToNullString(detail.ProcessCode)
complaint.ProcessMessage = lzUtils.StringToNullString(detail.ProcessMessage)
complaint.ProcessRemark = lzUtils.StringToNullString(detail.ProcessRemark)
complaint.GmtRiskFinishTime = s.parseTime(detail.GmtRiskFinishTime)
complaint.ComplainUrl = lzUtils.StringToNullString(detail.ComplainUrl)
// 处理图片列表
if len(detail.ProcessImgUrlList) > 0 {
imgJson, _ := json.Marshal(detail.ProcessImgUrlList)
complaint.ProcessImgUrlList = lzUtils.StringToNullString(string(imgJson))
}
if len(detail.CertifyInfo) > 0 {
certifyJson, _ := json.Marshal(detail.CertifyInfo)
complaint.CertifyInfo = lzUtils.StringToNullString(string(certifyJson))
}
// 更新支付宝投诉表
if err := s.ComplaintAlipayModel.UpdateWithVersion(transCtx, session, complaint); err != nil {
return errors.Wrapf(err, "更新支付宝投诉表失败, task_id: %s", complaint.TaskId)
}
// 3. 更新交易信息:先删除旧的,再插入新的
oldTrades, _ := s.ComplaintAlipayTradeModel.FindAll(transCtx,
s.ComplaintAlipayTradeModel.SelectBuilder().
Where("complaint_id = ? AND del_state = ?", complaint.Id, 0), "")
for _, oldTrade := range oldTrades {
oldTrade.DelState = 1
oldTrade.DeleteTime = lzUtils.TimeToNullTime(time.Now())
s.ComplaintAlipayTradeModel.UpdateWithVersion(transCtx, session, oldTrade)
}
// 插入新的交易信息
for _, tradeInfo := range detail.ComplaintTradeInfoList {
trade := &model.ComplaintAlipayTrade{
Id: uuid.NewString(),
ComplaintId: complaint.Id,
AlipayTradeId: lzUtils.Int64ToNullInt64(tradeInfo.Id),
AlipayComplaintRecordId: lzUtils.Int64ToNullInt64(tradeInfo.ComplaintRecordId),
TradeNo: lzUtils.StringToNullString(tradeInfo.TradeNo),
OutNo: lzUtils.StringToNullString(tradeInfo.OutNo),
GmtTrade: s.parseTime(tradeInfo.GmtTrade),
GmtRefund: s.parseTime(tradeInfo.GmtRefund),
Status: lzUtils.StringToNullString(tradeInfo.Status),
StatusDescription: lzUtils.StringToNullString(tradeInfo.StatusDescription),
}
if tradeInfo.Amount != "" {
amount, err := s.parseDecimal(tradeInfo.Amount)
if err == nil {
trade.Amount = lzUtils.Float64ToNullFloat64(amount)
}
}
if _, err := s.ComplaintAlipayTradeModel.Insert(transCtx, session, trade); err != nil {
return errors.Wrapf(err, "插入投诉交易信息失败, task_id: %s", complaint.TaskId)
}
}
return nil
})
}
// SaveComplaint 保存投诉数据到数据库(用于定时任务同步)
func (s *AlipayComplaintService) SaveComplaint(ctx context.Context, complaintInfo *alipay.SecurityRiskComplaintInfo) error {
// 检查投诉是否已存在(通过 task_id
existing, err := s.ComplaintAlipayModel.FindOneByTaskId(ctx, complaintInfo.TaskId)
if err != nil && err != model.ErrNotFound {
return errors.Wrapf(err, "查询投诉失败, task_id: %s", complaintInfo.TaskId)
}
return s.ComplaintAlipayModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
var complaintRecord *model.ComplaintAlipay
var complaintMain *model.ComplaintMain
var isUpdate bool
if existing != nil {
// 更新现有投诉
complaintRecord = existing
isUpdate = true
// 查找主表记录
if complaintRecord.ComplaintId != "" {
complaintMain, err = s.ComplaintMainModel.FindOne(transCtx, complaintRecord.ComplaintId)
if err != nil {
return errors.Wrapf(err, "查找投诉主表失败, complaint_id: %s", complaintRecord.ComplaintId)
}
}
} else {
// 创建新投诉
complaintRecord = &model.ComplaintAlipay{
Id: uuid.NewString(),
}
isUpdate = false
}
// 1. 创建或更新投诉主表记录
if complaintMain == nil {
// 创建新的主表记录
complaintMain = &model.ComplaintMain{
Id: uuid.NewString(),
Type: "alipay",
Name: lzUtils.StringToNullString(complaintInfo.OppositeName),
Contact: lzUtils.StringToNullString(complaintInfo.Contact),
Content: lzUtils.StringToNullString(complaintInfo.ComplainContent),
Status: lzUtils.StringToNullString(complaintInfo.Status),
StatusDescription: lzUtils.StringToNullString(complaintInfo.StatusDescription),
}
// 如果有交易单号,尝试关联订单
if complaintInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, complaintInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
}
}
// 如果主表没有订单ID尝试从交易信息中查找
if !complaintMain.OrderId.Valid && len(complaintInfo.ComplaintTradeInfoList) > 0 {
for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList {
if tradeInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
break
}
}
// 也可以尝试通过 out_no商家订单号查找
if tradeInfo.OutNo != "" {
order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo)
if err == nil && order != nil {
complaintMain.OrderId = lzUtils.StringToNullString(order.Id)
break
}
}
}
}
if _, err := s.ComplaintMainModel.Insert(transCtx, session, complaintMain); err != nil {
return errors.Wrapf(err, "创建投诉主表失败, task_id: %s", complaintInfo.TaskId)
}
complaintRecord.ComplaintId = complaintMain.Id
} else {
// 更新主表信息
complaintMain.Name = lzUtils.StringToNullString(complaintInfo.OppositeName)
complaintMain.Contact = lzUtils.StringToNullString(complaintInfo.Contact)
complaintMain.Content = lzUtils.StringToNullString(complaintInfo.ComplainContent)
complaintMain.Status = lzUtils.StringToNullString(complaintInfo.Status)
complaintMain.StatusDescription = lzUtils.StringToNullString(complaintInfo.StatusDescription)
// 如果主表没有订单ID尝试关联订单
if !complaintMain.OrderId.Valid {
if complaintInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, complaintInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
}
}
// 尝试从交易信息中查找
if !complaintMain.OrderId.Valid && len(complaintInfo.ComplaintTradeInfoList) > 0 {
for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList {
if tradeInfo.TradeNo != "" {
orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo)
if orderId != "" {
complaintMain.OrderId = lzUtils.StringToNullString(orderId)
break
}
}
if tradeInfo.OutNo != "" {
order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo)
if err == nil && order != nil {
complaintMain.OrderId = lzUtils.StringToNullString(order.Id)
break
}
}
}
}
}
if err := s.ComplaintMainModel.UpdateWithVersion(transCtx, session, complaintMain); err != nil {
return errors.Wrapf(err, "更新投诉主表失败, task_id: %s", complaintInfo.TaskId)
}
}
// 2. 填充支付宝投诉表数据
complaintRecord.AlipayId = complaintInfo.Id
complaintRecord.TaskId = complaintInfo.TaskId
complaintRecord.OppositePid = lzUtils.StringToNullString(complaintInfo.OppositePid)
complaintRecord.OppositeName = lzUtils.StringToNullString(complaintInfo.OppositeName)
if complaintInfo.ComplainAmount != "" {
amount, err := s.parseDecimal(complaintInfo.ComplainAmount)
if err == nil {
complaintRecord.ComplainAmount = lzUtils.Float64ToNullFloat64(amount)
}
}
complaintRecord.GmtComplain = s.parseTime(complaintInfo.GmtComplain)
complaintRecord.GmtProcess = s.parseTime(complaintInfo.GmtProcess)
complaintRecord.GmtOverdue = s.parseTime(complaintInfo.GmtOverdue)
complaintRecord.ComplainContent = lzUtils.StringToNullString(complaintInfo.ComplainContent)
complaintRecord.TradeNo = lzUtils.StringToNullString(complaintInfo.TradeNo)
complaintRecord.Status = lzUtils.StringToNullString(complaintInfo.Status)
complaintRecord.StatusDescription = lzUtils.StringToNullString(complaintInfo.StatusDescription)
complaintRecord.ProcessCode = lzUtils.StringToNullString(complaintInfo.ProcessCode)
complaintRecord.ProcessMessage = lzUtils.StringToNullString(complaintInfo.ProcessMessage)
complaintRecord.ProcessRemark = lzUtils.StringToNullString(complaintInfo.ProcessRemark)
complaintRecord.GmtRiskFinishTime = s.parseTime(complaintInfo.GmtRiskFinishTime)
complaintRecord.ComplainUrl = lzUtils.StringToNullString(complaintInfo.ComplainUrl)
// 处理图片列表转换为JSON字符串
if len(complaintInfo.ProcessImgUrlList) > 0 {
imgJson, _ := json.Marshal(complaintInfo.ProcessImgUrlList)
complaintRecord.ProcessImgUrlList = lzUtils.StringToNullString(string(imgJson))
}
if len(complaintInfo.CertifyInfo) > 0 {
certifyJson, _ := json.Marshal(complaintInfo.CertifyInfo)
complaintRecord.CertifyInfo = lzUtils.StringToNullString(string(certifyJson))
}
// 保存或更新支付宝投诉表
if isUpdate {
if err := s.ComplaintAlipayModel.UpdateWithVersion(transCtx, session, complaintRecord); err != nil {
return errors.Wrapf(err, "更新支付宝投诉表失败, task_id: %s", complaintInfo.TaskId)
}
} else {
if _, err := s.ComplaintAlipayModel.Insert(transCtx, session, complaintRecord); err != nil {
return errors.Wrapf(err, "插入支付宝投诉表失败, task_id: %s", complaintInfo.TaskId)
}
}
// 3. 保存交易信息
if len(complaintInfo.ComplaintTradeInfoList) > 0 {
// 先删除旧的交易信息(如果存在)
if isUpdate {
oldTrades, _ := s.ComplaintAlipayTradeModel.FindAll(transCtx,
s.ComplaintAlipayTradeModel.SelectBuilder().
Where("complaint_id = ? AND del_state = ?", complaintRecord.Id, 0), "")
for _, oldTrade := range oldTrades {
oldTrade.DelState = 1
oldTrade.DeleteTime = lzUtils.TimeToNullTime(time.Now())
s.ComplaintAlipayTradeModel.UpdateWithVersion(transCtx, session, oldTrade)
}
}
// 插入新的交易信息
for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList {
trade := &model.ComplaintAlipayTrade{
Id: uuid.NewString(),
ComplaintId: complaintRecord.Id,
AlipayTradeId: lzUtils.Int64ToNullInt64(tradeInfo.Id),
AlipayComplaintRecordId: lzUtils.Int64ToNullInt64(tradeInfo.ComplaintRecordId),
TradeNo: lzUtils.StringToNullString(tradeInfo.TradeNo),
OutNo: lzUtils.StringToNullString(tradeInfo.OutNo),
GmtTrade: s.parseTime(tradeInfo.GmtTrade),
GmtRefund: s.parseTime(tradeInfo.GmtRefund),
Status: lzUtils.StringToNullString(tradeInfo.Status),
StatusDescription: lzUtils.StringToNullString(tradeInfo.StatusDescription),
}
if tradeInfo.Amount != "" {
amount, err := s.parseDecimal(tradeInfo.Amount)
if err == nil {
trade.Amount = lzUtils.Float64ToNullFloat64(amount)
}
}
if _, err := s.ComplaintAlipayTradeModel.Insert(transCtx, session, trade); err != nil {
return errors.Wrapf(err, "插入投诉交易信息失败, task_id: %s", complaintInfo.TaskId)
}
}
}
return nil
})
}
// GetLatestComplainTime 查询数据库中最新投诉的投诉时间
func (s *AlipayComplaintService) GetLatestComplainTime(ctx context.Context) (time.Time, error) {
// 从支付宝投诉表查询最新投诉时间(因为主表没有 gmt_complain 字段)
builder := s.ComplaintAlipayModel.SelectBuilder().
Where("del_state = ?", 0).
OrderBy("gmt_complain DESC").
Limit(1)
complaints, err := s.ComplaintAlipayModel.FindAll(ctx, builder, "")
if err != nil {
return time.Time{}, errors.Wrapf(err, "查询最新投诉时间失败")
}
if len(complaints) == 0 {
return time.Time{}, nil // 数据库为空
}
// 返回最新投诉的投诉时间
if complaints[0].GmtComplain.Valid {
return complaints[0].GmtComplain.Time, nil
}
return time.Time{}, nil
}
// parseTime 解析时间字符串
func (s *AlipayComplaintService) parseTime(timeStr string) sql.NullTime {
if timeStr == "" {
return sql.NullTime{Valid: false}
}
// 尝试多种时间格式
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02",
}
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return sql.NullTime{Time: t, Valid: true}
}
}
return sql.NullTime{Valid: false}
}
// parseDecimal 解析金额字符串
func (s *AlipayComplaintService) parseDecimal(amountStr string) (float64, error) {
if amountStr == "" {
return 0, nil
}
// 使用 fmt.Sscanf 解析
var amount float64
_, err := fmt.Sscanf(amountStr, "%f", &amount)
return amount, err
}
// findOrderByPlatformOrderId 根据支付宝交易单号查找订单ID
func (s *AlipayComplaintService) findOrderByPlatformOrderId(ctx context.Context, platformOrderId string) string {
if platformOrderId == "" {
return ""
}
// 通过 PlatformOrderId 查找订单
builder := s.OrderModel.SelectBuilder().
Where("platform_order_id = ? AND del_state = ?", platformOrderId, 0).
Limit(1)
orders, err := s.OrderModel.FindAll(ctx, builder, "")
if err != nil || len(orders) == 0 {
return ""
}
return orders[0].Id
}

View File

@@ -63,7 +63,6 @@ func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, out
NotifyURL: a.config.NotifyUrl, // 异步回调通知地址
},
}
// 获取APP支付字符串这里会签名
payStr, err := client.TradeAppPay(p)
if err != nil {

View File

@@ -33,19 +33,22 @@ func generateAuthDateRange() string {
}
type ApiRequestService struct {
config config.Config
featureModel model.FeatureModel
productFeatureModel model.ProductFeatureModel
tianyuanapi *tianyuanapi.Client
config config.Config
featureModel model.FeatureModel
productFeatureModel model.ProductFeatureModel
userFeatureWhitelistModel model.UserFeatureWhitelistModel
whitelistService *WhitelistService
tianyuanapi *tianyuanapi.Client
}
// NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService
func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService {
func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, userFeatureWhitelistModel model.UserFeatureWhitelistModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService {
return &ApiRequestService{
config: c,
featureModel: featureModel,
productFeatureModel: productFeatureModel,
tianyuanapi: tianyuanapi,
config: c,
featureModel: featureModel,
productFeatureModel: productFeatureModel,
userFeatureWhitelistModel: userFeatureWhitelistModel,
tianyuanapi: tianyuanapi,
}
}
@@ -61,6 +64,13 @@ type APIResponseData struct {
func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) {
var ctx, cancel = context.WithCancel(context.Background())
defer cancel()
// 从params中提取id_card用于白名单检查
idCard := gjson.GetBytes(params, "id_card").String()
// 查询白名单如果提供了id_card集中由 WhitelistService 处理
whitelistedFeatureApiIds, _ := a.whitelistService.GetWhitelistedFeatureApisByIdCard(ctx, idCard)
build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{
"product_id": productID,
})
@@ -109,6 +119,18 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]
Success: false,
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
// 检查是否在白名单中
if whitelistedFeatureApiIds[feature.ApiId] {
// 在白名单中返回空结构data 为 null保持与正常返回结构一致
// 直接设置 data 为 null 的 JSON 字节
result.Data = []byte("null")
result.Success = true
result.Timestamp = timestamp
resultsCh <- result
return
}
var (
resp json.RawMessage
preprocessErr error

View File

@@ -173,7 +173,7 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
用户声明与承诺:
本人在授权签署前,已通过实名认证及动态验证码验证(或其他身份验证手段),确认本授权行为为本人真实意思表示,平台已履行身份验证义务。
本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人信用评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人风险评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
若用户冒名签署或提供虚假信息,由用户自行承担全部法律责任,平台不承担任何后果。
特别提示:

View File

@@ -0,0 +1,560 @@
package service
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"strings"
"ycc-server/app/main/api/internal/config"
"ycc-server/app/main/model"
"ycc-server/pkg/lzkit/crypto"
"ycc-server/pkg/lzkit/lzUtils"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
// WhitelistService 白名单领域服务,集中处理白名单相关的业务逻辑
type WhitelistService struct {
config config.Config
UserFeatureWhitelistModel model.UserFeatureWhitelistModel
WhitelistOrderModel model.WhitelistOrderModel
WhitelistOrderItemModel model.WhitelistOrderItemModel
QueryModel model.QueryModel
FeatureModel model.FeatureModel
}
// NewWhitelistService 创建白名单服务
func NewWhitelistService(
c config.Config,
userFeatureWhitelistModel model.UserFeatureWhitelistModel,
whitelistOrderModel model.WhitelistOrderModel,
whitelistOrderItemModel model.WhitelistOrderItemModel,
queryModel model.QueryModel,
featureModel model.FeatureModel,
) *WhitelistService {
return &WhitelistService{
config: c,
UserFeatureWhitelistModel: userFeatureWhitelistModel,
WhitelistOrderModel: whitelistOrderModel,
WhitelistOrderItemModel: whitelistOrderItemModel,
QueryModel: queryModel,
FeatureModel: featureModel,
}
}
// EnsureFreeWhitelist 免费下架:如果还没有生效白名单,则创建一条免费白名单记录
func (s *WhitelistService) EnsureFreeWhitelist(
ctx context.Context,
session sqlx.Session,
idCard string,
feature *model.Feature,
userId string,
orderId string,
) error {
// 检查是否已存在生效白名单
builder := s.UserFeatureWhitelistModel.SelectBuilder().
Where("id_card = ? AND feature_id = ?", idCard, feature.Id)
records, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "")
if err != nil {
return errors.Wrap(err, "查询白名单记录失败")
}
for _, r := range records {
if r.Status == 1 {
// 已经下架,直接返回
return nil
}
}
wl := &model.UserFeatureWhitelist{
Id: uuid.NewString(),
IdCard: idCard,
FeatureId: feature.Id,
FeatureApiId: feature.ApiId,
UserId: userId,
OrderId: lzUtils.StringToNullString(orderId),
WhitelistOrderId: lzUtils.StringToNullString(""),
Amount: 0,
Status: 1,
}
_, err = s.UserFeatureWhitelistModel.Insert(ctx, session, wl)
if err != nil {
return errors.Wrap(err, "创建免费白名单记录失败")
}
return nil
}
// CreateWhitelistByPaidOrder 根据已支付的白名单订单,创建对应的白名单记录
func (s *WhitelistService) CreateWhitelistByPaidOrder(
ctx context.Context,
session sqlx.Session,
order *model.Order,
whitelistOrder *model.WhitelistOrder,
) error {
if whitelistOrder.Status != 2 {
// 只处理已支付状态
return nil
}
itemBuilder := s.WhitelistOrderItemModel.SelectBuilder().
Where("order_id = ?", whitelistOrder.Id)
items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "")
if err != nil {
return errors.Wrap(err, "查询白名单订单明细失败")
}
for _, item := range items {
wl := &model.UserFeatureWhitelist{
Id: uuid.NewString(),
IdCard: whitelistOrder.IdCard,
FeatureId: item.FeatureId,
FeatureApiId: item.FeatureApiId,
UserId: whitelistOrder.UserId,
OrderId: lzUtils.StringToNullString(order.Id),
WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id),
Amount: item.Price,
Status: 1,
}
if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil {
return errors.Wrap(err, "创建白名单记录失败")
}
}
return nil
}
// GetWhitelistedFeatureApisByIdCard 获取某个身份证号已下架的 feature_api_id 集合
func (s *WhitelistService) GetWhitelistedFeatureApisByIdCard(
ctx context.Context,
idCard string,
) (map[string]bool, error) {
result := make(map[string]bool)
if idCard == "" {
return result, nil
}
builder := s.UserFeatureWhitelistModel.SelectBuilder().
Where("id_card = ? AND status = ?", idCard, 1)
list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "")
if err != nil {
return nil, errors.Wrap(err, "查询白名单失败")
}
for _, wl := range list {
result[wl.FeatureApiId] = true
}
return result, nil
}
// CheckWhitelistExists 检查指定身份证号和模块是否已有生效的白名单记录
func (s *WhitelistService) CheckWhitelistExists(
ctx context.Context,
idCard string,
featureId string,
) (bool, error) {
if idCard == "" || featureId == "" {
return false, nil
}
builder := s.UserFeatureWhitelistModel.SelectBuilder().
Where("id_card = ? AND feature_id = ? AND status = ?", idCard, featureId, 1)
list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "")
if err != nil {
return false, errors.Wrap(err, "查询白名单记录失败")
}
return len(list) > 0, nil
}
// ProcessOfflineFeature 统一下架处理:处理免费下架或检查付费下架
// 返回needPay(是否需要支付), amount(金额), whitelistCreated(是否已创建白名单)
func (s *WhitelistService) ProcessOfflineFeature(
ctx context.Context,
session sqlx.Session,
idCard string,
featureApiId string,
userId string,
orderId string,
) (needPay bool, amount float64, whitelistCreated bool, err error) {
// 1. 提取主模块ID并查询feature信息
mainApiId := s.extractMainApiId(featureApiId)
feature, err := s.getFeatureByApiId(ctx, mainApiId)
if err != nil {
return false, 0, false, err
}
// 2. 检查是否已有白名单
exists, err := s.CheckWhitelistExists(ctx, idCard, feature.Id)
if err != nil {
return false, 0, false, err
}
if exists {
// 已有白名单,直接返回成功
return false, 0, true, nil
}
price := feature.WhitelistPrice
// 3. 免费下架:直接创建白名单记录
if price <= 0 {
if err := s.EnsureFreeWhitelist(ctx, session, idCard, feature, userId, orderId); err != nil {
return false, 0, false, err
}
return false, 0, true, nil
}
// 4. 付费下架:检查是否已有支付成功的订单
paidOrderId, err := s.findPaidWhitelistOrder(ctx, userId, idCard, feature.Id)
if err != nil {
return false, 0, false, err
}
// 5. 如果已有支付成功订单,补创建白名单记录
if paidOrderId != "" {
if err := s.createWhitelistFromPaidOrder(ctx, session, idCard, feature, userId, orderId, paidOrderId, price); err != nil {
return false, 0, false, err
}
return false, price, true, nil
}
// 6. 需要支付
return true, price, false, nil
}
// extractMainApiId 提取主模块ID去掉下划线后的部分
func (s *WhitelistService) extractMainApiId(featureApiId string) string {
if idx := strings.Index(featureApiId, "_"); idx > 0 {
return featureApiId[:idx]
}
return featureApiId
}
// getFeatureByApiId 根据API ID查询feature信息
func (s *WhitelistService) getFeatureByApiId(ctx context.Context, apiId string) (*model.Feature, error) {
feature, err := s.FeatureModel.FindOneByApiId(ctx, apiId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrap(err, "模块不存在")
}
return nil, errors.Wrap(err, "查询模块信息失败")
}
return feature, nil
}
// findPaidWhitelistOrder 查找已支付的白名单订单中是否包含指定feature
// 返回paidOrderId如果找到已支付订单error
func (s *WhitelistService) findPaidWhitelistOrder(
ctx context.Context,
userId string,
idCard string,
featureId string,
) (string, error) {
orderBuilder := s.WhitelistOrderModel.SelectBuilder().
Where("user_id = ? AND id_card = ? AND status = ?", userId, idCard, 2) // 2表示已支付
orders, err := s.WhitelistOrderModel.FindAll(ctx, orderBuilder, "")
if err != nil {
return "", errors.Wrap(err, "查询白名单订单失败")
}
// 查找已支付订单中是否包含该feature
for _, order := range orders {
itemBuilder := s.WhitelistOrderItemModel.SelectBuilder().
Where("order_id = ? AND feature_id = ?", order.Id, featureId)
items, itemErr := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "")
if itemErr != nil {
return "", errors.Wrap(itemErr, "查询白名单订单明细失败")
}
if len(items) > 0 {
return order.Id, nil
}
}
return "", nil
}
// createWhitelistFromPaidOrder 根据已支付订单创建白名单记录
func (s *WhitelistService) createWhitelistFromPaidOrder(
ctx context.Context,
session sqlx.Session,
idCard string,
feature *model.Feature,
userId string,
orderId string,
paidOrderId string,
price float64,
) error {
wl := &model.UserFeatureWhitelist{
Id: uuid.NewString(),
IdCard: idCard,
FeatureId: feature.Id,
FeatureApiId: feature.ApiId,
UserId: userId,
OrderId: lzUtils.StringToNullString(orderId),
WhitelistOrderId: lzUtils.StringToNullString(paidOrderId),
Amount: price,
Status: 1, // 生效
}
if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil {
return errors.Wrap(err, "根据已支付订单创建白名单记录失败")
}
return nil
}
// DeleteFeatureFromQueryData 从报告数据中删除指定模块的数据
// queryId: 查询记录IDQuery表的ID
// featureApiId: 要删除的模块API标识
func (s *WhitelistService) DeleteFeatureFromQueryData(
ctx context.Context,
session sqlx.Session,
queryId string,
featureApiId string,
) error {
// 1. 获取查询记录
queryModel, err := s.getQueryModel(ctx, queryId)
if err != nil {
return err
}
if queryModel == nil {
// 报告不存在或数据为空,直接返回成功
return nil
}
// 2. 解密并解析报告数据
mainApiId := s.extractMainApiId(featureApiId)
dataArray, key, err := s.decryptQueryData(queryModel)
if err != nil {
return err
}
// 3. 清空对应模块的数据(将 data 字段设置为 null
modifiedArray, hasModified := s.clearFeatureData(dataArray, mainApiId)
if !hasModified {
logx.Infof("删除报告数据:查询记录 %s 中未找到模块 %s 的数据,跳过删除", queryId, featureApiId)
return nil
}
// 4. 重新加密并更新数据库
if err := s.updateQueryData(ctx, session, queryModel, modifiedArray, key); err != nil {
return err
}
logx.Infof("删除报告数据成功:查询记录 %s模块 %s已将对应模块的 data 字段设置为 null", queryId, featureApiId)
return nil
}
// getQueryModel 获取查询记录如果不存在或数据为空则返回nil
func (s *WhitelistService) getQueryModel(ctx context.Context, queryId string) (*model.Query, error) {
queryModel, err := s.QueryModel.FindOne(ctx, queryId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
logx.Infof("删除报告数据:查询记录 %s 不存在,跳过删除", queryId)
return nil, nil
}
return nil, errors.Wrap(err, "查询报告记录失败")
}
// 如果报告数据为空,直接返回
if !queryModel.QueryData.Valid || queryModel.QueryData.String == "" {
logx.Infof("删除报告数据:查询记录 %s 对应的报告数据为空,跳过删除", queryId)
return nil, nil
}
return queryModel, nil
}
// decryptQueryData 解密并解析报告数据
func (s *WhitelistService) decryptQueryData(queryModel *model.Query) ([]map[string]interface{}, []byte, error) {
// 获取加密密钥
secretKey := s.config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
return nil, nil, errors.Wrap(decodeErr, "获取AES密钥失败")
}
// 解密报告数据
decryptedData, decryptErr := crypto.AesDecrypt(queryModel.QueryData.String, key)
if decryptErr != nil {
return nil, nil, errors.Wrap(decryptErr, "解密报告数据失败")
}
// 解析JSON数组
var dataArray []map[string]interface{}
unmarshalErr := json.Unmarshal(decryptedData, &dataArray)
if unmarshalErr != nil {
return nil, nil, errors.Wrap(unmarshalErr, "解析报告数据失败")
}
return dataArray, key, nil
}
// clearFeatureData 清空指定模块的数据(将 data 字段设置为 null
// 返回修改后的数组和是否进行了修改
func (s *WhitelistService) clearFeatureData(dataArray []map[string]interface{}, mainApiId string) ([]map[string]interface{}, bool) {
modifiedArray := make([]map[string]interface{}, 0, len(dataArray))
hasModified := false
for _, item := range dataArray {
// 深拷贝 item避免修改原数据
newItem := make(map[string]interface{})
for k, v := range item {
newItem[k] = v
}
apiID, ok := item["apiID"].(string)
if !ok {
// 如果apiID不存在或类型不对保留原样
modifiedArray = append(modifiedArray, newItem)
continue
}
// 提取主模块ID进行比较
itemMainApiId := s.extractMainApiId(apiID)
// 如果主模块ID匹配将 data 字段设置为 null
if itemMainApiId == mainApiId {
newItem["data"] = nil
hasModified = true
}
modifiedArray = append(modifiedArray, newItem)
}
return modifiedArray, hasModified
}
// updateQueryData 重新加密并更新数据库
func (s *WhitelistService) updateQueryData(
ctx context.Context,
session sqlx.Session,
queryModel *model.Query,
filteredArray []map[string]interface{},
key []byte,
) error {
// 重新序列化
filteredBytes, marshalErr := json.Marshal(filteredArray)
if marshalErr != nil {
return errors.Wrap(marshalErr, "序列化过滤后的报告数据失败")
}
// 重新加密
encryptedData, encryptErr := crypto.AesEncrypt(filteredBytes, key)
if encryptErr != nil {
return errors.Wrap(encryptErr, "加密过滤后的报告数据失败")
}
// 更新数据库
queryModel.QueryData = sql.NullString{
String: encryptedData,
Valid: true,
}
updateErr := s.QueryModel.UpdateWithVersion(ctx, session, queryModel)
if updateErr != nil {
return errors.Wrap(updateErr, "更新报告数据失败")
}
return nil
}
// CheckQueryDataContainsFeature 检查报告数据中是否包含指定的模块
// 返回 true 表示包含该模块false 表示不包含(已删除)
func (s *WhitelistService) CheckQueryDataContainsFeature(
ctx context.Context,
queryId string,
featureApiId string,
) (bool, error) {
// 1. 获取查询记录
queryModel, err := s.getQueryModel(ctx, queryId)
if err != nil {
return false, err
}
if queryModel == nil {
// 报告不存在,认为数据已删除
return false, nil
}
// 2. 解密并解析报告数据
mainApiId := s.extractMainApiId(featureApiId)
dataArray, _, err := s.decryptQueryData(queryModel)
if err != nil {
return false, err
}
// 3. 检查数据中是否包含该模块(且 data 不为 null
for _, item := range dataArray {
apiID, ok := item["apiID"].(string)
if !ok {
continue
}
// 提取主模块ID进行比较
itemMainApiId := s.extractMainApiId(apiID)
if itemMainApiId == mainApiId {
// 找到了该模块,检查 data 字段
dataValue, exists := item["data"]
if !exists || dataValue == nil {
// data 字段不存在或为 null认为数据已删除
return false, nil
}
// data 字段存在且不为 null认为数据存在
return true, nil
}
}
// 未找到该模块的数据,说明已删除
return false, nil
}
// ProcessPaidWhitelistOrder 处理已支付的白名单订单:创建白名单记录并删除报告数据
// order: 支付订单Order表
// whitelistOrder: 白名单订单WhitelistOrder表
func (s *WhitelistService) ProcessPaidWhitelistOrder(
ctx context.Context,
session sqlx.Session,
order *model.Order,
whitelistOrder *model.WhitelistOrder,
) error {
if whitelistOrder.Status != 2 {
// 只处理已支付状态
return nil
}
// 查询订单明细
itemBuilder := s.WhitelistOrderItemModel.SelectBuilder().
Where("order_id = ?", whitelistOrder.Id)
items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "")
if err != nil {
return errors.Wrap(err, "查询白名单订单明细失败")
}
// 为每个明细创建白名单记录并删除报告数据
for _, item := range items {
// 创建白名单记录
wl := &model.UserFeatureWhitelist{
Id: uuid.NewString(),
IdCard: whitelistOrder.IdCard,
FeatureId: item.FeatureId,
FeatureApiId: item.FeatureApiId,
UserId: whitelistOrder.UserId,
OrderId: lzUtils.StringToNullString(""), // 查询订单ID如果有的话会在后续步骤中设置
WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id),
Amount: item.Price,
Status: 1, // 生效
}
if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil {
return errors.Wrap(err, "创建白名单记录失败")
}
// 尝试删除报告数据
// 注意由于支付回调时可能不知道具体的查询订单ID这里先尝试根据 id_card 查找
// 如果找不到对应的报告,就跳过删除步骤(不影响主流程)
// 实际的报告数据删除应该在 OfflineFeature 接口中完成(如果提供了 orderId
// 这里暂时不删除,因为无法确定是哪个具体的查询订单
logx.Infof("白名单订单支付成功:订单 %s模块 %s已创建白名单记录。如需删除报告数据请在 OfflineFeature 接口中提供查询订单ID", whitelistOrder.OrderNo, item.FeatureApiId)
}
return nil
}