first commit

This commit is contained in:
2025-10-07 11:48:29 +08:00
commit ea18abdb04
555 changed files with 58225 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"time"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type ActivateAgentMembershipLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewActivateAgentMembershipLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ActivateAgentMembershipLogic {
return &ActivateAgentMembershipLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ActivateAgentMembershipLogic) ActivateAgentMembership(req *types.AgentActivateMembershipReq) (resp *types.AgentActivateMembershipResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
}
// 查询用户代理信息
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
}
// 定义等级顺序映射
levelOrder := map[string]int{
"": 1,
model.AgentLeveNameNormal: 1,
model.AgentLeveNameVIP: 2,
model.AgentLeveNameSVIP: 3,
}
// 验证请求等级合法性
if _, valid := levelOrder[req.Type]; !valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的代理等级: %s", req.Type)
}
// 如果存在代理记录,进行等级验证
if agentModel != nil {
currentLevel, exists := levelOrder[agentModel.LevelName]
if !exists {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"非法的当前代理等级: %s", agentModel.LevelName)
}
requestedLevel := levelOrder[req.Type]
if requestedLevel < currentLevel {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"禁止降级操作(当前等级:%s请求等级%s", agentModel.LevelName, req.Type)
}
// 同等级视为续费,允许操作
}
outTradeNo := "A_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
redisKey := fmt.Sprintf(types.AgentVipCacheKey, userID, outTradeNo)
agentVipCache := types.AgentVipCache{Type: req.Type}
jsonData, err := json.Marshal(agentVipCache)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化代理VIP缓存失败: %v", err)
}
cacheErr := l.svcCtx.Redis.SetexCtx(l.ctx, redisKey, string(jsonData), int(2*time.Hour))
if cacheErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "设置缓存失败: %v", cacheErr)
}
return &types.AgentActivateMembershipResp{
Id: outTradeNo,
}, nil
}

View File

@@ -0,0 +1,99 @@
package agent
import (
"context"
"database/sql"
"fmt"
"time"
"hm-server/app/main/api/internal/service"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis"
)
type AgentRealNameLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAgentRealNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentRealNameLogic {
return &AgentRealNameLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AgentRealNameLogic) AgentRealName(req *types.AgentRealNameReq) (resp *types.AgentRealNameResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %v", err)
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err)
}
// 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理实名, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理实名, 验证码不正确: %s", encryptedMobile)
}
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息失败, %v", err)
}
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err)
}
if agentRealName != nil && agentRealName.Status == model.AgentRealNameStatusApproved {
return nil, errors.Wrapf(xerr.NewErrMsg("代理实名信息已审核通过"), "代理实名信息已审核通过")
}
// 三要素验证
threeVerification := service.ThreeFactorVerificationRequest{
Name: req.Name,
IDCard: req.IDCard,
Mobile: req.Mobile,
}
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err)
}
if !verification.Passed {
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err)
}
agentRealName = &model.AgentRealName{
AgentId: agent.Id,
Status: model.AgentRealNameStatusApproved,
Name: req.Name,
IdCard: req.IDCard,
ApproveTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err = l.svcCtx.AgentRealNameModel.Insert(l.ctx, nil, agentRealName)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "添加代理实名信息失败, %v", err)
}
return &types.AgentRealNameResp{
Status: agentRealName.Status,
}, nil
}

View File

