diff --git a/app/main/api/desc/admin/admin_agent.api b/app/main/api/desc/admin/admin_agent.api index 1a0d805..e6d2658 100644 --- a/app/main/api/desc/admin/admin_agent.api +++ b/app/main/api/desc/admin/admin_agent.api @@ -251,6 +251,7 @@ type ( AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) RelationAgentId *int64 `form:"relation_agent_id,optional"` // 关联代理ID(可选) Type *string `form:"type,optional"` // 奖励类型(可选) + Status *int64 `form:"status,optional"` // 状态(可选) } // 代理奖励列表项 @@ -260,6 +261,8 @@ type ( RelationAgentId int64 `json:"relation_agent_id"` // 关联代理ID Amount float64 `json:"amount"` // 金额 Type string `json:"type"` // 奖励类型 + Status int64 `json:"status"` // 状态:0=正常, 1=已取消 + Remark string `json:"remark"` // 备注 CreateTime string `json:"create_time"` // 创建时间 } @@ -319,6 +322,7 @@ type ( AgentId int64 `json:"agent_id"` // 代理ID DeductedAgentId int64 `json:"deducted_agent_id"` // 被扣代理ID Amount float64 `json:"amount"` // 金额 + RefundedAmount float64 `json:"refunded_amount"` // 已退款金额 ProductName string `json:"product_name"` // 产品名 Type string `json:"type"` // 类型(cost/pricing) Status int64 `json:"status"` // 状态 diff --git a/app/main/api/etc/main.example.yaml b/app/main/api/etc/main.example.yaml index c04ee99..6531004 100644 --- a/app/main/api/etc/main.example.yaml +++ b/app/main/api/etc/main.example.yaml @@ -61,7 +61,6 @@ Applepay: LoadPrivateKeyPath: "etc/merchant/AuthKey_XXXXXXXXXXXX.p8" SystemConfig: ThreeVerify: true - CommissionSafeMode: false # 佣金安全防御模式:true-冻结模式,false-直接结算模式 WechatH5: AppID: "xxxx" AppSecret: "xxxx" diff --git a/app/main/api/internal/config/config.go b/app/main/api/internal/config/config.go index caf387b..47c642c 100644 --- a/app/main/api/internal/config/config.go +++ b/app/main/api/internal/config/config.go @@ -100,8 +100,7 @@ type YushanConfig struct { Url string } type SystemConfig struct { - ThreeVerify bool // 是否开启三级实名认证 - CommissionSafeMode bool // 佣金安全防御模式:true-冻结模式(status=1,进入frozen_balance),false-直接结算(status=0,进入balance) + ThreeVerify bool // 是否开启三级实名认证 } type WechatH5Config struct { AppID string diff --git a/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go index 3174b84..f7efe71 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go @@ -36,6 +36,9 @@ func (l *AdminGetAgentRewardListLogic) AdminGetAgentRewardList(req *types.AdminG if req.Type != nil && *req.Type != "" { builder = builder.Where(squirrel.Eq{"type": *req.Type}) } + if req.Status != nil { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } list, total, err := l.svcCtx.AgentRewardsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") if err != nil { return nil, err diff --git a/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go b/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go index ec4c6fb..8edeb38 100644 --- a/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go @@ -25,7 +25,7 @@ func NewAdminGetSystemConfigLogic(ctx context.Context, svcCtx *svc.ServiceContex func (l *AdminGetSystemConfigLogic) AdminGetSystemConfig() (resp *types.AdminGetSystemConfigResp, err error) { resp = &types.AdminGetSystemConfigResp{ - CommissionSafeMode: l.svcCtx.Config.SystemConfig.CommissionSafeMode, + CommissionSafeMode: l.svcCtx.AgentConfigModel.IsCommissionSafeMode(l.ctx), } return } diff --git a/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go b/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go index d047728..71f9ce7 100644 --- a/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go +++ b/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go @@ -24,9 +24,11 @@ func NewAdminUpdateSystemConfigLogic(ctx context.Context, svcCtx *svc.ServiceCon } func (l *AdminUpdateSystemConfigLogic) AdminUpdateSystemConfig(req *types.AdminUpdateSystemConfigReq) (resp *types.AdminUpdateSystemConfigResp, err error) { - // 更新佣金安全防御模式配置 if req.CommissionSafeMode != nil { - l.svcCtx.Config.SystemConfig.CommissionSafeMode = *req.CommissionSafeMode + if err := l.svcCtx.AgentConfigModel.SetCommissionSafeMode(l.ctx, *req.CommissionSafeMode); err != nil { + logx.Errorf("更新佣金安全防御模式失败: %v", err) + return nil, err + } logx.Infof("更新系统配置:佣金安全防御模式设置为 %v", *req.CommissionSafeMode) } diff --git a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go index f0dfae2..af52ab9 100644 --- a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go +++ b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go @@ -6,7 +6,6 @@ import ( "fmt" "time" - paylogic "bdrp-server/app/main/api/internal/logic/pay" "bdrp-server/app/main/api/internal/svc" "bdrp-server/app/main/api/internal/types" "bdrp-server/app/main/model" @@ -93,8 +92,8 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type } // 退款成功后,按本次退款金额更新代理佣金状态并扣除钱包金额 - // 注意:refundAmount 为本次实际退款金额,可以是部分退款 - _ = paylogic.HandleCommissionAndWalletDeduction(l.ctx, l.svcCtx, nil, order, req.RefundAmount) + // 使用 AgentService 中的共用退款扣款逻辑 + l.svcCtx.AgentService.HandleOrderRefundDeduction(l.ctx, nil, order, req.RefundAmount) return &types.AdminRefundOrderResp{ Status: model.OrderStatusRefunded, diff --git a/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go b/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go index cf205ce..de4ab20 100644 --- a/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go +++ b/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go @@ -1,11 +1,13 @@ package agent import ( - "context" + "bdrp-server/app/main/api/internal/service" "bdrp-server/app/main/model" "bdrp-server/common/ctxdata" "bdrp-server/common/xerr" "bdrp-server/pkg/lzkit/lzUtils" + "context" + "time" "github.com/jinzhu/copier" "github.com/pkg/errors" @@ -40,6 +42,9 @@ func (l *GetAgentMembershipProductConfigLogic) GetAgentMembershipProductConfig(r if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取代理信息失败: %v", err) } + if service.IsMembershipExpired(agentModel, time.Now()) { + return nil, errors.Wrapf(xerr.NewErrMsg("会员已过期"), "获取会员用户报告配置,会员已过期: agent_id=%d", agentModel.Id) + } if agentModel.LevelName == "" { agentModel.LevelName = model.AgentLeveNameNormal } diff --git a/app/main/api/internal/logic/agent/getagentproductconfiglogic.go b/app/main/api/internal/logic/agent/getagentproductconfiglogic.go index 0121dca..4bcd737 100644 --- a/app/main/api/internal/logic/agent/getagentproductconfiglogic.go +++ b/app/main/api/internal/logic/agent/getagentproductconfiglogic.go @@ -1,11 +1,13 @@ package agent import ( - "context" + "bdrp-server/app/main/api/internal/service" "bdrp-server/app/main/model" "bdrp-server/common/ctxdata" "bdrp-server/common/xerr" + "context" "math" + "time" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/mr" @@ -104,6 +106,10 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP cancel(findAncestorAgentErr) return } + if service.IsMembershipExpired(ancestorAgentModel, time.Now()) { + writer.Write(&agentProductConfig) + return + } if ancestorAgentModel.LevelName == "" { ancestorAgentModel.LevelName = model.AgentLeveNameNormal } diff --git a/app/main/api/internal/logic/agent/getagentrevenueinfologic.go b/app/main/api/internal/logic/agent/getagentrevenueinfologic.go index 4968e59..efdd638 100644 --- a/app/main/api/internal/logic/agent/getagentrevenueinfologic.go +++ b/app/main/api/internal/logic/agent/getagentrevenueinfologic.go @@ -175,6 +175,11 @@ func calculateActiveReward(rewards []*model.AgentRewards) types.ActiveReward { last30dStart := now.AddDate(0, 0, -30) // 近30天 for _, r := range rewards { + // 跳过已取消的奖励(退款取消) + if r.Status == 1 { + continue + } + createTime := r.CreateTime amount := r.Amount @@ -219,11 +224,12 @@ func addToPeriods(res *types.ActiveReward, amount float64, today, last7d, last30 // 分类添加具体字段 func addToData(data *types.ActiveRewardData, amount float64, t string) { + // 所有类型都累加到总奖励 + data.NewActiveReward += amount + switch t { case "withdraw": data.SubWithdrawReward += amount - case "new_active": - data.NewActiveReward += amount case "upgrade": data.SubUpgradeReward += amount case "promotion": diff --git a/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go index cdb86c8..8f92bb1 100644 --- a/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go +++ b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go @@ -1,10 +1,12 @@ package agent import ( - "context" + "bdrp-server/app/main/api/internal/service" "bdrp-server/app/main/model" "bdrp-server/common/ctxdata" "bdrp-server/common/xerr" + "context" + "time" "github.com/pkg/errors" @@ -37,6 +39,9 @@ func (l *SaveAgentMembershipUserConfigLogic) SaveAgentMembershipUserConfig(req * if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置: %v", err) } + if service.IsMembershipExpired(agentModel, time.Now()) { + return errors.Wrapf(xerr.NewErrMsg("会员已过期"), "保存会员代理报告配置,会员已过期: agent_id=%d", agentModel.Id) + } var agentMembershipUserConfigModel *model.AgentMembershipUserConfig agentMembershipUserConfigModel, err = l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentModel.Id, req.ProductID) diff --git a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go index 82c1903..04d7b97 100644 --- a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go +++ b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go @@ -11,7 +11,6 @@ import ( "strings" "time" - "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" "github.com/zeromicro/go-zero/core/logx" @@ -22,153 +21,6 @@ func roundMoney(v float64) float64 { return math.Round(v*100) / 100 } -// HandleCommissionAndWalletDeduction 处理退款后的佣金状态更新和钱包金额扣除 -// refundAmount 为本次实际退款金额(单位:元),从代理侧总共需要承担的金额 -// 该函数会优先冲减当前订单相关的佣金(基于 RefundedAmount),不足部分再从钱包余额/冻结余额中扣除 -func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.ServiceContext, session sqlx.Session, order *model.Order, refundAmount float64) error { - refundAmount = roundMoney(refundAmount) - if refundAmount <= 0 { - return nil - } - - // 查询当前订单关联的所有佣金记录(包括已结算和冻结),剔除已经完全退款的 - commissionBuilder := svcCtx.AgentCommissionModel.SelectBuilder() - commissions, commissionsErr := svcCtx.AgentCommissionModel.FindAll(ctx, commissionBuilder.Where(squirrel.And{ - squirrel.Eq{"order_id": order.Id}, - squirrel.NotEq{"status": 2}, // 排除已全部退款的佣金 - }), "") - if commissionsErr != nil { - logx.Errorf("查询代理佣金失败,订单ID: %d, 错误: %v", order.Id, commissionsErr) - return nil // 返回 nil,因为佣金更新失败不应影响退款流程 - } - - if len(commissions) == 0 { - return nil - } - - // 剩余需要由佣金 + 钱包共同承担的退款金额 - remainRefundAmount := refundAmount - - // 记录每个代理本次需要从钱包扣除的金额,避免同一代理多条佣金时重复查钱包并产生多条流水 - type walletAdjust struct { - agentId int64 - amount float64 // 需要从该代理钱包扣除的金额(正数) - } - walletAdjustMap := make(map[int64]*walletAdjust) - - // 1. 先在佣金记录上做冲减:增加 RefundedAmount,必要时将状态置为已退款 - for _, commission := range commissions { - available := roundMoney(commission.Amount - commission.RefundedAmount) - if available <= 0 { - continue - } - - if remainRefundAmount <= 0 { - break - } - - // 当前这条佣金最多可冲减 available,本次实际冲减 currentRefund - currentRefund := available - if currentRefund > remainRefundAmount { - currentRefund = remainRefundAmount - } - currentRefund = roundMoney(currentRefund) - - // 更新佣金的已退款金额 - commission.RefundedAmount = roundMoney(commission.RefundedAmount + currentRefund) - // 如果这条佣金已经被完全冲减,则标记为已退款 - if commission.RefundedAmount >= roundMoney(commission.Amount) { - commission.Status = 2 - } - - // 更新佣金状态到数据库 - var updateCommissionErr error - if session != nil { - updateCommissionErr = svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, commission) - } else { - updateCommissionErr = svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, nil, commission) - } - if updateCommissionErr != nil { - logx.Errorf("更新代理佣金状态失败,佣金ID: %d, 订单ID: %d, 错误: %v", commission.Id, order.Id, updateCommissionErr) - continue // 如果佣金状态更新失败,就不继续计入本次冲减 - } - - // 记录该代理需要从钱包扣除的金额(可能后续还有其他佣金叠加) - wa, ok := walletAdjustMap[commission.AgentId] - if !ok { - wa = &walletAdjust{agentId: commission.AgentId} - walletAdjustMap[commission.AgentId] = wa - } - wa.amount = roundMoney(wa.amount + currentRefund) - - remainRefundAmount = roundMoney(remainRefundAmount - currentRefund) - } - - // 2. 再按代理维度,从钱包(冻结余额/可用余额)中扣除对应金额 - for _, wa := range walletAdjustMap { - if wa.amount <= 0 { - continue - } - - // 处理用户钱包的金额扣除 - wallet, err := svcCtx.AgentWalletModel.FindOneByAgentId(ctx, wa.agentId) - if err != nil { - logx.Errorf("查询代理钱包失败,代理ID: %d, 错误: %v", wa.agentId, err) - continue - } - - // 记录变动前的余额 - balanceBefore := wallet.Balance - frozenBalanceBefore := wallet.FrozenBalance - - // 优先从冻结余额中扣除(与原先“冻结佣金优先使用冻结余额”的设计一致) - deduct := roundMoney(wa.amount) - if wallet.FrozenBalance >= deduct { - wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - deduct) - } else { - remaining := roundMoney(deduct - wallet.FrozenBalance) - wallet.FrozenBalance = 0 - // 可用余额可以为负数,由业务承担风险 - wallet.Balance = roundMoney(wallet.Balance - remaining) - } - - // 变动后余额和冻结余额 - balanceAfter := roundMoney(wallet.Balance) - frozenBalanceAfter := roundMoney(wallet.FrozenBalance) - // 更新钱包 - var updateWalletErr error - if session != nil { - updateWalletErr = svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) - } else { - updateWalletErr = svcCtx.AgentWalletModel.UpdateWithVersion(ctx, nil, wallet) - } - if updateWalletErr != nil { - logx.Errorf("更新代理钱包失败,代理ID: %d, 错误: %v", wa.agentId, updateWalletErr) - continue - } - // 创建钱包交易流水记录(退款) - transErr := svcCtx.AgentService.CreateWalletTransaction( - ctx, - session, - wa.agentId, - model.WalletTransactionTypeRefund, - roundMoney(-wa.amount*-1), // 钱包流水金额为负数 - balanceBefore, - balanceAfter, - frozenBalanceBefore, - frozenBalanceAfter, - order.OrderNo, - 0, // 这里不强绑到某一条具体佣金记录,按订单维度记录 - "订单退款,佣金已扣除", - ) - if transErr != nil { - logx.Errorf("创建代理钱包流水记录失败,代理ID: %d, 错误: %v", wa.agentId, transErr) - continue - } - } - return nil -} - type WechatPayRefundCallbackLogic struct { logx.Logger ctx context.Context @@ -227,8 +79,8 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st return errors.Wrapf(err, "更新查询订单状态失败: %s", orderNo) } - // 退款成功时,按本次实际退款金额更新代理佣金状态并扣除钱包金额 - _ = HandleCommissionAndWalletDeduction(ctx, l.svcCtx, session, order, refundAmountYuan) + // 退款成功时,按本次实际退款金额执行共用扣款流程 + l.svcCtx.AgentService.HandleOrderRefundDeduction(ctx, session, order, refundAmountYuan) } // 查找最新的pending状态的退款记录 diff --git a/app/main/api/internal/middleware/membershipinterceptormiddleware.go b/app/main/api/internal/middleware/membershipinterceptormiddleware.go new file mode 100644 index 0000000..eb1e325 --- /dev/null +++ b/app/main/api/internal/middleware/membershipinterceptormiddleware.go @@ -0,0 +1,67 @@ +package middleware + +import ( + "net/http" + "time" + + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/model" + jwtx "bdrp-server/common/jwt" + + "github.com/zeromicro/go-zero/core/logx" +) + +const ( + HeaderMembershipExpired = "X-Membership-Expired" +) + +// MembershipExpiredInterceptor 检测代理会员是否过期,过期则写入响应头 +// 依赖 ctx 中的 claims(由 AuthInterceptorMiddleware 注入)和 svcCtx 中的 AgentModel +type MembershipExpiredInterceptor struct { + AgentModel model.AgentModel +} + +func NewMembershipExpiredInterceptor(agentModel model.AgentModel) *MembershipExpiredInterceptor { + return &MembershipExpiredInterceptor{ + AgentModel: agentModel, + } +} + +func (m *MembershipExpiredInterceptor) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 先执行业务逻辑 + next(w, r) + + // 业务完成后,尝试检测会员过期状态 + claims, err := getClaimsFromRequest(r) + if err != nil || claims == nil { + return + } + + // 只检查正式用户 + if claims.UserType != model.UserTypeNormal { + return + } + + agent, err := m.AgentModel.FindOneByUserId(r.Context(), claims.UserId) + if err != nil { + return + } + + if service.IsMembershipExpired(agent, time.Now()) { + w.Header().Set(HeaderMembershipExpired, "true") + logx.Infof("检测到代理会员已过期,写入响应头,代理ID: %d, 用户ID: %d", agent.Id, claims.UserId) + } + } +} + +func getClaimsFromRequest(r *http.Request) (*jwtx.JwtClaims, error) { + value := r.Context().Value(jwtx.ExtraKey) + if value == nil { + return nil, nil + } + if claims, ok := value.(*jwtx.JwtClaims); ok { + return claims, nil + } + return nil, nil +} diff --git a/app/main/api/internal/queue/agentMembershipExpireHandle.go b/app/main/api/internal/queue/agentMembershipExpireHandle.go new file mode 100644 index 0000000..aeb6c81 --- /dev/null +++ b/app/main/api/internal/queue/agentMembershipExpireHandle.go @@ -0,0 +1,49 @@ +package queue + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" +) + +type AgentMembershipExpireHandleHandler struct { + svcCtx *svc.ServiceContext +} + +func NewAgentMembershipExpireHandleHandler(svcCtx *svc.ServiceContext) *AgentMembershipExpireHandleHandler { + return &AgentMembershipExpireHandleHandler{ + svcCtx: svcCtx, + } +} + +func (l *AgentMembershipExpireHandleHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + var payload types.MsgAgentMembershipExpireHandlePayload + if err := json.Unmarshal(t.Payload(), &payload); err != nil { + logx.Errorf("解析会员到期处理任务payload失败: %v", err) + return err + } + if payload.AgentID <= 0 { + return fmt.Errorf("无效代理ID: %d", payload.AgentID) + } + + now := time.Now() + downgraded, err := service.DowngradeExpiredMembership(ctx, l.svcCtx.AgentModel, payload.AgentID, now) + if err != nil { + logx.Errorf("会员到期处理失败,代理ID: %d, 错误: %v", payload.AgentID, err) + return err + } + if downgraded { + logx.Infof("会员到期处理成功,代理ID: %d 已降级为普通代理", payload.AgentID) + } else { + logx.Infof("会员到期处理跳过,代理ID: %d 当前无需降级", payload.AgentID) + } + return nil +} diff --git a/app/main/api/internal/queue/agentMembershipExpireScan.go b/app/main/api/internal/queue/agentMembershipExpireScan.go new file mode 100644 index 0000000..051be8d --- /dev/null +++ b/app/main/api/internal/queue/agentMembershipExpireScan.go @@ -0,0 +1,68 @@ +package queue + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/api/internal/svc" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" +) + +type AgentMembershipExpireScanHandler struct { + svcCtx *svc.ServiceContext +} + +func NewAgentMembershipExpireScanHandler(svcCtx *svc.ServiceContext) *AgentMembershipExpireScanHandler { + return &AgentMembershipExpireScanHandler{ + svcCtx: svcCtx, + } +} + +func (l *AgentMembershipExpireScanHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + now := time.Now() + dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + dayEnd := dayStart.Add(24*time.Hour - time.Second) + + // 1. 扫描当天将到期会员,并为每个会员安排准点处理任务 + todayAgents, err := service.ListTodayWillExpireAgents(ctx, l.svcCtx.AgentModel, dayStart, dayEnd) + if err != nil { + logx.Errorf("扫描当天到期会员失败: %v", err) + return err + } + for _, agent := range todayAgents { + processAt := agent.MembershipExpiryTime.Time + if processAt.Before(now) { + processAt = now + } + if l.svcCtx.AsynqService != nil { + if sendErr := l.svcCtx.AsynqService.SendAgentMembershipExpireHandleTask(agent.Id, processAt); sendErr != nil { + logx.Errorf("安排会员到期处理任务失败,代理ID: %d, 错误: %v", agent.Id, sendErr) + } + } + } + + // 2. 补偿处理:扫描已过期但尚未降级的会员并立即降级 + expiredAgents, err := service.ListExpiredUnprocessedAgents(ctx, l.svcCtx.AgentModel, now) + if err != nil { + logx.Errorf("扫描已过期未处理会员失败: %v", err) + return err + } + + downgradedCount := 0 + for _, agent := range expiredAgents { + downgraded, degradeErr := service.DowngradeExpiredMembership(ctx, l.svcCtx.AgentModel, agent.Id, now) + if degradeErr != nil { + logx.Errorf("补偿降级失败,代理ID: %d, 错误: %v", agent.Id, degradeErr) + continue + } + if downgraded { + downgradedCount++ + } + } + + logx.Infof("会员到期扫描完成,当天到期会员: %d, 补偿降级数量: %d", len(todayAgents), downgradedCount) + return nil +} diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index 5b33087..5575150 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -10,7 +10,6 @@ import ( "path" "regexp" "strings" - paylogic "bdrp-server/app/main/api/internal/logic/pay" "bdrp-server/app/main/api/internal/service" "bdrp-server/app/main/api/internal/svc" "bdrp-server/app/main/api/internal/types" @@ -283,8 +282,8 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error return fmt.Errorf("更新订单状态失败: %v", updateOrderErr) } - // 使用公共函数按本次退款金额处理佣金和钱包扣除 - _ = paylogic.HandleCommissionAndWalletDeduction(ctx, l.svcCtx, nil, order, order.Amount) + // 使用 AgentService 中的共用退款扣款逻辑 + l.svcCtx.AgentService.HandleOrderRefundDeduction(ctx, nil, order, order.Amount) return asynq.SkipRetry } else { diff --git a/app/main/api/internal/queue/routes.go b/app/main/api/internal/queue/routes.go index 5c18130..a3ed2bb 100644 --- a/app/main/api/internal/queue/routes.go +++ b/app/main/api/internal/queue/routes.go @@ -1,12 +1,14 @@ package queue import ( - "context" - "fmt" "bdrp-server/app/main/api/internal/svc" "bdrp-server/app/main/api/internal/types" + "context" + "fmt" + "time" "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" ) type CronJob struct { @@ -14,6 +16,8 @@ type CronJob struct { svcCtx *svc.ServiceContext } +const AgentMembershipExpireScanTaskTime = "5 0 * * *" + func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob { return &CronJob{ ctx: ctx, @@ -31,6 +35,29 @@ func (l *CronJob) Register() *asynq.ServeMux { if err != nil { panic(fmt.Sprintf("定时任务注册失败:%v", err)) } + + // 注册会员到期扫描任务(每天凌晨执行) + expireScanTask := asynq.NewTask(types.MsgAgentMembershipExpireScan, nil, nil) + _, err = scheduler.Register(AgentMembershipExpireScanTaskTime, expireScanTask) + if err != nil { + panic(fmt.Sprintf("会员到期扫描任务注册失败:%v", err)) + } + + // 启动补偿:服务启动后立即触发一次扫描任务(按日期去重) + client := asynq.NewClient(redisClientOpt) + startupTaskID := fmt.Sprintf("agent_membership_expire_scan_startup_%s", time.Now().Format("20060102")) + _, enqueueErr := client.Enqueue( + asynq.NewTask(types.MsgAgentMembershipExpireScan, nil), + asynq.MaxRetry(1), + asynq.TaskID(startupTaskID), + ) + if enqueueErr != nil { + logx.Errorf("启动补偿扫描任务入队失败: %v", enqueueErr) + } + if closeErr := client.Close(); closeErr != nil { + logx.Errorf("关闭启动补偿任务客户端失败: %v", closeErr) + } + scheduler.Start() fmt.Println("定时任务启动!!!") @@ -38,6 +65,8 @@ func (l *CronJob) Register() *asynq.ServeMux { mux.Handle(types.MsgPaySuccessQuery, NewPaySuccessNotifyUserHandler(l.svcCtx)) mux.Handle(types.MsgCleanQueryData, NewCleanQueryDataHandler(l.svcCtx)) mux.Handle(types.MsgUnfreezeCommission, NewUnfreezeCommissionHandler(l.svcCtx)) + mux.Handle(types.MsgAgentMembershipExpireScan, NewAgentMembershipExpireScanHandler(l.svcCtx)) + mux.Handle(types.MsgAgentMembershipExpireHandle, NewAgentMembershipExpireHandleHandler(l.svcCtx)) return mux } diff --git a/app/main/api/internal/service/agentMembershipService.go b/app/main/api/internal/service/agentMembershipService.go new file mode 100644 index 0000000..1a970bf --- /dev/null +++ b/app/main/api/internal/service/agentMembershipService.go @@ -0,0 +1,70 @@ +package service + +import ( + "context" + "database/sql" + "time" + + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" +) + +// IsMembershipExpired 判断代理会员是否已过期(仅VIP/SVIP会被判定) +func IsMembershipExpired(agent *model.Agent, now time.Time) bool { + if agent == nil { + return false + } + if agent.LevelName != model.AgentLeveNameVIP && agent.LevelName != model.AgentLeveNameSVIP { + return false + } + if !agent.MembershipExpiryTime.Valid { + return true + } + return !agent.MembershipExpiryTime.Time.After(now) +} + +// DowngradeExpiredMembership 将已过期会员降级为普通代理,返回是否发生了降级 +func DowngradeExpiredMembership(ctx context.Context, agentModel model.AgentModel, agentID int64, now time.Time) (bool, error) { + agent, err := agentModel.FindOne(ctx, agentID) + if err != nil { + return false, err + } + if !IsMembershipExpired(agent, now) { + return false, nil + } + agent.LevelName = model.AgentLeveNameNormal + ClearMembershipOnDowngrade(agent) + agent.UpdateTime = now + if err = agentModel.UpdateWithVersion(ctx, nil, agent); err != nil { + return false, err + } + return true, nil +} + +// ListTodayWillExpireAgents 查询当天将到期的VIP/SVIP代理 +func ListTodayWillExpireAgents(ctx context.Context, agentModel model.AgentModel, start, end time.Time) ([]*model.Agent, error) { + builder := agentModel.SelectBuilder(). + Where("level_name IN (?, ?)", model.AgentLeveNameVIP, model.AgentLeveNameSVIP). + Where("membership_expiry_time IS NOT NULL"). + Where("membership_expiry_time >= ?", start). + Where("membership_expiry_time <= ?", end). + Where("del_state = ?", globalkey.DelStateNo) + + return agentModel.FindAll(ctx, builder, "membership_expiry_time ASC") +} + +// ListExpiredUnprocessedAgents 查询已过期但仍为VIP/SVIP的代理 +func ListExpiredUnprocessedAgents(ctx context.Context, agentModel model.AgentModel, now time.Time) ([]*model.Agent, error) { + builder := agentModel.SelectBuilder(). + Where("level_name IN (?, ?)", model.AgentLeveNameVIP, model.AgentLeveNameSVIP). + Where("membership_expiry_time IS NOT NULL"). + Where("membership_expiry_time <= ?", now). + Where("del_state = ?", globalkey.DelStateNo) + + return agentModel.FindAll(ctx, builder, "membership_expiry_time ASC") +} + +// ClearMembershipOnDowngrade 降级时清理会员有效期,避免脏状态 +func ClearMembershipOnDowngrade(agent *model.Agent) { + agent.MembershipExpiryTime = sql.NullTime{} +} diff --git a/app/main/api/internal/service/agentService.go b/app/main/api/internal/service/agentService.go index c0625eb..528afda 100644 --- a/app/main/api/internal/service/agentService.go +++ b/app/main/api/internal/service/agentService.go @@ -1,14 +1,17 @@ package service import ( - "context" - "database/sql" - "fmt" "bdrp-server/app/main/api/internal/config" "bdrp-server/app/main/model" "bdrp-server/common/globalkey" "bdrp-server/pkg/lzkit/lzUtils" + "context" + "database/sql" + "fmt" + "math" + "time" + "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/sqlx" @@ -34,6 +37,7 @@ type AgentService struct { AgentActiveStatModel model.AgentActiveStatModel AgentWithdrawalModel model.AgentWithdrawalModel AgentWalletTransactionModel model.AgentWalletTransactionModel + AgentConfigModel model.AgentConfigModel AsynqService *AsynqService } @@ -44,7 +48,7 @@ func NewAgentService(c config.Config, orderModel model.OrderModel, agentModel mo agentMembershipRechargeOrderModel model.AgentMembershipRechargeOrderModel, agentMembershipUserConfigModel model.AgentMembershipUserConfigModel, agentProductConfigModel model.AgentProductConfigModel, agentPlatformDeductionModel model.AgentPlatformDeductionModel, - agentActiveStatModel model.AgentActiveStatModel, agentWithdrawalModel model.AgentWithdrawalModel, agentWalletTransactionModel model.AgentWalletTransactionModel, asynqService *AsynqService) *AgentService { + agentActiveStatModel model.AgentActiveStatModel, agentWithdrawalModel model.AgentWithdrawalModel, agentWalletTransactionModel model.AgentWalletTransactionModel, agentConfigModel model.AgentConfigModel, asynqService *AsynqService) *AgentService { return &AgentService{ config: c, @@ -66,6 +70,7 @@ func NewAgentService(c config.Config, orderModel model.OrderModel, agentModel mo AgentActiveStatModel: agentActiveStatModel, AgentWithdrawalModel: agentWithdrawalModel, AgentWalletTransactionModel: agentWalletTransactionModel, + AgentConfigModel: agentConfigModel, AsynqService: asynqService, } } @@ -113,6 +118,9 @@ func (l *AgentService) AgentProcess(ctx context.Context, order *model.Order) err return findAgentModelErr } if AncestorModel != nil { + if IsMembershipExpired(AncestorModel, time.Now()) { + AncestorModel.LevelName = model.AgentLeveNameNormal + } if AncestorModel.LevelName == "" { AncestorModel.LevelName = model.AgentLeveNameNormal } @@ -155,7 +163,7 @@ func (l *AgentService) AgentProcess(ctx context.Context, order *model.Order) err // 根据安全防御模式配置决定佣金处理方式 var commissionStatus int64 - if l.config.SystemConfig.CommissionSafeMode { + if l.AgentConfigModel.IsCommissionSafeMode(ctx) { // 安全防御模式:佣金冻结在frozen_balance中 ancestorWallet.FrozenBalance += ancestorCommissionAmount commissionStatus = 1 // 冻结状态 @@ -227,7 +235,7 @@ func (l *AgentService) AgentProcess(ctx context.Context, order *model.Order) err // 在事务提交后,仅在安全防御模式下触发解冻任务 // 注意:这里发送的是任务,实际解冻将在指定时间后由队列处理 - if l.AsynqService != nil && l.config.SystemConfig.CommissionSafeMode { + if l.AsynqService != nil && l.AgentConfigModel.IsCommissionSafeMode(ctx) { // 仅在安全防御模式下,才需要发送解冻任务 // 获取刚创建的佣金记录ID // 由于我们需要佣金记录ID来触发解冻任务,但事务中无法获取,我们可以在事务后查询 @@ -275,7 +283,7 @@ func (l *AgentService) AgentCommission(ctx context.Context, agentID int64, order frozenBalanceBefore := agentWalletModel.FrozenBalance // 根据安全防御模式配置决定佣金状态和钱包操作 - if l.config.SystemConfig.CommissionSafeMode { + if l.AgentConfigModel.IsCommissionSafeMode(ctx) { // 安全防御模式:佣金冻结在frozen_balance中 agentWalletModel.FrozenBalance += finalCommission } else { @@ -286,7 +294,7 @@ func (l *AgentService) AgentCommission(ctx context.Context, agentID int64, order // 根据安全防御模式配置决定佣金状态 commissionStatus := int64(1) // 默认为冻结状态 - if !l.config.SystemConfig.CommissionSafeMode { + if !l.AgentConfigModel.IsCommissionSafeMode(ctx) { commissionStatus = 0 // 非安全模式直接设置为已结算 } @@ -343,6 +351,9 @@ func (l *AgentService) AncestorCommission(ctx context.Context, descendantId int6 if err != nil { return 0, err } + if IsMembershipExpired(agentModel, time.Now()) { + agentModel.LevelName = model.AgentLeveNameNormal + } if agentModel.LevelName == "" { agentModel.LevelName = model.AgentLeveNameNormal } @@ -415,6 +426,17 @@ func (l *AgentService) PlatformPricing(ctx context.Context, agentID int64, order // CommissionCost 上级底价成本 func (l *AgentService) CommissionCost(ctx context.Context, descendantId int64, AncestorId int64, agentMembershipConfigModel *model.AgentMembershipConfig, productID int64, orderId int64, session sqlx.Session) (float64, error) { + ancestorModel, findAncestorErr := l.AgentModel.FindOne(ctx, AncestorId) + if findAncestorErr != nil { + if errors.Is(findAncestorErr, model.ErrNotFound) { + return 0, nil + } + return 0, findAncestorErr + } + if IsMembershipExpired(ancestorModel, time.Now()) { + return 0, nil + } + if agentMembershipConfigModel.PriceIncreaseAmount.Valid { // 拥有则查看该上级设定的成本 agentMembershipUserConfigModel, findAgentMembershipUserConfigModelErr := l.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(ctx, AncestorId, productID) @@ -449,6 +471,17 @@ func (l *AgentService) CommissionCost(ctx context.Context, descendantId int64, A // CommissionPricing 上级提价成本 func (l *AgentService) CommissionPricing(ctx context.Context, descendantId int64, AncestorId int64, agentMembershipConfigModel *model.AgentMembershipConfig, productID int64, pricing float64, orderId int64, session sqlx.Session) (float64, error) { + ancestorModel, findAncestorErr := l.AgentModel.FindOne(ctx, AncestorId) + if findAncestorErr != nil { + if errors.Is(findAncestorErr, model.ErrNotFound) { + return 0, nil + } + return 0, findAncestorErr + } + if IsMembershipExpired(ancestorModel, time.Now()) { + return 0, nil + } + //看上级代理等级否有拥有定价标准收益功能 if agentMembershipConfigModel.PriceIncreaseMax.Valid && agentMembershipConfigModel.PriceRatio.Valid { // 拥有则查看该上级设定的成本 @@ -517,6 +550,9 @@ func (l *AgentService) GiveUpgradeReward(ctx context.Context, agentID int64, old } // 获取上级代理的等级配置 + if IsMembershipExpired(ancestorModel, time.Now()) { + ancestorModel.LevelName = model.AgentLeveNameNormal + } if ancestorModel.LevelName == "" { ancestorModel.LevelName = model.AgentLeveNameNormal } @@ -661,6 +697,9 @@ func (l *AgentService) GiveWithdrawReward(ctx context.Context, agentID int64, wi } // 获取上级代理的等级配置 + if IsMembershipExpired(ancestorModel, time.Now()) { + ancestorModel.LevelName = model.AgentLeveNameNormal + } if ancestorModel.LevelName == "" { ancestorModel.LevelName = model.AgentLeveNameNormal } @@ -848,3 +887,211 @@ func (l *AgentService) CreateWalletTransaction(ctx context.Context, session sqlx } return nil } + +// HandleOrderRefundDeduction 处理订单退款后的佣金扣款流程 +// refundAmount: 本次实际退款金额 +// 扣款顺序: +// 1. 扣减推广代理佣金(先扣冻结,再扣余额) +// 2. 扣减上级抽佣 +// 3. 取消上级推广奖励(无论退多少都取消) +// 4. 不足部分继续从推广代理钱包扣 +// 平台抽佣不受影响 +func (l *AgentService) HandleOrderRefundDeduction(ctx context.Context, session sqlx.Session, order *model.Order, refundAmount float64) { + refundAmount = roundRefundMoney(refundAmount) + if refundAmount <= 0 { + return + } + + remainRefundAmount := refundAmount + + // 查找订单关联的代理订单,获取推广人代理ID + agentOrder, err := l.AgentOrderModel.FindOneByOrderId(ctx, order.Id) + if err != nil || agentOrder == nil { + logx.Errorf("退款扣款:查询代理订单失败,订单ID: %d, 错误: %v", order.Id, err) + return + } + promoterAgentId := agentOrder.AgentId + + // 查找上级代理ID + var ancestorAgentId int64 = 0 + agentClosure, closureErr := l.AgentClosureModel.FindOneByDescendantIdDepth(ctx, promoterAgentId, 1) + if closureErr == nil && agentClosure != nil { + ancestorAgentId = agentClosure.AncestorId + } + + // 第1步:扣减推广代理佣金 + promoterCommissions, _ := l.AgentCommissionModel.FindAll(ctx, + l.AgentCommissionModel.SelectBuilder().Where(squirrel.And{ + squirrel.Eq{"order_id": order.Id}, + squirrel.Eq{"agent_id": promoterAgentId}, + squirrel.NotEq{"status": 2}, + }), "") + + for _, commission := range promoterCommissions { + if remainRefundAmount <= 0 { + break + } + available := roundRefundMoney(commission.Amount - commission.RefundedAmount) + if available <= 0 { + continue + } + + currentRefund := available + if currentRefund > remainRefundAmount { + currentRefund = remainRefundAmount + } + currentRefund = roundRefundMoney(currentRefund) + + commission.RefundedAmount = roundRefundMoney(commission.RefundedAmount + currentRefund) + if commission.RefundedAmount >= roundRefundMoney(commission.Amount) { + commission.Status = 2 + } + + if err := l.AgentCommissionModel.UpdateWithVersion(ctx, session, commission); err != nil { + logx.Errorf("退款扣款:更新推广佣金失败,佣金ID: %d, 错误: %v", commission.Id, err) + continue + } + + deductFromAgentWallet(ctx, l, session, promoterAgentId, currentRefund, + fmt.Sprintf("订单退款,推广佣金扣除,订单号: %s", order.OrderNo)) + + remainRefundAmount = roundRefundMoney(remainRefundAmount - currentRefund) + } + + // 第2步:扣减上级抽佣 + if ancestorAgentId > 0 && remainRefundAmount > 0 { + deductions, _ := l.AgentCommissionDeductionModel.FindAll(ctx, + l.AgentCommissionDeductionModel.SelectBuilder().Where(map[string]interface{}{ + "order_id": order.Id, + "agent_id": ancestorAgentId, + }), "") + + for _, deduction := range deductions { + if deduction.Status == 2 { + continue + } + if remainRefundAmount <= 0 { + break + } + available := roundRefundMoney(deduction.Amount - deduction.RefundedAmount) + if available <= 0 { + continue + } + + currentRefund := available + if currentRefund > remainRefundAmount { + currentRefund = remainRefundAmount + } + currentRefund = roundRefundMoney(currentRefund) + + deduction.RefundedAmount = roundRefundMoney(deduction.RefundedAmount + currentRefund) + if deduction.RefundedAmount >= roundRefundMoney(deduction.Amount) { + deduction.Status = 2 + } + + if err := l.AgentCommissionDeductionModel.UpdateWithVersion(ctx, session, deduction); err != nil { + logx.Errorf("退款扣款:更新上级抽佣失败,ID: %d, 错误: %v", deduction.Id, err) + continue + } + + deductFromAgentWallet(ctx, l, session, ancestorAgentId, currentRefund, + fmt.Sprintf("订单退款,上级抽佣扣除,订单号: %s", order.OrderNo)) + + remainRefundAmount = roundRefundMoney(remainRefundAmount - currentRefund) + } + } + + // 第3步:取消上级推广奖励(无论退多少都取消) + if ancestorAgentId > 0 { + rewards, _ := l.AgentRewardsModel.FindAll(ctx, + l.AgentRewardsModel.SelectBuilder().Where(map[string]interface{}{ + "agent_id": ancestorAgentId, + "relation_agent_id": promoterAgentId, + "type": model.AgentRewardsTypeDescendantPromotion, + "status": 0, + }), "id DESC") + + if len(rewards) > 0 { + reward := rewards[0] + rewardAmount := roundRefundMoney(reward.Amount) + + reward.Status = 1 + reward.Remark = fmt.Sprintf("订单退款取消奖励,订单号: %s", order.OrderNo) + + if err := l.AgentRewardsModel.UpdateWithVersion(ctx, session, reward); err != nil { + logx.Errorf("退款扣款:取消推广奖励失败,奖励ID: %d, 错误: %v", reward.Id, err) + } else { + deductFromAgentWallet(ctx, l, session, ancestorAgentId, rewardAmount, + fmt.Sprintf("订单退款,取消推广奖励,订单号: %s", order.OrderNo)) + } + } + } + + // 第4步:不足部分从推广代理钱包扣 + if remainRefundAmount > 0 { + deductFromAgentWallet(ctx, l, session, promoterAgentId, remainRefundAmount, + fmt.Sprintf("订单退款,不足部分从钱包扣除,订单号: %s", order.OrderNo)) + } +} + +// roundRefundMoney 四舍五入到分 +func roundRefundMoney(v float64) float64 { + return math.Round(v*100) / 100 +} + +// deductFromAgentWallet 从代理钱包扣除金额(先冻结后余额) +func deductFromAgentWallet(ctx context.Context, l *AgentService, session sqlx.Session, agentId int64, amount float64, remark string) { + amount = roundRefundMoney(amount) + if amount <= 0 { + return + } + + wallet, err := l.AgentWalletModel.FindOneByAgentId(ctx, agentId) + if err != nil { + logx.Errorf("退款扣款:查询代理钱包失败,代理ID: %d, 错误: %v", agentId, err) + return + } + + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + if wallet.FrozenBalance >= amount { + wallet.FrozenBalance = roundRefundMoney(wallet.FrozenBalance - amount) + } else { + remaining := roundRefundMoney(amount - wallet.FrozenBalance) + wallet.FrozenBalance = 0 + wallet.Balance = roundRefundMoney(wallet.Balance - remaining) + } + + balanceAfter := roundRefundMoney(wallet.Balance) + frozenBalanceAfter := roundRefundMoney(wallet.FrozenBalance) + + var updateErr error + if session != nil { + updateErr = l.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) + } else { + updateErr = l.AgentWalletModel.UpdateWithVersion(ctx, nil, wallet) + } + if updateErr != nil { + logx.Errorf("退款扣款:更新代理钱包失败,代理ID: %d, 错误: %v", agentId, updateErr) + return + } + + transErr := l.CreateWalletTransaction( + ctx, + session, + agentId, + model.WalletTransactionTypeRefund, + roundRefundMoney(-amount), + balanceBefore, + balanceAfter, + frozenBalanceBefore, + frozenBalanceAfter, + "", + 0, + remark, + ) + if transErr != nil { + logx.Errorf("退款扣款:创建钱包流水失败,代理ID: %d, 错误: %v", agentId, transErr) + } +} diff --git a/app/main/api/internal/service/asynqService.go b/app/main/api/internal/service/asynqService.go index fa8ce1b..6d27e69 100644 --- a/app/main/api/internal/service/asynqService.go +++ b/app/main/api/internal/service/asynqService.go @@ -4,6 +4,7 @@ package service import ( "encoding/json" + "fmt" "time" "bdrp-server/app/main/api/internal/config" @@ -90,3 +91,32 @@ func (s *AsynqService) SendUnfreezeCommissionTask(commissionID int64) error { logx.Infof("发送佣金解冻任务成功,任务ID: %s, 队列: %s, 佣金ID: %d", info.ID, info.Queue, commissionID) return nil } + +// SendAgentMembershipExpireHandleTask 发送代理会员到期处理任务 +func (s *AsynqService) SendAgentMembershipExpireHandleTask(agentID int64, processAt time.Time) error { + payload := types.MsgAgentMembershipExpireHandlePayload{ + AgentID: agentID, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + logx.Errorf("发送会员到期处理任务失败 (无法编码 payload): %v, 代理ID: %d", err, agentID) + return err + } + + taskID := fmt.Sprintf("agent_membership_expire_handle_%d", agentID) + options := []asynq.Option{ + asynq.ProcessAt(processAt), + asynq.MaxRetry(5), + asynq.TaskID(taskID), + } + task := asynq.NewTask(types.MsgAgentMembershipExpireHandle, payloadBytes, options...) + + info, err := s.client.Enqueue(task) + if err != nil { + logx.Errorf("发送会员到期处理任务失败 (加入队列失败): %+v, 代理ID: %d, TaskID: %s", err, agentID, taskID) + return err + } + + logx.Infof("发送会员到期处理任务成功,任务ID: %s, 队列: %s, 代理ID: %d, 执行时间: %s", info.ID, info.Queue, agentID, processAt.Format("2006-01-02 15:04:05")) + return nil +} diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index 3dc2b52..45165d1 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -65,6 +65,7 @@ type ServiceContext struct { AgentWithdrawalTaxModel model.AgentWithdrawalTaxModel AgentWithdrawalTaxExemptionModel model.AgentWithdrawalTaxExemptionModel AgentWalletTransactionModel model.AgentWalletTransactionModel + AgentConfigModel model.AgentConfigModel // 管理后台相关模型 AdminApiModel model.AdminApiModel @@ -155,6 +156,7 @@ func NewServiceContext(c config.Config) *ServiceContext { agentWithdrawalTaxModel := model.NewAgentWithdrawalTaxModel(db, cacheConf) agentWithdrawalTaxExemptionModel := model.NewAgentWithdrawalTaxExemptionModel(db, cacheConf) agentWalletTransactionModel := model.NewAgentWalletTransactionModel(db, cacheConf) + agentConfigModel := model.NewAgentConfigModel(db, cacheConf) // ============================== 管理后台相关模型 ============================== adminApiModel := model.NewAdminApiModel(db, cacheConf) adminMenuModel := model.NewAdminMenuModel(db, cacheConf) @@ -196,7 +198,7 @@ func NewServiceContext(c config.Config) *ServiceContext { agentCommissionModel, agentCommissionDeductionModel, agentWalletModel, agentLinkModel, agentOrderModel, agentRewardsModel, agentMembershipConfigModel, agentMembershipRechargeOrderModel, agentMembershipUserConfigModel, agentProductConfigModel, agentPlatformDeductionModel, - agentActiveStatModel, agentWithdrawalModel, agentWalletTransactionModel, asynqService) + agentActiveStatModel, agentWithdrawalModel, agentWalletTransactionModel, agentConfigModel, asynqService) userService := service.NewUserService(&c, userModel, userAuthModel, userTempModel, agentModel) dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel) adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel, @@ -265,6 +267,7 @@ func NewServiceContext(c config.Config) *ServiceContext { AgentWithdrawalTaxModel: agentWithdrawalTaxModel, AgentWithdrawalTaxExemptionModel: agentWithdrawalTaxExemptionModel, AgentWalletTransactionModel: agentWalletTransactionModel, + AgentConfigModel: agentConfigModel, // 管理后台相关模型 AdminApiModel: adminApiModel, diff --git a/app/main/api/internal/types/payload.go b/app/main/api/internal/types/payload.go index 6e0310b..083178b 100644 --- a/app/main/api/internal/types/payload.go +++ b/app/main/api/internal/types/payload.go @@ -7,3 +7,7 @@ type MsgPaySuccessQueryPayload struct { type MsgUnfreezeCommissionPayload struct { CommissionID int64 `json:"commission_id"` } + +type MsgAgentMembershipExpireHandlePayload struct { + AgentID int64 `json:"agent_id"` +} diff --git a/app/main/api/internal/types/taskname.go b/app/main/api/internal/types/taskname.go index 714bae6..f55f65d 100644 --- a/app/main/api/internal/types/taskname.go +++ b/app/main/api/internal/types/taskname.go @@ -3,3 +3,5 @@ package types const MsgPaySuccessQuery = "msg:pay_success:query" const MsgCleanQueryData = "msg:clean_query_data" const MsgUnfreezeCommission = "msg:unfreeze_commission" +const MsgAgentMembershipExpireScan = "msg:agent_membership_expire_scan" +const MsgAgentMembershipExpireHandle = "msg:agent_membership_expire_handle" diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index 9e24e55..596baf4 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -339,6 +339,7 @@ type AdminGetAgentRewardListReq struct { AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) RelationAgentId *int64 `form:"relation_agent_id,optional"` // 关联代理ID(可选) Type *string `form:"type,optional"` // 奖励类型(可选) + Status *int64 `form:"status,optional"` // 状态(可选) } type AdminGetAgentRewardListResp struct { @@ -1080,6 +1081,7 @@ type AgentCommissionDeductionListItem struct { AgentId int64 `json:"agent_id"` // 代理ID DeductedAgentId int64 `json:"deducted_agent_id"` // 被扣代理ID Amount float64 `json:"amount"` // 金额 + RefundedAmount float64 `json:"refunded_amount"` // 已退款金额 ProductName string `json:"product_name"` // 产品名 Type string `json:"type"` // 类型(cost/pricing) Status int64 `json:"status"` // 状态 @@ -1255,6 +1257,8 @@ type AgentRewardListItem struct { RelationAgentId int64 `json:"relation_agent_id"` // 关联代理ID Amount float64 `json:"amount"` // 金额 Type string `json:"type"` // 奖励类型 + Status int64 `json:"status"` // 状态:0=正常, 1=已取消 + Remark string `json:"remark"` // 备注 CreateTime string `json:"create_time"` // 创建时间 } diff --git a/app/main/api/main.go b/app/main/api/main.go index ab2c396..a44b410 100644 --- a/app/main/api/main.go +++ b/app/main/api/main.go @@ -1,16 +1,16 @@ package main import ( - "context" - "flag" - "fmt" - "os" "bdrp-server/app/main/api/internal/config" "bdrp-server/app/main/api/internal/handler" "bdrp-server/app/main/api/internal/middleware" "bdrp-server/app/main/api/internal/queue" "bdrp-server/app/main/api/internal/service" "bdrp-server/app/main/api/internal/svc" + "context" + "flag" + "fmt" + "os" "github.com/zeromicro/go-zero/core/logx" @@ -58,6 +58,7 @@ func main() { server := rest.MustNewServer(c.RestConf) server.Use(middleware.GlobalSourceInterceptor) + server.Use(middleware.NewMembershipExpiredInterceptor(svcContext.AgentModel).Handle) defer server.Stop() handler.RegisterHandlers(server, svcContext) diff --git a/app/main/model/agentCommissionDeductionModel_gen.go b/app/main/model/agentCommissionDeductionModel_gen.go index 1537d11..615fdc4 100644 --- a/app/main/model/agentCommissionDeductionModel_gen.go +++ b/app/main/model/agentCommissionDeductionModel_gen.go @@ -11,7 +11,6 @@ import ( "time" "bdrp-server/common/globalkey" - "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/stores/builder" @@ -59,8 +58,9 @@ type ( AgentId int64 `db:"agent_id"` DeductedAgentId int64 `db:"deducted_agent_id"` // 被抽佣代理ID Amount float64 `db:"amount"` - ProductId int64 `db:"product_id"` // 产品ID - OrderId sql.NullInt64 `db:"order_id"` // 关联订单ID + RefundedAmount float64 `db:"refunded_amount"` // 已退款金额 + ProductId int64 `db:"product_id"` // 产品ID + OrderId sql.NullInt64 `db:"order_id"` // 关联订单ID Type string `db:"type"` Status int64 `db:"status"` // 状态 CreateTime time.Time `db:"create_time"` @@ -82,11 +82,11 @@ func (m *defaultAgentCommissionDeductionModel) Insert(ctx context.Context, sessi data.DelState = globalkey.DelStateNo bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, data.Id) return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentCommissionDeductionRowsExpectAutoSet) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentCommissionDeductionRowsExpectAutoSet) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) } - return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) }, bdrpAgentCommissionDeductionIdKey) } @@ -112,9 +112,9 @@ func (m *defaultAgentCommissionDeductionModel) Update(ctx context.Context, sessi return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentCommissionDeductionRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) } - return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) }, bdrpAgentCommissionDeductionIdKey) } @@ -130,9 +130,9 @@ func (m *defaultAgentCommissionDeductionModel) UpdateWithVersion(ctx context.Con sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentCommissionDeductionRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) } - return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.RefundedAmount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) }, bdrpAgentCommissionDeductionIdKey) if err != nil { return err diff --git a/app/main/model/agentConfigModel.go b/app/main/model/agentConfigModel.go new file mode 100644 index 0000000..7ed9ccc --- /dev/null +++ b/app/main/model/agentConfigModel.go @@ -0,0 +1,57 @@ +package model + +import ( + "context" + "strconv" + + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AgentConfigModel = (*customAgentConfigModel)(nil) + +type ( + // AgentConfigModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentConfigModel. + AgentConfigModel interface { + agentConfigModel + IsCommissionSafeMode(ctx context.Context) bool + SetCommissionSafeMode(ctx context.Context, enabled bool) error + } + + customAgentConfigModel struct { + *defaultAgentConfigModel + } +) + +// 配置键常量 +const ( + AgentConfigKeyCommissionSafeMode = "commission_safe_mode" +) + +// NewAgentConfigModel returns a model for the database table. +func NewAgentConfigModel(conn sqlx.SqlConn, c cache.CacheConf) AgentConfigModel { + return &customAgentConfigModel{ + defaultAgentConfigModel: newAgentConfigModel(conn, c), + } +} + +// IsCommissionSafeMode 从数据库查询佣金安全防御模式是否开启 +func (m *customAgentConfigModel) IsCommissionSafeMode(ctx context.Context) bool { + config, err := m.FindOneByConfigKey(ctx, AgentConfigKeyCommissionSafeMode) + if err != nil { + return false + } + enabled, _ := strconv.ParseBool(config.ConfigValue) + return enabled +} + +// SetCommissionSafeMode 更新佣金安全防御模式开关 +func (m *customAgentConfigModel) SetCommissionSafeMode(ctx context.Context, enabled bool) error { + config, err := m.FindOneByConfigKey(ctx, AgentConfigKeyCommissionSafeMode) + if err != nil { + return err + } + config.ConfigValue = strconv.FormatBool(enabled) + return m.UpdateWithVersion(ctx, nil, config) +} diff --git a/app/main/model/agentConfigModel_gen.go b/app/main/model/agentConfigModel_gen.go new file mode 100644 index 0000000..556bd49 --- /dev/null +++ b/app/main/model/agentConfigModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentConfigFieldNames = builder.RawFieldNames(&AgentConfig{}) + agentConfigRows = strings.Join(agentConfigFieldNames, ",") + agentConfigRowsExpectAutoSet = strings.Join(stringx.Remove(agentConfigFieldNames, "`create_time`", "`update_time`"), ",") + agentConfigRowsWithPlaceHolder = strings.Join(stringx.Remove(agentConfigFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentConfigIdPrefix = "cache:bdrp:agentConfig:id:" + cacheBdrpAgentConfigConfigKeyPrefix = "cache:bdrp:agentConfig:configKey:" +) + +type ( + agentConfigModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentConfig) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentConfig, error) + FindOneByConfigKey(ctx context.Context, configKey string) (*AgentConfig, error) + Update(ctx context.Context, session sqlx.Session, data *AgentConfig) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentConfig) error + Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error + SelectBuilder() squirrel.SelectBuilder + DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentConfig) error + FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error) + FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error) + FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*AgentConfig, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentConfig, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentConfig, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentConfig, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentConfig, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentConfigModel struct { + sqlc.CachedConn + table string + } + + AgentConfig struct { + Id int64 `db:"id"` // 主键ID + ConfigKey string `db:"config_key"` // 配置键名 + ConfigValue string `db:"config_value"` // 配置值 + Remark string `db:"remark"` // 备注说明 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + } +) + +func newAgentConfigModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentConfigModel { + return &defaultAgentConfigModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_config`", + } +} + +func (m *defaultAgentConfigModel) Insert(ctx context.Context, session sqlx.Session, data *AgentConfig) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigConfigKeyPrefix, data.ConfigKey) + bdrpAgentConfigIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, data.Id) + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, agentConfigRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.ConfigKey, data.ConfigValue, data.Remark, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.Id, data.ConfigKey, data.ConfigValue, data.Remark, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentConfigConfigKeyKey, bdrpAgentConfigIdKey) +} + +func (m *defaultAgentConfigModel) FindOne(ctx context.Context, id int64) (*AgentConfig, error) { + bdrpAgentConfigIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, id) + var resp AgentConfig + err := m.QueryRowCtx(ctx, &resp, bdrpAgentConfigIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) FindOneByConfigKey(ctx context.Context, configKey string) (*AgentConfig, error) { + bdrpAgentConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigConfigKeyPrefix, configKey) + var resp AgentConfig + err := m.QueryRowIndexCtx(ctx, &resp, bdrpAgentConfigConfigKeyKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `config_key` = ? and del_state = ? limit 1", agentConfigRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, configKey, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) Update(ctx context.Context, session sqlx.Session, newData *AgentConfig) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpAgentConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigConfigKeyPrefix, data.ConfigKey) + bdrpAgentConfigIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, data.Id) + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ConfigKey, newData.ConfigValue, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.ConfigKey, newData.ConfigValue, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, bdrpAgentConfigConfigKeyKey, bdrpAgentConfigIdKey) +} + +func (m *defaultAgentConfigModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentConfig) error { + + oldVersion := newData.Version + newData.Version += 1 + + var sqlResult sql.Result + var err error + + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + bdrpAgentConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigConfigKeyPrefix, data.ConfigKey) + bdrpAgentConfigIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, data.Id) + sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ConfigKey, newData.ConfigValue, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.ConfigKey, newData.ConfigValue, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, bdrpAgentConfigConfigKeyKey, bdrpAgentConfigIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentConfigModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentConfig) error { + data.DelState = globalkey.DelStateYes + data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := m.UpdateWithVersion(ctx, session, data); err != nil { + return errors.Wrapf(errors.New("delete soft failed "), "AgentConfigModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentConfigModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field") + } + + builder = builder.Columns("IFNULL(SUM(" + field + "),0)") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp float64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultAgentConfigModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field") + } + + builder = builder.Columns("COUNT(" + field + ")") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp int64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultAgentConfigModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentConfig, error) { + + builder = builder.Columns(agentConfigRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return nil, err + } + + var resp []*AgentConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentConfig, error) { + + builder = builder.Columns(agentConfigRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*AgentConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentConfig, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentConfigRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, total, err + } + + var resp []*AgentConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentConfigModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentConfig, error) { + + builder = builder.Columns(agentConfigRows) + + if preMinId > 0 { + builder = builder.Where(" id < ? ", preMinId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*AgentConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentConfig, error) { + + builder = builder.Columns(agentConfigRows) + + if preMaxId > 0 { + builder = builder.Where(" id > ? ", preMaxId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*AgentConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentConfigModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error { + + return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error { + return fn(ctx, session) + }) + +} + +func (m *defaultAgentConfigModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentConfigModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpAgentConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigConfigKeyPrefix, data.ConfigKey) + bdrpAgentConfigIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, id) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + if session != nil { + return session.ExecCtx(ctx, query, id) + } + return conn.ExecCtx(ctx, query, id) + }, bdrpAgentConfigConfigKeyKey, bdrpAgentConfigIdKey) + return err +} +func (m *defaultAgentConfigModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentConfigIdPrefix, primary) +} +func (m *defaultAgentConfigModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentConfigModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentRewardsModel_gen.go b/app/main/model/agentRewardsModel_gen.go index 67c8aec..31a56a9 100644 --- a/app/main/model/agentRewardsModel_gen.go +++ b/app/main/model/agentRewardsModel_gen.go @@ -11,7 +11,6 @@ import ( "time" "bdrp-server/common/globalkey" - "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/stores/builder" @@ -27,7 +26,7 @@ var ( agentRewardsRowsExpectAutoSet = strings.Join(stringx.Remove(agentRewardsFieldNames, "`id`", "`create_time`", "`update_time`"), ",") agentRewardsRowsWithPlaceHolder = strings.Join(stringx.Remove(agentRewardsFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" - cacheHmAgentRewardsIdPrefix = "cache:bdrp:agentRewards:id:" + cacheBdrpAgentRewardsIdPrefix = "cache:bdrp:agentRewards:id:" ) type ( @@ -60,6 +59,8 @@ type ( RelationAgentId sql.NullInt64 `db:"relation_agent_id"` // 关联代理ID Amount float64 `db:"amount"` Type string `db:"type"` + Status int64 `db:"status"` // 状态:0=正常, 1=已取消 + Remark string `db:"remark"` // 备注 CreateTime time.Time `db:"create_time"` UpdateTime time.Time `db:"update_time"` // 更新时间 DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 @@ -77,20 +78,20 @@ func newAgentRewardsModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentRew func (m *defaultAgentRewardsModel) Insert(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) { data.DelState = globalkey.DelStateNo - hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, data.Id) + bdrpAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, data.Id) return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, agentRewardsRowsExpectAutoSet) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentRewardsRowsExpectAutoSet) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version) + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) } - return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version) - }, hmAgentRewardsIdKey) + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentRewardsIdKey) } func (m *defaultAgentRewardsModel) FindOne(ctx context.Context, id int64) (*AgentRewards, error) { - hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, id) + bdrpAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, id) var resp AgentRewards - err := m.QueryRowCtx(ctx, &resp, hmAgentRewardsIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + err := m.QueryRowCtx(ctx, &resp, bdrpAgentRewardsIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentRewardsRows, m.table) return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo) }) @@ -105,14 +106,14 @@ func (m *defaultAgentRewardsModel) FindOne(ctx context.Context, id int64) (*Agen } func (m *defaultAgentRewardsModel) Update(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) { - hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, data.Id) + bdrpAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, data.Id) return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentRewardsRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id) + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version, data.Id) } - return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id) - }, hmAgentRewardsIdKey) + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version, data.Id) + }, bdrpAgentRewardsIdKey) } func (m *defaultAgentRewardsModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentRewards) error { @@ -123,14 +124,14 @@ func (m *defaultAgentRewardsModel) UpdateWithVersion(ctx context.Context, sessio var sqlResult sql.Result var err error - hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, data.Id) + bdrpAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, data.Id) sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentRewardsRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) } - return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) - }, hmAgentRewardsIdKey) + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, bdrpAgentRewardsIdKey) if err != nil { return err } @@ -348,18 +349,18 @@ func (m *defaultAgentRewardsModel) SelectBuilder() squirrel.SelectBuilder { return squirrel.Select().From(m.table) } func (m *defaultAgentRewardsModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { - hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, id) + bdrpAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, id) _, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("delete from %s where `id` = ?", m.table) if session != nil { return session.ExecCtx(ctx, query, id) } return conn.ExecCtx(ctx, query, id) - }, hmAgentRewardsIdKey) + }, bdrpAgentRewardsIdKey) return err } func (m *defaultAgentRewardsModel) formatPrimary(primary interface{}) string { - return fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, primary) + return fmt.Sprintf("%s%v", cacheBdrpAgentRewardsIdPrefix, primary) } func (m *defaultAgentRewardsModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentRewardsRows, m.table) diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index 1659cbe..ae3990e 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -13,7 +13,7 @@ $tables = @( # "agent_closure", # "agent_commission" # "agent_wallet_transaction" - # "agent_commission_deduction" + # "agent_commission_deduction", # "agent_link", # "agent_membership_config", # "agent_membership_recharge_order" @@ -21,7 +21,7 @@ $tables = @( # "agent_order", # "agent_platform_deduction" # "agent_product_config", - # "agent_rewards", + # "agent_rewards" # "agent_wallet", # "agent_real_name" # "agent_withdrawal" @@ -38,7 +38,7 @@ $tables = @( # "query_cleanup_log" # "query_cleanup_detail" # "query_cleanup_config" - "user" + # "user" # "user_auth" # "user_temp" # "example" @@ -55,6 +55,7 @@ $tables = @( # "admin_promotion_link_stats_total" # "admin_promotion_link_stats_history" # "admin_promotion_order" + "agent_config" )