This commit is contained in:
2025-12-02 19:57:10 +08:00
parent 3440744179
commit 08ff223ff8
188 changed files with 12337 additions and 7212 deletions

View File

@@ -0,0 +1,131 @@
# API调用失败退款时的代理处理确认
## 执行流程分析
### 关键执行顺序
```
1. 支付成功 → 创建代理订单ProcessStatus = 0未处理
2. 支付回调 → 发送查询任务
3. 查询任务执行:
├─ 创建查询记录query表状态为 "pending" ✅ 第77-87行
├─ 生成授权书 ✅ 第108-142行
├─ 调用API第164行 可能失败
│ ├─ 如果成功 → 继续执行
│ └─ 如果失败 → return handleError第166行 直接返回
├─ [API成功才会执行到这里]
├─ 保存响应数据 ✅ 第177-182行
├─ 更新查询状态为 "success" ✅ 第184-189行
└─ 发送代理处理任务 ✅ 第192行关键只有到这里才会发送
```
### 关键代码位置
**第164-167行**
```go
combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
if err != nil {
return l.handleError(ctx, err, order, query) // ← 直接返回,不会继续执行
}
```
**第192行发送代理处理任务**
```go
// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
logx.Errorf("发送代理处理任务失败订单ID: %d, 错误: %v", order.Id, asyncErr)
}
```
---
## 确认结果
### ✅ API调用失败时的情况第166-167行
**执行流程**
1. 支付时创建代理订单(`ProcessStatus = 0`
2. API调用失败第164行
3. **直接返回** `handleError`第166行
4. 进入退款流程
5. **第192行不会执行**因为已经return了
**代理状态**
- ✅ 代理订单 `ProcessStatus = 0`(未处理)
- ✅ 代理**没有收到**佣金
- ✅ 代理**没有收到**返佣
- ✅ 代理处理任务**没有发送**
---
## 业务逻辑分析
### ✅ 符合业务逻辑
**理由**
1. **服务未完成交付**
- API调用失败意味着无法生成查询报告
- 用户支付了订单,但服务没有成功交付
2. **用户权益保护**
- 平台退款给用户是合理的
- 用户没有获得服务,不应该承担费用
3. **代理收益逻辑**
- 代理的收益应该基于**服务成功交付**
- 而不是仅仅因为用户支付了订单
- 如果服务未成功交付,代理不应该获得收益
4. **当前实现正确**
- 只有在查询成功后第184行更新状态为 "success"
- 才会发送代理处理任务第192行
- API调用失败时查询未成功代理任务不会发送
---
## 结论
### ✅ 当前处理完全正确
**对于 API 调用失败退款的情况**
- ✅ 代理订单未处理(`ProcessStatus = 0`
- ✅ 代理没有收到佣金和返佣
- ✅ 代理处理任务没有发送
-**完全符合业务逻辑**
**业务逻辑合理性**
- ✅ 代理收益应该在服务成功交付后才发放
- ✅ API调用失败意味着服务未交付不应发放收益
- ✅ 退款给用户是合理的,保护用户权益
---
## 不需要修改
当前逻辑已经正确处理了这种情况,**无需修改**。
**关键保护点**
1. 代理处理任务只在查询成功后发送第192行
2. API调用失败会直接返回不会执行到第192行
3. 代理订单状态保持为0代理没有收益
---
## 需要注意的其他场景
虽然当前场景处理正确,但需要注意以下场景:
### ⚠️ 场景:代理已处理但订单被退款
如果:
1. API调用成功
2. 查询状态更新为 "success"
3. 发送代理处理任务
4. 代理处理任务执行,发放佣金和返佣(`ProcessStatus = 1`
5. **之后**订单被退款(比如管理员手动退款)
这种情况下需要撤销代理收益(需要另外处理,不是当前场景)。

Binary file not shown.

Binary file not shown.

View File

@@ -301,17 +301,16 @@ type (
AdminAuditRealNameResp {
Success bool `json:"success"`
}
// 系统配置查询
// 系统配置查询(价格配置已移除,改为产品配置表管理)
AdminGetAgentConfigResp {
BasePrice float64 `json:"base_price"` // 基础底价
SystemMaxPrice float64 `json:"system_max_price"` // 系统价格上限
PriceThreshold float64 `json:"price_threshold"` // 提价标准阈值
PriceFeeRate float64 `json:"price_fee_rate"` // 提价手续费比例
LevelBonus LevelBonusConfig `json:"level_bonus"` // 等级加成配置
UpgradeFee UpgradeFeeConfig `json:"upgrade_fee"` // 升级费用配置
UpgradeRebate UpgradeRebateConfig `json:"upgrade_rebate"` // 升级返佣配置
TaxRate float64 `json:"tax_rate"` // 税率
TaxExemptionAmount float64 `json:"tax_exemption_amount"` // 免税额度
LevelBonus LevelBonusConfig `json:"level_bonus"` // 等级加成配置
UpgradeFee UpgradeFeeConfig `json:"upgrade_fee"` // 升级费用配置
UpgradeRebate UpgradeRebateConfig `json:"upgrade_rebate"` // 升级返佣配置
DirectParentRebate DirectParentRebateConfig `json:"direct_parent_rebate"` // 直接上级返佣配置
MaxGoldRebateAmount float64 `json:"max_gold_rebate_amount"` // 黄金代理最大返佣金额
CommissionFreeze CommissionFreezeConfig `json:"commission_freeze"` // 佣金冻结配置
TaxRate float64 `json:"tax_rate"` // 税率
TaxExemptionAmount float64 `json:"tax_exemption_amount"` // 免税额度
}
LevelBonusConfig {
Diamond int64 `json:"diamond"` // 钻石加成0
@@ -327,23 +326,36 @@ type (
NormalToGoldRebate float64 `json:"normal_to_gold_rebate"` // 普通→黄金返佣139
ToDiamondRebate float64 `json:"to_diamond_rebate"` // 升级为钻石返佣680
}
// 系统配置更新
DirectParentRebateConfig {
Diamond float64 `json:"diamond"` // 直接上级是钻石的返佣金额6元
Gold float64 `json:"gold"` // 直接上级是黄金的返佣金额3元
Normal float64 `json:"normal"` // 直接上级是普通的返佣金额2元
}
CommissionFreezeConfig {
Ratio float64 `json:"ratio"` // 佣金冻结比例例如0.1表示10%
Threshold float64 `json:"threshold"` // 佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)
Days int64 `json:"days"` // 佣金冻结解冻天数单位例如30表示30天后解冻
}
// 系统配置更新(价格配置已移除,改为产品配置表管理)
AdminUpdateAgentConfigReq {
BasePrice *float64 `json:"base_price,optional"` // 基础底价
SystemMaxPrice *float64 `json:"system_max_price,optional"` // 系统价格上限
PriceThreshold *float64 `json:"price_threshold,optional"` // 提价标准阈值
PriceFeeRate *float64 `json:"price_fee_rate,optional"` // 提价手续费比例
TaxRate *float64 `json:"tax_rate,optional"` // 税率
TaxExemptionAmount *float64 `json:"tax_exemption_amount,optional"` // 免税额度
LevelBonus *LevelBonusConfig `json:"level_bonus,optional"` // 等级加成配置
UpgradeFee *UpgradeFeeConfig `json:"upgrade_fee,optional"` // 升级费用配置
UpgradeRebate *UpgradeRebateConfig `json:"upgrade_rebate,optional"` // 升级返佣配置
DirectParentRebate *DirectParentRebateConfig `json:"direct_parent_rebate,optional"` // 直接上级返佣配置
MaxGoldRebateAmount *float64 `json:"max_gold_rebate_amount,optional"` // 黄金代理最大返佣金额
CommissionFreeze *CommissionFreezeConfig `json:"commission_freeze,optional"` // 佣金冻结配置
TaxRate *float64 `json:"tax_rate,optional"` // 税率
TaxExemptionAmount *float64 `json:"tax_exemption_amount,optional"` // 免税额度
}
AdminUpdateAgentConfigResp {
Success bool `json:"success"`
}
// 产品配置分页查询
AdminGetAgentProductConfigListReq {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"pageSize"` // 每页数量
ProductId *int64 `form:"product_id,optional"` // 产品ID可选
Page int64 `form:"page"` // 页码
PageSize int64 `form:"pageSize"` // 每页数量
ProductId *int64 `form:"product_id,optional"` // 产品ID可选
ProductName *string `form:"product_name,optional"` // 产品名称(可选,用于搜索)
}
AgentProductConfigItem {
Id int64 `json:"id"` // 主键
@@ -362,12 +374,11 @@ type (
}
// 产品配置更新
AdminUpdateAgentProductConfigReq {
Id int64 `json:"id"` // 主键
BasePrice float64 `json:"base_price"` // 基础底价
PriceRangeMin float64 `json:"price_range_min"` // 最定价
PriceRangeMax float64 `json:"price_range_max"` // 最高定价
PriceThreshold float64 `json:"price_threshold"` // 提价标准阈值
PriceFeeRate float64 `json:"price_fee_rate"` // 提价手续费比例
Id int64 `json:"id"` // 主键
BasePrice float64 `json:"base_price"` // 基础底价
PriceRangeMax float64 `json:"price_range_max"` // 最定价(对应数据库 system_max_price
PriceThreshold *float64 `json:"price_threshold,optional"` // 提价标准阈值(可选)
PriceFeeRate *float64 `json:"price_fee_rate,optional"` // 提价手续费比例(可选)
}
AdminUpdateAgentProductConfigResp {
Success bool `json:"success"`

View File

@@ -53,7 +53,6 @@ type (
PaymentScene string `form:"payment_scene,optional"` // 支付平台
Amount float64 `form:"amount,optional"` // 金额
Status string `form:"status,optional"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
IsPromotion int64 `form:"is_promotion,optional,default=-1"` // 是否推广订单0-否1-是
CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始
CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束
PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始
@@ -80,7 +79,6 @@ type (
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
}
@@ -102,7 +100,6 @@ type (
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
UpdateTime string `json:"update_time"` // 更新时间
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
@@ -116,7 +113,6 @@ type (
PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额
Status string `json:"status,default=pending"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
IsPromotion int64 `json:"is_promotion,default=0"` // 是否推广订单0-否1-是
}
// 创建响应
AdminCreateOrderResp {
@@ -134,7 +130,6 @@ type (
Status *string `json:"status,optional"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
PayTime *string `json:"pay_time,optional"` // 支付时间
RefundTime *string `json:"refund_time,optional"` // 退款时间
IsPromotion *int64 `json:"is_promotion,optional"` // 是否推广订单0-否1-是
}
// 更新响应
AdminUpdateOrderResp {

View File

@@ -1,182 +0,0 @@
syntax = "v1"
info (
title: "推广服务"
desc: "推广服务"
version: "v1"
)
@server (
prefix: api/v1/admin/promotion/link
group: admin_promotion
middleware: AdminAuthInterceptor
)
service main {
@doc "获取推广链接列表"
@handler GetPromotionLinkList
get /list (GetPromotionLinkListReq) returns (GetPromotionLinkListResp)
@doc "获取推广链接详情"
@handler GetPromotionLinkDetail
get /detail/:id (GetPromotionLinkDetailReq) returns (GetPromotionLinkDetailResp)
@doc "创建推广链接"
@handler CreatePromotionLink
post /create (CreatePromotionLinkReq) returns (CreatePromotionLinkResp)
@doc "更新推广链接"
@handler UpdatePromotionLink
put /update/:id (UpdatePromotionLinkReq) returns (UpdatePromotionLinkResp)
@doc "删除推广链接"
@handler DeletePromotionLink
delete /delete/:id (DeletePromotionLinkReq) returns (DeletePromotionLinkResp)
}
type (
// 列表请求
GetPromotionLinkListReq {
Page int64 `form:"page,default=1"` // 页码
PageSize int64 `form:"pageSize,default=20"` // 每页数量
Name string `form:"name,optional"` // 链接名称
Url string `form:"url,optional"` // 推广链接URL
}
// 列表响应
GetPromotionLinkListResp {
Total int64 `json:"total"` // 总数
Items []PromotionLinkItem `json:"items"` // 列表
}
// 列表项
PromotionLinkItem {
Id int64 `json:"id"` // 链接ID
Name string `json:"name"` // 链接名称
Url string `json:"url"` // 推广链接URL
ClickCount int64 `json:"click_count"` // 点击数
PayCount int64 `json:"pay_count"` // 付费次数
PayAmount string `json:"pay_amount"` // 付费金额
CreateTime string `json:"create_time"` // 创建时间
LastClickTime string `json:"last_click_time,optional"` // 最后点击时间
LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间
}
// 详情请求
GetPromotionLinkDetailReq {
Id int64 `path:"id"` // 链接ID
}
// 详情响应
GetPromotionLinkDetailResp {
Name string `json:"name"` // 链接名称
Url string `json:"url"` // 推广链接URL
ClickCount int64 `json:"click_count"` // 点击数
PayCount int64 `json:"pay_count"` // 付费次数
PayAmount string `json:"pay_amount"` // 付费金额
CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间
LastClickTime string `json:"last_click_time,optional"` // 最后点击时间
LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间
}
// 创建请求
CreatePromotionLinkReq {
Name string `json:"name"` // 链接名称
}
// 创建响应
CreatePromotionLinkResp {
Id int64 `json:"id"` // 链接ID
Url string `json:"url"` // 生成的推广链接URL
}
// 更新请求
UpdatePromotionLinkReq {
Id int64 `path:"id"` // 链接ID
Name *string `json:"name,optional"` // 链接名称
}
// 更新响应
UpdatePromotionLinkResp {
Success bool `json:"success"` // 是否成功
}
// 删除请求
DeletePromotionLinkReq {
Id int64 `path:"id"` // 链接ID
}
// 删除响应
DeletePromotionLinkResp {
Success bool `json:"success"` // 是否成功
}
)
@server (
prefix: api/v1/admin/promotion/link
group: admin_promotion
middleware: AdminAuthInterceptor
)
service main {
@doc "记录链接点击"
@handler RecordLinkClick
get /record/:path (RecordLinkClickReq) returns (RecordLinkClickResp)
}
type (
// 记录链接点击请求
RecordLinkClickReq {
Path string `path:"path"` // 链接路径
}
// 记录链接点击响应
RecordLinkClickResp {
Success bool `json:"success"` // 是否成功
}
)
@server (
prefix: api/v1/admin/promotion/stats
group: admin_promotion
middleware: AdminAuthInterceptor
)
service main {
@doc "获取推广历史记录"
@handler GetPromotionStatsHistory
get /history (GetPromotionStatsHistoryReq) returns ([]PromotionStatsHistoryItem)
@doc "获取推广总统计"
@handler GetPromotionStatsTotal
get /total (GetPromotionStatsTotalReq) returns (GetPromotionStatsTotalResp)
}
type (
// 获取推广历史记录请求
GetPromotionStatsHistoryReq {
StartDate string `form:"start_date"` // 开始日期格式YYYY-MM-DD
EndDate string `form:"end_date"` // 结束日期格式YYYY-MM-DD
}
// 推广历史记录项
PromotionStatsHistoryItem {
Id int64 `json:"id"` // 记录ID
LinkId int64 `json:"link_id"` // 链接ID
PayAmount float64 `json:"pay_amount"` // 金额
ClickCount int64 `json:"click_count"` // 点击数
PayCount int64 `json:"pay_count"` // 付费次数
StatsDate string `json:"stats_date"` // 统计日期
}
// 获取推广总统计请求
GetPromotionStatsTotalReq {
}
// 获取推广总统计响应
GetPromotionStatsTotalResp {
TodayPayAmount float64 `json:"today_pay_amount"` // 今日金额
TodayClickCount int64 `json:"today_click_count"` // 今日点击数
TodayPayCount int64 `json:"today_pay_count"` // 今日付费次数
TotalPayAmount float64 `json:"total_pay_amount"` // 总金额
TotalClickCount int64 `json:"total_click_count"` // 总点击数
TotalPayCount int64 `json:"total_pay_count"` // 总付费次数
}
)

View File

@@ -32,11 +32,15 @@ type (
LinkIdentifier string `form:"link_identifier"` // 推广链接标识
}
GetLinkDataResp {
AgentId int64 `json:"agent_id"` // 代理ID
ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 代理设定价格
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
ProductName string `json:"product_name"` // 产品名称
AgentId int64 `json:"agent_id"` // 代理ID
ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 代理设定价格
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
ProductName string `json:"product_name"` // 产品名称
ProductEn string `json:"product_en"` // 产品英文标识
SellPrice float64 `json:"sell_price"` // 销售价格(使用代理设定价格)
Description string `json:"description"` // 产品描述
Features []Feature `json:"features"` // 产品功能列表
}
AgentApplyReq {
Region string `json:"region,optional"` // 区域(可选)
@@ -94,10 +98,33 @@ type (
Remark string `json:"remark"` // 备注
CreateTime string `json:"create_time"` // 创建时间
}
// 删除邀请码
DeleteInviteCodeReq {
Id int64 `json:"id"` // 邀请码ID
}
DeleteInviteCodeResp {}
// 获取邀请链接
GetInviteLinkReq {
InviteCode string `form:"invite_code"` // 邀请码
TargetPath string `form:"target_path,optional"` // 目标地址(可选,默认为注册页面)
}
GetInviteLinkResp {
InviteLink string `json:"invite_link"` // 邀请链接
QrCodeUrl string `json:"qr_code_url"` // 二维码URL
}
// 获取代理等级特权信息
GetLevelPrivilegeResp {
Levels []LevelPrivilegeItem `json:"levels"` // 各等级特权信息列表
}
LevelPrivilegeItem {
Level int64 `json:"level"` // 等级1=普通2=黄金3=钻石
LevelName string `json:"level_name"` // 等级名称
LevelBonus float64 `json:"level_bonus"` // 等级加成(元)
PriceReduction float64 `json:"price_reduction"` // 底价降低(元,相对于当前等级)
PromoteRebate string `json:"promote_rebate"` // 推广返佣说明
MaxPromoteRebate float64 `json:"max_promote_rebate"` // 最高推广返佣金额(元)
UpgradeRebate string `json:"upgrade_rebate"` // 下级升级返佣说明
Privileges []string `json:"privileges"` // 特权列表
CanUpgradeSubordinate bool `json:"can_upgrade_subordinate"` // 是否可以升级下级
}
)
@@ -127,10 +154,22 @@ service main {
@handler GetTeamStatistics
get /team/statistics returns (TeamStatisticsResp)
// 获取转化率统计
@handler GetConversionRate
get /conversion/rate returns (ConversionRateResp)
// 获取下级列表
@handler GetSubordinateList
get /subordinate/list (GetSubordinateListReq) returns (GetSubordinateListResp)
// 获取下级贡献详情
@handler GetSubordinateContributionDetail
get /subordinate/contribution/detail (GetSubordinateContributionDetailReq) returns (GetSubordinateContributionDetailResp)
// 获取团队列表
@handler GetTeamList
get /team/list (GetTeamListReq) returns (GetTeamListResp)
// 获取收益信息
@handler GetRevenueInfo
get /revenue returns (GetRevenueInfoResp)
@@ -139,10 +178,14 @@ service main {
@handler GetCommissionList
get /commission/list (GetCommissionListReq) returns (GetCommissionListResp)
// 获取返佣记录
// 获取返佣记录(推广返佣)
@handler GetRebateList
get /rebate/list (GetRebateListReq) returns (GetRebateListResp)
// 获取升级返佣记录
@handler GetUpgradeRebateList
get /rebate/upgrade/list (GetUpgradeRebateListReq) returns (GetUpgradeRebateListResp)
// 获取升级记录
@handler GetUpgradeList
get /upgrade/list (GetUpgradeListReq) returns (GetUpgradeListResp)
@@ -175,9 +218,17 @@ service main {
@handler GetInviteCodeList
get /invite_code/list (GetInviteCodeListReq) returns (GetInviteCodeListResp)
// 获取邀请链接
// 删除邀请码
@handler DeleteInviteCode
post /invite_code/delete (DeleteInviteCodeReq) returns (DeleteInviteCodeResp)
// 获取邀请链接(根据邀请码生成)
@handler GetInviteLink
get /invite_link returns (GetInviteLinkResp)
get /invite_link (GetInviteLinkReq) returns (GetInviteLinkResp)
// 获取代理等级特权信息
@handler GetLevelPrivilege
get /level/privilege returns (GetLevelPrivilegeResp)
}
type (
@@ -194,11 +245,13 @@ type (
}
// 生成推广链接
AgentGeneratingLinkReq {
ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 设定价格
ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 设定价格
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
}
AgentGeneratingLinkResp {
LinkIdentifier string `json:"link_identifier"` // 推广链接标识
FullLink string `json:"full_link"` // 完整短链URL
}
// 产品配置
AgentProductConfigResp {
@@ -207,8 +260,7 @@ type (
ProductConfigItem {
ProductId int64 `json:"product_id"` // 产品ID
ProductName string `json:"product_name"` // 产品名称
BasePrice float64 `json:"base_price"` // 基础底价
LevelBonus float64 `json:"level_bonus"` // 等级加成
ProductEn string `json:"product_en"` // 产品英文标识
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
PriceRangeMin float64 `json:"price_range_min"` // 最低价格
PriceRangeMax float64 `json:"price_range_max"` // 最高价格
@@ -217,15 +269,31 @@ type (
}
// 团队统计
TeamStatisticsResp {
TotalCount int64 `json:"total_count"` // 团队总人数(包括自己)
DirectCount int64 `json:"direct_count"` // 直接下级数量
IndirectCount int64 `json:"indirect_count"` // 间接下级数量
ByLevel TeamLevelStats `json:"by_level"` // 按等级统计
TotalCount int64 `json:"total_count"` // 团队总人数(包括自己)
DirectCount int64 `json:"direct_count"` // 直接下级数量
IndirectCount int64 `json:"indirect_count"` // 间接下级数量
GoldCount int64 `json:"gold_count"` // 黄金代理数量
NormalCount int64 `json:"normal_count"` // 普通代理数量
TodayNewMembers int64 `json:"today_new_members"` // 今日新增成员
MonthNewMembers int64 `json:"month_new_members"` // 本月新增成员
}
TeamLevelStats {
Diamond int64 `json:"diamond"` // 钻石数量
Gold int64 `json:"gold"` // 黄金数量
Normal int64 `json:"normal"` // 普通数量
// 转化率统计
ConversionRateResp {
MyConversionRate ConversionRateData `json:"my_conversion_rate"` // 我的转化率
SubordinateConversionRate ConversionRateData `json:"subordinate_conversion_rate"` // 我的下级转化率
}
ConversionRateData {
Daily []PeriodConversionData `json:"daily"` // 日统计(今日、昨日、前日)
Weekly []PeriodConversionData `json:"weekly"` // 周统计(这周、前一周、前两周)
Monthly []PeriodConversionData `json:"monthly"` // 月统计(本月、上月、前两月)
}
PeriodConversionData {
PeriodLabel string `json:"period_label"` // 时间段标签(如:今日、昨日、前日)
QueryUsers int64 `json:"query_users"` // 查询订单数(订单数量,不是用户数)
PaidUsers int64 `json:"paid_users"` // 付费订单数(订单数量,不是用户数)
TotalAmount float64 `json:"total_amount"` // 总金额(已支付订单的总金额)
QueryUserCount int64 `json:"query_user_count"` // 查询用户数去重user_id后的用户数量
PaidUserCount int64 `json:"paid_user_count"` // 付费用户数去重user_id后的用户数量
}
// 下级列表
GetSubordinateListReq {
@@ -245,12 +313,103 @@ type (
TotalOrders int64 `json:"total_orders"` // 总订单数
TotalAmount float64 `json:"total_amount"` // 总金额
}
// 下级贡献详情
GetSubordinateContributionDetailReq {
SubordinateId int64 `form:"subordinate_id"` // 下级代理ID
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量
TabType string `form:"tab_type,optional"` // 标签页类型order=订单列表invite=邀请列表
}
GetSubordinateContributionDetailResp {
Mobile string `json:"mobile"` // 手机号
LevelName string `json:"level_name"` // 等级名称
CreateTime string `json:"create_time"` // 创建时间
OrderStats OrderStatistics `json:"order_stats"` // 订单统计(仅统计有返佣的订单)
RebateStats RebateStatistics `json:"rebate_stats"` // 返佣统计
InviteStats InviteStatistics `json:"invite_stats"` // 邀请统计
OrderList []OrderItem `json:"order_list"` // 订单列表(仅有贡献的订单)
InviteList []InviteItem `json:"invite_list"` // 邀请列表
OrderListTotal int64 `json:"order_list_total"` // 订单列表总数
InviteListTotal int64 `json:"invite_list_total"` // 邀请列表总数
}
OrderStatistics {
TotalOrders int64 `json:"total_orders"` // 总订单量(仅统计有返佣的订单)
MonthOrders int64 `json:"month_orders"` // 月订单
TodayOrders int64 `json:"today_orders"` // 今日订单
}
RebateStatistics {
TotalRebateAmount float64 `json:"total_rebate_amount"` // 总贡献返佣金额
TodayRebateAmount float64 `json:"today_rebate_amount"` // 今日贡献返佣金额
MonthRebateAmount float64 `json:"month_rebate_amount"` // 月贡献返佣金额
}
InviteStatistics {
TotalInvites int64 `json:"total_invites"` // 总的邀请
TodayInvites int64 `json:"today_invites"` // 今日邀请
MonthInvites int64 `json:"month_invites"` // 月邀请
}
OrderItem {
OrderNo string `json:"order_no"` // 订单号
ProductId int64 `json:"product_id"` // 产品ID
ProductName string `json:"product_name"` // 产品名称
OrderAmount float64 `json:"order_amount"` // 订单金额
RebateAmount float64 `json:"rebate_amount"` // 返佣金额
CreateTime string `json:"create_time"` // 创建时间
}
InviteItem {
AgentId int64 `json:"agent_id"` // 代理ID
Level int64 `json:"level"` // 等级
LevelName string `json:"level_name"` // 等级名称
Mobile string `json:"mobile"` // 手机号
CreateTime string `json:"create_time"` // 邀请时间
}
// 团队列表
GetTeamListReq {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量
Mobile string `form:"mobile,optional"` // 手机号(可选,用于搜索)
}
GetTeamListResp {
Statistics TeamStatistics `json:"statistics"` // 统计数据
Total int64 `json:"total"` // 总数
List []TeamMemberItem `json:"list"` // 列表
}
TeamStatistics {
TotalMembers int64 `json:"total_members"` // 团队成员总数
TodayNewMembers int64 `json:"today_new_members"` // 团队人员今日新增
MonthNewMembers int64 `json:"month_new_members"` // 团队人员月新增
TotalQueries int64 `json:"total_queries"` // 团队总查询总量
TodayPromotions int64 `json:"today_promotions"` // 团队今日推广量
MonthPromotions int64 `json:"month_promotions"` // 团队月推广量
TotalEarnings float64 `json:"total_earnings"` // 依靠团队得到的总收益
TodayEarnings float64 `json:"today_earnings"` // 今日收益
MonthEarnings float64 `json:"month_earnings"` // 月收益
}
TeamMemberItem {
AgentId int64 `json:"agent_id"` // 代理ID
Level int64 `json:"level"` // 等级
LevelName string `json:"level_name"` // 等级名称
Mobile string `json:"mobile"` // 手机号
CreateTime string `json:"create_time"` // 加入团队时间
TodayQueries int64 `json:"today_queries"` // 当日查询量
TotalQueries int64 `json:"total_queries"` // 总查询量
TotalRebateAmount float64 `json:"total_rebate_amount"` // 返佣给我的总金额
TodayRebateAmount float64 `json:"today_rebate_amount"` // 返佣给我的今日金额
TotalInvites int64 `json:"total_invites"` // 邀请加入团队的总人数
TodayInvites int64 `json:"today_invites"` // 邀请加入团队的今日人数
IsDirect bool `json:"is_direct"` // 是否直接下级
}
// 收益信息
GetRevenueInfoResp {
Balance float64 `json:"balance"` // 可用余额
FrozenBalance float64 `json:"frozen_balance"` // 冻结余额
TotalEarnings float64 `json:"total_earnings"` // 累计收益
TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益)
WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现
CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金)
CommissionToday float64 `json:"commission_today"` // 佣金今日收益
CommissionMonth float64 `json:"commission_month"` // 佣金本月收益
RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣)
RebateToday float64 `json:"rebate_today"` // 返佣今日收益
RebateMonth float64 `json:"rebate_month"` // 返佣本月收益
}
// 佣金记录
GetCommissionListReq {
@@ -264,27 +423,51 @@ type (
CommissionItem {
Id int64 `json:"id"` // 记录ID
OrderId int64 `json:"order_id"` // 订单ID
OrderNo string `json:"order_no"` // 订单号
ProductName string `json:"product_name"` // 产品名称
Amount float64 `json:"amount"` // 佣金金额
Status int64 `json:"status"` // 状态1=已发放2=已冻结3=已取消
CreateTime string `json:"create_time"` // 创建时间
}
// 返佣记录
// 返佣记录(推广返佣)
GetRebateListReq {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型可选1=直接上级返佣2=钻石上级返佣3=黄金上级返佣
}
GetRebateListResp {
Total int64 `json:"total"` // 总数
List []RebateItem `json:"list"` // 列表
}
RebateItem {
Id int64 `json:"id"` // 记录ID
SourceAgentId int64 `json:"source_agent_id"` // 来源代理ID
OrderId int64 `json:"order_id"` // 订单ID
RebateType int64 `json:"rebate_type"` // 返佣类型1=直接上级2=钻石上级3=黄金上级
Amount float64 `json:"amount"` // 返佣金额
CreateTime string `json:"create_time"` // 创建时间
Id int64 `json:"id"` // 记录ID
SourceAgentId int64 `json:"source_agent_id"` // 来源代理ID
SourceAgentMobile string `json:"source_agent_mobile"` // 来源代理手机号
SourceAgentLevel int64 `json:"source_agent_level"` // 来源代理等级1=普通2=黄金3=钻石
OrderId int64 `json:"order_id"` // 订单ID
OrderNo string `json:"order_no"` // 订单号
RebateType int64 `json:"rebate_type"` // 返佣类型1=直接上级2=钻石上级3=黄金上级
Amount float64 `json:"amount"` // 返佣金额
CreateTime string `json:"create_time"` // 创建时间
}
// 升级返佣记录
GetUpgradeRebateListReq {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量
}
GetUpgradeRebateListResp {
Total int64 `json:"total"` // 总数
List []UpgradeRebateItem `json:"list"` // 列表
}
UpgradeRebateItem {
Id int64 `json:"id"` // 记录ID
SourceAgentId int64 `json:"source_agent_id"` // 来源代理ID升级的代理
SourceAgentMobile string `json:"source_agent_mobile"` // 来源代理手机号
OrderNo string `json:"order_no"` // 订单号
FromLevel int64 `json:"from_level"` // 原等级
ToLevel int64 `json:"to_level"` // 目标等级
Amount float64 `json:"amount"` // 返佣金额
CreateTime string `json:"create_time"` // 创建时间
}
// 升级记录
GetUpgradeListReq {
@@ -365,3 +548,19 @@ type (
}
)
// ============================================
// 短链重定向接口公开访问不需要认证和api前缀
// ============================================
@server (
group: agent
)
service main {
// 短链重定向
@handler ShortLinkRedirect
get /s/:shortCode returns (ShortLinkRedirectResp)
}
type (
ShortLinkRedirectResp {}
)

View File

@@ -46,8 +46,8 @@ service main {
type (
PaymentReq {
Id string `json:"id"`
PayMethod string `json:"pay_method"`
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"`
PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式)
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"`
}
PaymentResp {
PrepayData interface{} `json:"prepay_data"`

View File

@@ -18,7 +18,6 @@ import "./front/authorization.api"
import "./admin/auth.api"
import "./admin/menu.api"
import "./admin/role.api"
import "./admin/promotion.api"
import "./admin/order.api"
import "./admin/admin_user.api"
import "./admin/platform_user.api"

View File

@@ -41,13 +41,13 @@ Alipay:
Wxpay:
AppID: "wx442ee1ac1ee75917"
MchID: "1682635136"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
MchID: "1687993434"
MchCertificateSerialNumber: "241E4BCF5B69AAAC48451DB2C7ED794EF8B3A3D3"
MchApiv3Key: "aB3cD5eF7gH9iJ1kL2mN4oP6qR8sT0uV"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601"
MchPublicKeyID: "PUB_KEY_ID_0116879934342025120200181745004208"
MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781"
MchPlatformRAS: "5630D013C88EA348BF66E642B6C39AA0180D4B15"
NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/wechat/refund_callback"
Applepay:
@@ -63,8 +63,8 @@ Ali:
SystemConfig:
ThreeVerify: false
WechatH5:
AppID: "wxa581992dc74d860e"
AppSecret: "4de1fbf521712247542d49907fcd5dbf"
AppID: "wx442ee1ac1ee75917"
AppSecret: "c80474909db42f63913b7a307b3bee17"
WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
@@ -76,8 +76,6 @@ AdminConfig:
AccessSecret: "jK8nP3qR7tV2xZ5aB9cD1eF6gH4iJ0kL8mN5oP6qR7sT"
AccessExpire: 604800
RefreshAfter: 302400
AdminPromotion:
URLDomain: "https://tianyuandb.com/p"
TaxConfig:
TaxRate: 0.06
TaxExemptionAmount: 0.00
@@ -87,4 +85,7 @@ Tianyuanapi:
BaseURL: "https://api.tianyuanapi.com"
Timeout: 60
Authorization:
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
FileBaseURL: "https://www.onecha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
Promotion:
PromotionDomain: "http://localhost:8888" # 推广域名(用于生成短链)
OfficialDomain: "http://localhost:5678" # 正式站点域名(短链重定向的目标域名)

View File

@@ -29,19 +29,19 @@ Alipay:
AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPath: "etc/merchant/alipayRootCert.crt"
IsProduction: true
NotifyUrl: "https://www.quannengcha.com/api/v1/pay/alipay/callback"
ReturnURL: "https://www.quannengcha.com/payment/result"
NotifyUrl: "https://www.onecha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.onecha.cn/payment/result"
Wxpay:
AppID: "wx442ee1ac1ee75917"
MchID: "1682635136"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
MchID: "1687993434"
MchCertificateSerialNumber: "241E4BCF5B69AAAC48451DB2C7ED794EF8B3A3D3"
MchApiv3Key: "aB3cD5eF7gH9iJ1kL2mN4oP6qR8sT0uV"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601"
MchPublicKeyID: "PUB_KEY_ID_0116879934342025120200181745004208"
MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781"
NotifyUrl: "https://www.quannengcha.com/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.quannengcha.com/api/v1/wechat/refund_callback"
MchPlatformRAS: "5630D013C88EA348BF66E642B6C39AA0180D4B15"
NotifyUrl: "https://www.onecha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.onecha.cn/api/v1/wechat/refund_callback"
Applepay:
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"
@@ -64,8 +64,6 @@ AdminConfig:
AccessSecret: "jK8nP3qR7tV2xZ5aB9cD1eF6gH4iJ0kL8mN5oP6qR7sT"
AccessExpire: 604800
RefreshAfter: 302400
AdminPromotion:
URLDomain: "https://tianyuandb.com/p"
TaxConfig:
TaxRate: 0.06
TaxExemptionAmount: 0.00
@@ -75,4 +73,7 @@ Tianyuanapi:
BaseURL: "https://api.tianyuanapi.com"
Timeout: 60
Authorization:
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
FileBaseURL: "https://www.onecha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
Promotion:
PromotionDomain: "https://p.onecha.cn" # 推广域名(用于生成短链)
OfficialDomain: "https://www.onecha.cn" # 正式站点域名(短链重定向的目标域名)

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCP6fWm1vXXybH
m3Ne6PjacGrN2+iMrzWZlzdHCZ31udDPqSUYaZ+78b441KZK/CJFQWeSJ/1h//A+
BGsQDKvE/fj2QzN1KkOuQ8WJXNGpixE5uu5bv/QTN/ukurGdA1aO2aFCANumlOmB
HkB/B2so57ii8iQQjwK2xM4r3oOU/IfcFGKL+9/QjLGFFp9PJXCDBCgrxxlZGaj1
3wowlfVOzlaX94gemQsCYVkuAFIYMAnFHs9cKNZQIU80somW/yy2Gy38N6n7NnbD
nvFSaq4GoDROqRgKbRZ5e706d/p7A3aS/2oRqq1jomUIugK8g++LmoHFTgfhfQkI
v1aG/nPzAgMBAAECggEAD2RN31J2J42xm/V0YdviBCUOQXugZK1peN8jkSxw6Myt
gBbuCo4sCw9vvD8VYjGyYXx6QXmLuV03YyKkfSQT5EsflBvlEu6jaEaUe3rwXhfX
6JQoWPrP00oHVZk5g7CFBlK2VW2N+hgonIOSJr6mvhoGZlr7gphiZasYjx9Vm9N3
Pbnfru5ttzplYNniwH3DF6ph8VmdbD1nnbWSKLXvHCsXQT2wBcnsIagIH3vyq6K1
pc5abWsQJrixOPebpI8jD5w0HxHAqVLx58H/OC2zW/roAw1WS2AkueJ1j7dQ7Z0C
mc9Xexz5gcAP0nMAQv+LP7iYqsa/niFhfcTFWfdxkQKBgQD5JkKNmInU2/IVYCwO
c483MCSv1+MnbRXlb7vut8T0IupHTU6hCge6C3q3HsjbKSBn8bRChtPUzvw9JFxK
QWKiQqQDPLDJ08AIKhfQD2JiLtoikkZN0bF6OTL+Soney1yGx51mlfHM194+PcCJ
jF7iWdMVbcBwHbgydNxxIS5cKQKBgQDHlvQ4lw6gvLILpGK494/vNYSJP/Jmd66V
3oSGYi84YRKTSwH4NlbBVVieb3Dv+pPugbsXEuFHBif7WsivbYgNTE9++8Yvt0gh
duB1G4yh7m/ylQeSuipgQU9tozrU/15cWwmcCRV50wWXBGoVEM0kf7mzEKSxmjYk
Qzko/zxSuwKBgQCY6Bc+SViFz3qSDdTcBaXma+CIHsmlH7ipd9px1kzEvEzl95cD
FGHLl1H34qfIgUQHJvrHPXHyEBoT+CW/2MMM7DM2XV/ubctT92ln4pkxwqlTQExv
Y/s1FLesAtj8Z/hgK0/5bprYab9WmZV5lTGCXzhB1XqeFE9AgCHuODv4iQKBgQC8
g2uwd5ytXQydymokYk9klJvWNrvw5GHV1BJAC0Smb6lnzZTSqCBRAxdsrb1yLK7E
u2vGY2K7/qiM1DZw23eBd+4t9gg+0VIjqXBfq+GsoNTDvtckUwnrWER5PY831ut9
N89fvYS3SAUjmlvIAdKBAtKWusWTqiAxJ/05J7oGOQKBgB5PSr5i0LlupIbKui9t
XtXnRqGPxxrZZUpTkyrGOAnlCz/zq2QiwFpBWo/NMHOp0KmxzJpQ8yEY2LWlRZ61
Oc9m0J/HtPw3Ohi1treBosEVG/0NOI9Tq1Obny23N51MVibdW6zEIyGUp/DbFS8h
5DljdOYX9IYIHHn3Ig4GeTGe
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAh8YNl16EkVKW
IHDiPyx5Zz93osD4n2E7oJXPEOSGpumhsAMjXsRd32JulYDtD/B/phA/mxQiEf84
Um4VKC16pNAEtpEyrO7ZZRhrPk2AMck6Jm81nXLoppttuS0B3VkOE/1UuvAbIz1y
VliRLDTiIbuSM8p1rMfpsJGo4CoLaerJgzXL6MHZA0+Fhn4PQLkLIt57jC0Jh2Dg
ayH7Ru/wgwgq6upXb7rXj0ZzMer5kkA446mLis9P6Nz1GiTUMyUy+tnORK1EpWLg
tUifsfXsKBLlUcOrNyG1+TyeMOuY2p592Au8J2eZbwyXKRDvK2oJzsDQW04pGyJc
oW1vObmLAgMBAAECggEAPc0XgRNezrULSo99TNK0hv/iepeu09/tSUOh8wbcJHD9
u94RE9B+vhdPtGmfKfmc3IzE2HYCP3GBeGXVWks8VgsDjw+/igHC5duyu/IS1Jym
mFjwB8jTsuSQLedsEBYqWP+HqSQcoMluFv6qjWcgTpo/aI3hZmahAV2hVBEozeKR
Va+EssjI46k894Kr6s9rb9nk8hCORuLuqDXfWJdxT+UixMeYftrgmHXk6CCUb2Ct
EjMuxi66KyfVu9w5wS0DuE583mDIgTKmD+deJWxcVyJJMJDCULY4fotWhQb2ro9L
qndaCgBC+sOAB/PrO31E40hZhjgdToSq5SvUWgjUCQKBgQD6/zSzEGJYzjS3544l
PWF92WT3aFJxT3+Mgd/BrTWaY2LykbDoioM/Kp+5D1bB446k534xO6uwr3LuDCOE
jZGy/6+HQeDHSLfDZ+LgWQdEbakbniR57HXG293x3Mp5jTlZOXc8ryGURXaCP8Sy
xwIiZPUgpo4xA0Myt/CnjW9OhwKBgQDEXjkc4tyVTsgcVevxBjT4o3g2ihYouCdt
ClDr6iZ8Mi5A0vCcuy1A3uI5BZnax11AIwqapgjOdWgWEtyjQJ84bhesyI7km/Ya
AeaelsgSf+mAfFgTarWb+KpD5l0jxJAlX/1PAQU6vXuUPdA4PtBbKyUKHLY0kMXr
wE4vbPpZ3QKBgQDGvwpFt/WICFAqR0qBJmdqNZgDaDHP03lWEwjQ3YySYZxaIw8I
M5XVkLTE3uZ9xOiQn1WHOo6q62KAKFB/h3IVYOzmlz2bz3LjYgF+UEC26HZ9je2o
NZrVCghmmcQiF7ePdTd7b9mRBKfgXwor3fVMstB/OCNjoAe3w3rl0dKPRQKBgQC2
oIbvdZH/DqkPqV1o6QPk5muMFbrsHfEU+U4vSrKGOUlJIqWCrpY0ydWNgMcJcPcq
Ciz3jUmNciXeuaYX8qbiHYnJOTGkLeShZXktrz/d7Lamt35WeJz0tTztL1caR9pj
2DVG/8T0T3uacC6x0MGIuMSW9gMDOk3Ipy5P70OaxQKBgQDFzgdJxU9Ra6AzbyBL
hcaKb+moNEo+peKCImwY1n3FeRM6XZ59qQaGV1lYu5KPUj9T/SVaJm6DCYFKqM0r
T1prq2LeR69nB7Dpsr2TKp57L86DoCqbxOBnWxZ/6Em65hoRYe7CAtn3yFQGKm9T
+EdUfn1gf7AWjQAgo3bis3TaMQ==
-----END PRIVATE KEY-----

View File

@@ -1,9 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwSy7dS/ICZV38tI0HxM
SAIE7+Ug92qryuNlkNyaNDRjfsykHsrPCSsUUQEZblBNmZOLfLQxmAaWC+cQqWCv
zfy4rXGAHE1widWFkHGzQzaw6cB0VdDXatK9yAt1PgXdp5jzBRzOn9Z3u4t0s771
2zjuxCnLxMq84DovNgh2y0LBiuorWbtuTFTd8SXUGk2Jyuojq/02U3KTuyh+7SmW
ffJXKrzhrKwSpGh59e/fFxqX2xGlVoJ1kdohMZPo/7k+e5jP7qjrf93l7JVeUKYa
V27hNVowJ4oho21WVCJ1AYo41IbPJWI+6WxlaVeoR4zKix0Mb2timaWayyLoN53y
aQIDAQAB
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4el5skKyhhV+lFP/lx2x
MIQ14WFzoywBmy7Jd/UnKp5i8g85rwsFKvkMD9QqdQgoUPdnKKpvKiJeQqwUeXUC
ogVZxedg+wCj4FuOmctHTJVwWaqZ07uom78nvDJhgRCsgWR7UBRq8v9MymbPC4p7
IkGuq+lLkYPyJFMGpk33fAua4NkKxBseyLHbB9t3vSlUFh8x2JlIYxC531362qbS
+L2m0B2stMyQEdYxpYCtS3nsEG+ib2Du3GiT+5pAmXxZ6DGyr4jAlAWDnljwJZEf
xJPECXSlcAsHdI6ugkC+9DwPjLs1mrEQ/BevmTT2o0wPigCRNi9xZf178L1sptYy
DwIDAQAB
-----END PUBLIC KEY-----

View File

@@ -7,23 +7,23 @@ import (
type Config struct {
rest.RestConf
DataSource string
CacheRedis cache.CacheConf
JwtAuth JwtAuth // JWT 鉴权相关配置
VerifyCode VerifyCode
Encrypt Encrypt
Alipay AlipayConfig
Wxpay WxpayConfig
Applepay ApplepayConfig
Tianyuanapi TianyuanapiConfig
SystemConfig SystemConfig
WechatH5 WechatH5Config
Authorization AuthorizationConfig // 授权书配置
WechatMini WechatMiniConfig
Query QueryConfig
AdminConfig AdminConfig
AdminPromotion AdminPromotion
TaxConfig TaxConfig
DataSource string
CacheRedis cache.CacheConf
JwtAuth JwtAuth // JWT 鉴权相关配置
VerifyCode VerifyCode
Encrypt Encrypt
Alipay AlipayConfig
Wxpay WxpayConfig
Applepay ApplepayConfig
Tianyuanapi TianyuanapiConfig
SystemConfig SystemConfig
WechatH5 WechatH5Config
Authorization AuthorizationConfig // 授权书配置
WechatMini WechatMiniConfig
Query QueryConfig
AdminConfig AdminConfig
TaxConfig TaxConfig
Promotion PromotionConfig // 推广链接配置
}
// JwtAuth 用于 JWT 鉴权配置
@@ -83,8 +83,8 @@ type WechatH5Config struct {
AppSecret string
}
type WechatMiniConfig struct {
AppID string
AppSecret string
AppID string
AppSecret string
}
type QueryConfig struct {
ShareLinkExpire int64
@@ -95,9 +95,6 @@ type AdminConfig struct {
RefreshAfter int64
}
type AdminPromotion struct {
URLDomain string
}
type TaxConfig struct {
TaxRate float64
TaxExemptionAmount float64
@@ -112,3 +109,9 @@ type TianyuanapiConfig struct {
type AuthorizationConfig struct {
FileBaseURL string // 授权书文件访问基础URL
}
// PromotionConfig 推广链接配置
type PromotionConfig struct {
PromotionDomain string // 推广域名(用于生成短链)
OfficialDomain string // 正式站点域名(短链重定向的目标域名)
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminAuditAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminAuditAgentReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminAuditAgentLogic(r.Context(), svcCtx)
resp, err := l.AdminAuditAgent(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminAuditRealNameHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminAuditRealNameReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminAuditRealNameLogic(r.Context(), svcCtx)
resp, err := l.AdminAuditRealName(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminAuditWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminAuditWithdrawalReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminAuditWithdrawalLogic(r.Context(), svcCtx)
resp, err := l.AdminAuditWithdrawal(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,19 +3,15 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/common/result"
)
func AdminGetAgentConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := admin_agent.NewAdminGetAgentConfigLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentConfig()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminGetAgentOrderListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminGetAgentOrderListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminGetAgentOrderListLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentOrderList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminGetAgentProductConfigListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminGetAgentProductConfigListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminGetAgentProductConfigListLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentProductConfigList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminGetAgentRealNameListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminGetAgentRealNameListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminGetAgentRealNameListLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentRealNameList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminGetAgentRebateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminGetAgentRebateListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminGetAgentRebateListLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentRebateList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminGetAgentUpgradeListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminGetAgentUpgradeListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminGetAgentUpgradeListLogic(r.Context(), svcCtx)
resp, err := l.AdminGetAgentUpgradeList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminUpdateAgentConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminUpdateAgentConfigReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminUpdateAgentConfigLogic(r.Context(), svcCtx)
resp, err := l.AdminUpdateAgentConfig(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/admin_agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AdminUpdateAgentProductConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminUpdateAgentProductConfigReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminUpdateAgentProductConfigLogic(r.Context(), svcCtx)
resp, err := l.AdminUpdateAgentProductConfig(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,30 +0,0 @@
package admin_promotion
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeletePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeletePromotionLinkReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewDeletePromotionLinkLogic(r.Context(), svcCtx)
err := l.DeletePromotionLink(&req)
result.HttpResult(r, w, nil, err)
}
}

View File

@@ -1,30 +0,0 @@
package admin_promotion
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPromotionLinkDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetPromotionLinkDetailReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewGetPromotionLinkDetailLogic(r.Context(), svcCtx)
resp, err := l.GetPromotionLinkDetail(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,30 +0,0 @@
package admin_promotion
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPromotionStatsHistoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetPromotionStatsHistoryReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewGetPromotionStatsHistoryLogic(r.Context(), svcCtx)
resp, err := l.GetPromotionStatsHistory(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,30 +0,0 @@
package admin_promotion
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpdatePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdatePromotionLinkReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewUpdatePromotionLinkLogic(r.Context(), svcCtx)
err := l.UpdatePromotionLink(&req)
result.HttpResult(r, w, nil, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ApplyUpgradeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ApplyUpgradeReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewApplyUpgradeLogic(r.Context(), svcCtx)
resp, err := l.ApplyUpgrade(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ApplyWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ApplyWithdrawalReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewApplyWithdrawalLogic(r.Context(), svcCtx)
resp, err := l.ApplyWithdrawal(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,20 +1,19 @@
package admin_promotion
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RecordLinkClickHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func DeleteInviteCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RecordLinkClickReq
var req types.DeleteInviteCodeReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
@@ -23,8 +22,8 @@ func RecordLinkClickHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewRecordLinkClickLogic(r.Context(), svcCtx)
resp, err := l.RecordLinkClick(&req)
l := agent.NewDeleteInviteCodeLogic(r.Context(), svcCtx)
resp, err := l.DeleteInviteCode(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetCommissionListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetCommissionListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetCommissionListLogic(r.Context(), svcCtx)
resp, err := l.GetCommissionList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,17 @@
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/common/result"
)
func GetConversionRateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := agent.NewGetConversionRateLogic(r.Context(), svcCtx)
resp, err := l.GetConversionRate()
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -5,13 +5,26 @@ import (
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetInviteLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetInviteLinkReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetInviteLinkLogic(r.Context(), svcCtx)
resp, err := l.GetInviteLink()
resp, err := l.GetInviteLink(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,18 @@
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/common/result"
)
func GetLevelPrivilegeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := agent.NewGetLevelPrivilegeLogic(r.Context(), svcCtx)
resp, err := l.GetLevelPrivilege()
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetRebateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetRebateListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetRebateListLogic(r.Context(), svcCtx)
resp, err := l.GetRebateList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,19 +3,15 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/common/result"
)
func GetRevenueInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := agent.NewGetRevenueInfoLogic(r.Context(), svcCtx)
resp, err := l.GetRevenueInfo()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,20 +1,19 @@
package admin_promotion
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPromotionStatsTotalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func GetSubordinateContributionDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetPromotionStatsTotalReq
var req types.GetSubordinateContributionDetailReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
@@ -23,8 +22,8 @@ func GetPromotionStatsTotalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewGetPromotionStatsTotalLogic(r.Context(), svcCtx)
resp, err := l.GetPromotionStatsTotal(&req)
l := agent.NewGetSubordinateContributionDetailLogic(r.Context(), svcCtx)
resp, err := l.GetSubordinateContributionDetail(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetSubordinateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetSubordinateListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetSubordinateListLogic(r.Context(), svcCtx)
resp, err := l.GetSubordinateList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,20 +1,19 @@
package admin_promotion
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreatePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func GetTeamListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreatePromotionLinkReq
var req types.GetTeamListReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
@@ -23,8 +22,8 @@ func CreatePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewCreatePromotionLinkLogic(r.Context(), svcCtx)
resp, err := l.CreatePromotionLink(&req)
l := agent.NewGetTeamListLogic(r.Context(), svcCtx)
resp, err := l.GetTeamList(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,19 +3,15 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/common/result"
)
func GetTeamStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := agent.NewGetTeamStatisticsLogic(r.Context(), svcCtx)
resp, err := l.GetTeamStatistics()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetUpgradeListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetUpgradeListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetUpgradeListLogic(r.Context(), svcCtx)
resp, err := l.GetUpgradeList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -1,20 +1,19 @@
package admin_promotion
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/admin_promotion"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPromotionLinkListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func GetUpgradeRebateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetPromotionLinkListReq
var req types.GetUpgradeRebateListReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
@@ -23,8 +22,8 @@ func GetPromotionLinkListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_promotion.NewGetPromotionLinkListLogic(r.Context(), svcCtx)
resp, err := l.GetPromotionLinkList(&req)
l := agent.NewGetUpgradeRebateListLogic(r.Context(), svcCtx)
resp, err := l.GetUpgradeRebateList(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetWithdrawalListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetWithdrawalListReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetWithdrawalListLogic(r.Context(), svcCtx)
resp, err := l.GetWithdrawalList(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,21 @@
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PromotionRedirectHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := agent.NewPromotionRedirectLogic(r.Context(), svcCtx)
err := l.PromotionRedirect(r, w)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
}
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RealNameAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RealNameAuthReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewRealNameAuthLogic(r.Context(), svcCtx)
resp, err := l.RealNameAuth(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,29 @@
package agent
import (
"net/http"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ShortLinkRedirectHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 从URL.Path解析shortCode
// 路径格式:/s/{shortCode}
path := r.URL.Path
var shortCode string
if len(path) >= 3 && path[:3] == "/s/" {
shortCode = path[3:]
}
l := agent.NewShortLinkRedirectLogic(r.Context(), svcCtx)
err := l.ShortLinkRedirect(shortCode, r, w)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
}
}
}

View File

@@ -3,26 +3,28 @@ package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpgradeSubordinateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpgradeSubordinateReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewUpgradeSubordinateLogic(r.Context(), svcCtx)
resp, err := l.UpgradeSubordinate(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -13,7 +13,6 @@ import (
admin_order "ycc-server/app/main/api/internal/handler/admin_order"
admin_platform_user "ycc-server/app/main/api/internal/handler/admin_platform_user"
admin_product "ycc-server/app/main/api/internal/handler/admin_product"
admin_promotion "ycc-server/app/main/api/internal/handler/admin_promotion"
admin_query "ycc-server/app/main/api/internal/handler/admin_query"
admin_role "ycc-server/app/main/api/internal/handler/admin_role"
admin_role_api "ycc-server/app/main/api/internal/handler/admin_role_api"
@@ -425,81 +424,6 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/admin/product"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
[]rest.Route{
{
// 创建推广链接
Method: http.MethodPost,
Path: "/create",
Handler: admin_promotion.CreatePromotionLinkHandler(serverCtx),
},
{
// 删除推广链接
Method: http.MethodDelete,
Path: "/delete/:id",
Handler: admin_promotion.DeletePromotionLinkHandler(serverCtx),
},
{
// 获取推广链接详情
Method: http.MethodGet,
Path: "/detail/:id",
Handler: admin_promotion.GetPromotionLinkDetailHandler(serverCtx),
},
{
// 获取推广链接列表
Method: http.MethodGet,
Path: "/list",
Handler: admin_promotion.GetPromotionLinkListHandler(serverCtx),
},
{
// 更新推广链接
Method: http.MethodPut,
Path: "/update/:id",
Handler: admin_promotion.UpdatePromotionLinkHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/v1/admin/promotion/link"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
[]rest.Route{
{
// 记录链接点击
Method: http.MethodGet,
Path: "/record/:path",
Handler: admin_promotion.RecordLinkClickHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/v1/admin/promotion/link"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
[]rest.Route{
{
// 获取推广历史记录
Method: http.MethodGet,
Path: "/history",
Handler: admin_promotion.GetPromotionStatsHistoryHandler(serverCtx),
},
{
// 获取推广总统计
Method: http.MethodGet,
Path: "/total",
Handler: admin_promotion.GetPromotionStatsTotalHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/v1/admin/promotion/stats"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
@@ -693,6 +617,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/commission/list",
Handler: agent.GetCommissionListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/conversion/rate",
Handler: agent.GetConversionRateHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/generating_link",
@@ -703,6 +632,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/info",
Handler: agent.GetAgentInfoHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/invite_code/delete",
Handler: agent.DeleteInviteCodeHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/invite_code/generate",
@@ -718,6 +652,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/invite_link",
Handler: agent.GetInviteLinkHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/level/privilege",
Handler: agent.GetLevelPrivilegeHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/product_config",
@@ -733,16 +672,31 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/rebate/list",
Handler: agent.GetRebateListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/rebate/upgrade/list",
Handler: agent.GetUpgradeRebateListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/revenue",
Handler: agent.GetRevenueInfoHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/subordinate/contribution/detail",
Handler: agent.GetSubordinateContributionDetailHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/subordinate/list",
Handler: agent.GetSubordinateListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/team/list",
Handler: agent.GetTeamListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/team/statistics",
@@ -779,6 +733,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/agent"),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/s/:shortCode",
Handler: agent.ShortLinkRedirectHandler(serverCtx),
},
},
)
server.AddRoutes(
[]rest.Route{
{

View File

@@ -34,6 +34,14 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
value, _ := strconv.ParseFloat(config.ConfigValue, 64)
return value
}
getConfigInt := func(key string) int64 {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if err != nil {
return 0
}
value, _ := strconv.ParseInt(config.ConfigValue, 10, 64)
return value
}
// 获取等级加成配置
level1Bonus := getConfigFloat("level_1_bonus")
@@ -48,11 +56,20 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
upgradeToGoldRebate := getConfigFloat("upgrade_to_gold_rebate")
upgradeToDiamondRebate := getConfigFloat("upgrade_to_diamond_rebate")
// 获取直接上级返佣配置
directParentAmountDiamond := getConfigFloat("direct_parent_amount_diamond")
directParentAmountGold := getConfigFloat("direct_parent_amount_gold")
directParentAmountNormal := getConfigFloat("direct_parent_amount_normal")
// 获取黄金代理最大返佣金额
maxGoldRebateAmount := getConfigFloat("max_gold_rebate_amount")
// 获取佣金冻结配置
commissionFreezeRatio := getConfigFloat("commission_freeze_ratio")
commissionFreezeThreshold := getConfigFloat("commission_freeze_threshold")
commissionFreezeDays := getConfigInt("commission_freeze_days")
return &types.AdminGetAgentConfigResp{
BasePrice: getConfigFloat("base_price"),
SystemMaxPrice: getConfigFloat("system_max_price"),
PriceThreshold: getConfigFloat("price_threshold"),
PriceFeeRate: getConfigFloat("price_fee_rate"),
LevelBonus: types.LevelBonusConfig{
Normal: int64(level1Bonus),
Gold: int64(level2Bonus),
@@ -67,6 +84,17 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
NormalToGoldRebate: upgradeToGoldRebate,
ToDiamondRebate: upgradeToDiamondRebate,
},
DirectParentRebate: types.DirectParentRebateConfig{
Diamond: directParentAmountDiamond,
Gold: directParentAmountGold,
Normal: directParentAmountNormal,
},
MaxGoldRebateAmount: maxGoldRebateAmount,
CommissionFreeze: types.CommissionFreezeConfig{
Ratio: commissionFreezeRatio,
Threshold: commissionFreezeThreshold,
Days: commissionFreezeDays,
},
TaxRate: getConfigFloat("tax_rate"),
TaxExemptionAmount: getConfigFloat("tax_exemption_amount"),
}, nil

View File

@@ -31,10 +31,16 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo)
// 如果提供了产品ID直接过滤
if req.ProductId != nil {
builder = builder.Where("product_id = ?", *req.ProductId)
}
// 如果提供了产品名称,通过关联查询 product 表过滤
if req.ProductName != nil && *req.ProductName != "" {
builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ? AND del_state = ?)", "%"+*req.ProductName+"%", globalkey.DelStateNo)
}
// 分页查询
page := req.Page
if page <= 0 {
@@ -50,9 +56,19 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置列表失败, %v", err)
}
// 组装响应
// 组装响应(通过 product_id 查询产品名称)
items := make([]types.AgentProductConfigItem, 0, len(configs))
for _, config := range configs {
// 通过 product_id 查询产品信息获取产品名称
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, config.ProductId)
productName := ""
if err == nil {
productName = product.ProductName
} else {
// 如果产品不存在,记录日志但不影响主流程
l.Infof("查询产品信息失败, productId: %d, err: %v", config.ProductId, err)
}
priceThreshold := 0.0
if config.PriceThreshold.Valid {
priceThreshold = config.PriceThreshold.Float64
@@ -66,7 +82,7 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
items = append(items, types.AgentProductConfigItem{
Id: config.Id,
ProductId: config.ProductId,
ProductName: config.ProductName,
ProductName: productName,
BasePrice: config.BasePrice,
PriceRangeMin: config.BasePrice, // 最低定价等于基础底价
PriceRangeMax: config.SystemMaxPrice,

View File

@@ -41,19 +41,76 @@ func (l *AdminUpdateAgentConfigLogic) AdminUpdateAgentConfig(req *types.AdminUpd
return l.svcCtx.AgentConfigModel.UpdateWithVersion(l.ctx, nil, config)
}
// 更新各个配置
if err := updateConfig("base_price", req.BasePrice); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新基础底价失败, %v", err)
// 更新等级加成配置
if req.LevelBonus != nil {
if err := updateConfig("level_1_bonus", func() *float64 { v := float64(req.LevelBonus.Normal); return &v }()); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新普通代理等级加成失败, %v", err)
}
if err := updateConfig("level_2_bonus", func() *float64 { v := float64(req.LevelBonus.Gold); return &v }()); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新黄金代理等级加成失败, %v", err)
}
if err := updateConfig("level_3_bonus", func() *float64 { v := float64(req.LevelBonus.Diamond); return &v }()); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钻石代理等级加成失败, %v", err)
}
}
if err := updateConfig("system_max_price", req.SystemMaxPrice); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新系统价格上限失败, %v", err)
// 更新升级费用配置
if req.UpgradeFee != nil {
if err := updateConfig("upgrade_to_gold_fee", &req.UpgradeFee.NormalToGold); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新普通→黄金升级费用失败, %v", err)
}
if err := updateConfig("upgrade_to_diamond_fee", &req.UpgradeFee.NormalToDiamond); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新普通→钻石升级费用失败, %v", err)
}
// gold_to_diamond 是计算得出的,不需要更新
}
if err := updateConfig("price_threshold", req.PriceThreshold); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提价标准阈值失败, %v", err)
// 更新升级返佣配置
if req.UpgradeRebate != nil {
if err := updateConfig("upgrade_to_gold_rebate", &req.UpgradeRebate.NormalToGoldRebate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新普通→黄金返佣失败, %v", err)
}
if err := updateConfig("upgrade_to_diamond_rebate", &req.UpgradeRebate.ToDiamondRebate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新升级为钻石返佣失败, %v", err)
}
}
if err := updateConfig("price_fee_rate", req.PriceFeeRate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提价手续费比例失败, %v", err)
// 更新直接上级返佣配置
if req.DirectParentRebate != nil {
if err := updateConfig("direct_parent_amount_diamond", &req.DirectParentRebate.Diamond); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新直接上级是钻石的返佣金额失败, %v", err)
}
if err := updateConfig("direct_parent_amount_gold", &req.DirectParentRebate.Gold); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新直接上级是黄金的返佣金额失败, %v", err)
}
if err := updateConfig("direct_parent_amount_normal", &req.DirectParentRebate.Normal); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新直接上级是普通的返佣金额失败, %v", err)
}
}
// 更新黄金代理最大返佣金额
if err := updateConfig("max_gold_rebate_amount", req.MaxGoldRebateAmount); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新黄金代理最大返佣金额失败, %v", err)
}
// 更新佣金冻结配置
if req.CommissionFreeze != nil {
if err := updateConfig("commission_freeze_ratio", &req.CommissionFreeze.Ratio); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结比例失败, %v", err)
}
if err := updateConfig("commission_freeze_threshold", &req.CommissionFreeze.Threshold); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结阈值失败, %v", err)
}
// 更新解冻天数(整数类型)
if req.CommissionFreeze.Days > 0 {
daysFloat := float64(req.CommissionFreeze.Days)
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
}
}
}
// 更新税费配置
if err := updateConfig("tax_rate", req.TaxRate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新税率失败, %v", err)
}

View File

@@ -37,8 +37,20 @@ func (l *AdminUpdateAgentProductConfigLogic) AdminUpdateAgentProductConfig(req *
// 更新配置字段
config.BasePrice = req.BasePrice
config.SystemMaxPrice = req.PriceRangeMax
config.PriceThreshold = sql.NullFloat64{Float64: req.PriceThreshold, Valid: true}
config.PriceFeeRate = sql.NullFloat64{Float64: req.PriceFeeRate, Valid: true}
// 价格阈值(可选)
if req.PriceThreshold != nil {
config.PriceThreshold = sql.NullFloat64{Float64: *req.PriceThreshold, Valid: true}
} else {
config.PriceThreshold = sql.NullFloat64{Valid: false}
}
// 提价手续费比例(可选)
if req.PriceFeeRate != nil {
config.PriceFeeRate = sql.NullFloat64{Float64: *req.PriceFeeRate, Valid: true}
} else {
config.PriceFeeRate = sql.NullFloat64{Valid: false}
}
// 更新配置
if err := l.svcCtx.AgentProductConfigModel.UpdateWithVersion(l.ctx, nil, config); err != nil {

View File

@@ -2,6 +2,7 @@ package admin_auth
import (
"context"
"os"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
@@ -30,9 +31,11 @@ func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminL
}
func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.AdminLoginResp, err error) {
// 1. 验证验证码
if !req.Captcha {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha)
// 1. 验证验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
if !req.Captcha {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha)
}
}
// 2. 验证用户名和密码

View File

@@ -72,20 +72,6 @@ func (l *AdminCreateOrderLogic) AdminCreateOrder(req *types.AdminCreateOrderReq)
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 获取订单ID失败 err: %v", err)
}
// 如果是推广订单,创建推广订单记录
if req.IsPromotion == 1 {
promotionOrder := &model.AdminPromotionOrder{
OrderId: orderId,
Version: 1,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
_, err = l.svcCtx.AdminPromotionOrderModel.Insert(ctx, session, promotionOrder)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 创建推广订单失败 err: %v", err)
}
}
return nil
})

View File

@@ -41,15 +41,6 @@ func (l *AdminDeleteOrderLogic) AdminDeleteOrder(req *types.AdminDeleteOrderReq)
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 删除订单失败 err: %v", err)
}
// 删除关联的推广订单记录
promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(ctx, order.Id)
if err == nil && promotionOrder != nil {
err = l.svcCtx.AdminPromotionOrderModel.DeleteSoft(ctx, session, promotionOrder)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 删除推广订单失败 err: %v", err)
}
}
return nil
})

View File

@@ -40,13 +40,6 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询产品失败 err: %v", err)
}
// 判断是否为推广订单
var isPromotion int64
promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(l.ctx, order.Id)
if err == nil && promotionOrder != nil {
isPromotion = 1
}
// 判断是否为代理订单并获取代理处理状态
var isAgentOrder bool
var agentProcessStatus string
@@ -118,7 +111,6 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD
Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
IsPromotion: isPromotion,
QueryState: queryState,
IsAgentOrder: isAgentOrder,
AgentProcessStatus: agentProcessStatus,

View File

@@ -54,9 +54,6 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
if req.Status != "" {
builder = builder.Where("status = ?", req.Status)
}
if req.IsPromotion != -1 {
builder = builder.Where("id IN (SELECT order_id FROM admin_promotion_order WHERE del_state = 0)")
}
// 时间范围查询
if req.CreateTimeStart != "" {
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
@@ -274,11 +271,6 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
if order.RefundTime.Valid {
item.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05")
}
// 判断是否为推广订单
promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(l.ctx, order.Id)
if err == nil && promotionOrder != nil {
item.IsPromotion = 1
}
// 设置代理订单相关字段
if agentOrderMap[order.Id] {

View File

@@ -7,7 +7,6 @@ import (
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/xerr"
"github.com/pkg/errors"
@@ -76,30 +75,6 @@ func (l *AdminUpdateOrderLogic) AdminUpdateOrder(req *types.AdminUpdateOrderReq)
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 更新订单失败 err: %v", err)
}
// 处理推广订单状态
if req.IsPromotion != nil {
promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(ctx, order.Id)
if err == nil && promotionOrder != nil {
// 如果存在推广订单记录但不需要推广,则删除
if *req.IsPromotion == 0 {
err = l.svcCtx.AdminPromotionOrderModel.DeleteSoft(ctx, session, promotionOrder)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 删除推广订单失败 err: %v", err)
}
}
} else if *req.IsPromotion == 1 {
// 如果需要推广但不存在记录,则创建
newPromotionOrder := &model.AdminPromotionOrder{
OrderId: order.Id,
Version: 1,
}
_, err = l.svcCtx.AdminPromotionOrderModel.Insert(ctx, session, newPromotionOrder)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 创建推广订单失败 err: %v", err)
}
}
}
return nil
})

View File

@@ -2,14 +2,15 @@ package admin_product
import (
"context"
"database/sql"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/xerr"
"database/sql"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminCreateProductLogic struct {
@@ -37,14 +38,44 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
SellPrice: req.SellPrice,
}
// 2. 数据库操作
result, err := l.svcCtx.ProductModel.Insert(l.ctx, nil, data)
// 2. 数据库操作(使用事务确保产品表和代理产品配置表同步)
var productId int64
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 2.1 插入产品
result, err := l.svcCtx.ProductModel.Insert(ctx, session, data)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建产品失败, err: %v, req: %+v", err, req)
}
productId, err = result.LastInsertId()
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"获取产品ID失败, err: %v", err)
}
// 2.2 同步创建代理产品配置(使用默认值)
agentProductConfig := &model.AgentProductConfig{
ProductId: productId,
BasePrice: 0.00, // 默认基础底价
SystemMaxPrice: 9999.99, // 默认系统价格上限
PriceThreshold: sql.NullFloat64{Valid: false}, // 默认不设阈值
PriceFeeRate: sql.NullFloat64{Valid: false}, // 默认不收费
}
_, err = l.svcCtx.AgentProductConfigModel.Insert(ctx, session, agentProductConfig)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建代理产品配置失败, err: %v, productId: %d", err, productId)
}
return nil
})
if err != nil {
return nil, err
}
// 3. 返回结果
id, _ := result.LastInsertId()
return &types.AdminCreateProductResp{Id: id}, nil
return &types.AdminCreateProductResp{Id: productId}, nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminDeleteProductLogic struct {
@@ -32,11 +33,33 @@ func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProdu
"查找产品失败, err: %v, id: %d", err, req.Id)
}
// 2. 执行软删除
err = l.svcCtx.ProductModel.DeleteSoft(l.ctx, nil, record)
// 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 2.1 软删除产品
err = l.svcCtx.ProductModel.DeleteSoft(ctx, session, record)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除产品失败, err: %v, id: %d", err, req.Id)
}
// 2.2 同步软删除代理产品配置
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, req.Id)
if err != nil {
// 如果代理产品配置不存在,记录日志但不影响主流程
l.Infof("同步删除代理产品配置失败:代理产品配置不存在, productId: %d, err: %v", req.Id, err)
} else {
err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"同步删除代理产品配置失败, err: %v, productId: %d", err, req.Id)
}
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除产品失败, err: %v, id: %d", err, req.Id)
return nil, err
}
// 3. 返回结果

View File

@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminUpdateProductLogic struct {
@@ -53,11 +54,20 @@ func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProdu
record.SellPrice = *req.SellPrice
}
// 3. 执行更新操作
_, err = l.svcCtx.ProductModel.Update(l.ctx, nil, record)
// 3. 执行更新操作(使用事务确保产品表和代理产品配置表同步)
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 3.1 更新产品
_, err = l.svcCtx.ProductModel.Update(ctx, session, record)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"更新产品失败, err: %v, req: %+v", err, req)
}
return nil
})
if err != nil {
return nil, err
}
// 4. 返回结果

View File

@@ -1,136 +0,0 @@
package admin_promotion
import (
"context"
"crypto/rand"
"fmt"
"math/big"
"time"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type CreatePromotionLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreatePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreatePromotionLinkLogic {
return &CreatePromotionLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 生成6位随机字符串大小写字母和数字
func generateRandomString() (string, error) {
const (
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
length = 6
)
result := make([]byte, length)
for i := 0; i < length; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
if err != nil {
return "", err
}
result[i] = chars[num.Int64()]
}
return string(result), nil
}
func (l *CreatePromotionLinkLogic) CreatePromotionLink(req *types.CreatePromotionLinkReq) (resp *types.CreatePromotionLinkResp, err error) {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接, 获取用户信息失败, %+v", getUidErr)
}
// 生成唯一URL
var url string
maxRetries := 5 // 最大重试次数
for i := 0; i < maxRetries; i++ {
// 生成6位随机字符串
randomStr, err := generateRandomString()
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接, 生成随机字符串失败, %+v", err)
}
// 检查URL是否已存在
existLink, err := l.svcCtx.AdminPromotionLinkModel.FindOneByUrl(l.ctx, randomStr)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建推广链接, 检查URL是否存在失败, %+v", err)
}
if existLink != nil {
continue // URL已存在继续尝试
}
// URL可用
url = randomStr
break
}
if url == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接失败, 多次尝试生成唯一URL均失败")
}
url = fmt.Sprintf("%s/%s", l.svcCtx.Config.AdminPromotion.URLDomain, url)
// 创建推广链接
link := &model.AdminPromotionLink{
Name: req.Name,
Url: url,
AdminUserId: adminUserId,
}
var linkId int64
err = l.svcCtx.AdminPromotionLinkModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
result, err := l.svcCtx.AdminPromotionLinkModel.Insert(l.ctx, session, link)
if err != nil {
return fmt.Errorf("创建推广链接失败, %+v", err)
}
linkId, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("获取推广链接ID失败, %+v", err)
}
// 创建总统计记录
totalStats := &model.AdminPromotionLinkStatsTotal{
LinkId: linkId,
}
_, err = l.svcCtx.AdminPromotionLinkStatsTotalModel.Insert(l.ctx, session, totalStats)
if err != nil {
return fmt.Errorf("创建推广链接总统计记录失败, %+v", err)
}
// 创建统计历史记录
historyStats := &model.AdminPromotionLinkStatsHistory{
LinkId: linkId,
StatsDate: time.Now().Truncate(24 * time.Hour),
}
_, err = l.svcCtx.AdminPromotionLinkStatsHistoryModel.Insert(l.ctx, session, historyStats)
if err != nil {
return fmt.Errorf("创建推广链接统计历史记录失败, %+v", err)
}
return nil
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建推广链接失败, %+v", err)
}
return &types.CreatePromotionLinkResp{
Id: linkId,
Url: url,
}, nil
}