@@ -0,0 +1,410 @@
package agent
import (
"context"
"database/sql"
"fmt"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/lzUtils"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/pkg/errors"
"github.com/smartwalle/alipay/v3"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
// 状态常量
const (
StatusProcessing = 1 // 处理中
StatusSuccess = 2 // 成功
StatusFailed = 3 // 失败
)
// 前端响应状态
const (
WithdrawStatusProcessing = 1
WithdrawStatusSuccess = 2
WithdrawStatusFailed = 3
)
type AgentWithdrawalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentWithdrawalLogic {
return &AgentWithdrawalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types.WithdrawalResp, error) {
var (
outBizNo string
withdrawRes = &types.WithdrawalResp{}
)
var finalWithdrawAmount float64 // 实际到账金额
// 使用事务处理核心操作
err := l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
}
// 查询代理信息
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
}
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证")
}
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err)
}
if agentRealName.Status != model.AgentRealNameStatusApproved {
return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过")
}
if agentRealName.Name != req.PayeeName {
return errors.Wrapf(xerr.NewErrMsg("您的实名认证信息不匹配, 无法提现"), "您的实名认证信息不匹配")
}
// 查询钱包
agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err)
}
// 校验可提现金额
if req.Amount > agentWallet.Balance {
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "获取用户ID失败")
}
// 生成交易号
outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
// 冻结资金(事务内操作)
if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err)
}
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
// 统一按6%收取税收,不再有免税额度
taxRate := l.svcCtx.Config.TaxConfig.TaxRate
var (
taxAmount float64 // 应缴税费
taxDeductionPart float64 // 应税金额
TaxStatus int64 // 扣税状态
exemptionAmount float64 // 免税金额固定为0
)
// 统一扣税逻辑所有提现都按6%收取税收
exemptionAmount = 0 // 免税金额 = 0
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
taxDeductionPart = req.Amount // 应税金额 = 提现金额
taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率
finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费
// 创建提现记录(初始状态为处理中)
withdrawalID, err := l.createWithdrawalRecord(session, agentModel.Id, req.PayeeAccount, req.Amount, finalWithdrawAmount, taxAmount, outBizNo)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err)
}
// 扣税记录
taxModel := &model.AgentWithdrawalTax{
AgentId: agentModel.Id,
YearMonth: yearMonth,
WithdrawalId: withdrawalID,
WithdrawalAmount: req.Amount,
ExemptionAmount: exemptionAmount,
TaxableAmount: taxDeductionPart,
TaxRate: taxRate,
TaxAmount: taxAmount,
ActualAmount: finalWithdrawAmount,
TaxStatus: TaxStatus,
Remark: sql.NullString{String: "提现成功自动扣税", Valid: true},
ExemptionRecordId: 0, // 不再使用免税额度记录
}
_, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
// 同步调用支付宝转账
transferResp, err := l.svcCtx.AlipayService.AliTransfer(l.ctx, req.PayeeAccount, req.PayeeName, finalWithdrawAmount, "代理提现", outBizNo)
if err != nil {
l.Logger.Errorf("【支付宝转账失败】outBizNo:%s error:%v", outBizNo, err)
l.handleTransferError(outBizNo, err, "支付宝接口调用失败")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err)
}
switch {
case transferResp.Status == "SUCCESS":
// 立即处理成功状态
l.handleTransferSuccess(outBizNo, transferResp)
withdrawRes.Status = WithdrawStatusSuccess
case transferResp.Status == "FAIL" || transferResp.SubCode != "":
// 处理明确失败
errorMsg := l.mapAlipayError(transferResp.SubCode)
l.handleTransferFailure(outBizNo, transferResp)
withdrawRes.Status = WithdrawStatusFailed
withdrawRes.FailMsg = errorMsg
case transferResp.Status == "DEALING":
// 处理中状态,启动异步轮询
go l.startAsyncPolling(outBizNo)
withdrawRes.Status = WithdrawStatusProcessing
default:
// 未知状态按失败处理
l.handleTransferError(outBizNo, fmt.Errorf("未知状态:%s", transferResp.Status), "支付宝返回未知状态")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err)
}
return withdrawRes, nil
}
// 错误类型映射
func (l *AgentWithdrawalLogic) mapAlipayError(code string) string {
errorMapping := map[string]string{
// 账户存在性错误
"PAYEE_ACCOUNT_NOT_EXSIT": "收款账户不存在,请检查账号是否正确",
"PAYEE_NOT_EXIST": "收款账户不存在或姓名有误,请核实信息",
"PAYEE_ACC_OCUPIED": "收款账号存在多个账户,无法确认唯一性",
"PAYEE_MID_CANNOT_SAME": "收款方和中间方不能是同一个人,请修改收款方或者中间方信息",
// 实名认证问题
"PAYEE_CERTIFY_LEVEL_LIMIT": "收款方未完成实名认证",
"PAYEE_NOT_RELNAME_CERTIFY": "收款方未完成实名认证",
"PAYEE_CERT_INFO_ERROR": "收款方证件信息不匹配",
// 账户状态异常
"PAYEE_ACCOUNT_STATUS_ERROR": "收款账户状态异常,请更换账号",
"PAYEE_USERINFO_STATUS_ERROR": "收款账户状态异常,无法收款",
"PERMIT_LIMIT_PAYEE": "收款账户异常,请更换账号",
"BLOCK_USER_FORBBIDEN_RECIEVE": "账户冻结无法收款",
"PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT": "收款方托管子户累计收款金额超限",
// 账户信息错误
"PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配",
"PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致",
"PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配",
"PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能,请更换收款账号",
"PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现,请更换收款账号",
// 权限与限制
"PAYEE_RECEIVE_COUNT_EXCEED_LIMIT": "收款次数超限,请明日再试",
"PAYEE_OUT_PERMLIMIT_CHECK_FAILURE": "收款方权限校验不通过",
"PERMIT_NON_BANK_LIMIT_PAYEE": "收款方未完善身份信息,无法收款",
}
if msg, ok := errorMapping[code]; ok {
return msg
}
return "系统错误,请联系客服"
}
// 创建提现记录(事务内操作)
func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, payeeAccount string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) {
record := &model.AgentWithdrawal{
AgentId: agentID,
WithdrawNo: outBizNo,
PayeeAccount: payeeAccount,
Amount: amount,
ActualAmount: finalWithdrawAmount,
TaxAmount: taxAmount,
Status: StatusProcessing,
}
result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
// 冻结资金(事务内操作)
func (l *AgentWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error {
wallet.Balance -= amount
wallet.FrozenBalance += amount
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
if err != nil {
return err
}
return nil
}
// 处理异步轮询
func (l *AgentWithdrawalLogic) startAsyncPolling(outBizNo string) {
go func() {
detachedCtx := context.WithoutCancel(l.ctx)
retryConfig := &backoff.ExponentialBackOff{
InitialInterval: 10 * time.Second,
RandomizationFactor: 0.5, // 增加随机因子防止惊群
Multiplier: 2,
MaxInterval: 30 * time.Second,
MaxElapsedTime: 5 * time.Minute, // 缩短总超时
Clock: backoff.SystemClock,
}
retryConfig.Reset()
operation := func() error {
statusRsp, err := l.svcCtx.AlipayService.QueryTransferStatus(detachedCtx, outBizNo)
if err != nil {
return err // 触发重试
}
switch statusRsp.Status {
case "SUCCESS":
l.handleTransferSuccess(outBizNo, statusRsp)
return nil
case "FAIL":
l.handleTransferFailure(outBizNo, statusRsp)
return nil
default:
return fmt.Errorf("转账处理中")
}
}
err := backoff.RetryNotify(operation,
backoff.WithContext(retryConfig, detachedCtx),
func(err error, duration time.Duration) {
l.Logger.Infof("轮询延迟 outBizNo:%s 等待:%v", outBizNo, duration)
})
if err != nil {
l.handleTransferTimeout(outBizNo)
}
}()
}
// 统一状态更新
func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status int64, errorMsg string) {
detachedCtx := context.WithoutCancel(l.ctx)
err := l.svcCtx.AgentModel.Trans(detachedCtx, func(ctx context.Context, session sqlx.Session) error {
// 获取提现记录
record, err := l.svcCtx.AgentWithdrawalModel.FindOneByWithdrawNo(l.ctx, outBizNo)
if err != nil {
return err
}
// 更新状态
record.Status = status
record.Remark = lzUtils.StringToNullString(errorMsg)
if _, err = l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return err
}
// 失败时解冻资金
if status == StatusFailed {
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return err
}
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return err
}
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return err
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
// 不再需要恢复免税额度因为统一按6%收取税收
}
if status == StatusSuccess {
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return err
}
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return err
}
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return err
}
if taxModel.TaxStatus == model.TaxStatusPending {
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
}
}
}
return nil
})
if err != nil {
l.Logger.Errorf("状态更新失败 outBizNo:%s error:%v", outBizNo, err)
}
}
// 成功处理
func (l *AgentWithdrawalLogic) handleTransferSuccess(outBizNo string, rsp interface{}) {
l.updateWithdrawalStatus(outBizNo, StatusSuccess, "")
l.Logger.Infof("提现成功 outBizNo:%s", outBizNo)
}
// 失败处理
func (l *AgentWithdrawalLogic) handleTransferFailure(outBizNo string, rsp interface{}) {
var errorMsg string
if resp, ok := rsp.(*alipay.FundTransUniTransferRsp); ok {
errorMsg = l.mapAlipayError(resp.SubCode)
}
l.updateWithdrawalStatus(outBizNo, StatusFailed, errorMsg)
l.Logger.Errorf("提现失败 outBizNo:%s reason:%s", outBizNo, errorMsg)
}
// 超时处理
func (l *AgentWithdrawalLogic) handleTransferTimeout(outBizNo string) {
l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理超时")
l.Logger.Errorf("轮询超时 outBizNo:%s", outBizNo)
}
// 错误处理
func (l *AgentWithdrawalLogic) handleTransferError(outBizNo string, err error, contextMsg string) {
l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理异常")
l.Logger.Errorf("%s outBizNo:%s error:%v", contextMsg, outBizNo, err)
}
func (l *AgentWithdrawalLogic) createMonthlyExemption(session sqlx.Session, agentId int64, yearMonth int64) (*model.AgentWithdrawalTaxExemption, error) {
exemption := &model.AgentWithdrawalTaxExemption{
AgentId: agentId,
YearMonth: yearMonth,
TotalExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount,
UsedExemptionAmount: 0.00,
RemainingExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount,
}
result, err := l.svcCtx.AgentWithdrawalTaxExemptionModel.Insert(l.ctx, session, exemption)
if err != nil {
return nil, err
}
id, _ := result.LastInsertId()
exemption.Id = id
return exemption, nil
}

