fix
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
541
app/main/api/internal/service/alipayComplaintService.go
Normal file
541
app/main/api/internal/service/alipayComplaintService.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -173,7 +173,7 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
|
||||
|
||||
用户声明与承诺:
|
||||
本人在授权签署前,已通过实名认证及动态验证码验证(或其他身份验证手段),确认本授权行为为本人真实意思表示,平台已履行身份验证义务。
|
||||
本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人信用评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
|
||||
本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人风险评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
|
||||
若用户冒名签署或提供虚假信息,由用户自行承担全部法律责任,平台不承担任何后果。
|
||||
|
||||
特别提示:
|
||||
|
||||
560
app/main/api/internal/service/whitelistService.go
Normal file
560
app/main/api/internal/service/whitelistService.go
Normal 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: 查询记录ID(Query表的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
|
||||
}
|
||||
Reference in New Issue
Block a user