View File

@@ -1,91 +0,0 @@
package admin_promotion
import (
"context"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type DeletePromotionLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeletePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeletePromotionLinkLogic {
return &DeletePromotionLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeletePromotionLinkLogic) DeletePromotionLink(req *types.DeletePromotionLinkReq) error {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "删除推广链接, 获取用户信息失败, %+v", getUidErr)
}
// 获取链接信息
link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id)
if err != nil {
return errors.Wrapf(err, "删除推广链接, 获取链接信息失败, %+v", err)
}
// 验证用户权限
if link.AdminUserId != adminUserId {
return errors.Wrapf(xerr.NewErrMsg("无权限删除此链接"), "删除推广链接, 无权限删除此链接, %+v", link)
}
// 在事务中执行所有删除操作
err = l.svcCtx.AdminPromotionLinkModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 软删除链接
err = l.svcCtx.AdminPromotionLinkModel.DeleteSoft(l.ctx, session, link)
if err != nil {
return errors.Wrapf(err, "删除推广链接, 软删除链接失败, %+v", err)
}
// 软删除总统计记录
totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, link.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "删除推广链接, 获取总统计记录失败, %+v", err)
}
if totalStats != nil {
err = l.svcCtx.AdminPromotionLinkStatsTotalModel.DeleteSoft(l.ctx, session, totalStats)
if err != nil {
return errors.Wrapf(err, "删除推广链接, 软删除总统计记录失败, %+v", err)
}
}
// 软删除历史统计记录
builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder()
builder = builder.Where("link_id = ?", link.Id)
historyStats, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "")
if err != nil {
return errors.Wrapf(err, "删除推广链接, 获取历史统计记录失败, %+v", err)
}
for _, stat := range historyStats {
err = l.svcCtx.AdminPromotionLinkStatsHistoryModel.DeleteSoft(l.ctx, session, stat)
if err != nil {
return errors.Wrapf(err, "删除推广链接, 软删除历史统计记录失败, %+v", err)
}
}
return nil
})
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除推广链接失败, %+v", err)
}
return nil
}