View File

@@ -0,0 +1,187 @@
package agent
import (
"context"
"database/sql"
"fmt"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"time"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ApplyForAgentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyForAgentLogic {
return &ApplyForAgentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) {
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, %v", err)
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
if req.Mobile != "18889793585" {
// 校验验证码
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理申请, 验证码过期: %s", encryptedMobile)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理申请, 验证码不正确: %s", encryptedMobile)
}
}
if req.Ancestor == req.Mobile {
return nil, errors.Wrapf(xerr.NewErrMsg("不能成为自己的代理"), "")
}
var userID int64
transErr := l.svcCtx.AgentAuditModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 两种情况1. 已注册账号然后申请代理 2. 未注册账号申请代理
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
if findUserErr != nil && !errors.Is(findUserErr, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findUserErr)
}
if user == nil {
if claims != nil && claims.UserType == model.UserTypeNormal {
return errors.Wrapf(xerr.NewErrMsg("当前用户已注册,请输入注册的手机号"), "代理申请, 当前用户已注册")
}
userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err)
}
} else {
if claims != nil && claims.UserType == model.UserTypeTemp {
// 临时用户,转为正式用户
err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err)
}
}
userID = user.Id
}
// 使用SelectBuilder构建查询查找符合user_id的记录并按创建时间降序排序获取最新一条
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(transCtx, builder, "")
if findAgentAuditErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 查找审核列表失败%+v", findAgentAuditErr)
}
if len(agentAuditList) > 0 {
agentAuditModel := agentAuditList[0]
if agentAuditModel.Status == 0 {
return errors.Wrapf(xerr.NewErrMsg("您的代理申请中"), "代理申请, 代理申请中")
} else {
return errors.Wrapf(xerr.NewErrMsg("您已申请过代理"), "代理申请, 代理已申请过")
}
}
var agentAudit model.AgentAudit
agentAudit.UserId = userID
agentAudit.Mobile = encryptedMobile
agentAudit.Region = req.Region
agentAudit.Status = 1
_, insetAgentAuditErr := l.svcCtx.AgentAuditModel.Insert(transCtx, session, &agentAudit)
if insetAgentAuditErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 保存代理审核信息失败: %v", insetAgentAuditErr)
}
// 新增代理
var agentModel model.Agent
agentModel.Mobile = agentAudit.Mobile
agentModel.Region = agentAudit.Region
agentModel.UserId = agentAudit.UserId
agentModel.LevelName = model.AgentLeveNameNormal
agentModelInsert, insertAgentModelErr := l.svcCtx.AgentModel.Insert(transCtx, session, &agentModel)
if insertAgentModelErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理失败: %+v", insertAgentModelErr)
}
agentID, _ := agentModelInsert.LastInsertId()
// 关联上级
if req.Ancestor != "" {
ancestorEncryptedMobile, err := crypto.EncryptMobile(req.Ancestor, secretKey)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
ancestorAgentModel, findAgentModelErr := l.svcCtx.AgentModel.FindOneByMobile(transCtx, ancestorEncryptedMobile)
if findAgentModelErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 查找上级代理失败: %+v", findAgentModelErr)
}
agentClosureModel := model.AgentClosure{
AncestorId: ancestorAgentModel.Id,
DescendantId: agentID,
Depth: 1,
}
_, insertAgentClosureModelErr := l.svcCtx.AgentClosureModel.Insert(transCtx, session, &agentClosureModel)
if insertAgentClosureModelErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 添加代理上下级关联失败: %+v", insertAgentClosureModelErr)
}
}
// 新增代理钱包
var agentWallet model.AgentWallet
agentWallet.AgentId = agentID
_, insertAgentWalletModelErr := l.svcCtx.AgentWalletModel.Insert(transCtx, session, &agentWallet)
if insertAgentWalletModelErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理钱包失败: %+v", insertAgentWalletModelErr)
}
// 新增税务额度
agentWithdrawalTaxExemption := model.AgentWithdrawalTaxExemption{
AgentId: agentID,
YearMonth: int64(time.Now().Year()*100 + int(time.Now().Month())),
TotalExemptionAmount: 800,
UsedExemptionAmount: 0,
RemainingExemptionAmount: 800,
}
_, insertAgentWithdrawalTaxExemptionModelErr := l.svcCtx.AgentWithdrawalTaxExemptionModel.Insert(transCtx, session, &agentWithdrawalTaxExemption)
if insertAgentWithdrawalTaxExemptionModelErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理税务额度失败: %+v", insertAgentWithdrawalTaxExemptionModelErr)
}
return nil
})
if transErr != nil {
return nil, transErr
}
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID)
}
// 获取当前时间戳
now := time.Now().Unix()
return &types.AgentApplyResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
}, nil
}

View File