View File

@@ -1,65 +0,0 @@
package admin_promotion
import (
"context"
"fmt"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetPromotionLinkDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetPromotionLinkDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionLinkDetailLogic {
return &GetPromotionLinkDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPromotionLinkDetailLogic) GetPromotionLinkDetail(req *types.GetPromotionLinkDetailReq) (resp *types.GetPromotionLinkDetailResp, err error) {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr)
}
// 获取链接信息
link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(err, "获取链接信息失败, %+v", err)
}
// 验证用户权限
if link.AdminUserId != adminUserId {
return nil, errors.Wrapf(xerr.NewErrMsg("无权限访问此链接"), "获取链接信息失败, 无权限访问此链接, %+v", link)
}
// 获取总统计
totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOne(l.ctx, link.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(err, "获取总统计失败, %+v", err)
}
return &types.GetPromotionLinkDetailResp{
Name: link.Name,
Url: link.Url,
ClickCount: totalStats.ClickCount,
PayCount: totalStats.PayCount,
PayAmount: fmt.Sprintf("%.2f", totalStats.PayAmount),
CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: link.UpdateTime.Format("2006-01-02 15:04:05"),
LastClickTime: totalStats.LastClickTime.Time.Format("2006-01-02 15:04:05"),
LastPayTime: totalStats.LastPayTime.Time.Format("2006-01-02 15:04:05"),
}, nil
}

View File

@@ -1,104 +0,0 @@
package admin_promotion
import (
"context"
"fmt"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetPromotionLinkListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetPromotionLinkListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionLinkListLogic {
return &GetPromotionLinkListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPromotionLinkListLogic) GetPromotionLinkList(req *types.GetPromotionLinkListReq) (resp *types.GetPromotionLinkListResp, err error) {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr)
}
// 构建查询条件
builder := l.svcCtx.AdminPromotionLinkModel.SelectBuilder()
builder = builder.Where("admin_user_id = ?", adminUserId)
if req.Name != "" {
builder = builder.Where("name LIKE ?", "%"+req.Name+"%")
}
if req.Url != "" {
builder = builder.Where("url LIKE ?", "%"+req.Url+"%")
}
// 获取列表和总数
links, total, err := l.svcCtx.AdminPromotionLinkModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
if err != nil {
return nil, errors.Wrapf(err, "获取推广链接列表失败, %+v", err)
}
// 使用MapReduce并发获取统计数据
items := make([]types.PromotionLinkItem, len(links))
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for _, link := range links {
source <- link
}
}, func(item interface{}, writer mr.Writer[types.PromotionLinkItem], cancel func(error)) {
link := item.(*model.AdminPromotionLink)
// 获取总统计
totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, link.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
cancel(errors.Wrapf(err, "获取总统计失败, linkId: %d, %+v", link.Id, err))
return
}
writer.Write(types.PromotionLinkItem{
Id: link.Id,
Name: link.Name,
Url: link.Url,
ClickCount: totalStats.ClickCount,
PayCount: totalStats.PayCount,
PayAmount: fmt.Sprintf("%.2f", totalStats.PayAmount),
CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"),
LastClickTime: func() string {
if totalStats.LastClickTime.Valid {
return totalStats.LastClickTime.Time.Format("2006-01-02 15:04:05")
}
return ""
}(),
LastPayTime: func() string {
if totalStats.LastPayTime.Valid {
return totalStats.LastPayTime.Time.Format("2006-01-02 15:04:05")
}
return ""
}(),
})
}, func(pipe <-chan types.PromotionLinkItem, cancel func(error)) {
for i := 0; i < len(links); i++ {
item := <-pipe
items[i] = item
}
})
if err != nil {
return nil, errors.Wrapf(err, "获取推广链接统计数据失败, %+v", err)
}
return &types.GetPromotionLinkListResp{
Total: total,
Items: items,
}, nil
}

View File

@@ -1,83 +0,0 @@
package admin_promotion
import (
"context"
"time"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetPromotionStatsHistoryLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetPromotionStatsHistoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionStatsHistoryLogic {
return &GetPromotionStatsHistoryLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPromotionStatsHistoryLogic) GetPromotionStatsHistory(req *types.GetPromotionStatsHistoryReq) (resp []types.PromotionStatsHistoryItem, err error) {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr)
}
// 构建查询条件
builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder()
// 如果有日期范围,添加日期过滤
if req.StartDate != "" && req.EndDate != "" {
startDate, err := time.Parse("2006-01-02", req.StartDate)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "开始日期格式错误")
}
endDate, err := time.Parse("2006-01-02", req.EndDate)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "结束日期格式错误")
}
// 将结束日期设置为当天的最后一刻
endDate = endDate.Add(24*time.Hour - time.Second)
builder = builder.Where("stats_date BETWEEN ? AND ?", startDate, endDate)
}
// 获取历史统计数据
historyStats, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "stats_date DESC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取历史统计数据失败")
}
// 转换为响应格式
resp = make([]types.PromotionStatsHistoryItem, 0, len(historyStats))
for _, stat := range historyStats {
// 验证链接是否属于当前用户
link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, stat.LinkId)
if err != nil {
continue // 如果链接不存在,跳过该记录
}
if link.AdminUserId != adminUserId {
continue // 如果链接不属于当前用户,跳过该记录
}
resp = append(resp, types.PromotionStatsHistoryItem{
Id: stat.Id,
LinkId: stat.LinkId,
PayAmount: stat.PayAmount,
ClickCount: stat.ClickCount,
PayCount: stat.PayCount,
StatsDate: stat.StatsDate.Format("2006-01-02"),
})
}
return resp, nil
}

View File

@@ -1,166 +0,0 @@
package admin_promotion
import (
"context"
"time"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetPromotionStatsTotalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetPromotionStatsTotalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionStatsTotalLogic {
return &GetPromotionStatsTotalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPromotionStatsTotalLogic) GetPromotionStatsTotal(req *types.GetPromotionStatsTotalReq) (resp *types.GetPromotionStatsTotalResp, err error) {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr)
}
// 获取用户的所有推广链接
linkBuilder := l.svcCtx.AdminPromotionLinkModel.SelectBuilder()
linkBuilder = linkBuilder.Where("admin_user_id = ?", adminUserId)
links, err := l.svcCtx.AdminPromotionLinkModel.FindAll(l.ctx, linkBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广链接列表失败, %+v", err)
}
// 如果没有推广链接,返回空统计
if len(links) == 0 {
return &types.GetPromotionStatsTotalResp{}, nil
}
// 构建链接ID列表
linkIds := make([]int64, len(links))
for i, link := range links {
linkIds[i] = link.Id
}
// 获取并计算总统计数据
var totalClickCount, totalPayCount int64
var totalPayAmount float64
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for _, linkId := range linkIds {
source <- linkId
}
}, func(item interface{}, writer mr.Writer[struct {
ClickCount int64
PayCount int64
PayAmount float64
}], cancel func(error)) {
linkId := item.(int64)
stats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, linkId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取总统计数据失败, linkId: %d, %+v", linkId, err))
return
}
if stats != nil {
writer.Write(struct {
ClickCount int64
PayCount int64
PayAmount float64
}{
ClickCount: stats.ClickCount,
PayCount: stats.PayCount,
PayAmount: stats.PayAmount,
})
}
}, func(pipe <-chan struct {
ClickCount int64
PayCount int64
PayAmount float64
}, cancel func(error)) {
for stats := range pipe {
totalClickCount += stats.ClickCount
totalPayCount += stats.PayCount
totalPayAmount += stats.PayAmount
}
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取总统计数据失败, %+v", err)
}
// 获取今日统计数据
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
var todayClickCount, todayPayCount int64
var todayPayAmount float64
err = mr.MapReduceVoid(func(source chan<- interface{}) {
for _, linkId := range linkIds {
source <- linkId
}
}, func(item interface{}, writer mr.Writer[struct {
ClickCount int64
PayCount int64
PayAmount float64
}], cancel func(error)) {
linkId := item.(int64)
builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder()
builder = builder.Where("link_id = ? AND DATE(stats_date) = DATE(?)", linkId, today)
histories, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取今日统计数据失败, linkId: %d, %+v", linkId, err))
return
}
var clickCount, payCount int64
var payAmount float64
for _, history := range histories {
clickCount += history.ClickCount
payCount += history.PayCount
payAmount += history.PayAmount
}
writer.Write(struct {
ClickCount int64
PayCount int64
PayAmount float64
}{
ClickCount: clickCount,
PayCount: payCount,
PayAmount: payAmount,
})
}, func(pipe <-chan struct {
ClickCount int64
PayCount int64
PayAmount float64
}, cancel func(error)) {
for stats := range pipe {
todayClickCount += stats.ClickCount
todayPayCount += stats.PayCount
todayPayAmount += stats.PayAmount
}
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取今日统计数据失败, %+v", err)
}
return &types.GetPromotionStatsTotalResp{
TodayClickCount: int64(todayClickCount),
TodayPayCount: int64(todayPayCount),
TodayPayAmount: todayPayAmount,
TotalClickCount: int64(totalClickCount),
TotalPayCount: int64(totalPayCount),
TotalPayAmount: totalPayAmount,
}, nil
}

View File

@@ -1,57 +0,0 @@
package admin_promotion
import (
"context"
"fmt"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type RecordLinkClickLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewRecordLinkClickLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RecordLinkClickLogic {
return &RecordLinkClickLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *RecordLinkClickLogic) RecordLinkClick(req *types.RecordLinkClickReq) (resp *types.RecordLinkClickResp, err error) {
// 校验路径格式
if len(req.Path) != 6 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的推广链接路径")
}
// 检查是否只包含大小写字母和数字
for _, char := range req.Path {
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的推广链接路径")
}
}
url := fmt.Sprintf("%s/%s", l.svcCtx.Config.AdminPromotion.URLDomain, req.Path)
link, err := l.svcCtx.AdminPromotionLinkModel.FindOneByUrl(l.ctx, url)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "无效的推广链接路径")
}
// 使用 statsService 更新点击统计
err = l.svcCtx.AdminPromotionLinkStatsService.UpdateLinkStats(l.ctx, link.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新点击统计失败: %+v", err)
}
return &types.RecordLinkClickResp{
Success: true,
}, nil
}

View File