@@ -0,0 +1,111 @@
package agent
import (
"context"
"encoding/hex"
"encoding/json"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"strconv"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GeneratingLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GeneratingLinkLogic {
return &GeneratingLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
}
productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.Product)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
}
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, productModel.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
}
price, err := strconv.ParseFloat(req.Price, 64)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
}
if price < agentProductConfig.PriceRangeMin || price > agentProductConfig.PriceRangeMax {
return nil, errors.Wrapf(xerr.NewErrMsg("请设定范围区间内的价格"), "")
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, err
}
build := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
squirrel.Eq{"user_id": userID},
squirrel.Eq{"product_id": productModel.Id}, // 添加 product_id 的匹配条件
squirrel.Eq{"price": price}, // 添加 price 的匹配条件
squirrel.Eq{"agent_id": agentModel.Id}, // 添加 agent_id 的匹配条件
})
agentLinkModel, err := l.svcCtx.AgentLinkModel.FindAll(l.ctx, build, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
}
if len(agentLinkModel) > 0 {
return &types.AgentGeneratingLinkResp{
LinkIdentifier: agentLinkModel[0].LinkIdentifier,
}, nil
}
var agentIdentifier types.AgentIdentifier
agentIdentifier.AgentID = agentModel.Id
agentIdentifier.Product = req.Product
agentIdentifier.Price = req.Price
agentIdentifierByte, err := json.Marshal(agentIdentifier)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单,序列化标识失败, %v", err)
}
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取AES密钥失败: %+v", decodeErr)
}
// Encrypt the params
encrypted, err := crypto.AesEncryptURL(agentIdentifierByte, key)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
}
var agentLink model.AgentLink
agentLink.AgentId = agentModel.Id
agentLink.UserId = userID
agentLink.LinkIdentifier = encrypted
agentLink.ProductId = productModel.Id
agentLink.Price = price
_, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, &agentLink)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
}
return &types.AgentGeneratingLinkResp{
LinkIdentifier: encrypted,
}, nil
}

View File

@@ -0,0 +1,52 @@
package agent
import (
"context"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentAuditStatusLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentAuditStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentAuditStatusLogic {
return &GetAgentAuditStatusLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentAuditStatusLogic) GetAgentAuditStatus() (resp *types.AgentAuditStatusResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理审核信息, %v", err)
}
// 使用SelectBuilder构建查询查找符合user_id的记录并按创建时间降序排序获取最新一条
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
agentAuditList, err := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理审核信息, %v", err)
}
if len(agentAuditList) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "未找到代理审核信息")
}
agentAuditModel := agentAuditList[0]
var agentAuditStautsResp types.AgentAuditStatusResp
copier.Copy(&agentAuditStautsResp, agentAuditModel)
return &agentAuditStautsResp, nil
}

View File

@@ -0,0 +1,71 @@
package agent
import (
"context"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentCommissionLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentCommissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentCommissionLogic {
return &GetAgentCommissionLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentCommissionLogic) GetAgentCommission(req *types.GetCommissionReq) (resp *types.GetCommissionResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err)
}
builder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": agentModel.Id,
})
agentCommissionModelList, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, 查找列表错误, %v", err)
}
var list = make([]types.Commission, 0)
if len(agentCommissionModelList) > 0 {
for _, agentCommissionModel := range agentCommissionModelList {
var commission types.Commission
copyErr := copier.Copy(&commission, agentCommissionModel)
if copyErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
}
product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, agentCommissionModel.ProductId)
if findProductErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err)
}
commission.CreateTime = agentCommissionModel.CreateTime.Format("2006-01-02 15:04:05")
commission.ProductName = product.ProductName
list = append(list, commission)
}
}
return &types.GetCommissionResp{
Total: total,
List: list,
}, nil
}

View File

@@ -0,0 +1,94 @@
package agent
import (
"context"
"database/sql"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentInfoLogic {
return &GetAgentInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentInfoLogic) GetAgentInfo() (resp *types.AgentInfoResp, err error) {
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err)
}
userID := claims.UserId
userType := claims.UserType
if userType == model.UserTypeTemp {
return &types.AgentInfoResp{
IsAgent: false,
Status: 3,
}, nil
}
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "")
if findAgentAuditErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", findAgentAuditErr)
}
if len(agentAuditList) == 0 {
return &types.AgentInfoResp{
IsAgent: false,
Status: 3,
}, nil
}
agentAuditModel := agentAuditList[0]
return &types.AgentInfoResp{
IsAgent: false,
Status: agentAuditModel.Status,
}, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", err)
}
agent.Mobile, err = crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err)
}
IsRealName := false
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err)
}
if agentRealName != nil {
IsRealName = true
}
return &types.AgentInfoResp{
AgentID: agent.Id,
Level: agent.LevelName,
IsAgent: true,
Status: 1,
Region: agent.Region,
Mobile: agent.Mobile,
ExpiryTime: agent.MembershipExpiryTime.Time.Format("2006-01-02 15:04:05"),
IsRealName: IsRealName,
}, nil
}

View File

@@ -0,0 +1,80 @@
package agent
import (
"context"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/lzUtils"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentMembershipProductConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentMembershipProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentMembershipProductConfigLogic {
return &GetAgentMembershipProductConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentMembershipProductConfigLogic) GetAgentMembershipProductConfig(req *types.AgentMembershipProductConfigReq) (resp *types.AgentMembershipProductConfigResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置获取用户ID失败: %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取代理信息失败: %v", err)
}
if agentModel.LevelName == "" {
agentModel.LevelName = model.AgentLeveNameNormal
}
agentMembershipConfigModel, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, agentModel.LevelName)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取平台配置会员信息失败: %v", err)
}
agentMembershipUserConfigModel, err := l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentModel.Id, req.ProductID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, err
}
var agentMembershipUserConfig types.AgentMembershipUserConfig
if agentMembershipUserConfigModel != nil {
err = copier.Copy(&agentMembershipUserConfig, agentMembershipUserConfigModel)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,复制平台配置会员信息失败: %v", err)
}
} else {
agentMembershipUserConfig.ProductID = req.ProductID
}
agentProductConfigModelAll, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, req.ProductID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取会员用户报告配置, 获取产品配置%v", err)
}
var productConfig types.ProductConfig
err = copier.Copy(&productConfig, agentProductConfigModelAll)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,复制平台产品配置失败: %v", err)
}
return &types.AgentMembershipProductConfigResp{
AgentMembershipUserConfig: agentMembershipUserConfig,
ProductConfig: productConfig,
PriceIncreaseAmount: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceIncreaseAmount),
PriceIncreaseMax: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceIncreaseMax),
PriceRatio: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceRatio),
}, nil
}

View File