@@ -1,57 +0,0 @@
package admin_promotion
import (
"context"
"time"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdatePromotionLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdatePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePromotionLinkLogic {
return &UpdatePromotionLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdatePromotionLinkLogic) UpdatePromotionLink(req *types.UpdatePromotionLinkReq) error {
// 获取当前用户ID
adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新推广链接, 获取用户信息失败, %+v", getUidErr)
}
// 获取链接信息
link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id)
if err != nil {
return errors.Wrapf(err, "更新推广链接, 获取链接信息失败, %+v", err)
}
// 验证用户权限
if link.AdminUserId != adminUserId {
return errors.Wrapf(xerr.NewErrMsg("无权限修改此链接"), "更新推广链接, 无权限修改此链接, %+v", link)
}
// 更新链接信息
link.Name = *req.Name
link.UpdateTime = time.Now()
_, err = l.svcCtx.AdminPromotionLinkModel.Update(l.ctx, nil, link)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新推广链接, 更新链接信息失败, %+v", err)
}
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"os"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
@@ -51,8 +52,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return nil, errors.Wrapf(xerr.NewErrMsg("必须提供邀请码才能成为代理,请联系平台或代理获取邀请码"), "")
}
// 2. 校验验证码
if req.Mobile != "18889793585" {
// 2. 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
@@ -138,9 +139,9 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
// 4.5 创建代理记录
newAgent := &model.Agent{
UserId: userID,
Level: targetLevel,
Mobile: encryptedMobile,
UserId: userID,
Level: targetLevel,
Mobile: encryptedMobile,
}
if req.Region != "" {
newAgent.Region = sql.NullString{String: req.Region, Valid: true}
@@ -179,7 +180,7 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
// 建立关系
relation := &model.AgentRelation{
ParentId: parentAgent.Id,
ChildId: agentId,
ChildId: agentId,
RelationType: 1, // 直接关系
}
if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil {
@@ -197,7 +198,7 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
// 设置自己为团队首领
newAgent.TeamLeaderId = sql.NullInt64{Int64: agentId, Valid: true}
if err := l.svcCtx.AgentModel.UpdateWithVersion(transCtx, session, newAgent); err != nil {
if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil {
return errors.Wrapf(err, "更新团队首领失败")
}
} else {
@@ -232,6 +233,19 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return errors.Wrapf(err, "更新邀请码状态失败")
}
// 4.9 记录邀请码使用历史(用于统计和查询)
usage := &model.AgentInviteCodeUsage{
InviteCodeId: inviteCodeModel.Id,
Code: inviteCodeModel.Code,
UserId: userID,
AgentId: newAgent.Id,
AgentLevel: targetLevel,
UsedTime: time.Now(),
}
if _, err := l.svcCtx.AgentInviteCodeUsageModel.Insert(transCtx, session, usage); err != nil {
return errors.Wrapf(err, "记录邀请码使用历史失败")
}
return nil
})

View File

@@ -6,7 +6,6 @@ import (
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/lzUtils"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -55,8 +54,14 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
}
// 3. 计算升级费用和返佣
upgradeFee := l.svcCtx.AgentService.GetUpgradeFee(fromLevel, toLevel)
rebateAmount := l.svcCtx.AgentService.GetUpgradeRebate(fromLevel, toLevel)
upgradeFee, err := l.svcCtx.AgentService.GetUpgradeFee(l.ctx, fromLevel, toLevel)
if err != nil {
return nil, errors.Wrapf(err, "获取升级费用失败")
}
rebateAmount, err := l.svcCtx.AgentService.GetUpgradeRebate(l.ctx, fromLevel, toLevel)
if err != nil {
return nil, errors.Wrapf(err, "获取升级返佣金额失败")
}
// 4. 查找原直接上级(用于返佣)
var rebateAgentId int64
@@ -68,18 +73,18 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
rebateAgentId = parent.Id
}
// 5. 使用事务处理升级
// 5. 创建升级记录(待支付状态)
var upgradeId int64
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 5.1 创建升级记录
// 5.1 创建升级记录(状态为待支付)
upgradeRecord := &model.AgentUpgrade{
AgentId: agent.Id,
FromLevel: fromLevel,
ToLevel: toLevel,
UpgradeType: 1, // 自主付费
UpgradeFee: upgradeFee,
RebateAmount: rebateAmount,
Status: 1, // 待处理
AgentId: agent.Id,
FromLevel: fromLevel,
ToLevel: toLevel,
UpgradeType: 1, // 自主付费
UpgradeFee: upgradeFee,
RebateAmount: rebateAmount,
Status: 1, // 待支付1=待支付2=已支付3=已完成4=已取消)
}
if rebateAgentId > 0 {
upgradeRecord.RebateAgentId = sql.NullInt64{Int64: rebateAgentId, Valid: true}
@@ -91,23 +96,7 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
}
upgradeId, _ = upgradeResult.LastInsertId()
// 5.2 处理支付(这里假设支付已在外层处理,只记录订单号)
// 实际支付应该在创建升级记录之前完成
// 注意:支付订单号需要从支付回调中获取,这里暂时留空
// 5.3 执行升级操作
if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, agent.Id, toLevel, 1, upgradeFee, rebateAmount, "", 0); err != nil {
return errors.Wrapf(err, "执行升级操作失败")
}
// 5.4 更新升级记录状态
upgradeRecord.Id = upgradeId
upgradeRecord.Status = 2 // 已完成
upgradeRecord.Remark = lzUtils.StringToNullString("升级成功")
if err := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); err != nil {
return errors.Wrapf(err, "更新升级记录失败")
}
// 注意:升级操作将在支付成功后通过支付回调完成
return nil
})
@@ -115,10 +104,10 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
return nil, err
}
// 返回响应(订单号需要从支付回调中获取,这里暂时返回空
// 返回响应(订单号将在支付接口中生成
return &types.ApplyUpgradeResp{
UpgradeId: upgradeId,
OrderNo: "", // 需要从支付回调中获取
OrderNo: "", // 将在支付接口中生成
}, nil
}

View File

@@ -0,0 +1,72 @@
package agent
import (
"context"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteInviteCodeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteInviteCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteInviteCodeLogic {
return &DeleteInviteCodeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteInviteCodeLogic) DeleteInviteCode(req *types.DeleteInviteCodeReq) (resp *types.DeleteInviteCodeResp, err error) {
// 1. 获取当前代理信息
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
agent, 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.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 查询邀请码是否存在且属于当前代理
inviteCode, err := l.svcCtx.AgentInviteCodeModel.FindOne(l.ctx, req.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不存在"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
}
// 3. 验证邀请码是否属于当前代理
if !inviteCode.AgentId.Valid || inviteCode.AgentId.Int64 != agent.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("无权删除此邀请码"), "")
}
// 4. 检查邀请码是否已被使用
if inviteCode.Status == 1 {
return nil, errors.Wrapf(xerr.NewErrMsg("已使用的邀请码无法删除"), "")
}
// 5. 执行软删除
err = l.svcCtx.AgentInviteCodeModel.DeleteSoft(l.ctx, nil, inviteCode)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除邀请码失败, %v", err)
}
return &types.DeleteInviteCodeResp{}, nil
}

View File

@@ -2,12 +2,17 @@ package agent
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/tool"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
@@ -49,19 +54,25 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 获取系统配置
basePrice, err := l.getConfigFloat("base_price")
// 2. 获取产品配置(必须存在)
productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, req.ProductId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取基础底价配置失败, %v", err)
}
systemMaxPrice, err := l.getConfigFloat("system_max_price")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取系统价格上限配置失败, %v", err)
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品配置不存在, productId: %d请先在后台配置产品价格参数", req.ProductId)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, productId: %d, %v", req.ProductId, err)
}
// 4. 计算实际底价(基础底价 + 等级加成
levelBonus := l.getLevelBonus(agentModel.Level)
// 3. 获取等级加成配置
levelBonus, err := l.getLevelBonus(agentModel.Level)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取等级加成配置失败, %v", err)
}
// 4. 计算实际底价(产品基础底价 + 等级加成)
basePrice := productConfig.BasePrice
actualBasePrice := basePrice + float64(levelBonus)
systemMaxPrice := productConfig.SystemMaxPrice
// 5. 验证设定价格范围
if req.SetPrice < actualBasePrice || req.SetPrice > systemMaxPrice {
@@ -82,9 +93,18 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
}
if len(existingLinks) > 0 {
// 已存在,直接返回
// 已存在,检查是否有短链,如果没有则生成
targetPath := req.TargetPath
if targetPath == "" {
targetPath = "/agent/promotionInquire/"
}
shortLink, err := l.getOrCreateShortLink(1, existingLinks[0].Id, 0, existingLinks[0].LinkIdentifier, "", targetPath)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取或创建短链失败, %v", err)
}
return &types.AgentGeneratingLinkResp{
LinkIdentifier: existingLinks[0].LinkIdentifier,
FullLink: shortLink,
}, nil
}
@@ -120,39 +140,187 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
ActualBasePrice: actualBasePrice,
}
_, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, agentLink)
result, err := l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, agentLink)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存推广链接失败, %v", err)
}
// 获取插入的ID
linkId, err := result.LastInsertId()
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广链接ID失败, %v", err)
}
// 使用默认target_path如果未提供
targetPath := req.TargetPath
if targetPath == "" {
targetPath = "/agent/promotionInquire/"
}
// 生成短链类型1=推广报告)
shortLink, err := l.createShortLink(1, linkId, 0, encrypted, "", targetPath)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成短链失败, %v", err)
}
return &types.AgentGeneratingLinkResp{
LinkIdentifier: encrypted,
FullLink: shortLink,
}, nil
}
// getLevelBonus 获取等级加成
func (l *GeneratingLinkLogic) getLevelBonus(level int64) int64 {
// getLevelBonus 获取等级加成(从配置表读取)
func (l *GeneratingLinkLogic) getLevelBonus(level int64) (int64, error) {
var configKey string
switch level {
case 1: // 普通
return 6
configKey = "level_1_bonus"
case 2: // 黄金
return 3
configKey = "level_2_bonus"
case 3: // 钻石
return 0
configKey = "level_3_bonus"
default:
return 0
return 0, nil
}
}
// getConfigFloat 获取配置值(浮点数)
func (l *GeneratingLinkLogic) getConfigFloat(configKey string) (float64, error) {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey)
if err != nil {
return 0, err
// 配置不存在时返回默认值
l.Errorf("获取等级加成配置失败, level: %d, key: %s, err: %v使用默认值", level, configKey, err)
switch level {
case 1:
return 6, nil
case 2:
return 3, nil
case 3:
return 0, nil
}
return 0, nil
}
value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue)
}
return value, nil
return int64(value), nil
}
// getOrCreateShortLink 获取或创建短链
// type: 1=推广报告(promotion), 2=邀请好友(invite)
// linkId: 推广链接ID仅推广报告使用
// inviteCodeId: 邀请码ID仅邀请好友使用
// linkIdentifier: 推广链接标识(仅推广报告使用)
// inviteCode: 邀请码(仅邀请好友使用)
// targetPath: 目标地址(前端传入)
func (l *GeneratingLinkLogic) getOrCreateShortLink(linkType int64, linkId, inviteCodeId int64, linkIdentifier, inviteCode, targetPath string) (string, error) {
// 先查询是否已存在短链
var existingShortLink *model.AgentShortLink
var err error
if linkType == 1 {
// 推广报告类型使用link_id查询
if linkId > 0 {
existingShortLink, err = l.svcCtx.AgentShortLinkModel.FindOneByLinkIdTypeDelState(l.ctx, sql.NullInt64{Int64: linkId, Valid: true}, linkType, globalkey.DelStateNo)
}
} else {
// 邀请好友类型使用invite_code_id查询
if inviteCodeId > 0 {
existingShortLink, err = l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullInt64{Int64: inviteCodeId, Valid: true}, linkType, globalkey.DelStateNo)
}
}
if err == nil && existingShortLink != nil {
// 已存在短链,直接返回
return l.buildShortLinkURL(existingShortLink.ShortCode), nil
}
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(err, "查询短链失败")
}
// 不存在,创建新的短链
return l.createShortLink(linkType, linkId, inviteCodeId, linkIdentifier, inviteCode, targetPath)
}
// createShortLink 创建短链
// type: 1=推广报告(promotion), 2=邀请好友(invite)
func (l *GeneratingLinkLogic) createShortLink(linkType int64, linkId, inviteCodeId int64, linkIdentifier, inviteCode, targetPath string) (string, error) {
promotionConfig := l.svcCtx.Config.Promotion
// 如果没有配置推广域名,返回空字符串(保持向后兼容)
if promotionConfig.PromotionDomain == "" {
l.Errorf("推广域名未配置,返回空链接")
return "", nil
}
// 验证target_path
if targetPath == "" {
return "", errors.Wrapf(xerr.NewErrMsg("目标地址不能为空"), "")
}
// 对于推广报告类型,将 linkIdentifier 拼接到 target_path
if linkType == 1 && linkIdentifier != "" {
// 如果 target_path 以 / 结尾,直接拼接 linkIdentifier
if strings.HasSuffix(targetPath, "/") {
targetPath = targetPath + url.QueryEscape(linkIdentifier)
} else {
// 否则在末尾添加 / 再拼接
targetPath = targetPath + "/" + url.QueryEscape(linkIdentifier)
}
}
// 生成短链标识6位随机字符串大小写字母+数字)
var shortCode string
maxRetries := 10 // 最大重试次数
for retry := 0; retry < maxRetries; retry++ {
shortCode = tool.Krand(6, tool.KC_RAND_KIND_ALL)
// 检查短链标识是否已存在
_, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 短链标识不存在,可以使用
break
}
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查短链标识失败, %v", err)
}
// 短链标识已存在,继续生成
if retry == maxRetries-1 {
return "", errors.Wrapf(xerr.NewErrMsg("生成短链失败,请重试"), "")
}
}
// 创建短链记录
shortLink := &model.AgentShortLink{
Type: linkType,
ShortCode: shortCode,
TargetPath: targetPath,
PromotionDomain: promotionConfig.PromotionDomain,
}
// 根据类型设置对应字段
if linkType == 1 {
// 推广报告类型
shortLink.LinkId = sql.NullInt64{Int64: linkId, Valid: linkId > 0}
if linkIdentifier != "" {
shortLink.LinkIdentifier = sql.NullString{String: linkIdentifier, Valid: true}
}
} else if linkType == 2 {
// 邀请好友类型
shortLink.InviteCodeId = sql.NullInt64{Int64: inviteCodeId, Valid: inviteCodeId > 0}
if inviteCode != "" {
shortLink.InviteCode = sql.NullString{String: inviteCode, Valid: true}
}
}
_, err := l.svcCtx.AgentShortLinkModel.Insert(l.ctx, nil, shortLink)
if err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存短链失败, %v", err)
}
return l.buildShortLinkURL(shortCode), nil
}
// buildShortLinkURL 构建短链URL
func (l *GeneratingLinkLogic) buildShortLinkURL(shortCode string) string {
promotionConfig := l.svcCtx.Config.Promotion
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, shortCode)
}

View File