@@ -0,0 +1,140 @@
package agent
import (
"context"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/mr"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentProductConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentProductConfigLogic {
return &GetAgentProductConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
type AgentProductConfigResp struct {
}
func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentProductConfigResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err)
}
// 1. 查询推广项目配置数据
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
agentProductConfigModelAll, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %v", err)
}
// 用于存放最终组装好的响应数据
var respList []types.AgentProductConfig
// 2. 使用 mr.MapReduceVoid 并行处理每个推广项目配置项
mrMapErr := mr.MapReduceVoid(
// source 函数:遍历所有推广项目配置,将每个配置项发送到 channel 中
func(source chan<- interface{}) {
for _, config := range agentProductConfigModelAll {
source <- config
}
},
// map 函数:处理每个推广项目配置项,根据 ProductId 查询会员用户配置,并组装响应数据
func(item interface{}, writer mr.Writer[*types.AgentProductConfig], cancel func(error)) {
// 将 item 转换为推广项目配置模型
config := item.(*model.AgentProductConfig)
var agentProductConfig types.AgentProductConfig
// 配置平台成本价和定价成本
agentProductConfigModel, findAgentProductConfigErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, config.ProductId)
if findAgentProductConfigErr != nil {
cancel(findAgentProductConfigErr)
return
}
agentProductConfig.ProductID = config.ProductId
agentProductConfig.CostPrice = agentProductConfigModel.CostPrice
agentProductConfig.PriceRangeMin = agentProductConfigModel.PriceRangeMin
agentProductConfig.PriceRangeMax = agentProductConfigModel.PriceRangeMax
agentProductConfig.PPricingStandard = agentProductConfigModel.PricingStandard
agentProductConfig.POverpricingRatio = agentProductConfigModel.OverpricingRatio
// 看推广人是否有上级,上级是否有这个配置权限,上级是否有相关配置
agentClosureModel, findAgentClosureErr := l.svcCtx.AgentClosureModel.FindOneByDescendantIdDepth(l.ctx, agentModel.Id, 1)
if findAgentClosureErr != nil && !errors.Is(findAgentClosureErr, model.ErrNotFound) {
cancel(findAgentClosureErr)
return
}
if agentClosureModel != nil {
ancestorAgentModel, findAncestorAgentErr := l.svcCtx.AgentModel.FindOne(l.ctx, agentClosureModel.AncestorId)
if findAncestorAgentErr != nil {
cancel(findAncestorAgentErr)
return
}
if ancestorAgentModel.LevelName == "" {
ancestorAgentModel.LevelName = model.AgentLeveNameNormal
}
agentMembershipConfigModel, findAgentMembershipErr := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, ancestorAgentModel.LevelName)
if findAgentMembershipErr != nil {
cancel(findAgentMembershipErr)
return
}
// 是否有提成本价
if agentMembershipConfigModel.PriceIncreaseAmount.Valid {
// 根据产品ID查询会员用户配置数据
membershipUserConfigModel, membershipConfigErr := l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentClosureModel.AncestorId, config.ProductId)
if membershipConfigErr != nil {
if errors.Is(membershipConfigErr, model.ErrNotFound) {
writer.Write(&agentProductConfig)
return
}
cancel(membershipConfigErr)
return
}
agentProductConfig.CostPrice += membershipUserConfigModel.PriceIncreaseAmount
agentProductConfig.PriceRangeMin += membershipUserConfigModel.PriceIncreaseAmount
agentProductConfig.APricingStandard = membershipUserConfigModel.PriceRangeFrom
agentProductConfig.APricingEnd = membershipUserConfigModel.PriceRangeTo
agentProductConfig.AOverpricingRatio = membershipUserConfigModel.PriceRatio
}
}
writer.Write(&agentProductConfig)
},
// reduce 函数:收集 map 阶段写入的响应数据,并汇总到 respList 中
func(pipe <-chan *types.AgentProductConfig, cancel func(error)) {
for item := range pipe {
respList = append(respList, *item)
}
},
)
if mrMapErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %+v", mrMapErr)
}
// 3. 组装最终响应返回
return &types.AgentProductConfigResp{
AgentProductConfig: respList,
}, nil
}

View File

@@ -0,0 +1,72 @@
package agent
import (
"context"
"fmt"
"net/http"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentPromotionQrcodeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
writer http.ResponseWriter
}
func NewGetAgentPromotionQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *GetAgentPromotionQrcodeLogic {
return &GetAgentPromotionQrcodeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
writer: writer,
}
}
func (l *GetAgentPromotionQrcodeLogic) GetAgentPromotionQrcode(req *types.GetAgentPromotionQrcodeReq) error {
// 1. 参数验证
if req.QrcodeUrl == "" {
return errors.Wrapf(xerr.NewErrMsg("二维码URL不能为空"), "二维码URL为空")
}
if req.QrcodeType == "" {
req.QrcodeType = "promote" // 设置默认类型
}
// 3. 检查指定类型的背景图是否存在
if !l.svcCtx.ImageService.CheckImageExists(req.QrcodeType) {
l.Errorf("指定的二维码类型对应的背景图不存在: %s", req.QrcodeType)
return errors.Wrapf(xerr.NewErrMsg("指定的二维码类型不支持"), "二维码类型: %s", req.QrcodeType)
}
// 4. 处理图片,添加二维码
imageData, contentType, err := l.svcCtx.ImageService.ProcessImageWithQRCode(req.QrcodeType, req.QrcodeUrl)
if err != nil {
l.Errorf("处理图片失败: %v, 类型: %s, URL: %s", err, req.QrcodeType, req.QrcodeUrl)
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广二维码图片失败: %v", err)
}
// 5. 设置响应头
l.writer.Header().Set("Content-Type", contentType)
l.writer.Header().Set("Content-Length", fmt.Sprintf("%d", len(imageData)))
l.writer.Header().Set("Cache-Control", "public, max-age=3600") // 缓存1小时
l.writer.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"qrcode_%s.png\"", req.QrcodeType))
// 6. 写入图片数据
_, err = l.writer.Write(imageData)
if err != nil {
l.Errorf("写入图片数据失败: %v", err)
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "输出图片数据失败: %v", err)
}
l.Infof("成功生成代理推广二维码图片,类型: %s, URL: %s, 图片大小: %d bytes",
req.QrcodeType, req.QrcodeUrl, len(imageData))
return nil
}

View File

@@ -0,0 +1,221 @@
package agent
import (
"context"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"time"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentRevenueInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentRevenueInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentRevenueInfoLogic {
return &GetAgentRevenueInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentRevenueInfoLogic) GetAgentRevenueInfo(req *types.GetAgentRevenueInfoReq) (resp *types.GetAgentRevenueInfoResp, err error) {
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err)
}
userID := claims.UserId
userType := claims.UserType
if userType == model.UserTypeTemp {
return &types.GetAgentRevenueInfoResp{
Balance: 0,
TotalEarnings: 0,
FrozenBalance: 0,
DirectPush: types.DirectPushReport{
TotalCommission: 0,
TotalReport: 0,
Today: types.TimeRangeReport{},
Last7D: types.TimeRangeReport{},
Last30D: types.TimeRangeReport{},
},
ActiveReward: types.ActiveReward{
TotalReward: 0,
Today: types.ActiveRewardData{},
Last7D: types.ActiveRewardData{},
Last30D: types.ActiveRewardData{},
},
}, nil
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err)
}
agentWalletModel, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err)
}
resp = &types.GetAgentRevenueInfoResp{}
resp.Balance = agentWalletModel.Balance
resp.TotalEarnings = agentWalletModel.TotalEarnings
resp.FrozenBalance = agentWalletModel.FrozenBalance
// 直推报告统计
//now := time.Now()
//startTime := now.AddDate(0, 0, -30).Format("2006-01-02 15:04:05")
//endTime := now.Format("2006-01-02 15:04:05")
// 直推报告佣金
agentCommissionModelBuild := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where(squirrel.Eq{"agent_id": agentModel.Id})
//.Where(squirrel.Expr("create_time BETWEEN ? AND ?", startTime, endTime))
agentCommissionsModel, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, agentCommissionModelBuild, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err)
}
// 筛选分类
directPush, err := calculateDirectPushReport(agentCommissionsModel, nil)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err)
}
// 绑定到响应体
resp.DirectPush = directPush
// 活跃下级统计
agentRewardsModelBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where("agent_id = ?", agentModel.Id)
agentRewardsModel, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, agentRewardsModelBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err)
}
activeReward := calculateActiveReward(agentRewardsModel)
resp.ActiveReward = activeReward
return resp, nil
}
// 统计直推报告的独立函数
func calculateDirectPushReport(commissions []*model.AgentCommission, loc *time.Location) (types.DirectPushReport, error) {
// 初始化报告结构
report := types.DirectPushReport{
Today: types.TimeRangeReport{},
Last7D: types.TimeRangeReport{},
Last30D: types.TimeRangeReport{},
}
// 获取当前中国时间
now := time.Now()
// 计算时间分界点
todayStart := now.Add(-24 * time.Hour)
last7dStart := now.AddDate(0, 0, -7)
last30dStart := now.AddDate(0, 0, -30)
// 遍历所有佣金记录
for _, c := range commissions {
// 转换时区
createTime := c.CreateTime
// 统计总量
report.TotalCommission += c.Amount
report.TotalReport++
// 近24小时滚动周期
if createTime.After(todayStart) {
report.Today.Commission += c.Amount
report.Today.Report++
}
// 近7天滚动周期
if createTime.After(last7dStart) {
report.Last7D.Commission += c.Amount
report.Last7D.Report++
}
// 近30天滚动周期
if createTime.After(last30dStart) {
report.Last30D.Commission += c.Amount
report.Last30D.Report++
}
}
return report, nil
}
func calculateActiveReward(rewards []*model.AgentRewards) types.ActiveReward {
result := types.ActiveReward{
Today: types.ActiveRewardData{},
Last7D: types.ActiveRewardData{},
Last30D: types.ActiveRewardData{},
}
now := time.Now()
todayStart := now.Add(-24 * time.Hour) // 近24小时
last7dStart := now.AddDate(0, 0, -7) // 近7天
last30dStart := now.AddDate(0, 0, -30) // 近30天
for _, r := range rewards {
createTime := r.CreateTime
amount := r.Amount
// 总奖励累加
result.TotalReward += amount
// 时间范围判断
isToday := createTime.After(todayStart)
isLast7d := createTime.After(last7dStart)
isLast30d := createTime.After(last30dStart)
// 类型分类统计
switch r.Type {
case model.AgentRewardsTypeDescendantWithdraw:
addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "withdraw")
case model.AgentRewardsTypeDescendantNewActive:
addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "new_active")
case model.AgentRewardsTypeDescendantUpgradeSvip, model.AgentRewardsTypeDescendantUpgradeVip:
addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "upgrade")
case model.AgentRewardsTypeDescendantPromotion:
addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "promotion")
}
}
return result
}
// 统一处理时间段累加
func addToPeriods(res *types.ActiveReward, amount float64, today, last7d, last30d bool, t string) {
if today {
addToData(&res.Today, amount, t)
}
if last7d {
addToData(&res.Last7D, amount, t)
}
if last30d {
addToData(&res.Last30D, amount, t)
}
}
// 分类添加具体字段
func addToData(data *types.ActiveRewardData, amount float64, t string) {
switch t {
case "withdraw":
data.SubWithdrawReward += amount
case "new_active":
data.NewActiveReward += amount
case "upgrade":
data.SubUpgradeReward += amount
case "promotion":
data.SubPromoteReward += amount
}
}

View File

@@ -0,0 +1,68 @@
package agent
import (
"context"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentRewardsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentRewardsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentRewardsLogic {
return &GetAgentRewardsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentRewardsLogic) GetAgentRewards(req *types.GetRewardsReq) (resp *types.GetRewardsResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理奖励列表, %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励列表, %v", err)
}
builder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": agentModel.Id,
})
agentRewardsModelList, total, err := l.svcCtx.AgentRewardsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励列表, 查找列表错误, %v", err)
}
var list = make([]types.Rewards, 0)
if len(agentRewardsModelList) > 0 {
for _, agentRewardsModel := range agentRewardsModelList {
var rewards types.Rewards
copyErr := copier.Copy(&rewards, agentRewardsModel)
if copyErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理奖励列表, %v", err)
}
rewards.CreateTime = agentRewardsModel.CreateTime.Format("2006-01-02 15:04:05")
list = append(list, rewards)
}
}
return &types.GetRewardsResp{
Total: total,
List: list,
}, nil
return
}

View File