@@ -44,68 +44,58 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 获取系统配置
basePrice, err := l.getConfigFloat("base_price")
// 2. 获取等级加成配置(从系统配置表读取)
levelBonus, err := l.getLevelBonus(agentModel.Level)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取基础底价配置失败, %v", err)
}
systemMaxPrice, err := l.getConfigFloat("system_max_price")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取系统价格上限配置失败, %v", err)
}
priceThreshold, err := l.getConfigFloat("price_threshold")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取提价标准阈值配置失败, %v", err)
}
priceFeeRate, err := l.getConfigFloat("price_fee_rate")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取提价手续费比例配置失败, %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取等级加成配置失败, %v", err)
}
// 3. 计算等级加成
levelBonus := l.getLevelBonus(agentModel.Level)
// 4. 查询所有产品配置
// 3. 查询所有产品配置
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
productConfigs, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, %v", err)
}
// 5. 组装响应数据
// 4. 组装响应数据(通过 product_id 查询产品名称和英文标识)
var respList []types.ProductConfigItem
for _, productConfig := range productConfigs {
// 使用产品配置中的基础底价,如果没有则使用系统配置的基础底价
productBasePrice := basePrice
if productConfig.BasePrice > 0 {
productBasePrice = productConfig.BasePrice
// 通过 product_id 查询产品信息获取产品名称和英文标识
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productConfig.ProductId)
productName := ""
productEn := ""
if err == nil {
productName = product.ProductName
productEn = product.ProductEn
} else {
// 如果产品不存在,记录日志但不影响主流程
l.Infof("查询产品信息失败, productId: %d, err: %v", productConfig.ProductId, err)
}
// 使用产品配置的基础底价(必须配置)
productBasePrice := productConfig.BasePrice
// 计算该产品的实际底价
productActualBasePrice := productBasePrice + float64(levelBonus)
// 价格范围:实际底价 ≤ 设定价格 ≤ 系统价格上限(或产品配置的最高价格
// 价格范围:实际底价 ≤ 设定价格 ≤ 产品配置的最高价格
priceRangeMin := productActualBasePrice
priceRangeMax := systemMaxPrice
if productConfig.SystemMaxPrice > 0 && productConfig.SystemMaxPrice < systemMaxPrice {
priceRangeMax = productConfig.SystemMaxPrice
}
priceRangeMax := productConfig.SystemMaxPrice
// 使用产品配置的提价阈值和手续费比例,如果没有则使用系统配置
productPriceThreshold := priceThreshold
if productConfig.PriceThreshold.Valid && productConfig.PriceThreshold.Float64 > 0 {
// 使用产品配置的提价阈值和手续费比例,如果为NULL则使用0
productPriceThreshold := 0.0
if productConfig.PriceThreshold.Valid {
productPriceThreshold = productConfig.PriceThreshold.Float64
}
productPriceFeeRate := priceFeeRate
if productConfig.PriceFeeRate.Valid && productConfig.PriceFeeRate.Float64 > 0 {
productPriceFeeRate := 0.0
if productConfig.PriceFeeRate.Valid {
productPriceFeeRate = productConfig.PriceFeeRate.Float64
}
respList = append(respList, types.ProductConfigItem{
ProductId: productConfig.ProductId,
ProductName: productConfig.ProductName,
BasePrice: productBasePrice,
LevelBonus: float64(levelBonus),
ProductName: productName,
ProductEn: productEn,
ActualBasePrice: productActualBasePrice,
PriceRangeMin: priceRangeMin,
PriceRangeMax: priceRangeMax,
@@ -119,29 +109,38 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
}, nil
}
// getLevelBonus 获取等级加成
func (l *GetAgentProductConfigLogic) getLevelBonus(level int64) int64 {
// getLevelBonus 获取等级加成(从配置表读取)
func (l *GetAgentProductConfigLogic) getLevelBonus(level int64) (int64, error) {
var configKey string
switch level {
case 1: // 普通
return 6
configKey = "level_1_bonus"
case 2: // 黄金
return 3
configKey = "level_2_bonus"
case 3: // 钻石
return 0
configKey = "level_3_bonus"
default:
return 0
return 0, nil
}
}
// getConfigFloat 获取配置值(浮点数)
func (l *GetAgentProductConfigLogic) getConfigFloat(configKey string) (float64, error) {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey)
if err != nil {
return 0, err
// 配置不存在时返回默认值
l.Errorf("获取等级加成配置失败, level: %d, key: %s, err: %v使用默认值", level, configKey, err)
switch level {
case 1:
return 6, nil
case 2:
return 3, nil
case 3:
return 0, nil
}
return 0, nil
}
value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue)
}
return value, nil
return int64(value), nil
}

View File

@@ -61,7 +61,7 @@ func (l *GetCommissionListLogic) GetCommissionList(req *types.GetCommissionListR
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentCommissionModel.FindCount(l.ctx, builder, "")
total, err := l.svcCtx.AgentCommissionModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询佣金总数失败, %v", err)
}
@@ -85,9 +85,19 @@ func (l *GetCommissionListLogic) GetCommissionList(req *types.GetCommissionListR
}
}
// 查询订单号
orderNo := ""
if commission.OrderId > 0 {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, commission.OrderId)
if err == nil {
orderNo = order.OrderNo
}
}
list = append(list, types.CommissionItem{
Id: commission.Id,
OrderId: commission.OrderId,
OrderNo: orderNo,
ProductName: productName,
Amount: commission.Amount,
Status: commission.Status,

View File

@@ -0,0 +1,492 @@
package agent
import (
"context"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetConversionRateLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetConversionRateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetConversionRateLogic {
return &GetConversionRateLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetConversionRateLogic) GetConversionRate() (resp *types.ConversionRateResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, 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.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 统计我的转化率
myConversionRate := l.calculateConversionRate(agent.Id, nil)
// 3. 统计下级转化率(按时间段动态查询历史下级关系)
subordinateConversionRate := l.calculateSubordinateConversionRate(agent.Id)
return &types.ConversionRateResp{
MyConversionRate: myConversionRate,
SubordinateConversionRate: subordinateConversionRate,
}, nil
}
// calculateSubordinateConversionRate 计算下级转化率(考虑历史关系)
func (l *GetConversionRateLogic) calculateSubordinateConversionRate(parentAgentId int64) types.ConversionRateData {
// 使用Asia/Shanghai时区与数据库保持一致
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
// 日统计:今日、昨日、前日
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
todayEnd := todayStart.AddDate(0, 0, 1)
yesterdayStart := todayStart.AddDate(0, 0, -1)
yesterdayEnd := todayStart
dayBeforeStart := todayStart.AddDate(0, 0, -2)
dayBeforeEnd := yesterdayStart
daily := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "今日", todayStart, todayEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "昨日", yesterdayStart, yesterdayEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "前日", dayBeforeStart, dayBeforeEnd),
}
// 周统计:本周、上周、上上周
weekdayOffset := int(now.Weekday())
if weekdayOffset == 0 {
weekdayOffset = 7 // 周日算作第7天
}
thisWeekStart := now.AddDate(0, 0, -(weekdayOffset - 1))
thisWeekStart = time.Date(thisWeekStart.Year(), thisWeekStart.Month(), thisWeekStart.Day(), 0, 0, 0, 0, loc)
thisWeekEnd := now
lastWeekStart := thisWeekStart.AddDate(0, 0, -7)
lastWeekEnd := thisWeekStart
lastTwoWeekStart := thisWeekStart.AddDate(0, 0, -14)
lastTwoWeekEnd := lastWeekStart
weekly := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "本周", thisWeekStart, thisWeekEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上周", lastWeekStart, lastWeekEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上上周", lastTwoWeekStart, lastTwoWeekEnd),
}
// 月统计:本月、上月、前两月
thisMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
thisMonthEnd := now
lastMonthStart := thisMonthStart.AddDate(0, -1, 0)
lastMonthEnd := thisMonthStart
lastTwoMonthStart := thisMonthStart.AddDate(0, -2, 0)
lastTwoMonthEnd := lastMonthStart
monthly := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "本月", thisMonthStart, thisMonthEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上月", lastMonthStart, lastMonthEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "前两月", lastTwoMonthStart, lastTwoMonthEnd),
}
return types.ConversionRateData{
Daily: daily,
Weekly: weekly,
Monthly: monthly,
}
}
// calculateConversionRate 计算转化率
// agentId > 0 时统计该代理的转化率,否则统计 subordinateIds 列表的转化率
func (l *GetConversionRateLogic) calculateConversionRate(agentId int64, subordinateIds []int64) types.ConversionRateData {
// 使用Asia/Shanghai时区与数据库保持一致
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
// 日统计:今日、昨日、前日
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
todayEnd := todayStart.AddDate(0, 0, 1)
yesterdayStart := todayStart.AddDate(0, 0, -1)
yesterdayEnd := todayStart
dayBeforeStart := todayStart.AddDate(0, 0, -2)
dayBeforeEnd := yesterdayStart
daily := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "今日", todayStart, todayEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "昨日", yesterdayStart, yesterdayEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "前日", dayBeforeStart, dayBeforeEnd),
}
// 周统计:本周、上周、上上周
// 本周本周一00:00:00 到现在
weekdayOffset := int(now.Weekday())
if weekdayOffset == 0 {
weekdayOffset = 7 // 周日算作第7天
}
// 计算本周一:当前日期减去(weekdayOffset-1)天
thisWeekStart := now.AddDate(0, 0, -(weekdayOffset - 1))
thisWeekStart = time.Date(thisWeekStart.Year(), thisWeekStart.Month(), thisWeekStart.Day(), 0, 0, 0, 0, loc)
thisWeekEnd := now
lastWeekStart := thisWeekStart.AddDate(0, 0, -7)
lastWeekEnd := thisWeekStart
lastTwoWeekStart := thisWeekStart.AddDate(0, 0, -14)
lastTwoWeekEnd := lastWeekStart
weekly := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "本周", thisWeekStart, thisWeekEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上周", lastWeekStart, lastWeekEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上上周", lastTwoWeekStart, lastTwoWeekEnd),
}
// 月统计:本月、上月、前两月
thisMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
thisMonthEnd := now
lastMonthStart := thisMonthStart.AddDate(0, -1, 0)
lastMonthEnd := thisMonthStart
lastTwoMonthStart := thisMonthStart.AddDate(0, -2, 0)
lastTwoMonthEnd := lastMonthStart
monthly := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "本月", thisMonthStart, thisMonthEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上月", lastMonthStart, lastMonthEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "前两月", lastTwoMonthStart, lastTwoMonthEnd),
}
return types.ConversionRateData{
Daily: daily,
Weekly: weekly,
Monthly: monthly,
}
}
// calculatePeriodConversion 计算指定时间段的转化率数据
func (l *GetConversionRateLogic) calculatePeriodConversion(agentId int64, subordinateIds []int64, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData {
// 构建 agent_order 查询条件
agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo).
Where("create_time >= ? AND create_time < ?", startTime, endTime)
if agentId > 0 {
// 统计我的转化率
agentOrderBuilder = agentOrderBuilder.Where("agent_id = ?", agentId)
} else if len(subordinateIds) > 0 {
// 统计下级转化率
agentOrderBuilder = agentOrderBuilder.Where(squirrel.Eq{"agent_id": subordinateIds})
} else {
// 没有数据
l.Infof("calculatePeriodConversion: 没有代理ID或下级IDperiodLabel=%s", periodLabel)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 添加调试日志
if agentId == 0 && len(subordinateIds) > 0 {
l.Infof("calculatePeriodConversion: 统计下级转化率periodLabel=%s, startTime=%v, endTime=%v, subordinateIds数量=%d",
periodLabel, startTime, endTime, len(subordinateIds))
}
// 查询所有代理订单
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, agentOrderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询代理订单失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
if len(agentOrders) == 0 {
if agentId == 0 && len(subordinateIds) > 0 {
l.Infof("calculatePeriodConversion: 未找到代理订单periodLabel=%s, startTime=%v, endTime=%v",
periodLabel, startTime, endTime)
}
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
l.Infof("calculatePeriodConversion: 找到代理订单数量=%d, periodLabel=%s", len(agentOrders), periodLabel)
// 收集订单ID
orderIds := make([]int64, 0, len(agentOrders))
for _, ao := range agentOrders {
orderIds = append(orderIds, ao.OrderId)
}
// 查询订单信息
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().
Where(squirrel.Eq{"id": orderIds}).
Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询订单失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 统计查询订单数、付费订单数、用户数和总金额
var totalAmount float64
paidOrderCount := 0
queryUserSet := make(map[int64]bool)
paidUserSet := make(map[int64]bool)
for _, order := range orders {
// 查询用户数(所有订单的用户,去重)
queryUserSet[order.UserId] = true
// 付费订单数和总金额(只统计已支付的订单)
if order.Status == "paid" {
paidOrderCount++
paidUserSet[order.UserId] = true
totalAmount += order.Amount
}
}
// 查询订单数 = 所有订单数量
queryOrderCount := len(orders)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: int64(queryOrderCount), // 订单数量(保持向后兼容)
PaidUsers: int64(paidOrderCount), // 订单数量(保持向后兼容)
TotalAmount: totalAmount,
QueryUserCount: int64(len(queryUserSet)), // 用户数量(新增)
PaidUserCount: int64(len(paidUserSet)), // 用户数量(新增)
}
}
// calculateSubordinatePeriodConversion 计算指定时间段内下级转化率
// 结合使用agent_rebate表和agent_order表
// 1. 查询量通过agent_order表统计所有查询包括未付费的
// 2. 付费量和金额通过agent_rebate表统计只有付费的订单才会产生返佣
func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgentId int64, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData {
// 1. 查询agent_rebate表获取所有曾经给当前用户产生返佣的source_agent_id这些代理在某个时间点是下级
// 不限制时间,获取所有历史返佣记录,用于确定哪些代理曾经是下级
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo).
Where("source_agent_id != ?", parentAgentId)
allRebates, err := l.svcCtx.AgentRebateModel.FindAll(l.ctx, rebateBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询返佣记录失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 收集所有曾经产生返佣的source_agent_id这些代理在某个时间点是下级
sourceAgentIdSet := make(map[int64]bool)
paidOrderIdSet := make(map[int64]bool) // 已付费的订单ID有返佣的订单
paidOrderIdToAmount := make(map[int64]float64) // 已付费订单的金额
for _, rebate := range allRebates {
sourceAgentIdSet[rebate.SourceAgentId] = true
// 如果返佣记录的创建时间在时间段内,说明该订单在时间段内已付费
if rebate.CreateTime.After(startTime) || rebate.CreateTime.Equal(startTime) {
if rebate.CreateTime.Before(endTime) {
paidOrderIdSet[rebate.OrderId] = true
}
}
}
if len(sourceAgentIdSet) == 0 {
l.Infof("calculateSubordinatePeriodConversion: 未找到返佣记录periodLabel=%s, startTime=%v, endTime=%v",
periodLabel, startTime, endTime)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
sourceAgentIds := make([]int64, 0, len(sourceAgentIdSet))
for agentId := range sourceAgentIdSet {
sourceAgentIds = append(sourceAgentIds, agentId)
}
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 曾经产生返佣的代理数量=%d", periodLabel, len(sourceAgentIds))
// 2. 查询agent_order表统计这些代理在时间段内的所有订单包括未付费的
agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo).
Where("create_time >= ? AND create_time < ?", startTime, endTime).
Where(squirrel.Eq{"agent_id": sourceAgentIds})
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, agentOrderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询代理订单失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 查询到代理订单数量=%d", periodLabel, len(agentOrders))
if len(agentOrders) == 0 {
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 3. 通过order_id去重获取所有订单ID用于查询订单详情
orderIdSet := make(map[int64]bool)
orderIdToAgentOrder := make(map[int64]*model.AgentOrder)
for _, ao := range agentOrders {
orderIdSet[ao.OrderId] = true
// 如果同一个订单有多个agent_order记录保留金额更大的
if existing, exists := orderIdToAgentOrder[ao.OrderId]; exists {
if ao.OrderAmount > existing.OrderAmount {
orderIdToAgentOrder[ao.OrderId] = ao
}
} else {
orderIdToAgentOrder[ao.OrderId] = ao
}
}
orderIds := make([]int64, 0, len(orderIdSet))
for orderId := range orderIdSet {
orderIds = append(orderIds, orderId)
}
// 4. 查询订单信息
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().
Where(squirrel.Eq{"id": orderIds}).
Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询订单失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 5. 查询时间段内的返佣记录,获取已付费订单的金额
rebateBuilder = l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo).
Where("source_agent_id != ?", parentAgentId).
Where("create_time >= ? AND create_time < ?", startTime, endTime).
Where(squirrel.Eq{"order_id": orderIds})
rebates, err := l.svcCtx.AgentRebateModel.FindAll(l.ctx, rebateBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询返佣记录失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 记录已付费订单的金额使用agent_order的order_amount
for _, rebate := range rebates {
if ao, exists := orderIdToAgentOrder[rebate.OrderId]; exists {
paidOrderIdToAmount[rebate.OrderId] = ao.OrderAmount
}
}
// 6. 统计查询订单数、付费订单数、用户数和总金额
var totalAmount float64
paidOrderCount := 0
queryUserSet := make(map[int64]bool)
paidUserSet := make(map[int64]bool)
for _, order := range orders {
// 查询用户数(所有订单的用户,去重)
queryUserSet[order.UserId] = true
// 付费订单数和总金额只统计已付费的订单即order_id在paidOrderIdToAmount中
if _, isPaid := paidOrderIdToAmount[order.Id]; isPaid {
paidOrderCount++
paidUserSet[order.UserId] = true
// 使用agent_order的order_amount用户实际支付金额
totalAmount += paidOrderIdToAmount[order.Id]
}
}
// 查询订单数 = 所有订单数量
queryOrderCount := len(orders)
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 查询订单数=%d, 付费订单数=%d, 查询用户数=%d, 付费用户数=%d, 总金额=%.2f",
periodLabel, queryOrderCount, paidOrderCount, len(queryUserSet), len(paidUserSet), totalAmount)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: int64(queryOrderCount), // 订单数量(保持向后兼容)
PaidUsers: int64(paidOrderCount), // 订单数量(保持向后兼容)
TotalAmount: totalAmount,
QueryUserCount: int64(len(queryUserSet)), // 用户数量(新增)
PaidUserCount: int64(len(paidUserSet)), // 用户数量(新增)
}
}

View File

@@ -4,13 +4,14 @@ import (
"context"
"database/sql"
"fmt"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/tool"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
@@ -32,7 +33,7 @@ func NewGetInviteLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
}
}
func (l *GetInviteLinkLogic) GetInviteLink() (resp *types.GetInviteLinkResp, err error) {
func (l *GetInviteLinkLogic) GetInviteLink(req *types.GetInviteLinkReq) (resp *types.GetInviteLinkResp, err error) {
// 1. 获取当前代理信息
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
@@ -47,63 +48,109 @@ func (l *GetInviteLinkLogic) GetInviteLink() (resp *types.GetInviteLinkResp, err
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 为邀请链接生成一个邀请码(每次调用都生成新的,不过期)
// 这样代理可以分享这个链接,用户通过链接注册时会使用这个邀请码
var inviteCode string
err = l.svcCtx.AgentInviteCodeModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 生成8位随机邀请码
code := tool.Krand(8, tool.KC_RAND_KIND_ALL)
maxRetries := 10
for retry := 0; retry < maxRetries; retry++ {
_, err := l.svcCtx.AgentInviteCodeModel.FindOneByCode(transCtx, code)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
break
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查邀请码失败, %v", err)
}
code = tool.Krand(8, tool.KC_RAND_KIND_ALL)
if retry == maxRetries-1 {
return errors.Wrapf(xerr.NewErrMsg("生成邀请码失败,请重试"), "")
}
}
// 创建邀请码记录(用于链接,不过期)
inviteCodeRecord := &model.AgentInviteCode{
Code: code,
AgentId: sql.NullInt64{Int64: agent.Id, Valid: true},
TargetLevel: 1, // 代理发放的邀请码,目标等级为普通代理
Status: 0, // 未使用
ExpireTime: sql.NullTime{Valid: false}, // 不过期
Remark: sql.NullString{String: "邀请链接生成", Valid: true},
}
_, err := l.svcCtx.AgentInviteCodeModel.Insert(transCtx, session, inviteCodeRecord)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建邀请码失败, %v", err)
}
inviteCode = code
return nil
})
// 2. 验证邀请码参数
if req.InviteCode == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不能为空"), "")
}
// 3. 查询邀请码是否存在且属于当前代理
inviteCodeRecord, err := l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, req.InviteCode)
if err != nil {
return nil, err
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不存在"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
}
// 3. 生成邀请链接
// 使用配置中的前端域名,如果没有则使用默认值
frontendDomain := l.svcCtx.Config.AdminPromotion.URLDomain
if frontendDomain == "" {
frontendDomain = "https://example.com" // 默认值,需要配置
// 4. 验证邀请码是否属于当前代理
if !inviteCodeRecord.AgentId.Valid || inviteCodeRecord.AgentId.Int64 != agent.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("无权使用此邀请码"), "")
}
inviteLink := fmt.Sprintf("%s/register?invite_code=%s", frontendDomain, inviteCode)
// 4. 生成二维码URL使用ImageService
qrCodeUrl := fmt.Sprintf("%s/api/v1/image/qrcode?type=invitation&content=%s", frontendDomain, inviteLink)
// 5. 验证邀请码状态
if inviteCodeRecord.Status != 0 {
if inviteCodeRecord.Status == 1 {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "")
}
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "")
}
// 6. 验证邀请码是否过期
if inviteCodeRecord.ExpireTime.Valid && inviteCodeRecord.ExpireTime.Time.Before(time.Now()) {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "")
}
// 7. 使用默认target_path如果未提供
targetPath := req.TargetPath
if targetPath == "" {
targetPath = fmt.Sprintf("/register?invite_code=%s", req.InviteCode)
}
// 8. 生成短链类型2=邀请好友)
shortLink, err := l.createInviteShortLink(inviteCodeRecord.Id, req.InviteCode, targetPath)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成短链失败, %v", err)
}
return &types.GetInviteLinkResp{
InviteLink: inviteLink,
QrCodeUrl: qrCodeUrl,
InviteLink: shortLink,
}, nil
}
// createInviteShortLink 创建邀请好友短链
func (l *GetInviteLinkLogic) createInviteShortLink(inviteCodeId int64, inviteCode, targetPath string) (string, error) {
promotionConfig := l.svcCtx.Config.Promotion
// 如果没有配置推广域名,返回空字符串(保持向后兼容)
if promotionConfig.PromotionDomain == "" {
l.Errorf("推广域名未配置,返回空链接")
return "", nil
}
// 先查询是否已存在短链
existingShortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullInt64{Int64: inviteCodeId, Valid: true}, 2, globalkey.DelStateNo)
if err == nil && existingShortLink != nil {
// 已存在短链,直接返回
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingShortLink.ShortCode), nil
}
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(err, "查询短链失败")
}
// 生成短链标识6位随机字符串大小写字母+数字)
var shortCode string
maxRetries := 10 // 最大重试次数
for retry := 0; retry < maxRetries; retry++ {
shortCode = tool.Krand(6, tool.KC_RAND_KIND_ALL)
// 检查短链标识是否已存在
_, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 短链标识不存在,可以使用
break
}
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查短链标识失败, %v", err)
}
// 短链标识已存在,继续生成
if retry == maxRetries-1 {
return "", errors.Wrapf(xerr.NewErrMsg("生成短链失败,请重试"), "")
}
}
// 创建短链记录类型2=邀请好友)
shortLink := &model.AgentShortLink{
Type: 2, // 邀请好友
InviteCodeId: sql.NullInt64{Int64: inviteCodeId, Valid: inviteCodeId > 0},
InviteCode: sql.NullString{String: inviteCode, Valid: inviteCode != ""},
ShortCode: shortCode,
TargetPath: targetPath,
PromotionDomain: promotionConfig.PromotionDomain,
}
_, err = l.svcCtx.AgentShortLinkModel.Insert(l.ctx, nil, shortLink)
if err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存短链失败, %v", err)
}
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, shortCode), nil
}

View File

@@ -0,0 +1,188 @@
package agent
import (
"context"
"strconv"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLevelPrivilegeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetLevelPrivilegeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLevelPrivilegeLogic {
return &GetLevelPrivilegeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetLevelPrivilegeLogic) GetLevelPrivilege() (resp *types.GetLevelPrivilegeResp, err error) {
// 1. 获取当前代理等级
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
var currentLevel int64 = 1 // 默认普通代理
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
if agent != nil {
currentLevel = agent.Level
}
// 获取配置值的辅助函数
getConfigFloat := func(key string, defaultValue float64) float64 {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, key)
if err != nil {
return defaultValue
}
value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil {
return defaultValue
}
return value
}
// 获取等级加成配置
level1Bonus := getConfigFloat("level_1_bonus", 6.0)
level2Bonus := getConfigFloat("level_2_bonus", 3.0)
level3Bonus := getConfigFloat("level_3_bonus", 0.0)
// 获取当前等级的加成
var currentBonus float64
switch currentLevel {
case 1:
currentBonus = level1Bonus
case 2:
currentBonus = level2Bonus
case 3:
currentBonus = level3Bonus
default:
currentBonus = level1Bonus
}
// 获取升级返佣配置
upgradeToGoldRebate := getConfigFloat("upgrade_to_gold_rebate", 139.0)
upgradeToDiamondRebate := getConfigFloat("upgrade_to_diamond_rebate", 680.0)
// 获取直接上级返佣配置
directParentAmountDiamond := getConfigFloat("direct_parent_amount_diamond", 6.0)
directParentAmountGold := getConfigFloat("direct_parent_amount_gold", 3.0)
directParentAmountNormal := getConfigFloat("direct_parent_amount_normal", 2.0)
// 构建各等级特权信息
levels := []types.LevelPrivilegeItem{
{
Level: 1,
LevelName: "普通代理",
LevelBonus: level1Bonus,
PriceReduction: l.calculatePriceReduction(currentBonus, level1Bonus),
PromoteRebate: l.buildPromoteRebateDesc(1, directParentAmountDiamond, directParentAmountGold, directParentAmountNormal),
MaxPromoteRebate: directParentAmountDiamond, // 普通代理最高返佣是直接上级是钻石时的返佣
UpgradeRebate: l.buildUpgradeRebateDesc(1, upgradeToGoldRebate, upgradeToDiamondRebate),
Privileges: []string{
"基础代理特权",
"可生成推广链接",
"享受推广返佣",
"可邀请下级代理",
},
CanUpgradeSubordinate: false,
},
{
Level: 2,
LevelName: "黄金代理",
LevelBonus: level2Bonus,
PriceReduction: l.calculatePriceReduction(currentBonus, level2Bonus),
PromoteRebate: l.buildPromoteRebateDesc(2, directParentAmountDiamond, directParentAmountGold, directParentAmountNormal),
MaxPromoteRebate: level2Bonus, // 黄金代理最高返佣是等级加成全部返佣给钻石上级
UpgradeRebate: l.buildUpgradeRebateDesc(2, upgradeToGoldRebate, upgradeToDiamondRebate),
Privileges: []string{
"高级代理特权",
"更高的返佣比例",
"享受推广返佣",
"可邀请下级代理",
"更多推广权益",
},
CanUpgradeSubordinate: false,
},
{
Level: 3,
LevelName: "钻石代理",
LevelBonus: level3Bonus,
PriceReduction: l.calculatePriceReduction(currentBonus, level3Bonus),
PromoteRebate: l.buildPromoteRebateDesc(3, directParentAmountDiamond, directParentAmountGold, directParentAmountNormal),
MaxPromoteRebate: 0, // 钻石代理无返佣
UpgradeRebate: l.buildUpgradeRebateDesc(3, upgradeToGoldRebate, upgradeToDiamondRebate),
Privileges: []string{
"尊享代理特权",
"最高返佣比例",
"独立团队管理",
"可调整下级级别",
"可免费升级下级为黄金代理",
},
CanUpgradeSubordinate: true,
},
}
return &types.GetLevelPrivilegeResp{
Levels: levels,
}, nil
}
// buildPromoteRebateDesc 构建推广返佣说明
func (l *GetLevelPrivilegeLogic) buildPromoteRebateDesc(level int64, diamondAmount, goldAmount, normalAmount float64) string {
switch level {
case 1: // 普通代理等级加成6元
return "更高的推广返佣,最高可达¥" + l.formatFloat(diamondAmount)
case 2: // 黄金代理等级加成3元
return "更高的推广返佣,最高可达¥" + l.formatFloat(3.0)
case 3: // 钻石代理等级加成0元
return "无等级加成,享受最低底价"
default:
return ""
}
}
// buildUpgradeRebateDesc 构建下级升级返佣说明
func (l *GetLevelPrivilegeLogic) buildUpgradeRebateDesc(level int64, goldRebate, diamondRebate float64) string {
switch level {
case 1: // 普通代理
return "下级升级为黄金代理返佣¥" + l.formatFloat(goldRebate) + ",升级为钻石代理返佣¥" + l.formatFloat(diamondRebate)
case 2: // 黄金代理
return "下级升级为钻石代理返佣¥" + l.formatFloat(diamondRebate)
case 3: // 钻石代理
return "可免费升级下级为黄金代理"
default:
return ""
}
}
// calculatePriceReduction 计算底价降低(相对于当前等级)
func (l *GetLevelPrivilegeLogic) calculatePriceReduction(currentBonus, targetBonus float64) float64 {
reduction := currentBonus - targetBonus
if reduction < 0 {
return 0
}
return reduction
}
// formatFloat 格式化浮点数去掉末尾的0
func (l *GetLevelPrivilegeLogic) formatFloat(f float64) string {
s := strconv.FormatFloat(f, 'f', -1, 64)
return s
}