@@ -0,0 +1,200 @@
package agent
import (
"context"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentSubordinateContributionDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentSubordinateContributionDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentSubordinateContributionDetailLogic {
return &GetAgentSubordinateContributionDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentSubordinateContributionDetailLogic) GetAgentSubordinateContributionDetail(req *types.GetAgentSubordinateContributionDetailReq) (resp *types.GetAgentSubordinateContributionDetailResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 获取用户ID%v", err)
}
// 获取当前代理信息
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取代理信息%v", err)
}
// 获取下级代理信息
subordinateAgent, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.SubordinateID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取下级代理信息%v", err)
}
// 验证是否是当前代理的下级
closureBuilder := l.svcCtx.AgentClosureModel.SelectBuilder().Where(squirrel.Eq{
"ancestor_id": agentModel.Id,
"descendant_id": req.SubordinateID,
})
closureList, err := l.svcCtx.AgentClosureModel.FindAll(l.ctx, closureBuilder, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 验证代理关系%v", err)
}
if len(closureList) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 非法的代理关系")
}
closure := closureList[0]
// 获取佣金扣除记录
deductionBuilder := l.svcCtx.AgentCommissionDeductionModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": agentModel.Id,
"deducted_agent_id": req.SubordinateID,
})
deductionList, err := l.svcCtx.AgentCommissionDeductionModel.FindAll(l.ctx, deductionBuilder, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取佣金扣除记录%v", err)
}
// 获取奖励记录
rewardsBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": agentModel.Id,
"relation_agent_id": req.SubordinateID,
})
rewards, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, rewardsBuilder, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取奖励记录%v", err)
}
// 计算总贡献
var totalContribution float64
for _, v := range deductionList {
totalContribution += v.Amount
}
// 加上奖励金额
for _, v := range rewards {
totalContribution += v.Amount
}
// 获取佣金记录
commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": req.SubordinateID,
})
commissionList, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, commissionBuilder, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取佣金记录%v", err)
}
// 计算总收益和总单量
var totalEarnings float64
for _, v := range commissionList {
totalEarnings += v.Amount
}
// 初始化统计数据
stats := types.AgentSubordinateContributionStats{
CostCount: 0,
CostAmount: 0,
PricingCount: 0,
PricingAmount: 0,
DescendantPromotionCount: 0,
DescendantPromotionAmount: 0,
DescendantUpgradeVipCount: 0,
DescendantUpgradeVipAmount: 0,
DescendantUpgradeSvipCount: 0,
DescendantUpgradeSvipAmount: 0,
DescendantStayActiveCount: 0,
DescendantStayActiveAmount: 0,
DescendantNewActiveCount: 0,
DescendantNewActiveAmount: 0,
DescendantWithdrawCount: 0,
DescendantWithdrawAmount: 0,
}
// 统计佣金扣除记录
for _, v := range deductionList {
switch v.Type {
case "cost":
stats.CostCount++
stats.CostAmount += v.Amount
case "pricing":
stats.PricingCount++
stats.PricingAmount += v.Amount
}
}
// 统计奖励记录
for _, v := range rewards {
switch v.Type {
case "descendant_promotion":
stats.DescendantPromotionCount++
stats.DescendantPromotionAmount += v.Amount
case "descendant_upgrade_vip":
stats.DescendantUpgradeVipCount++
stats.DescendantUpgradeVipAmount += v.Amount
case "descendant_upgrade_svip":
stats.DescendantUpgradeSvipCount++
stats.DescendantUpgradeSvipAmount += v.Amount
case "descendant_stay_active":
stats.DescendantStayActiveCount++
stats.DescendantStayActiveAmount += v.Amount
case "descendant_new_active":
stats.DescendantNewActiveCount++
stats.DescendantNewActiveAmount += v.Amount
case "descendant_withdraw":
stats.DescendantWithdrawCount++
stats.DescendantWithdrawAmount += v.Amount
}
}
// 解密手机号
secretKey := l.svcCtx.Config.Encrypt.SecretKey
mobile, err := crypto.DecryptMobile(subordinateAgent.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 解密手机号失败: %v", err)
}
// 获取合并后的分页列表
unionDetails, total, err := l.svcCtx.AgentClosureModel.FindUnionPageListByPageWithTotal(l.ctx, agentModel.Id, req.SubordinateID, req.Page, req.PageSize)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取分页列表%v", err)
}
// 转换为响应类型
detailList := make([]types.AgentSubordinateContributionDetail, 0, len(unionDetails))
for _, v := range unionDetails {
detail := types.AgentSubordinateContributionDetail{
ID: v.Id,
CreateTime: v.CreateTime,
Amount: v.Amount,
Type: v.Type,
}
detailList = append(detailList, detail)
}
return &types.GetAgentSubordinateContributionDetailResp{
Mobile: maskPhone(mobile),
Total: total,
CreateTime: closure.CreateTime.Format("2006-01-02 15:04:05"),
TotalEarnings: totalEarnings,
TotalContribution: totalContribution,
TotalOrders: int64(len(commissionList)),
LevelName: subordinateAgent.LevelName,
List: detailList,
Stats: stats,
}, nil
}

View File

@@ -0,0 +1,173 @@
package agent
import (
"context"
"strings"
"time"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"hm-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetAgentSubordinateListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentSubordinateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentSubordinateListLogic {
return &GetAgentSubordinateListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentSubordinateListLogic) GetAgentSubordinateList(req *types.GetAgentSubordinateListReq) (resp *types.GetAgentSubordinateListResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级列表, 获取用户ID%v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理信息%v", err)
}
agentID := agentModel.Id
builder := l.svcCtx.AgentClosureModel.SelectBuilder().Where(squirrel.Eq{
"ancestor_id": agentID,
})
agentClosureModelList, total, err := l.svcCtx.AgentClosureModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理关系%v", err)
}
// 构建ID到CreateTime的映射
createTimeMap := make(map[int64]time.Time)
descendantIDs := make([]int64, 0)
for _, v := range agentClosureModelList {
descendantIDs = append(descendantIDs, v.DescendantId)
createTimeMap[v.DescendantId] = v.CreateTime
}
// 并发查询代理信息
agentMap := make(map[int64]*model.Agent)
var descendantList []types.AgentSubordinateList
err = mr.Finish(func() error {
return mr.MapReduceVoid(func(source chan<- interface{}) {
for _, id := range descendantIDs {
source <- id
}
}, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) {
id := item.(int64)
agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, id)
if err != nil {
cancel(err)
return
}
writer.Write(agent)
}, func(pipe <-chan interface{}, cancel func(error)) {
for item := range pipe {
agent := item.(*model.Agent)
agentMap[agent.Id] = agent
}
})
}, func() error {
// 并发查询佣金扣除信息
deductionBuilder := l.svcCtx.AgentCommissionDeductionModel.SelectBuilder().
Where(squirrel.Eq{"agent_id": agentID}).
Where(squirrel.Eq{"deducted_agent_id": descendantIDs})
deductionList, err := l.svcCtx.AgentCommissionDeductionModel.FindAll(l.ctx, deductionBuilder, "")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理佣金扣除信息%v", err)
}
deductionMap := make(map[int64]float64)
for _, v := range deductionList {
deductionMap[v.DeductedAgentId] += v.Amount
}
// 并发查询奖励信息
rewardsBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder().
Where(squirrel.Eq{"agent_id": agentID}).
Where(squirrel.Eq{"relation_agent_id": descendantIDs})
rewardsList, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, rewardsBuilder, "")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理奖励信息%v", err)
}
rewardsMap := make(map[int64]float64)
for _, v := range rewardsList {
if v.RelationAgentId.Valid {
rewardsMap[v.RelationAgentId.Int64] += v.Amount
}
}
// 并发查询佣金信息
commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where(squirrel.Eq{"agent_id": descendantIDs})
commissionList, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, commissionBuilder, "")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理佣金信息%v", err)
}
commissionMap := make(map[int64]float64)
orderCountMap := make(map[int64]int64)
for _, v := range commissionList {
commissionMap[v.AgentId] += v.Amount
orderCountMap[v.AgentId]++
}
// 构建返回结果
secretKey := l.svcCtx.Config.Encrypt.SecretKey
descendantList = make([]types.AgentSubordinateList, 0, len(descendantIDs))
for _, id := range descendantIDs {
agent, exists := agentMap[id]
if !exists {
continue
}
mobile, err := crypto.DecryptMobile(agent.Mobile, secretKey)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err)
}
subordinate := types.AgentSubordinateList{
ID: id,
Mobile: maskPhone(mobile),
LevelName: agent.LevelName,
CreateTime: createTimeMap[id].Format("2006-01-02 15:04:05"),
TotalContribution: deductionMap[id] + rewardsMap[id],
TotalEarnings: commissionMap[id],
TotalOrders: orderCountMap[id],
}
descendantList = append(descendantList, subordinate)
}
return nil
})
if err != nil {
return nil, err
}
return &types.GetAgentSubordinateListResp{
Total: total,
List: descendantList,
}, nil
}
// 手机号脱敏
func maskPhone(phone string) string {
length := len(phone)
if length < 8 {
return phone // 如果长度太短,可能不是手机号,不处理
}
// 保留前3位和后4位
return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:]
}