View File

@@ -2,9 +2,14 @@ package agent
import (
"context"
"sort"
"ycc-server/app/main/model"
"ycc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/mr"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
@@ -37,11 +42,61 @@ func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.G
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取产品信息失败, %v", err)
}
// 获取产品功能列表
build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{
"product_id": productModel.Id,
})
productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取产品功能列表失败, %v", err)
}
// 创建featureId到sort的映射用于后续排序
featureSortMap := make(map[int64]int64)
for _, productFeature := range productFeatureAll {
featureSortMap[productFeature.FeatureId] = productFeature.Sort
}
var features []types.Feature
mr.MapReduceVoid(func(source chan<- interface{}) {
for _, productFeature := range productFeatureAll {
source <- productFeature.FeatureId
}
}, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) {
id := item.(int64)
feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id)
if findFeatureErr != nil {
logx.WithContext(l.ctx).Errorf("获取产品功能失败: %d, err:%v", id, findFeatureErr)
return
}
if feature != nil && feature.Id > 0 {
writer.Write(feature)
}
}, func(pipe <-chan *model.Feature, cancel func(error)) {
for item := range pipe {
var feature types.Feature
_ = copier.Copy(&feature, item)
features = append(features, feature)
}
})
// 按照productFeature.Sort字段对features进行排序
sort.Slice(features, func(i, j int) bool {
sortI := featureSortMap[features[i].ID]
sortJ := featureSortMap[features[j].ID]
return sortI < sortJ
})
return &types.GetLinkDataResp{
AgentId: agentLinkModel.AgentId,
ProductId: agentLinkModel.ProductId,
SetPrice: agentLinkModel.SetPrice,
AgentId: agentLinkModel.AgentId,
ProductId: agentLinkModel.ProductId,
SetPrice: agentLinkModel.SetPrice,
ActualBasePrice: agentLinkModel.ActualBasePrice,
ProductName: productModel.ProductName,
ProductName: productModel.ProductName,
ProductEn: productModel.ProductEn,
SellPrice: agentLinkModel.SetPrice, // 使用代理设定价格作为销售价格
Description: productModel.Description,
Features: features,
}, nil
}

View File

@@ -6,6 +6,7 @@ import (
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
@@ -46,8 +47,14 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
// 2. 构建查询条件
builder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo).
OrderBy("create_time DESC")
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
// 如果指定了返佣类型则按类型筛选1, 2, 3
if req.RebateType != nil {
builder = builder.Where("rebate_type = ?", *req.RebateType)
}
builder = builder.OrderBy("create_time DESC")
// 3. 分页查询
page := req.Page
@@ -61,7 +68,7 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentRebateModel.FindCount(l.ctx, builder, "")
total, err := l.svcCtx.AgentRebateModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询返佣总数失败, %v", err)
}
@@ -76,13 +83,41 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
// 6. 组装响应
var list []types.RebateItem
for _, rebate := range rebates {
// 查询订单号
orderNo := ""
if rebate.OrderId > 0 {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, rebate.OrderId)
if err == nil {
orderNo = order.OrderNo
}
}
// 查询来源代理手机号和等级
sourceAgentMobile := ""
sourceAgentLevel := int64(0)
if rebate.SourceAgentId > 0 {
sourceAgent, err := l.svcCtx.AgentModel.FindOne(l.ctx, rebate.SourceAgentId)
if err == nil {
if sourceAgent.Mobile != "" {
decrypted, err := crypto.DecryptMobile(sourceAgent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil {
sourceAgentMobile = decrypted
}
}
sourceAgentLevel = sourceAgent.Level
}
}
list = append(list, types.RebateItem{
Id: rebate.Id,
SourceAgentId: rebate.SourceAgentId,
OrderId: rebate.OrderId,
RebateType: rebate.RebateType,
Amount: rebate.RebateAmount,
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
Id: rebate.Id,
SourceAgentId: rebate.SourceAgentId,
SourceAgentMobile: sourceAgentMobile,
SourceAgentLevel: sourceAgentLevel,
OrderId: rebate.OrderId,
OrderNo: orderNo,
RebateType: rebate.RebateType,
Amount: rebate.RebateAmount,
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
})
}

View File

@@ -2,8 +2,10 @@ package agent
import (
"context"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"github.com/pkg/errors"
@@ -49,10 +51,77 @@ func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp,
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包信息失败, %v", err)
}
// 获取当前时间
now := time.Now()
// 今日开始时间00:00:00
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
// 本月开始时间1号 00:00:00
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 3. 统计佣金总额(从 agent_commission 表统计)
commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
commissionTotal, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionBuilder, "amount")
// 3.1 统计佣金今日收益
commissionTodayBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, todayStart)
commissionToday, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionTodayBuilder, "amount")
// 3.2 统计佣金本月收益
commissionMonthBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, monthStart)
commissionMonth, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionMonthBuilder, "amount")
// 4. 统计返佣总额(包括推广返佣和升级返佣)
// 4.1 统计推广返佣(从 agent_rebate 表)
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
promoteRebateTotal, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount")
// 4.2 统计升级返佣(从 agent_upgrade 表,查询 rebate_agent_id = 当前代理ID 且 status = 2已完成且 upgrade_type = 1自主付费的记录
// 注意只要返佣给自己的都要统计不管升级后是否脱离关系rebate_agent_id 记录的是升级时的原直接上级)
upgradeRebateBuilder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("rebate_agent_id IS NOT NULL AND rebate_agent_id = ? AND status = ? AND upgrade_type = ? AND del_state = ?",
agent.Id, 2, 1, globalkey.DelStateNo)
upgradeRebateTotal, _ := l.svcCtx.AgentUpgradeModel.FindSum(l.ctx, upgradeRebateBuilder, "rebate_amount")
rebateTotal := promoteRebateTotal + upgradeRebateTotal
// 4.3 统计返佣今日收益
// 推广返佣今日
promoteRebateTodayBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, todayStart)
promoteRebateToday, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, promoteRebateTodayBuilder, "rebate_amount")
// 升级返佣今日
upgradeRebateTodayBuilder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("rebate_agent_id IS NOT NULL AND rebate_agent_id = ? AND status = ? AND upgrade_type = ? AND del_state = ? AND create_time >= ?",
agent.Id, 2, 1, globalkey.DelStateNo, todayStart)
upgradeRebateToday, _ := l.svcCtx.AgentUpgradeModel.FindSum(l.ctx, upgradeRebateTodayBuilder, "rebate_amount")
rebateToday := promoteRebateToday + upgradeRebateToday
// 4.4 统计返佣本月收益
// 推广返佣本月
promoteRebateMonthBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, monthStart)
promoteRebateMonth, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, promoteRebateMonthBuilder, "rebate_amount")
// 升级返佣本月
upgradeRebateMonthBuilder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("rebate_agent_id IS NOT NULL AND rebate_agent_id = ? AND status = ? AND upgrade_type = ? AND del_state = ? AND create_time >= ?",
agent.Id, 2, 1, globalkey.DelStateNo, monthStart)
upgradeRebateMonth, _ := l.svcCtx.AgentUpgradeModel.FindSum(l.ctx, upgradeRebateMonthBuilder, "rebate_amount")
rebateMonth := promoteRebateMonth + upgradeRebateMonth
return &types.GetRevenueInfoResp{
Balance: wallet.Balance,
FrozenBalance: wallet.FrozenBalance,
TotalEarnings: wallet.TotalEarnings,
Balance: wallet.Balance,
FrozenBalance: wallet.FrozenBalance,
TotalEarnings: wallet.TotalEarnings,
WithdrawnAmount: wallet.WithdrawnAmount,
CommissionTotal: commissionTotal, // 佣金累计总收益(推广订单获得的佣金)
CommissionToday: commissionToday, // 佣金今日收益
CommissionMonth: commissionMonth, // 佣金本月收益
RebateTotal: rebateTotal, // 返佣累计总收益(包括推广返佣和升级返佣)
RebateToday: rebateToday, // 返佣今日收益
RebateMonth: rebateMonth, // 返佣本月收益
}, nil
}

View File

@@ -0,0 +1,404 @@
package agent
import (
"context"
"sort"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetSubordinateContributionDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetSubordinateContributionDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubordinateContributionDetailLogic {
return &GetSubordinateContributionDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetSubordinateContributionDetailLogic) GetSubordinateContributionDetail(req *types.GetSubordinateContributionDetailReq) (resp *types.GetSubordinateContributionDetailResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取当前代理信息
agent, 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.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 验证下级代理是否存在,并且确实是当前代理的下级(直接或间接)
subordinate, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.SubordinateId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("下级代理不存在"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级代理信息失败, %v", err)
}
// 3. 验证下级关系(递归检查是否是下级)
isSubordinate := l.isSubordinate(agent.Id, req.SubordinateId)
if !isSubordinate {
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的下级"), "")
}
// 4. 解密手机号
mobile := ""
if subordinate.Mobile != "" {
decrypted, err := crypto.DecryptMobile(subordinate.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil {
mobile = decrypted
}
}
// 5. 获取等级名称
levelName := ""
switch subordinate.Level {
case 1:
levelName = "普通"
case 2:
levelName = "黄金"
case 3:
levelName = "钻石"
}
// 6. 计算时间范围
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 7. 统计订单数据(仅统计有返佣给当前用户的订单)
// 通过 agent_rebate 表统计 DISTINCT order_id
rebateBaseBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agent.Id, req.SubordinateId, globalkey.DelStateNo)
// 总订单量去重订单ID
totalOrders := l.countDistinctOrders(l.ctx, rebateBaseBuilder)
// 今日订单量
todayRebateBuilder := rebateBaseBuilder.Where("create_time >= ?", todayStart)
todayOrders := l.countDistinctOrders(l.ctx, todayRebateBuilder)
// 月订单量
monthRebateBuilder := rebateBaseBuilder.Where("create_time >= ?", monthStart)
monthOrders := l.countDistinctOrders(l.ctx, monthRebateBuilder)
// 8. 统计返佣金额
totalRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBaseBuilder, "rebate_amount")
todayRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, todayRebateBuilder, "rebate_amount")
monthRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, monthRebateBuilder, "rebate_amount")
// 9. 统计邀请数据
inviteBaseBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", req.SubordinateId, 1, globalkey.DelStateNo)
totalInvites, _ := l.svcCtx.AgentRelationModel.FindCount(l.ctx, inviteBaseBuilder, "id")
todayInviteBuilder := inviteBaseBuilder.Where("create_time >= ?", todayStart)
todayInvites, _ := l.svcCtx.AgentRelationModel.FindCount(l.ctx, todayInviteBuilder, "id")
monthInviteBuilder := inviteBaseBuilder.Where("create_time >= ?", monthStart)
monthInvites, _ := l.svcCtx.AgentRelationModel.FindCount(l.ctx, monthInviteBuilder, "id")
// 10. 分页查询订单列表或邀请列表
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
var orderList []types.OrderItem
var inviteList []types.InviteItem
var orderListTotal int64
var inviteListTotal int64
tabType := req.TabType
if tabType == "" {
tabType = "order" // 默认显示订单列表
}
if tabType == "order" {
// 查询订单列表(仅显示有返佣的订单)
orderList, orderListTotal, err = l.getOrderList(l.ctx, agent.Id, req.SubordinateId, page, pageSize)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单列表失败, %v", err)
}
} else if tabType == "invite" {
// 查询邀请列表
inviteList, inviteListTotal, err = l.getInviteList(l.ctx, req.SubordinateId, page, pageSize)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请列表失败, %v", err)
}
}
// 11. 组装响应
resp = &types.GetSubordinateContributionDetailResp{
Mobile: mobile,
LevelName: levelName,
CreateTime: subordinate.CreateTime.Format("2006-01-02 15:04:05"),
OrderStats: types.OrderStatistics{
TotalOrders: totalOrders,
MonthOrders: monthOrders,
TodayOrders: todayOrders,
},
RebateStats: types.RebateStatistics{
TotalRebateAmount: totalRebateAmount,
TodayRebateAmount: todayRebateAmount,
MonthRebateAmount: monthRebateAmount,
},
InviteStats: types.InviteStatistics{
TotalInvites: totalInvites,
TodayInvites: todayInvites,
MonthInvites: monthInvites,
},
OrderList: orderList,
InviteList: inviteList,
OrderListTotal: orderListTotal,
InviteListTotal: inviteListTotal,
}
return resp, nil
}
// isSubordinate 递归检查 targetId 是否是 parentId 的下级(直接或间接)
func (l *GetSubordinateContributionDetailLogic) isSubordinate(parentId, targetId int64) bool {
// 查询直接下级
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo)
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, builder, "")
if err != nil {
return false
}
for _, relation := range relations {
// 如果是直接下级,返回 true
if relation.ChildId == targetId {
return true
}
// 递归检查间接下级
if l.isSubordinate(relation.ChildId, targetId) {
return true
}
}
return false
}
// countDistinctOrders 统计去重的订单数量(通过返佣记录)
func (l *GetSubordinateContributionDetailLogic) countDistinctOrders(ctx context.Context, builder squirrel.SelectBuilder) int64 {
// 查询所有返佣记录在内存中去重订单ID
rebates, err := l.svcCtx.AgentRebateModel.FindAll(ctx, builder, "")
if err != nil {
return 0
}
orderIdSet := make(map[int64]bool)
for _, rebate := range rebates {
orderIdSet[rebate.OrderId] = true
}
return int64(len(orderIdSet))
}
// getOrderList 获取订单列表(仅显示有返佣的订单)
func (l *GetSubordinateContributionDetailLogic) getOrderList(ctx context.Context, agentId, subordinateId int64, page, pageSize int64) ([]types.OrderItem, int64, error) {
// 1. 查询所有返佣记录
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, subordinateId, globalkey.DelStateNo).
OrderBy("create_time DESC")
// 查询总数去重订单ID
totalBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, subordinateId, globalkey.DelStateNo)
total := l.countDistinctOrders(ctx, totalBuilder)
// 查询所有返佣记录
allRebates, err := l.svcCtx.AgentRebateModel.FindAll(ctx, rebateBuilder, "")
if err != nil {
return nil, 0, err
}
if len(allRebates) == 0 {
return []types.OrderItem{}, total, nil
}
// 2. 在内存中去重订单ID并按创建时间排序
type OrderRebateInfo struct {
OrderId int64
RebateId int64
ProductId int64
RebateAmount float64
CreateTime time.Time
}
orderMap := make(map[int64]*OrderRebateInfo) // orderId -> 最新的返佣信息
for _, rebate := range allRebates {
if existing, ok := orderMap[rebate.OrderId]; ok {
// 如果已存在,保留创建时间最新的
if rebate.CreateTime.After(existing.CreateTime) {
orderMap[rebate.OrderId] = &OrderRebateInfo{
OrderId: rebate.OrderId,
RebateId: rebate.Id,
ProductId: rebate.ProductId,
RebateAmount: rebate.RebateAmount,
CreateTime: rebate.CreateTime,
}
}
} else {
orderMap[rebate.OrderId] = &OrderRebateInfo{
OrderId: rebate.OrderId,
RebateId: rebate.Id,
ProductId: rebate.ProductId,
RebateAmount: rebate.RebateAmount,
CreateTime: rebate.CreateTime,
}
}
}
// 3. 转换为切片并按时间排序
orderList := make([]*OrderRebateInfo, 0, len(orderMap))
for _, info := range orderMap {
orderList = append(orderList, info)
}
// 按创建时间倒序排序
sort.Slice(orderList, func(i, j int) bool {
return orderList[i].CreateTime.After(orderList[j].CreateTime)
})
// 4. 分页
offset := (page - 1) * pageSize
end := offset + pageSize
if end > int64(len(orderList)) {
end = int64(len(orderList))
}
if offset >= int64(len(orderList)) {
return []types.OrderItem{}, total, nil
}
pagedOrderList := orderList[offset:end]
// 5. 组装订单列表
var resultList []types.OrderItem
productCache := make(map[int64]string) // 产品ID -> 产品名称缓存
for _, orderInfo := range pagedOrderList {
// 查询订单信息
order, err := l.svcCtx.OrderModel.FindOne(ctx, orderInfo.OrderId)
if err != nil {
continue
}
// 查询agent_order信息用于获取订单金额
agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(ctx, orderInfo.OrderId)
if err != nil {
continue
}
// 查询产品名称(使用缓存)
productName := ""
if name, ok := productCache[orderInfo.ProductId]; ok {
productName = name
} else {
product, err := l.svcCtx.ProductModel.FindOne(ctx, orderInfo.ProductId)
if err == nil {
productName = product.ProductName
productCache[orderInfo.ProductId] = productName
}
}
resultList = append(resultList, types.OrderItem{
OrderNo: order.OrderNo,
ProductId: orderInfo.ProductId,
ProductName: productName,
OrderAmount: agentOrder.OrderAmount,
RebateAmount: orderInfo.RebateAmount,
CreateTime: orderInfo.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return resultList, total, nil
}
// getInviteList 获取邀请列表
func (l *GetSubordinateContributionDetailLogic) getInviteList(ctx context.Context, subordinateId int64, page, pageSize int64) ([]types.InviteItem, int64, error) {
// 查询总数
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", subordinateId, 1, globalkey.DelStateNo)
total, err := l.svcCtx.AgentRelationModel.FindCount(ctx, builder, "id")
if err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * pageSize
builder = builder.OrderBy("create_time DESC").
Limit(uint64(pageSize)).Offset(uint64(offset))
relations, err := l.svcCtx.AgentRelationModel.FindAll(ctx, builder, "")
if err != nil {
return nil, 0, err
}
// 组装邀请列表
var inviteList []types.InviteItem
for _, relation := range relations {
// 查询被邀请的代理信息
invitedAgent, err := l.svcCtx.AgentModel.FindOne(ctx, relation.ChildId)
if err != nil {
continue
}
// 获取等级名称
levelName := ""
switch invitedAgent.Level {
case 1:
levelName = "普通"
case 2:
levelName = "黄金"
case 3:
levelName = "钻石"
}
// 解密手机号
mobile := ""
if invitedAgent.Mobile != "" {
decrypted, err := crypto.DecryptMobile(invitedAgent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil {
mobile = decrypted
}
}
inviteList = append(inviteList, types.InviteItem{
AgentId: invitedAgent.Id,
Level: invitedAgent.Level,
LevelName: levelName,
Mobile: mobile,
CreateTime: relation.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return inviteList, total, nil
}

View File

@@ -62,7 +62,7 @@ func (l *GetSubordinateListLogic) GetSubordinateList(req *types.GetSubordinateLi
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentRelationModel.FindCount(l.ctx, builder, "")
total, err := l.svcCtx.AgentRelationModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级总数失败, %v", err)
}

View File

@@ -0,0 +1,341 @@
package agent
import (
"context"
"sort"
"strings"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetTeamListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetTeamListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetTeamListLogic {
return &GetTeamListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetTeamListLogic) GetTeamList(req *types.GetTeamListReq) (resp *types.GetTeamListResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, 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.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 递归查询所有下级(直接+间接)
allSubordinateIds := make(map[int64]bool)
directSubordinateIds := make(map[int64]bool)
// 递归函数收集所有下级ID
var collectSubordinates func(int64) error
collectSubordinates = func(parentId int64) error {
// 查询直接下级
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo)
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, builder, "")
if err != nil {
return err
}
for _, relation := range relations {
// 如果是第一层,标记为直接下级
if parentId == agent.Id {
directSubordinateIds[relation.ChildId] = true
}
// 添加到所有下级集合
allSubordinateIds[relation.ChildId] = true
// 递归查询下级的下级
if err := collectSubordinates(relation.ChildId); err != nil {
return err
}
}
return nil
}
// 开始递归收集所有下级
if err := collectSubordinates(agent.Id); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级关系失败, %v", err)
}
// 3. 如果没有下级,返回空数据
if len(allSubordinateIds) == 0 {
return &types.GetTeamListResp{
Statistics: types.TeamStatistics{},
Total: 0,
List: []types.TeamMemberItem{},
}, nil
}
// 4. 将下级ID转换为切片用于查询
subordinateIds := make([]int64, 0, len(allSubordinateIds))
for id := range allSubordinateIds {
subordinateIds = append(subordinateIds, id)
}
// 5. 计算时间范围
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 6. 统计顶部数据
statistics := l.calculateTeamStatistics(agent.Id, subordinateIds, todayStart, monthStart)
// 7. 查询所有下级代理信息(不进行手机号过滤,因为需要模糊搜索)
builder := l.svcCtx.AgentModel.SelectBuilder().
Where(squirrel.Eq{"id": subordinateIds}).
Where("del_state = ?", globalkey.DelStateNo)
allTeamMembers, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询团队成员失败, %v", err)
}
// 8. 如果有手机号搜索条件,进行模糊匹配过滤(在内存中)
var filteredMembers []*model.Agent
searchMobile := strings.TrimSpace(req.Mobile)
if searchMobile != "" {
// 解密所有手机号并进行模糊匹配
for _, member := range allTeamMembers {
if member.Mobile != "" {
decryptedMobile, err := crypto.DecryptMobile(member.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil && strings.Contains(decryptedMobile, searchMobile) {
filteredMembers = append(filteredMembers, member)
}
}
}
} else {
// 没有搜索条件,使用所有成员
filteredMembers = allTeamMembers
}
// 9. 按创建时间倒序排序
sort.Slice(filteredMembers, func(i, j int) bool {
return filteredMembers[i].CreateTime.After(filteredMembers[j].CreateTime)
})
// 10. 分页处理
total := int64(len(filteredMembers))
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 计算分页范围
start := int(offset)
end := start + int(pageSize)
if start > len(filteredMembers) {
start = len(filteredMembers)
}
if end > len(filteredMembers) {
end = len(filteredMembers)
}
var teamMembers []*model.Agent
if start < end {
teamMembers = filteredMembers[start:end]
}
// 11. 组装响应列表
var list []types.TeamMemberItem
for _, member := range teamMembers {
memberItem := l.buildTeamMemberItem(agent.Id, member, directSubordinateIds, todayStart)
list = append(list, memberItem)
}
return &types.GetTeamListResp{
Statistics: statistics,
Total: total,
List: list,
}, nil
}
// calculateTeamStatistics 计算团队统计数据
// 注意:所有统计都基于 subordinateIds下级ID列表不包含自己的数据
func (l *GetTeamListLogic) calculateTeamStatistics(agentId int64, subordinateIds []int64, todayStart, monthStart time.Time) types.TeamStatistics {
// 团队成员总数:只统计下级,不包括自己
stats := types.TeamStatistics{
TotalMembers: int64(len(subordinateIds)),
}
// 统计今日和本月新增成员(只统计下级,不包括自己)
todayNewCount := int64(0)
monthNewCount := int64(0)
for _, id := range subordinateIds {
member, err := l.svcCtx.AgentModel.FindOne(l.ctx, id)
if err != nil {
continue
}
if member.CreateTime.After(todayStart) {
todayNewCount++
}
if member.CreateTime.After(monthStart) {
monthNewCount++
}
}
stats.TodayNewMembers = todayNewCount
stats.MonthNewMembers = monthNewCount
// 统计团队总查询量仅统计有返佣给当前用户的订单通过agent_rebate表去重统计order_id
if len(subordinateIds) > 0 {
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agentId, globalkey.DelStateNo).
Where("source_agent_id != ?", agentId). // 明确排除自己
Where(squirrel.Eq{"source_agent_id": subordinateIds})
// 统计去重的订单数量
totalQueries := l.countDistinctOrders(l.ctx, rebateBuilder)
stats.TotalQueries = totalQueries
// 今日推广量(仅统计有返佣的订单)
todayRebateBuilder := rebateBuilder.Where("create_time >= ?", todayStart)
todayPromotions := l.countDistinctOrders(l.ctx, todayRebateBuilder)
stats.TodayPromotions = todayPromotions
// 本月推广量(仅统计有返佣的订单)
monthRebateBuilder := rebateBuilder.Where("create_time >= ?", monthStart)
monthPromotions := l.countDistinctOrders(l.ctx, monthRebateBuilder)
stats.MonthPromotions = monthPromotions
}
// 统计收益:只统计从下级获得的返佣(依靠团队得到的收益)
// 返佣从agent_rebate表统计从下级获得的返佣
// agent_id = 当前代理ID获得返佣的代理
// source_agent_id IN 下级ID列表来源代理即产生订单的下级代理
// 明确排除自己source_agent_id != agentId确保不统计自己的数据
if len(subordinateIds) > 0 {
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agentId, globalkey.DelStateNo).
Where("source_agent_id != ?", agentId). // 明确排除自己
Where(squirrel.Eq{"source_agent_id": subordinateIds})
totalRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount")
todayRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder.Where("create_time >= ?", todayStart), "rebate_amount")
monthRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder.Where("create_time >= ?", monthStart), "rebate_amount")
stats.TotalEarnings = totalRebate
stats.TodayEarnings = todayRebate
stats.MonthEarnings = monthRebate
}
return stats
}
// countDistinctOrders 统计去重的订单数量(通过返佣记录)
func (l *GetTeamListLogic) countDistinctOrders(ctx context.Context, builder squirrel.SelectBuilder) int64 {
// 查询所有返佣记录在内存中去重订单ID
rebates, err := l.svcCtx.AgentRebateModel.FindAll(ctx, builder, "")
if err != nil {
return 0
}
orderIdSet := make(map[int64]bool)
for _, rebate := range rebates {
orderIdSet[rebate.OrderId] = true
}
return int64(len(orderIdSet))
}
// countDistinctOrdersForMember 统计单个成员的去重订单数量(通过返佣记录)
func (l *GetTeamListLogic) countDistinctOrdersForMember(ctx context.Context, builder squirrel.SelectBuilder) int64 {
// 查询所有返佣记录在内存中去重订单ID
rebates, err := l.svcCtx.AgentRebateModel.FindAll(ctx, builder, "")
if err != nil {
return 0
}
orderIdSet := make(map[int64]bool)
for _, rebate := range rebates {
orderIdSet[rebate.OrderId] = true
}
return int64(len(orderIdSet))
}
// buildTeamMemberItem 构建团队成员项
func (l *GetTeamListLogic) buildTeamMemberItem(agentId int64, member *model.Agent, directSubordinateIds map[int64]bool, todayStart time.Time) types.TeamMemberItem {
levelName := ""
switch member.Level {
case 1:
levelName = "普通"
case 2:
levelName = "黄金"
case 3:
levelName = "钻石"
}
// 解密手机号
mobile := ""
if member.Mobile != "" {
decrypted, err := crypto.DecryptMobile(member.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil {
mobile = decrypted
}
}
// 统计查询量仅统计有返佣给当前用户的订单通过agent_rebate表去重统计order_id
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, member.Id, globalkey.DelStateNo)
totalQueries := l.countDistinctOrdersForMember(l.ctx, rebateBuilder)
todayRebateBuilder := rebateBuilder.Where("create_time >= ?", todayStart)
todayQueries := l.countDistinctOrdersForMember(l.ctx, todayRebateBuilder)
// 统计返佣给我的金额从agent_rebate表source_agent_id = member.Id, agent_id = agentId
totalRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount")
todayRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder.Where("create_time >= ?", todayStart), "rebate_amount")
// 统计邀请人数从agent_relation表parent_id = member.Id
inviteBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", member.Id, 1, globalkey.DelStateNo)
totalInvites, _ := l.svcCtx.AgentRelationModel.FindCount(l.ctx, inviteBuilder, "id")
todayInvites, _ := l.svcCtx.AgentRelationModel.FindCount(l.ctx, inviteBuilder.Where("create_time >= ?", todayStart), "id")
// 判断是否直接下级
isDirect := directSubordinateIds[member.Id]
return types.TeamMemberItem{
AgentId: member.Id,
Level: member.Level,
LevelName: levelName,
Mobile: mobile,
CreateTime: member.CreateTime.Format("2006-01-02 15:04:05"),
TodayQueries: todayQueries,
TotalQueries: totalQueries,
TotalRebateAmount: totalRebateAmount,
TodayRebateAmount: todayRebateAmount,
TotalInvites: totalInvites,
TodayInvites: todayInvites,
IsDirect: isDirect,
}
}