View File

@@ -0,0 +1,66 @@
package agent
import (
"context"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentWithdrawalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalLogic {
return &GetAgentWithdrawalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentWithdrawalLogic) GetAgentWithdrawal(req *types.GetWithdrawalReq) (resp *types.GetWithdrawalResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, %v", err)
}
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().Where(squirrel.Eq{
"agent_id": agentModel.Id,
})
agentWithdrawalModelList, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, 查找列表错误, %v", err)
}
var list = make([]types.Withdrawal, 0)
if len(agentWithdrawalModelList) > 0 {
for _, agentWithdrawalModel := range agentWithdrawalModelList {
var withdrawal types.Withdrawal
copyErr := copier.Copy(&withdrawal, agentWithdrawalModel)
if copyErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err)
}
withdrawal.CreateTime = agentWithdrawalModel.CreateTime.Format("2006-01-02 15:04:05")
list = append(list, withdrawal)
}
}
return &types.GetWithdrawalResp{
Total: total,
List: list,
}, nil
}

View File

@@ -0,0 +1,42 @@
package agent
import (
"context"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAgentWithdrawalTaxExemptionLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAgentWithdrawalTaxExemptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalTaxExemptionLogic {
return &GetAgentWithdrawalTaxExemptionLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAgentWithdrawalTaxExemptionLogic) GetAgentWithdrawalTaxExemption(req *types.GetWithdrawalTaxExemptionReq) (resp *types.GetWithdrawalTaxExemptionResp, err error) {
_, err = ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %+v", err)
}
// 统一按6%收取税收,不再有免税额度
return &types.GetWithdrawalTaxExemptionResp{
TotalExemptionAmount: 0.00, // 免税总额度为0
UsedExemptionAmount: 0.00, // 已使用免税额度为0
RemainingExemptionAmount: 0.00, // 剩余免税额度为0
TaxRate: l.svcCtx.Config.TaxConfig.TaxRate, // 6%税收
}, nil
}

View File

@@ -0,0 +1,46 @@
package agent
import (
"context"
"hm-server/common/xerr"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLinkDataLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetLinkDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLinkDataLogic {
return &GetLinkDataLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.GetLinkDataResp, err error) {
agentLinkModel, err := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, req.LinkIdentifier)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err)
}
productModel, err := l.svcCtx.ProductModel.FindOne(l.ctx, agentLinkModel.ProductId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err)
}
var product types.Product
copier.Copy(&product, productModel)
product.SellPrice = agentLinkModel.Price
return &types.GetLinkDataResp{
Product: product,
}, nil
}

View File

@@ -0,0 +1,82 @@
package agent
import (
"context"
"hm-server/app/main/model"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/pkg/errors"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type SaveAgentMembershipUserConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSaveAgentMembershipUserConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveAgentMembershipUserConfigLogic {
return &SaveAgentMembershipUserConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SaveAgentMembershipUserConfigLogic) SaveAgentMembershipUserConfig(req *types.SaveAgentMembershipUserConfigReq) error {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置获取用户ID失败: %v", err)
}
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置: %v", err)
}
var agentMembershipUserConfigModel *model.AgentMembershipUserConfig
agentMembershipUserConfigModel, err = l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentModel.Id, req.ProductID)
// 检查记录是否存在
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 记录不存在,创建新的配置对象
agentMembershipUserConfigModel = &model.AgentMembershipUserConfig{
UserId: userID,
AgentId: agentModel.Id,
ProductId: req.ProductID,
PriceRatio: req.PriceRatio,
PriceIncreaseAmount: req.PriceIncreaseAmount,
PriceRangeFrom: req.PriceRangeFrom,
PriceRangeTo: req.PriceRangeTo,
}
// 插入新记录
_, err = l.svcCtx.AgentMembershipUserConfigModel.Insert(l.ctx, nil, agentMembershipUserConfigModel)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,插入新记录失败: %v", err)
}
return nil
}
// 其他错误
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,查询记录失败: %v", err)
}
// 记录存在,更新现有配置
agentMembershipUserConfigModel.PriceRatio = req.PriceRatio
agentMembershipUserConfigModel.PriceIncreaseAmount = req.PriceIncreaseAmount
agentMembershipUserConfigModel.PriceRangeFrom = req.PriceRangeFrom
agentMembershipUserConfigModel.PriceRangeTo = req.PriceRangeTo
_, err = l.svcCtx.AgentMembershipUserConfigModel.Update(l.ctx, nil, agentMembershipUserConfigModel)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,更新记录失败: %v", err)
}
return nil
}