View File

@@ -2,6 +2,7 @@ package agent
import (
"context"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
@@ -45,21 +46,68 @@ func (l *GetTeamStatisticsLogic) GetTeamStatistics() (resp *types.TeamStatistics
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 确定团队首领ID
teamLeaderId := agent.Id
if agent.TeamLeaderId.Valid {
teamLeaderId = agent.TeamLeaderId.Int64
// 2. 递归查询所有下级(直接+间接)
allSubordinateIds := make(map[int64]bool)
directSubordinateIds := make(map[int64]bool)
// 递归函数收集所有下级ID
var collectSubordinates func(int64) error
collectSubordinates = func(parentId int64) error {
// 查询直接下级
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo)
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, builder, "")
if err != nil {
return err
}
for _, relation := range relations {
// 如果是第一层,标记为直接下级
if parentId == agent.Id {
directSubordinateIds[relation.ChildId] = true
}
// 添加到所有下级集合
allSubordinateIds[relation.ChildId] = true
// 递归查询下级的下级
if err := collectSubordinates(relation.ChildId); err != nil {
return err
}
}
return nil
}
// 3. 查询团队所有成员(通过 team_leader_id
// 开始递归收集所有下级
if err := collectSubordinates(agent.Id); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级关系失败, %v", err)
}
// 3. 获取当前时间用于统计今日和本月新增
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
// 4. 如果没有下级,返回空数据
if len(allSubordinateIds) == 0 {
return &types.TeamStatisticsResp{
TotalCount: 0,
DirectCount: 0,
IndirectCount: 0,
GoldCount: 0,
NormalCount: 0,
TodayNewMembers: 0,
MonthNewMembers: 0,
}, nil
}
// 5. 将下级ID转换为切片用于查询
subordinateIds := make([]int64, 0, len(allSubordinateIds))
for id := range allSubordinateIds {
subordinateIds = append(subordinateIds, id)
}
// 6. 查询所有下级代理信息
builder := l.svcCtx.AgentModel.SelectBuilder().
Where(squirrel.Or{
squirrel.Eq{"team_leader_id": teamLeaderId},
squirrel.And{
squirrel.Eq{"id": teamLeaderId},
squirrel.Eq{"level": 3}, // 钻石代理自己也是团队首领
},
}).
Where(squirrel.Eq{"id": subordinateIds}).
Where("del_state = ?", globalkey.DelStateNo)
teamMembers, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
@@ -67,51 +115,43 @@ func (l *GetTeamStatisticsLogic) GetTeamStatistics() (resp *types.TeamStatistics
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询团队成员失败, %v", err)
}
// 4. 统计
totalCount := int64(len(teamMembers))
// 7. 统计
totalCount := int64(len(teamMembers)) // 下级总数(不包括自己)
directCount := int64(len(directSubordinateIds))
indirectCount := totalCount - directCount
level1Count := int64(0) // 普通
level2Count := int64(0) // 黄金
level3Count := int64(0) // 钻石
// 不再统计钻石,因为下级不可能是钻石
// 统计直接下级
directCount := int64(0)
builder = l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", agent.Id, 1, globalkey.DelStateNo)
directRelations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, builder, "")
if err == nil {
directCount = int64(len(directRelations))
}
todayNewCount := int64(0)
monthNewCount := int64(0)
for _, member := range teamMembers {
// 排除自己
if member.Id == agent.Id {
continue
}
// 统计等级(只统计普通和黄金)
switch member.Level {
case 1:
level1Count++
case 2:
level2Count++
case 3:
level3Count++
}
// 统计今日和本月新增
if member.CreateTime.After(todayStart) {
todayNewCount++
}
if member.CreateTime.After(monthStart) {
monthNewCount++
}
}
// 间接下级 = 总人数 - 直接下级 - 自己
indirectCount := totalCount - directCount - 1
if indirectCount < 0 {
indirectCount = 0
}
return &types.TeamStatisticsResp{
TotalCount: totalCount - 1, // 排除自己
DirectCount: directCount,
IndirectCount: indirectCount,
ByLevel: types.TeamLevelStats{
Normal: level1Count,
Gold: level2Count,
Diamond: level3Count,
},
TotalCount: totalCount, // 下级总数(不包括自己
DirectCount: directCount,
IndirectCount: indirectCount,
GoldCount: level2Count,
NormalCount: level1Count,
TodayNewMembers: todayNewCount,
MonthNewMembers: monthNewCount,
}, nil
}

View File

@@ -61,7 +61,7 @@ func (l *GetUpgradeListLogic) GetUpgradeList(req *types.GetUpgradeListReq) (resp
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentUpgradeModel.FindCount(l.ctx, builder, "")
total, err := l.svcCtx.AgentUpgradeModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录总数失败, %v", err)
}

View File

@@ -0,0 +1,119 @@
package agent
import (
"context"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUpgradeRebateListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUpgradeRebateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUpgradeRebateListLogic {
return &GetUpgradeRebateListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUpgradeRebateListLogic) GetUpgradeRebateList(req *types.GetUpgradeRebateListReq) (resp *types.GetUpgradeRebateListResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, 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.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 构建查询条件:查询 rebate_agent_id = 当前代理ID 且 status = 2已完成且 upgrade_type = 1自主付费的记录
// 注意rebate_agent_id 是 NullInt64 类型,需要同时检查 IS NOT NULL
// 只要返佣给自己的都要显示不管升级后是否脱离关系rebate_agent_id 记录的是升级时的原直接上级)
builder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
Where("rebate_agent_id IS NOT NULL AND rebate_agent_id = ? AND status = ? AND upgrade_type = ? AND del_state = ?",
agent.Id, 2, 1, globalkey.DelStateNo).
OrderBy("create_time DESC")
// 3. 分页查询
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentUpgradeModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级返佣总数失败, %v", err)
}
// 5. 查询列表
builder = builder.Limit(uint64(pageSize)).Offset(uint64(offset))
upgrades, err := l.svcCtx.AgentUpgradeModel.FindAll(l.ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级返佣列表失败, %v", err)
}
// 6. 组装响应
var list []types.UpgradeRebateItem
for _, upgrade := range upgrades {
// 查询来源代理手机号(升级的代理)
sourceAgentMobile := ""
if upgrade.AgentId > 0 {
sourceAgent, err := l.svcCtx.AgentModel.FindOne(l.ctx, upgrade.AgentId)
if err == nil {
if sourceAgent.Mobile != "" {
decrypted, err := crypto.DecryptMobile(sourceAgent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err == nil {
sourceAgentMobile = decrypted
}
}
}
}
// 获取订单号
orderNo := ""
if upgrade.OrderNo.Valid {
orderNo = upgrade.OrderNo.String
}
list = append(list, types.UpgradeRebateItem{
Id: upgrade.Id,
SourceAgentId: upgrade.AgentId,
SourceAgentMobile: sourceAgentMobile,
OrderNo: orderNo,
FromLevel: upgrade.FromLevel,
ToLevel: upgrade.ToLevel,
Amount: upgrade.RebateAmount,
CreateTime: upgrade.CreateTime.Format("2006-01-02 15:04:05"),
})
}
return &types.GetUpgradeRebateListResp{
Total: total,
List: list,
}, nil
}

View File

@@ -61,7 +61,7 @@ func (l *GetWithdrawalListLogic) GetWithdrawalList(req *types.GetWithdrawalListR
offset := (page - 1) * pageSize
// 4. 查询总数
total, err := l.svcCtx.AgentWithdrawalModel.FindCount(l.ctx, builder, "")
total, err := l.svcCtx.AgentWithdrawalModel.FindCount(l.ctx, builder, "id")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录总数失败, %v", err)
}

View File

@@ -0,0 +1,58 @@
package agent
import (
"context"
"fmt"
"net/http"
"net/url"
"ycc-server/app/main/model"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"ycc-server/app/main/api/internal/svc"
)
type PromotionRedirectLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewPromotionRedirectLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PromotionRedirectLogic {
return &PromotionRedirectLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// PromotionRedirect 推广链接重定向
// 从推广域名重定向到正式域名的推广页面
func (l *PromotionRedirectLogic) PromotionRedirect(r *http.Request, w http.ResponseWriter) error {
// 1. 获取link参数
linkIdentifier := r.URL.Query().Get("link")
if linkIdentifier == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "缺少link参数")
}
// 2. 验证linkIdentifier是否存在可选用于确保链接有效
_, err := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, linkIdentifier)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "推广链接不存在或已失效")
}
l.Errorf("查询推广链接失败: %v", err)
// 即使查询失败,也继续重定向,避免影响用户体验
}
// 3. 构建重定向URL使用相对路径由服务器配置处理域名
redirectURL := fmt.Sprintf("/agent/promotionInquire/%s", url.QueryEscape(linkIdentifier))
// 5. 执行重定向302临时重定向
l.Infof("推广链接重定向: linkIdentifier=%s, redirectURL=%s", linkIdentifier, redirectURL)
http.Redirect(w, r, redirectURL, http.StatusFound)
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"os"
"time"
"ycc-server/app/main/model"
"ycc-server/common/xerr"
@@ -40,8 +41,8 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
// 校验验证码
if req.Mobile != "18889793585" {
// 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
@@ -181,7 +182,7 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
// 设置自己为团队首领
newAgent.TeamLeaderId = sql.NullInt64{Int64: agentID, Valid: true}
if err := l.svcCtx.AgentModel.UpdateWithVersion(transCtx, session, newAgent); err != nil {
if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil {
return errors.Wrapf(err, "更新团队首领失败")
}
} else {
@@ -217,6 +218,19 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return errors.Wrapf(err, "更新邀请码状态失败")
}
// 4.7 记录邀请码使用历史(用于统计和查询)
usage := &model.AgentInviteCodeUsage{
InviteCodeId: inviteCodeModel.Id,
Code: inviteCodeModel.Code,
UserId: userID,
AgentId: agentID,
AgentLevel: targetLevel,
UsedTime: time.Now(),
}
if _, err := l.svcCtx.AgentInviteCodeUsageModel.Insert(transCtx, session, usage); err != nil {
return errors.Wrapf(err, "记录邀请码使用历史失败")
}
agentLevel = targetLevel
return nil
})

View File

@@ -0,0 +1,114 @@
package agent
import (
"context"
"fmt"
"net/http"
"strings"
"ycc-server/app/main/model"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"ycc-server/app/main/api/internal/svc"
)
type ShortLinkRedirectLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewShortLinkRedirectLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortLinkRedirectLogic {
return &ShortLinkRedirectLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// ShortLinkRedirect 短链重定向
// 从短链重定向到推广页面
func (l *ShortLinkRedirectLogic) ShortLinkRedirect(shortCode string, r *http.Request, w http.ResponseWriter) error {
// 1. 验证短链标识
if shortCode == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "缺少短链标识")
}
// 2. 查询短链记录
shortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "短链不存在或已失效")
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询短链失败, %v", err)
}
// 3. 根据类型验证链接有效性
if shortLink.Type == 1 {
// 推广报告类型验证linkIdentifier是否存在
if shortLink.LinkIdentifier.Valid && shortLink.LinkIdentifier.String != "" {
_, err = l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, shortLink.LinkIdentifier.String)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "推广链接不存在或已失效")
}
l.Errorf("查询推广链接失败: %v", err)
// 即使查询失败,也继续重定向,避免影响用户体验
}
}
} else if shortLink.Type == 2 {
// 邀请好友类型:验证邀请码是否存在
if shortLink.InviteCode.Valid && shortLink.InviteCode.String != "" {
_, err = l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, shortLink.InviteCode.String)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "邀请码不存在或已失效")
}
l.Errorf("查询邀请码失败: %v", err)
// 即使查询失败,也继续重定向,避免影响用户体验
}
}
}
// 4. 构建重定向URL
redirectURL := shortLink.TargetPath
if redirectURL == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短链目标地址为空")
}
// 如果 target_path 是相对路径,需要拼接正式域名
// 如果 target_path 已经是完整URL则直接使用
if !strings.HasPrefix(redirectURL, "http://") && !strings.HasPrefix(redirectURL, "https://") {
// 相对路径,需要拼接正式域名
officialDomain := l.svcCtx.Config.Promotion.OfficialDomain
if officialDomain == "" {
// 如果没有配置正式域名,使用当前请求的域名(向后兼容)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
officialDomain = fmt.Sprintf("%s://%s", scheme, r.Host)
}
// 确保正式域名不以 / 结尾
officialDomain = strings.TrimSuffix(officialDomain, "/")
// 确保 target_path 以 / 开头
if !strings.HasPrefix(redirectURL, "/") {
redirectURL = "/" + redirectURL
}
redirectURL = officialDomain + redirectURL
}
// 5. 执行重定向302临时重定向
linkIdentifierStr := ""
if shortLink.LinkIdentifier.Valid {
linkIdentifierStr = shortLink.LinkIdentifier.String
}
l.Infof("短链重定向: shortCode=%s, type=%d, linkIdentifier=%s, redirectURL=%s", shortCode, shortLink.Type, linkIdentifierStr, redirectURL)
http.Redirect(w, r, redirectURL, http.StatusFound)
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/lzUtils"
@@ -62,17 +63,10 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
return nil, errors.Wrapf(xerr.NewErrMsg("只能升级普通代理为黄金代理"), "")
}
// 5. 验证关系:必须是直接下级
parent, err := l.svcCtx.AgentService.FindDirectParent(l.ctx, subordinateAgent.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的直接下级"), "")
}
return nil, errors.Wrapf(err, "查询关系失败")
}
if parent.Id != operatorAgent.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的直接下级"), "")
// 5. 验证关系:必须是下级(直接或间接)
isSubordinate := l.isSubordinate(operatorAgent.Id, subordinateAgent.Id)
if !isSubordinate {
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的下级"), "")
}
// 6. 验证目标等级:只能升级为黄金
@@ -87,12 +81,12 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
upgradeRecord := &model.AgentUpgrade{
AgentId: subordinateAgent.Id,
FromLevel: 1, // 普通
ToLevel: toLevel,
UpgradeType: 2, // 钻石升级下级
UpgradeFee: 0, // 免费
RebateAmount: 0, // 无返佣
OperatorAgentId: sql.NullInt64{Int64: operatorAgent.Id, Valid: true},
Status: 1, // 待处理
ToLevel: toLevel,
UpgradeType: 2, // 钻石升级下级
UpgradeFee: 0, // 免费
RebateAmount: 0, // 无返佣
OperatorAgentId: sql.NullInt64{Int64: operatorAgent.Id, Valid: true},
Status: 1, // 待处理
}
upgradeResult, err := l.svcCtx.AgentUpgradeModel.Insert(transCtx, session, upgradeRecord)
@@ -125,3 +119,27 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
Success: true,
}, nil
}
// isSubordinate 递归检查 targetId 是否是 parentId 的下级(直接或间接)
func (l *UpgradeSubordinateLogic) isSubordinate(parentId, targetId int64) bool {
// 查询直接下级
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo)
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, builder, "")
if err != nil {
return false
}
for _, relation := range relations {
// 如果是直接下级,返回 true
if relation.ChildId == targetId {
return true
}
// 递归检查间接下级
if l.isSubordinate(relation.ChildId, targetId) {
return true
}
}
return false
}

View File

@@ -26,6 +26,6 @@ func NewGetAppVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
func (l *GetAppVersionLogic) GetAppVersion() (resp *types.GetAppVersionResp, err error) {
return &types.GetAppVersionResp{
Version: "1.0.0",
WgtUrl: "https://www.quannengcha.com/app_version/ycc_1.0.0.wgt",
WgtUrl: "https://www.onecha.cn/app_version/ycc_1.0.0.wgt",
}, nil
}

Some files were not shown because too many files have changed in this diff Show More