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 { AdminAuditRealNameResp {
Success bool `json:"success"` Success bool `json:"success"`
} }
// 系统配置查询 // 系统配置查询(价格配置已移除,改为产品配置表管理)
AdminGetAgentConfigResp { AdminGetAgentConfigResp {
BasePrice float64 `json:"base_price"` // 基础底价 LevelBonus LevelBonusConfig `json:"level_bonus"` // 等级加成配置
SystemMaxPrice float64 `json:"system_max_price"` // 系统价格上限 UpgradeFee UpgradeFeeConfig `json:"upgrade_fee"` // 升级费用配置
PriceThreshold float64 `json:"price_threshold"` // 提价标准阈值 UpgradeRebate UpgradeRebateConfig `json:"upgrade_rebate"` // 升级返佣配置
PriceFeeRate float64 `json:"price_fee_rate"` // 提价手续费比例 DirectParentRebate DirectParentRebateConfig `json:"direct_parent_rebate"` // 直接上级返佣配置
LevelBonus LevelBonusConfig `json:"level_bonus"` // 等级加成配置 MaxGoldRebateAmount float64 `json:"max_gold_rebate_amount"` // 黄金代理最大返佣金额
UpgradeFee UpgradeFeeConfig `json:"upgrade_fee"` // 升级费用配置 CommissionFreeze CommissionFreezeConfig `json:"commission_freeze"` // 佣金冻结配置
UpgradeRebate UpgradeRebateConfig `json:"upgrade_rebate"` // 升级返佣配置 TaxRate float64 `json:"tax_rate"` // 税率
TaxRate float64 `json:"tax_rate"` // 税率 TaxExemptionAmount float64 `json:"tax_exemption_amount"` // 免税额度
TaxExemptionAmount float64 `json:"tax_exemption_amount"` // 免税额度
} }
LevelBonusConfig { LevelBonusConfig {
Diamond int64 `json:"diamond"` // 钻石加成0 Diamond int64 `json:"diamond"` // 钻石加成0
@@ -327,23 +326,36 @@ type (
NormalToGoldRebate float64 `json:"normal_to_gold_rebate"` // 普通→黄金返佣139 NormalToGoldRebate float64 `json:"normal_to_gold_rebate"` // 普通→黄金返佣139
ToDiamondRebate float64 `json:"to_diamond_rebate"` // 升级为钻石返佣680 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 { AdminUpdateAgentConfigReq {
BasePrice *float64 `json:"base_price,optional"` // 基础底价 LevelBonus *LevelBonusConfig `json:"level_bonus,optional"` // 等级加成配置
SystemMaxPrice *float64 `json:"system_max_price,optional"` // 系统价格上限 UpgradeFee *UpgradeFeeConfig `json:"upgrade_fee,optional"` // 升级费用配置
PriceThreshold *float64 `json:"price_threshold,optional"` // 提价标准阈值 UpgradeRebate *UpgradeRebateConfig `json:"upgrade_rebate,optional"` // 升级返佣配置
PriceFeeRate *float64 `json:"price_fee_rate,optional"` // 提价手续费比例 DirectParentRebate *DirectParentRebateConfig `json:"direct_parent_rebate,optional"` // 直接上级返佣配置
TaxRate *float64 `json:"tax_rate,optional"` // 税率 MaxGoldRebateAmount *float64 `json:"max_gold_rebate_amount,optional"` // 黄金代理最大返佣金额
TaxExemptionAmount *float64 `json:"tax_exemption_amount,optional"` // 免税额度 CommissionFreeze *CommissionFreezeConfig `json:"commission_freeze,optional"` // 佣金冻结配置
TaxRate *float64 `json:"tax_rate,optional"` // 税率
TaxExemptionAmount *float64 `json:"tax_exemption_amount,optional"` // 免税额度
} }
AdminUpdateAgentConfigResp { AdminUpdateAgentConfigResp {
Success bool `json:"success"` Success bool `json:"success"`
} }
// 产品配置分页查询 // 产品配置分页查询
AdminGetAgentProductConfigListReq { AdminGetAgentProductConfigListReq {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码
PageSize int64 `form:"pageSize"` // 每页数量 PageSize int64 `form:"pageSize"` // 每页数量
ProductId *int64 `form:"product_id,optional"` // 产品ID可选 ProductId *int64 `form:"product_id,optional"` // 产品ID可选
ProductName *string `form:"product_name,optional"` // 产品名称(可选,用于搜索)
} }
AgentProductConfigItem { AgentProductConfigItem {
Id int64 `json:"id"` // 主键 Id int64 `json:"id"` // 主键
@@ -362,12 +374,11 @@ type (
} }
// 产品配置更新 // 产品配置更新
AdminUpdateAgentProductConfigReq { AdminUpdateAgentProductConfigReq {
Id int64 `json:"id"` // 主键 Id int64 `json:"id"` // 主键
BasePrice float64 `json:"base_price"` // 基础底价 BasePrice float64 `json:"base_price"` // 基础底价
PriceRangeMin float64 `json:"price_range_min"` // 最定价 PriceRangeMax float64 `json:"price_range_max"` // 最定价(对应数据库 system_max_price
PriceRangeMax float64 `json:"price_range_max"` // 最高定价 PriceThreshold *float64 `json:"price_threshold,optional"` // 提价标准阈值(可选)
PriceThreshold float64 `json:"price_threshold"` // 提价标准阈值 PriceFeeRate *float64 `json:"price_fee_rate,optional"` // 提价手续费比例(可选)
PriceFeeRate float64 `json:"price_fee_rate"` // 提价手续费比例
} }
AdminUpdateAgentProductConfigResp { AdminUpdateAgentProductConfigResp {
Success bool `json:"success"` Success bool `json:"success"`

View File

@@ -53,7 +53,6 @@ type (
PaymentScene string `form:"payment_scene,optional"` // 支付平台 PaymentScene string `form:"payment_scene,optional"` // 支付平台
Amount float64 `form:"amount,optional"` // 金额 Amount float64 `form:"amount,optional"` // 金额
Status string `form:"status,optional"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败 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"` // 创建时间开始 CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始
CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束
PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始 PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始
@@ -80,7 +79,6 @@ type (
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间 PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间 RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理 AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
} }
@@ -102,7 +100,6 @@ type (
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间 PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间 RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
UpdateTime string `json:"update_time"` // 更新时间 UpdateTime string `json:"update_time"` // 更新时间
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理 AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
@@ -116,7 +113,6 @@ type (
PaymentScene string `json:"payment_scene"` // 支付平台 PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额 Amount float64 `json:"amount"` // 金额
Status string `json:"status,default=pending"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败 Status string `json:"status,default=pending"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
IsPromotion int64 `json:"is_promotion,default=0"` // 是否推广订单0-否1-是
} }
// 创建响应 // 创建响应
AdminCreateOrderResp { AdminCreateOrderResp {
@@ -134,7 +130,6 @@ type (
Status *string `json:"status,optional"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败 Status *string `json:"status,optional"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
PayTime *string `json:"pay_time,optional"` // 支付时间 PayTime *string `json:"pay_time,optional"` // 支付时间
RefundTime *string `json:"refund_time,optional"` // 退款时间 RefundTime *string `json:"refund_time,optional"` // 退款时间
IsPromotion *int64 `json:"is_promotion,optional"` // 是否推广订单0-否1-是
} }
// 更新响应 // 更新响应
AdminUpdateOrderResp { 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"` // 推广链接标识 LinkIdentifier string `form:"link_identifier"` // 推广链接标识
} }
GetLinkDataResp { GetLinkDataResp {
AgentId int64 `json:"agent_id"` // 代理ID AgentId int64 `json:"agent_id"` // 代理ID
ProductId int64 `json:"product_id"` // 产品ID ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 代理设定价格 SetPrice float64 `json:"set_price"` // 代理设定价格
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价 ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
ProductName string `json:"product_name"` // 产品名称 ProductName string `json:"product_name"` // 产品名称
ProductEn string `json:"product_en"` // 产品英文标识
SellPrice float64 `json:"sell_price"` // 销售价格(使用代理设定价格)
Description string `json:"description"` // 产品描述
Features []Feature `json:"features"` // 产品功能列表
} }
AgentApplyReq { AgentApplyReq {
Region string `json:"region,optional"` // 区域(可选) Region string `json:"region,optional"` // 区域(可选)
@@ -94,10 +98,33 @@ type (
Remark string `json:"remark"` // 备注 Remark string `json:"remark"` // 备注
CreateTime string `json:"create_time"` // 创建时间 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 { GetInviteLinkResp {
InviteLink string `json:"invite_link"` // 邀请链接 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 @handler GetTeamStatistics
get /team/statistics returns (TeamStatisticsResp) get /team/statistics returns (TeamStatisticsResp)
// 获取转化率统计
@handler GetConversionRate
get /conversion/rate returns (ConversionRateResp)
// 获取下级列表 // 获取下级列表
@handler GetSubordinateList @handler GetSubordinateList
get /subordinate/list (GetSubordinateListReq) returns (GetSubordinateListResp) 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 @handler GetRevenueInfo
get /revenue returns (GetRevenueInfoResp) get /revenue returns (GetRevenueInfoResp)
@@ -139,10 +178,14 @@ service main {
@handler GetCommissionList @handler GetCommissionList
get /commission/list (GetCommissionListReq) returns (GetCommissionListResp) get /commission/list (GetCommissionListReq) returns (GetCommissionListResp)
// 获取返佣记录 // 获取返佣记录(推广返佣)
@handler GetRebateList @handler GetRebateList
get /rebate/list (GetRebateListReq) returns (GetRebateListResp) get /rebate/list (GetRebateListReq) returns (GetRebateListResp)
// 获取升级返佣记录
@handler GetUpgradeRebateList
get /rebate/upgrade/list (GetUpgradeRebateListReq) returns (GetUpgradeRebateListResp)
// 获取升级记录 // 获取升级记录
@handler GetUpgradeList @handler GetUpgradeList
get /upgrade/list (GetUpgradeListReq) returns (GetUpgradeListResp) get /upgrade/list (GetUpgradeListReq) returns (GetUpgradeListResp)
@@ -175,9 +218,17 @@ service main {
@handler GetInviteCodeList @handler GetInviteCodeList
get /invite_code/list (GetInviteCodeListReq) returns (GetInviteCodeListResp) get /invite_code/list (GetInviteCodeListReq) returns (GetInviteCodeListResp)
// 获取邀请链接 // 删除邀请码
@handler DeleteInviteCode
post /invite_code/delete (DeleteInviteCodeReq) returns (DeleteInviteCodeResp)
// 获取邀请链接(根据邀请码生成)
@handler GetInviteLink @handler GetInviteLink
get /invite_link returns (GetInviteLinkResp) get /invite_link (GetInviteLinkReq) returns (GetInviteLinkResp)
// 获取代理等级特权信息
@handler GetLevelPrivilege
get /level/privilege returns (GetLevelPrivilegeResp)
} }
type ( type (
@@ -194,11 +245,13 @@ type (
} }
// 生成推广链接 // 生成推广链接
AgentGeneratingLinkReq { AgentGeneratingLinkReq {
ProductId int64 `json:"product_id"` // 产品ID ProductId int64 `json:"product_id"` // 产品ID
SetPrice float64 `json:"set_price"` // 设定价格 SetPrice float64 `json:"set_price"` // 设定价格
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
} }
AgentGeneratingLinkResp { AgentGeneratingLinkResp {
LinkIdentifier string `json:"link_identifier"` // 推广链接标识 LinkIdentifier string `json:"link_identifier"` // 推广链接标识
FullLink string `json:"full_link"` // 完整短链URL
} }
// 产品配置 // 产品配置
AgentProductConfigResp { AgentProductConfigResp {
@@ -207,8 +260,7 @@ type (
ProductConfigItem { ProductConfigItem {
ProductId int64 `json:"product_id"` // 产品ID ProductId int64 `json:"product_id"` // 产品ID
ProductName string `json:"product_name"` // 产品名称 ProductName string `json:"product_name"` // 产品名称
BasePrice float64 `json:"base_price"` // 基础底价 ProductEn string `json:"product_en"` // 产品英文标识
LevelBonus float64 `json:"level_bonus"` // 等级加成
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价 ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
PriceRangeMin float64 `json:"price_range_min"` // 最低价格 PriceRangeMin float64 `json:"price_range_min"` // 最低价格
PriceRangeMax float64 `json:"price_range_max"` // 最高价格 PriceRangeMax float64 `json:"price_range_max"` // 最高价格
@@ -217,15 +269,31 @@ type (
} }
// 团队统计 // 团队统计
TeamStatisticsResp { TeamStatisticsResp {
TotalCount int64 `json:"total_count"` // 团队总人数(包括自己) TotalCount int64 `json:"total_count"` // 团队总人数(包括自己)
DirectCount int64 `json:"direct_count"` // 直接下级数量 DirectCount int64 `json:"direct_count"` // 直接下级数量
IndirectCount int64 `json:"indirect_count"` // 间接下级数量 IndirectCount int64 `json:"indirect_count"` // 间接下级数量
ByLevel TeamLevelStats `json:"by_level"` // 按等级统计 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"` // 钻石数量 ConversionRateResp {
Gold int64 `json:"gold"` // 黄金数量 MyConversionRate ConversionRateData `json:"my_conversion_rate"` // 我的转化率
Normal int64 `json:"normal"` // 普通数量 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 { GetSubordinateListReq {
@@ -245,12 +313,103 @@ type (
TotalOrders int64 `json:"total_orders"` // 总订单数 TotalOrders int64 `json:"total_orders"` // 总订单数
TotalAmount float64 `json:"total_amount"` // 总金额 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 { GetRevenueInfoResp {
Balance float64 `json:"balance"` // 可用余额 Balance float64 `json:"balance"` // 可用余额
FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 FrozenBalance float64 `json:"frozen_balance"` // 冻结余额
TotalEarnings float64 `json:"total_earnings"` // 累计收益 TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益)
WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现 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 { GetCommissionListReq {
@@ -264,27 +423,51 @@ type (
CommissionItem { CommissionItem {
Id int64 `json:"id"` // 记录ID Id int64 `json:"id"` // 记录ID
OrderId int64 `json:"order_id"` // 订单ID OrderId int64 `json:"order_id"` // 订单ID
OrderNo string `json:"order_no"` // 订单号
ProductName string `json:"product_name"` // 产品名称 ProductName string `json:"product_name"` // 产品名称
Amount float64 `json:"amount"` // 佣金金额 Amount float64 `json:"amount"` // 佣金金额
Status int64 `json:"status"` // 状态1=已发放2=已冻结3=已取消 Status int64 `json:"status"` // 状态1=已发放2=已冻结3=已取消
CreateTime string `json:"create_time"` // 创建时间 CreateTime string `json:"create_time"` // 创建时间
} }
// 返佣记录 // 返佣记录(推广返佣)
GetRebateListReq { GetRebateListReq {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数量 PageSize int64 `form:"page_size"` // 每页数量
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型可选1=直接上级返佣2=钻石上级返佣3=黄金上级返佣
} }
GetRebateListResp { GetRebateListResp {
Total int64 `json:"total"` // 总数 Total int64 `json:"total"` // 总数
List []RebateItem `json:"list"` // 列表 List []RebateItem `json:"list"` // 列表
} }
RebateItem { RebateItem {
Id int64 `json:"id"` // 记录ID Id int64 `json:"id"` // 记录ID
SourceAgentId int64 `json:"source_agent_id"` // 来源代理ID SourceAgentId int64 `json:"source_agent_id"` // 来源代理ID
OrderId int64 `json:"order_id"` // 订单ID SourceAgentMobile string `json:"source_agent_mobile"` // 来源代理手机号
RebateType int64 `json:"rebate_type"` // 返佣类型1=直接上级2=钻石上级3=黄金上级 SourceAgentLevel int64 `json:"source_agent_level"` // 来源代理等级1=普通2=黄金3=钻石
Amount float64 `json:"amount"` // 返佣金额 OrderId int64 `json:"order_id"` // 订单ID
CreateTime string `json:"create_time"` // 创建时间 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 { 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 ( type (
PaymentReq { PaymentReq {
Id string `json:"id"` Id string `json:"id"`
PayMethod string `json:"pay_method"` PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式)
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip"` PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"`
} }
PaymentResp { PaymentResp {
PrepayData interface{} `json:"prepay_data"` PrepayData interface{} `json:"prepay_data"`

View File

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

View File

@@ -41,13 +41,13 @@ Alipay:
Wxpay: Wxpay:
AppID: "wx442ee1ac1ee75917" AppID: "wx442ee1ac1ee75917"
MchID: "1682635136" MchID: "1687993434"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61" MchCertificateSerialNumber: "241E4BCF5B69AAAC48451DB2C7ED794EF8B3A3D3"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f" MchApiv3Key: "aB3cD5eF7gH9iJ1kL2mN4oP6qR8sT0uV"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem" MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601" MchPublicKeyID: "PUB_KEY_ID_0116879934342025120200181745004208"
MchPublicKeyPath: "etc/merchant/pub_key.pem" MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781" MchPlatformRAS: "5630D013C88EA348BF66E642B6C39AA0180D4B15"
NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/wechat/callback" NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/wechat/refund_callback" RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/wechat/refund_callback"
Applepay: Applepay:
@@ -63,8 +63,8 @@ Ali:
SystemConfig: SystemConfig:
ThreeVerify: false ThreeVerify: false
WechatH5: WechatH5:
AppID: "wxa581992dc74d860e" AppID: "wx442ee1ac1ee75917"
AppSecret: "4de1fbf521712247542d49907fcd5dbf" AppSecret: "c80474909db42f63913b7a307b3bee17"
WechatMini: WechatMini:
AppID: "wx781abb66b3368963" # 小程序的AppID AppID: "wx781abb66b3368963" # 小程序的AppID
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
@@ -76,8 +76,6 @@ AdminConfig:
AccessSecret: "jK8nP3qR7tV2xZ5aB9cD1eF6gH4iJ0kL8mN5oP6qR7sT" AccessSecret: "jK8nP3qR7tV2xZ5aB9cD1eF6gH4iJ0kL8mN5oP6qR7sT"
AccessExpire: 604800 AccessExpire: 604800
RefreshAfter: 302400 RefreshAfter: 302400
AdminPromotion:
URLDomain: "https://tianyuandb.com/p"
TaxConfig: TaxConfig:
TaxRate: 0.06 TaxRate: 0.06
TaxExemptionAmount: 0.00 TaxExemptionAmount: 0.00
@@ -87,4 +85,7 @@ Tianyuanapi:
BaseURL: "https://api.tianyuanapi.com" BaseURL: "https://api.tianyuanapi.com"
Timeout: 60 Timeout: 60
Authorization: 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" AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt"
AlipayRootCertPath: "etc/merchant/alipayRootCert.crt" AlipayRootCertPath: "etc/merchant/alipayRootCert.crt"
IsProduction: true IsProduction: true
NotifyUrl: "https://www.quannengcha.com/api/v1/pay/alipay/callback" NotifyUrl: "https://www.onecha.cn/api/v1/pay/alipay/callback"
ReturnURL: "https://www.quannengcha.com/payment/result" ReturnURL: "https://www.onecha.cn/payment/result"
Wxpay: Wxpay:
AppID: "wx442ee1ac1ee75917" AppID: "wx442ee1ac1ee75917"
MchID: "1682635136" MchID: "1687993434"
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61" MchCertificateSerialNumber: "241E4BCF5B69AAAC48451DB2C7ED794EF8B3A3D3"
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f" MchApiv3Key: "aB3cD5eF7gH9iJ1kL2mN4oP6qR8sT0uV"
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem" MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601" MchPublicKeyID: "PUB_KEY_ID_0116879934342025120200181745004208"
MchPublicKeyPath: "etc/merchant/pub_key.pem" MchPublicKeyPath: "etc/merchant/pub_key.pem"
MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781" MchPlatformRAS: "5630D013C88EA348BF66E642B6C39AA0180D4B15"
NotifyUrl: "https://www.quannengcha.com/api/v1/pay/wechat/callback" NotifyUrl: "https://www.onecha.cn/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.quannengcha.com/api/v1/wechat/refund_callback" RefundNotifyUrl: "https://www.onecha.cn/api/v1/wechat/refund_callback"
Applepay: Applepay:
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt" ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
SandboxVerifyURL: "https://api.storekit-sandbox.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" AccessSecret: "jK8nP3qR7tV2xZ5aB9cD1eF6gH4iJ0kL8mN5oP6qR7sT"
AccessExpire: 604800 AccessExpire: 604800
RefreshAfter: 302400 RefreshAfter: 302400
AdminPromotion:
URLDomain: "https://tianyuandb.com/p"
TaxConfig: TaxConfig:
TaxRate: 0.06 TaxRate: 0.06
TaxExemptionAmount: 0.00 TaxExemptionAmount: 0.00
@@ -75,4 +73,7 @@ Tianyuanapi:
BaseURL: "https://api.tianyuanapi.com" BaseURL: "https://api.tianyuanapi.com"
Timeout: 60 Timeout: 60
Authorization: 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----- -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCP6fWm1vXXybH MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAh8YNl16EkVKW
m3Ne6PjacGrN2+iMrzWZlzdHCZ31udDPqSUYaZ+78b441KZK/CJFQWeSJ/1h//A+ IHDiPyx5Zz93osD4n2E7oJXPEOSGpumhsAMjXsRd32JulYDtD/B/phA/mxQiEf84
BGsQDKvE/fj2QzN1KkOuQ8WJXNGpixE5uu5bv/QTN/ukurGdA1aO2aFCANumlOmB Um4VKC16pNAEtpEyrO7ZZRhrPk2AMck6Jm81nXLoppttuS0B3VkOE/1UuvAbIz1y
HkB/B2so57ii8iQQjwK2xM4r3oOU/IfcFGKL+9/QjLGFFp9PJXCDBCgrxxlZGaj1 VliRLDTiIbuSM8p1rMfpsJGo4CoLaerJgzXL6MHZA0+Fhn4PQLkLIt57jC0Jh2Dg
3wowlfVOzlaX94gemQsCYVkuAFIYMAnFHs9cKNZQIU80somW/yy2Gy38N6n7NnbD ayH7Ru/wgwgq6upXb7rXj0ZzMer5kkA446mLis9P6Nz1GiTUMyUy+tnORK1EpWLg
nvFSaq4GoDROqRgKbRZ5e706d/p7A3aS/2oRqq1jomUIugK8g++LmoHFTgfhfQkI tUifsfXsKBLlUcOrNyG1+TyeMOuY2p592Au8J2eZbwyXKRDvK2oJzsDQW04pGyJc
v1aG/nPzAgMBAAECggEAD2RN31J2J42xm/V0YdviBCUOQXugZK1peN8jkSxw6Myt oW1vObmLAgMBAAECggEAPc0XgRNezrULSo99TNK0hv/iepeu09/tSUOh8wbcJHD9
gBbuCo4sCw9vvD8VYjGyYXx6QXmLuV03YyKkfSQT5EsflBvlEu6jaEaUe3rwXhfX u94RE9B+vhdPtGmfKfmc3IzE2HYCP3GBeGXVWks8VgsDjw+/igHC5duyu/IS1Jym
6JQoWPrP00oHVZk5g7CFBlK2VW2N+hgonIOSJr6mvhoGZlr7gphiZasYjx9Vm9N3 mFjwB8jTsuSQLedsEBYqWP+HqSQcoMluFv6qjWcgTpo/aI3hZmahAV2hVBEozeKR
Pbnfru5ttzplYNniwH3DF6ph8VmdbD1nnbWSKLXvHCsXQT2wBcnsIagIH3vyq6K1 Va+EssjI46k894Kr6s9rb9nk8hCORuLuqDXfWJdxT+UixMeYftrgmHXk6CCUb2Ct
pc5abWsQJrixOPebpI8jD5w0HxHAqVLx58H/OC2zW/roAw1WS2AkueJ1j7dQ7Z0C EjMuxi66KyfVu9w5wS0DuE583mDIgTKmD+deJWxcVyJJMJDCULY4fotWhQb2ro9L
mc9Xexz5gcAP0nMAQv+LP7iYqsa/niFhfcTFWfdxkQKBgQD5JkKNmInU2/IVYCwO qndaCgBC+sOAB/PrO31E40hZhjgdToSq5SvUWgjUCQKBgQD6/zSzEGJYzjS3544l
c483MCSv1+MnbRXlb7vut8T0IupHTU6hCge6C3q3HsjbKSBn8bRChtPUzvw9JFxK PWF92WT3aFJxT3+Mgd/BrTWaY2LykbDoioM/Kp+5D1bB446k534xO6uwr3LuDCOE
QWKiQqQDPLDJ08AIKhfQD2JiLtoikkZN0bF6OTL+Soney1yGx51mlfHM194+PcCJ jZGy/6+HQeDHSLfDZ+LgWQdEbakbniR57HXG293x3Mp5jTlZOXc8ryGURXaCP8Sy
jF7iWdMVbcBwHbgydNxxIS5cKQKBgQDHlvQ4lw6gvLILpGK494/vNYSJP/Jmd66V xwIiZPUgpo4xA0Myt/CnjW9OhwKBgQDEXjkc4tyVTsgcVevxBjT4o3g2ihYouCdt
3oSGYi84YRKTSwH4NlbBVVieb3Dv+pPugbsXEuFHBif7WsivbYgNTE9++8Yvt0gh ClDr6iZ8Mi5A0vCcuy1A3uI5BZnax11AIwqapgjOdWgWEtyjQJ84bhesyI7km/Ya
duB1G4yh7m/ylQeSuipgQU9tozrU/15cWwmcCRV50wWXBGoVEM0kf7mzEKSxmjYk AeaelsgSf+mAfFgTarWb+KpD5l0jxJAlX/1PAQU6vXuUPdA4PtBbKyUKHLY0kMXr
Qzko/zxSuwKBgQCY6Bc+SViFz3qSDdTcBaXma+CIHsmlH7ipd9px1kzEvEzl95cD wE4vbPpZ3QKBgQDGvwpFt/WICFAqR0qBJmdqNZgDaDHP03lWEwjQ3YySYZxaIw8I
FGHLl1H34qfIgUQHJvrHPXHyEBoT+CW/2MMM7DM2XV/ubctT92ln4pkxwqlTQExv M5XVkLTE3uZ9xOiQn1WHOo6q62KAKFB/h3IVYOzmlz2bz3LjYgF+UEC26HZ9je2o
Y/s1FLesAtj8Z/hgK0/5bprYab9WmZV5lTGCXzhB1XqeFE9AgCHuODv4iQKBgQC8 NZrVCghmmcQiF7ePdTd7b9mRBKfgXwor3fVMstB/OCNjoAe3w3rl0dKPRQKBgQC2
g2uwd5ytXQydymokYk9klJvWNrvw5GHV1BJAC0Smb6lnzZTSqCBRAxdsrb1yLK7E oIbvdZH/DqkPqV1o6QPk5muMFbrsHfEU+U4vSrKGOUlJIqWCrpY0ydWNgMcJcPcq
u2vGY2K7/qiM1DZw23eBd+4t9gg+0VIjqXBfq+GsoNTDvtckUwnrWER5PY831ut9 Ciz3jUmNciXeuaYX8qbiHYnJOTGkLeShZXktrz/d7Lamt35WeJz0tTztL1caR9pj
N89fvYS3SAUjmlvIAdKBAtKWusWTqiAxJ/05J7oGOQKBgB5PSr5i0LlupIbKui9t 2DVG/8T0T3uacC6x0MGIuMSW9gMDOk3Ipy5P70OaxQKBgQDFzgdJxU9Ra6AzbyBL
XtXnRqGPxxrZZUpTkyrGOAnlCz/zq2QiwFpBWo/NMHOp0KmxzJpQ8yEY2LWlRZ61 hcaKb+moNEo+peKCImwY1n3FeRM6XZ59qQaGV1lYu5KPUj9T/SVaJm6DCYFKqM0r
Oc9m0J/HtPw3Ohi1treBosEVG/0NOI9Tq1Obny23N51MVibdW6zEIyGUp/DbFS8h T1prq2LeR69nB7Dpsr2TKp57L86DoCqbxOBnWxZ/6Em65hoRYe7CAtn3yFQGKm9T
5DljdOYX9IYIHHn3Ig4GeTGe +EdUfn1gf7AWjQAgo3bis3TaMQ==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 ( import (
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent" "ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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 { func ApplyUpgradeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var req types.ApplyUpgradeReq var req types.ApplyUpgradeReq
if err := httpx.Parse(r, &req); err != nil { 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 return
} }
l := agent.NewApplyUpgradeLogic(r.Context(), svcCtx) l := agent.NewApplyUpgradeLogic(r.Context(), svcCtx)
resp, err := l.ApplyUpgrade(&req) resp, err := l.ApplyUpgrade(&req)
if err != nil { result.HttpResult(r, w, resp, err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
} }
} }

View File

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

View File

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

View File

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

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/logic/agent"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/common/result" "ycc-server/common/result"
"ycc-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
) )
func GetInviteLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func GetInviteLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { 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) l := agent.NewGetInviteLinkLogic(r.Context(), svcCtx)
resp, err := l.GetInviteLink() resp, err := l.GetInviteLink(&req)
result.HttpResult(r, w, resp, err) 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 ( import (
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent" "ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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 { func GetRebateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var req types.GetRebateListReq var req types.GetRebateListReq
if err := httpx.Parse(r, &req); err != nil { 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 return
} }
l := agent.NewGetRebateListLogic(r.Context(), svcCtx) l := agent.NewGetRebateListLogic(r.Context(), svcCtx)
resp, err := l.GetRebateList(&req) resp, err := l.GetRebateList(&req)
if err != nil { result.HttpResult(r, w, resp, err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 ( import (
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent" "ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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 { func RealNameAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var req types.RealNameAuthReq var req types.RealNameAuthReq
if err := httpx.Parse(r, &req); err != nil { 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 return
} }
l := agent.NewRealNameAuthLogic(r.Context(), svcCtx) l := agent.NewRealNameAuthLogic(r.Context(), svcCtx)
resp, err := l.RealNameAuth(&req) resp, err := l.RealNameAuth(&req)
if err != nil { result.HttpResult(r, w, resp, err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
} }
} }

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 ( import (
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"ycc-server/app/main/api/internal/logic/agent" "ycc-server/app/main/api/internal/logic/agent"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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 { func UpgradeSubordinateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var req types.UpgradeSubordinateReq var req types.UpgradeSubordinateReq
if err := httpx.Parse(r, &req); err != nil { 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 return
} }
l := agent.NewUpgradeSubordinateLogic(r.Context(), svcCtx) l := agent.NewUpgradeSubordinateLogic(r.Context(), svcCtx)
resp, err := l.UpgradeSubordinate(&req) resp, err := l.UpgradeSubordinate(&req)
if err != nil { result.HttpResult(r, w, resp, err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
} }
} }

View File

@@ -13,7 +13,6 @@ import (
admin_order "ycc-server/app/main/api/internal/handler/admin_order" 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_platform_user "ycc-server/app/main/api/internal/handler/admin_platform_user"
admin_product "ycc-server/app/main/api/internal/handler/admin_product" 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_query "ycc-server/app/main/api/internal/handler/admin_query"
admin_role "ycc-server/app/main/api/internal/handler/admin_role" admin_role "ycc-server/app/main/api/internal/handler/admin_role"
admin_role_api "ycc-server/app/main/api/internal/handler/admin_role_api" 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"), 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( server.AddRoutes(
rest.WithMiddlewares( rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AdminAuthInterceptor}, []rest.Middleware{serverCtx.AdminAuthInterceptor},
@@ -693,6 +617,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/commission/list", Path: "/commission/list",
Handler: agent.GetCommissionListHandler(serverCtx), Handler: agent.GetCommissionListHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/conversion/rate",
Handler: agent.GetConversionRateHandler(serverCtx),
},
{ {
Method: http.MethodPost, Method: http.MethodPost,
Path: "/generating_link", Path: "/generating_link",
@@ -703,6 +632,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/info", Path: "/info",
Handler: agent.GetAgentInfoHandler(serverCtx), Handler: agent.GetAgentInfoHandler(serverCtx),
}, },
{
Method: http.MethodPost,
Path: "/invite_code/delete",
Handler: agent.DeleteInviteCodeHandler(serverCtx),
},
{ {
Method: http.MethodPost, Method: http.MethodPost,
Path: "/invite_code/generate", Path: "/invite_code/generate",
@@ -718,6 +652,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/invite_link", Path: "/invite_link",
Handler: agent.GetInviteLinkHandler(serverCtx), Handler: agent.GetInviteLinkHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/level/privilege",
Handler: agent.GetLevelPrivilegeHandler(serverCtx),
},
{ {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/product_config", Path: "/product_config",
@@ -733,16 +672,31 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/rebate/list", Path: "/rebate/list",
Handler: agent.GetRebateListHandler(serverCtx), Handler: agent.GetRebateListHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/rebate/upgrade/list",
Handler: agent.GetUpgradeRebateListHandler(serverCtx),
},
{ {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/revenue", Path: "/revenue",
Handler: agent.GetRevenueInfoHandler(serverCtx), Handler: agent.GetRevenueInfoHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/subordinate/contribution/detail",
Handler: agent.GetSubordinateContributionDetailHandler(serverCtx),
},
{ {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/subordinate/list", Path: "/subordinate/list",
Handler: agent.GetSubordinateListHandler(serverCtx), Handler: agent.GetSubordinateListHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/team/list",
Handler: agent.GetTeamListHandler(serverCtx),
},
{ {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/team/statistics", Path: "/team/statistics",
@@ -779,6 +733,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/agent"), rest.WithPrefix("/api/v1/agent"),
) )
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/s/:shortCode",
Handler: agent.ShortLinkRedirectHandler(serverCtx),
},
},
)
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {

View File

@@ -34,6 +34,14 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
value, _ := strconv.ParseFloat(config.ConfigValue, 64) value, _ := strconv.ParseFloat(config.ConfigValue, 64)
return value 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") level1Bonus := getConfigFloat("level_1_bonus")
@@ -48,11 +56,20 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
upgradeToGoldRebate := getConfigFloat("upgrade_to_gold_rebate") upgradeToGoldRebate := getConfigFloat("upgrade_to_gold_rebate")
upgradeToDiamondRebate := getConfigFloat("upgrade_to_diamond_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{ return &types.AdminGetAgentConfigResp{
BasePrice: getConfigFloat("base_price"),
SystemMaxPrice: getConfigFloat("system_max_price"),
PriceThreshold: getConfigFloat("price_threshold"),
PriceFeeRate: getConfigFloat("price_fee_rate"),
LevelBonus: types.LevelBonusConfig{ LevelBonus: types.LevelBonusConfig{
Normal: int64(level1Bonus), Normal: int64(level1Bonus),
Gold: int64(level2Bonus), Gold: int64(level2Bonus),
@@ -67,6 +84,17 @@ func (l *AdminGetAgentConfigLogic) AdminGetAgentConfig() (resp *types.AdminGetAg
NormalToGoldRebate: upgradeToGoldRebate, NormalToGoldRebate: upgradeToGoldRebate,
ToDiamondRebate: upgradeToDiamondRebate, 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"), TaxRate: getConfigFloat("tax_rate"),
TaxExemptionAmount: getConfigFloat("tax_exemption_amount"), TaxExemptionAmount: getConfigFloat("tax_exemption_amount"),
}, nil }, nil

View File

@@ -31,10 +31,16 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder(). builder := l.svcCtx.AgentProductConfigModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo) Where("del_state = ?", globalkey.DelStateNo)
// 如果提供了产品ID直接过滤
if req.ProductId != nil { if req.ProductId != nil {
builder = builder.Where("product_id = ?", *req.ProductId) 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 page := req.Page
if page <= 0 { if page <= 0 {
@@ -50,9 +56,19 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置列表失败, %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置列表失败, %v", err)
} }
// 组装响应 // 组装响应(通过 product_id 查询产品名称)
items := make([]types.AgentProductConfigItem, 0, len(configs)) items := make([]types.AgentProductConfigItem, 0, len(configs))
for _, config := range 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 priceThreshold := 0.0
if config.PriceThreshold.Valid { if config.PriceThreshold.Valid {
priceThreshold = config.PriceThreshold.Float64 priceThreshold = config.PriceThreshold.Float64
@@ -66,7 +82,7 @@ func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req
items = append(items, types.AgentProductConfigItem{ items = append(items, types.AgentProductConfigItem{
Id: config.Id, Id: config.Id,
ProductId: config.ProductId, ProductId: config.ProductId,
ProductName: config.ProductName, ProductName: productName,
BasePrice: config.BasePrice, BasePrice: config.BasePrice,
PriceRangeMin: config.BasePrice, // 最低定价等于基础底价 PriceRangeMin: config.BasePrice, // 最低定价等于基础底价
PriceRangeMax: config.SystemMaxPrice, 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) return l.svcCtx.AgentConfigModel.UpdateWithVersion(l.ctx, nil, config)
} }
// 更新各个配置 // 更新等级加成配置
if err := updateConfig("base_price", req.BasePrice); err != nil { if req.LevelBonus != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新基础底价失败, %v", err) 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 { if err := updateConfig("tax_rate", req.TaxRate); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新税率失败, %v", err) 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.BasePrice = req.BasePrice
config.SystemMaxPrice = req.PriceRangeMax 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 { if err := l.svcCtx.AgentProductConfigModel.UpdateWithVersion(l.ctx, nil, config); err != nil {

View File

@@ -2,6 +2,7 @@ package admin_auth
import ( import (
"context" "context"
"os"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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) { func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.AdminLoginResp, err error) {
// 1. 验证验证码 // 1. 验证验证码(开发环境下跳过验证码校验)
if !req.Captcha { if os.Getenv("ENV") != "development" {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha) if !req.Captcha {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha)
}
} }
// 2. 验证用户名和密码 // 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) 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 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) 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 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) 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 isAgentOrder bool
var agentProcessStatus string var agentProcessStatus string
@@ -118,7 +111,6 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD
Status: order.Status, Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"), CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"), UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
IsPromotion: isPromotion,
QueryState: queryState, QueryState: queryState,
IsAgentOrder: isAgentOrder, IsAgentOrder: isAgentOrder,
AgentProcessStatus: agentProcessStatus, AgentProcessStatus: agentProcessStatus,

View File

@@ -54,9 +54,6 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
if req.Status != "" { if req.Status != "" {
builder = builder.Where("status = ?", 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 != "" { if req.CreateTimeStart != "" {
builder = builder.Where("create_time >= ?", req.CreateTimeStart) builder = builder.Where("create_time >= ?", req.CreateTimeStart)
@@ -274,11 +271,6 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
if order.RefundTime.Valid { if order.RefundTime.Valid {
item.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05") 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] { if agentOrderMap[order.Id] {

View File

@@ -7,7 +7,6 @@ import (
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"github.com/pkg/errors" "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) 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 return nil
}) })

View File

@@ -2,14 +2,15 @@ package admin_product
import ( import (
"context" "context"
"database/sql"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"database/sql"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
) )
type AdminCreateProductLogic struct { type AdminCreateProductLogic struct {
@@ -37,14 +38,44 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
SellPrice: req.SellPrice, SellPrice: req.SellPrice,
} }
// 2. 数据库操作 // 2. 数据库操作(使用事务确保产品表和代理产品配置表同步)
result, err := l.svcCtx.ProductModel.Insert(l.ctx, nil, data) 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 { 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) "创建产品失败, 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. 返回结果 // 3. 返回结果
id, _ := result.LastInsertId() return &types.AdminCreateProductResp{Id: productId}, nil
return &types.AdminCreateProductResp{Id: id}, nil
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
) )
type AdminDeleteProductLogic struct { type AdminDeleteProductLogic struct {
@@ -32,11 +33,33 @@ func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProdu
"查找产品失败, err: %v, id: %d", err, req.Id) "查找产品失败, err: %v, id: %d", err, req.Id)
} }
// 2. 执行软删除 // 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
err = l.svcCtx.ProductModel.DeleteSoft(l.ctx, nil, record) 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 { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), return nil, err
"删除产品失败, err: %v, id: %d", err, req.Id)
} }
// 3. 返回结果 // 3. 返回结果

View File

@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
) )
type AdminUpdateProductLogic struct { type AdminUpdateProductLogic struct {
@@ -53,11 +54,20 @@ func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProdu
record.SellPrice = *req.SellPrice record.SellPrice = *req.SellPrice
} }
// 3. 执行更新操作 // 3. 执行更新操作(使用事务确保产品表和代理产品配置表同步)
_, err = l.svcCtx.ProductModel.Update(l.ctx, nil, record) 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 { 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) "更新产品失败, err: %v, req: %+v", err, req)
}
return nil
})
if err != nil {
return nil, err
} }
// 4. 返回结果 // 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" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"os"
"time" "time"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
@@ -51,8 +52,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return nil, errors.Wrapf(xerr.NewErrMsg("必须提供邀请码才能成为代理,请联系平台或代理获取邀请码"), "") return nil, errors.Wrapf(xerr.NewErrMsg("必须提供邀请码才能成为代理,请联系平台或代理获取邀请码"), "")
} }
// 2. 校验验证码 // 2. 校验验证码(开发环境下跳过验证码校验)
if req.Mobile != "18889793585" { if os.Getenv("ENV") != "development" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile) redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey) cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil { if err != nil {
@@ -138,9 +139,9 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
// 4.5 创建代理记录 // 4.5 创建代理记录
newAgent := &model.Agent{ newAgent := &model.Agent{
UserId: userID, UserId: userID,
Level: targetLevel, Level: targetLevel,
Mobile: encryptedMobile, Mobile: encryptedMobile,
} }
if req.Region != "" { if req.Region != "" {
newAgent.Region = sql.NullString{String: req.Region, Valid: true} 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{ relation := &model.AgentRelation{
ParentId: parentAgent.Id, ParentId: parentAgent.Id,
ChildId: agentId, ChildId: agentId,
RelationType: 1, // 直接关系 RelationType: 1, // 直接关系
} }
if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil { 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} 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, "更新团队首领失败") return errors.Wrapf(err, "更新团队首领失败")
} }
} else { } else {
@@ -232,6 +233,19 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return errors.Wrapf(err, "更新邀请码状态失败") 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 return nil
}) })

View File

@@ -6,7 +6,6 @@ import (
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"ycc-server/pkg/lzkit/lzUtils"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -55,8 +54,14 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
} }
// 3. 计算升级费用和返佣 // 3. 计算升级费用和返佣
upgradeFee := l.svcCtx.AgentService.GetUpgradeFee(fromLevel, toLevel) upgradeFee, err := l.svcCtx.AgentService.GetUpgradeFee(l.ctx, fromLevel, toLevel)
rebateAmount := l.svcCtx.AgentService.GetUpgradeRebate(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. 查找原直接上级(用于返佣) // 4. 查找原直接上级(用于返佣)
var rebateAgentId int64 var rebateAgentId int64
@@ -68,18 +73,18 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
rebateAgentId = parent.Id rebateAgentId = parent.Id
} }
// 5. 使用事务处理升级 // 5. 创建升级记录(待支付状态)
var upgradeId int64 var upgradeId int64
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 5.1 创建升级记录 // 5.1 创建升级记录(状态为待支付)
upgradeRecord := &model.AgentUpgrade{ upgradeRecord := &model.AgentUpgrade{
AgentId: agent.Id, AgentId: agent.Id,
FromLevel: fromLevel, FromLevel: fromLevel,
ToLevel: toLevel, ToLevel: toLevel,
UpgradeType: 1, // 自主付费 UpgradeType: 1, // 自主付费
UpgradeFee: upgradeFee, UpgradeFee: upgradeFee,
RebateAmount: rebateAmount, RebateAmount: rebateAmount,
Status: 1, // 待处理 Status: 1, // 待支付1=待支付2=已支付3=已完成4=已取消)
} }
if rebateAgentId > 0 { if rebateAgentId > 0 {
upgradeRecord.RebateAgentId = sql.NullInt64{Int64: rebateAgentId, Valid: true} upgradeRecord.RebateAgentId = sql.NullInt64{Int64: rebateAgentId, Valid: true}
@@ -91,23 +96,7 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
} }
upgradeId, _ = upgradeResult.LastInsertId() 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 return nil
}) })
@@ -115,10 +104,10 @@ func (l *ApplyUpgradeLogic) ApplyUpgrade(req *types.ApplyUpgradeReq) (resp *type
return nil, err return nil, err
} }
// 返回响应(订单号需要从支付回调中获取,这里暂时返回空 // 返回响应(订单号将在支付接口中生成
return &types.ApplyUpgradeResp{ return &types.ApplyUpgradeResp{
UpgradeId: upgradeId, UpgradeId: upgradeId,
OrderNo: "", // 需要从支付回调中获取 OrderNo: "", // 将在支付接口中生成
}, nil }, 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 ( import (
"context" "context"
"database/sql"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"net/url"
"strconv" "strconv"
"strings"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey" "ycc-server/common/globalkey"
"ycc-server/common/tool"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto" "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) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
} }
// 2. 获取系统配置 // 2. 获取产品配置(必须存在)
basePrice, err := l.getConfigFloat("base_price") productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, req.ProductId)
if err != nil { 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)
systemMaxPrice, err := l.getConfigFloat("system_max_price") }
if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品配置失败, productId: %d, %v", req.ProductId, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取系统价格上限配置失败, %v", err)
} }
// 4. 计算实际底价(基础底价 + 等级加成 // 3. 获取等级加成配置
levelBonus := l.getLevelBonus(agentModel.Level) 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) actualBasePrice := basePrice + float64(levelBonus)
systemMaxPrice := productConfig.SystemMaxPrice
// 5. 验证设定价格范围 // 5. 验证设定价格范围
if req.SetPrice < actualBasePrice || req.SetPrice > systemMaxPrice { if req.SetPrice < actualBasePrice || req.SetPrice > systemMaxPrice {
@@ -82,9 +93,18 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
} }
if len(existingLinks) > 0 { 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{ return &types.AgentGeneratingLinkResp{
LinkIdentifier: existingLinks[0].LinkIdentifier, LinkIdentifier: existingLinks[0].LinkIdentifier,
FullLink: shortLink,
}, nil }, nil
} }
@@ -120,39 +140,187 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
ActualBasePrice: actualBasePrice, ActualBasePrice: actualBasePrice,
} }
_, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, agentLink) result, err := l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, agentLink)
if err != nil { 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)
} }
// 获取插入的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{ return &types.AgentGeneratingLinkResp{
LinkIdentifier: encrypted, LinkIdentifier: encrypted,
FullLink: shortLink,
}, nil }, nil
} }
// getLevelBonus 获取等级加成 // getLevelBonus 获取等级加成(从配置表读取)
func (l *GeneratingLinkLogic) getLevelBonus(level int64) int64 { func (l *GeneratingLinkLogic) getLevelBonus(level int64) (int64, error) {
var configKey string
switch level { switch level {
case 1: // 普通 case 1: // 普通
return 6 configKey = "level_1_bonus"
case 2: // 黄金 case 2: // 黄金
return 3 configKey = "level_2_bonus"
case 3: // 钻石 case 3: // 钻石
return 0 configKey = "level_3_bonus"
default: default:
return 0 return 0, nil
} }
}
// getConfigFloat 获取配置值(浮点数)
func (l *GeneratingLinkLogic) getConfigFloat(configKey string) (float64, error) {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey) config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey)
if err != nil { 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) value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil { if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue) 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) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
} }
// 2. 获取系统配置 // 2. 获取等级加成配置(从系统配置表读取)
basePrice, err := l.getConfigFloat("base_price") levelBonus, err := l.getLevelBonus(agentModel.Level)
if err != nil { 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)
}
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)
} }
// 3. 计算等级加成 // 3. 查询所有产品配置
levelBonus := l.getLevelBonus(agentModel.Level)
// 4. 查询所有产品配置
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder() builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
productConfigs, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "") productConfigs, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "")
if err != nil { 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)
} }
// 5. 组装响应数据 // 4. 组装响应数据(通过 product_id 查询产品名称和英文标识)
var respList []types.ProductConfigItem var respList []types.ProductConfigItem
for _, productConfig := range productConfigs { for _, productConfig := range productConfigs {
// 使用产品配置中的基础底价,如果没有则使用系统配置的基础底价 // 通过 product_id 查询产品信息获取产品名称和英文标识
productBasePrice := basePrice product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productConfig.ProductId)
if productConfig.BasePrice > 0 { productName := ""
productBasePrice = productConfig.BasePrice 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) productActualBasePrice := productBasePrice + float64(levelBonus)
// 价格范围:实际底价 ≤ 设定价格 ≤ 系统价格上限(或产品配置的最高价格 // 价格范围:实际底价 ≤ 设定价格 ≤ 产品配置的最高价格
priceRangeMin := productActualBasePrice priceRangeMin := productActualBasePrice
priceRangeMax := systemMaxPrice priceRangeMax := productConfig.SystemMaxPrice
if productConfig.SystemMaxPrice > 0 && productConfig.SystemMaxPrice < systemMaxPrice {
priceRangeMax = productConfig.SystemMaxPrice
}
// 使用产品配置的提价阈值和手续费比例,如果没有则使用系统配置 // 使用产品配置的提价阈值和手续费比例,如果为NULL则使用0
productPriceThreshold := priceThreshold productPriceThreshold := 0.0
if productConfig.PriceThreshold.Valid && productConfig.PriceThreshold.Float64 > 0 { if productConfig.PriceThreshold.Valid {
productPriceThreshold = productConfig.PriceThreshold.Float64 productPriceThreshold = productConfig.PriceThreshold.Float64
} }
productPriceFeeRate := priceFeeRate productPriceFeeRate := 0.0
if productConfig.PriceFeeRate.Valid && productConfig.PriceFeeRate.Float64 > 0 { if productConfig.PriceFeeRate.Valid {
productPriceFeeRate = productConfig.PriceFeeRate.Float64 productPriceFeeRate = productConfig.PriceFeeRate.Float64
} }
respList = append(respList, types.ProductConfigItem{ respList = append(respList, types.ProductConfigItem{
ProductId: productConfig.ProductId, ProductId: productConfig.ProductId,
ProductName: productConfig.ProductName, ProductName: productName,
BasePrice: productBasePrice, ProductEn: productEn,
LevelBonus: float64(levelBonus),
ActualBasePrice: productActualBasePrice, ActualBasePrice: productActualBasePrice,
PriceRangeMin: priceRangeMin, PriceRangeMin: priceRangeMin,
PriceRangeMax: priceRangeMax, PriceRangeMax: priceRangeMax,
@@ -119,29 +109,38 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
}, nil }, nil
} }
// getLevelBonus 获取等级加成 // getLevelBonus 获取等级加成(从配置表读取)
func (l *GetAgentProductConfigLogic) getLevelBonus(level int64) int64 { func (l *GetAgentProductConfigLogic) getLevelBonus(level int64) (int64, error) {
var configKey string
switch level { switch level {
case 1: // 普通 case 1: // 普通
return 6 configKey = "level_1_bonus"
case 2: // 黄金 case 2: // 黄金
return 3 configKey = "level_2_bonus"
case 3: // 钻石 case 3: // 钻石
return 0 configKey = "level_3_bonus"
default: default:
return 0 return 0, nil
} }
}
// getConfigFloat 获取配置值(浮点数)
func (l *GetAgentProductConfigLogic) getConfigFloat(configKey string) (float64, error) {
config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey) config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey)
if err != nil { 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) value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil { if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue) 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 offset := (page - 1) * pageSize
// 4. 查询总数 // 4. 查询总数
total, err := l.svcCtx.AgentCommissionModel.FindCount(l.ctx, builder, "") total, err := l.svcCtx.AgentCommissionModel.FindCount(l.ctx, builder, "id")
if err != nil { 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)
} }
@@ -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{ list = append(list, types.CommissionItem{
Id: commission.Id, Id: commission.Id,
OrderId: commission.OrderId, OrderId: commission.OrderId,
OrderNo: orderNo,
ProductName: productName, ProductName: productName,
Amount: commission.Amount, Amount: commission.Amount,
Status: commission.Status, 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" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"time"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/tool" "ycc-server/common/tool"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"github.com/pkg/errors" "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/svc"
"ycc-server/app/main/api/internal/types" "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. 获取当前代理信息 // 1. 获取当前代理信息
userID, err := ctxdata.GetUidFromCtx(l.ctx) userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil { 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) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
} }
// 2. 为邀请链接生成一个邀请码(每次调用都生成新的,不过期) // 2. 验证邀请码参数
// 这样代理可以分享这个链接,用户通过链接注册时会使用这个邀请码 if req.InviteCode == "" {
var inviteCode string return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不能为空"), "")
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
})
// 3. 查询邀请码是否存在且属于当前代理
inviteCodeRecord, err := l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, req.InviteCode)
if err != nil { 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. 生成邀请链接 // 4. 验证邀请码是否属于当前代理
// 使用配置中的前端域名,如果没有则使用默认值 if !inviteCodeRecord.AgentId.Valid || inviteCodeRecord.AgentId.Int64 != agent.Id {
frontendDomain := l.svcCtx.Config.AdminPromotion.URLDomain return nil, errors.Wrapf(xerr.NewErrMsg("无权使用此邀请码"), "")
if frontendDomain == "" {
frontendDomain = "https://example.com" // 默认值,需要配置
} }
inviteLink := fmt.Sprintf("%s/register?invite_code=%s", frontendDomain, inviteCode)
// 4. 生成二维码URL使用ImageService // 5. 验证邀请码状态
qrCodeUrl := fmt.Sprintf("%s/api/v1/image/qrcode?type=invitation&content=%s", frontendDomain, inviteLink) 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{ return &types.GetInviteLinkResp{
InviteLink: inviteLink, InviteLink: shortLink,
QrCodeUrl: qrCodeUrl,
}, nil }, 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 ( import (
"context" "context"
"sort"
"ycc-server/app/main/model"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/mr"
"ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types" "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) 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{ return &types.GetLinkDataResp{
AgentId: agentLinkModel.AgentId, AgentId: agentLinkModel.AgentId,
ProductId: agentLinkModel.ProductId, ProductId: agentLinkModel.ProductId,
SetPrice: agentLinkModel.SetPrice, SetPrice: agentLinkModel.SetPrice,
ActualBasePrice: agentLinkModel.ActualBasePrice, ActualBasePrice: agentLinkModel.ActualBasePrice,
ProductName: productModel.ProductName, ProductName: productModel.ProductName,
ProductEn: productModel.ProductEn,
SellPrice: agentLinkModel.SetPrice, // 使用代理设定价格作为销售价格
Description: productModel.Description,
Features: features,
}, nil }, nil
} }

View File

@@ -6,6 +6,7 @@ import (
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey" "ycc-server/common/globalkey"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -46,8 +47,14 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
// 2. 构建查询条件 // 2. 构建查询条件
builder := l.svcCtx.AgentRebateModel.SelectBuilder(). builder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo). Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo)
OrderBy("create_time DESC")
// 如果指定了返佣类型则按类型筛选1, 2, 3
if req.RebateType != nil {
builder = builder.Where("rebate_type = ?", *req.RebateType)
}
builder = builder.OrderBy("create_time DESC")
// 3. 分页查询 // 3. 分页查询
page := req.Page page := req.Page
@@ -61,7 +68,7 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
offset := (page - 1) * pageSize offset := (page - 1) * pageSize
// 4. 查询总数 // 4. 查询总数
total, err := l.svcCtx.AgentRebateModel.FindCount(l.ctx, builder, "") total, err := l.svcCtx.AgentRebateModel.FindCount(l.ctx, builder, "id")
if err != nil { 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)
} }
@@ -76,13 +83,41 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t
// 6. 组装响应 // 6. 组装响应
var list []types.RebateItem var list []types.RebateItem
for _, rebate := range rebates { 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{ list = append(list, types.RebateItem{
Id: rebate.Id, Id: rebate.Id,
SourceAgentId: rebate.SourceAgentId, SourceAgentId: rebate.SourceAgentId,
OrderId: rebate.OrderId, SourceAgentMobile: sourceAgentMobile,
RebateType: rebate.RebateType, SourceAgentLevel: sourceAgentLevel,
Amount: rebate.RebateAmount, OrderId: rebate.OrderId,
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"), 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 ( import (
"context" "context"
"time"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"github.com/pkg/errors" "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) 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{ return &types.GetRevenueInfoResp{
Balance: wallet.Balance, Balance: wallet.Balance,
FrozenBalance: wallet.FrozenBalance, FrozenBalance: wallet.FrozenBalance,
TotalEarnings: wallet.TotalEarnings, TotalEarnings: wallet.TotalEarnings,
WithdrawnAmount: wallet.WithdrawnAmount, WithdrawnAmount: wallet.WithdrawnAmount,
CommissionTotal: commissionTotal, // 佣金累计总收益(推广订单获得的佣金)
CommissionToday: commissionToday, // 佣金今日收益
CommissionMonth: commissionMonth, // 佣金本月收益
RebateTotal: rebateTotal, // 返佣累计总收益(包括推广返佣和升级返佣)
RebateToday: rebateToday, // 返佣今日收益
RebateMonth: rebateMonth, // 返佣本月收益
}, nil }, 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 offset := (page - 1) * pageSize
// 4. 查询总数 // 4. 查询总数
total, err := l.svcCtx.AgentRelationModel.FindCount(l.ctx, builder, "") total, err := l.svcCtx.AgentRelationModel.FindCount(l.ctx, builder, "id")
if err != nil { 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)
} }

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 ( import (
"context" "context"
"time"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey" "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) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
} }
// 2. 确定团队首领ID // 2. 递归查询所有下级(直接+间接)
teamLeaderId := agent.Id allSubordinateIds := make(map[int64]bool)
if agent.TeamLeaderId.Valid { directSubordinateIds := make(map[int64]bool)
teamLeaderId = agent.TeamLeaderId.Int64
// 递归函数收集所有下级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(). builder := l.svcCtx.AgentModel.SelectBuilder().
Where(squirrel.Or{ Where(squirrel.Eq{"id": subordinateIds}).
squirrel.Eq{"team_leader_id": teamLeaderId},
squirrel.And{
squirrel.Eq{"id": teamLeaderId},
squirrel.Eq{"level": 3}, // 钻石代理自己也是团队首领
},
}).
Where("del_state = ?", globalkey.DelStateNo) Where("del_state = ?", globalkey.DelStateNo)
teamMembers, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "") 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) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询团队成员失败, %v", err)
} }
// 4. 统计 // 7. 统计
totalCount := int64(len(teamMembers)) totalCount := int64(len(teamMembers)) // 下级总数(不包括自己)
directCount := int64(len(directSubordinateIds))
indirectCount := totalCount - directCount
level1Count := int64(0) // 普通 level1Count := int64(0) // 普通
level2Count := int64(0) // 黄金 level2Count := int64(0) // 黄金
level3Count := int64(0) // 钻石 // 不再统计钻石,因为下级不可能是钻石
// 统计直接下级 todayNewCount := int64(0)
directCount := int64(0) monthNewCount := 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))
}
for _, member := range teamMembers { for _, member := range teamMembers {
// 排除自己 // 统计等级(只统计普通和黄金)
if member.Id == agent.Id {
continue
}
switch member.Level { switch member.Level {
case 1: case 1:
level1Count++ level1Count++
case 2: case 2:
level2Count++ 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{ return &types.TeamStatisticsResp{
TotalCount: totalCount - 1, // 排除自己 TotalCount: totalCount, // 下级总数(不包括自己
DirectCount: directCount, DirectCount: directCount,
IndirectCount: indirectCount, IndirectCount: indirectCount,
ByLevel: types.TeamLevelStats{ GoldCount: level2Count,
Normal: level1Count, NormalCount: level1Count,
Gold: level2Count, TodayNewMembers: todayNewCount,
Diamond: level3Count, MonthNewMembers: monthNewCount,
},
}, nil }, nil
} }

View File

@@ -61,7 +61,7 @@ func (l *GetUpgradeListLogic) GetUpgradeList(req *types.GetUpgradeListReq) (resp
offset := (page - 1) * pageSize offset := (page - 1) * pageSize
// 4. 查询总数 // 4. 查询总数
total, err := l.svcCtx.AgentUpgradeModel.FindCount(l.ctx, builder, "") total, err := l.svcCtx.AgentUpgradeModel.FindCount(l.ctx, builder, "id")
if err != nil { 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)
} }

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 offset := (page - 1) * pageSize
// 4. 查询总数 // 4. 查询总数
total, err := l.svcCtx.AgentWithdrawalModel.FindCount(l.ctx, builder, "") total, err := l.svcCtx.AgentWithdrawalModel.FindCount(l.ctx, builder, "id")
if err != nil { 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)
} }

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" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"os"
"time" "time"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/xerr" "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) 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) redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey) cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil { if err != nil {
@@ -181,7 +182,7 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
// 设置自己为团队首领 // 设置自己为团队首领
newAgent.TeamLeaderId = sql.NullInt64{Int64: agentID, Valid: true} 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, "更新团队首领失败") return errors.Wrapf(err, "更新团队首领失败")
} }
} else { } else {
@@ -217,6 +218,19 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return errors.Wrapf(err, "更新邀请码状态失败") 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 agentLevel = targetLevel
return nil 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" "database/sql"
"ycc-server/app/main/model" "ycc-server/app/main/model"
"ycc-server/common/ctxdata" "ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr" "ycc-server/common/xerr"
"ycc-server/pkg/lzkit/lzUtils" "ycc-server/pkg/lzkit/lzUtils"
@@ -62,17 +63,10 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
return nil, errors.Wrapf(xerr.NewErrMsg("只能升级普通代理为黄金代理"), "") return nil, errors.Wrapf(xerr.NewErrMsg("只能升级普通代理为黄金代理"), "")
} }
// 5. 验证关系:必须是直接下级 // 5. 验证关系:必须是下级(直接或间接)
parent, err := l.svcCtx.AgentService.FindDirectParent(l.ctx, subordinateAgent.Id) isSubordinate := l.isSubordinate(operatorAgent.Id, subordinateAgent.Id)
if err != nil { if !isSubordinate {
if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的下级"), "")
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的直接下级"), "")
}
return nil, errors.Wrapf(err, "查询关系失败")
}
if parent.Id != operatorAgent.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("该代理不是您的直接下级"), "")
} }
// 6. 验证目标等级:只能升级为黄金 // 6. 验证目标等级:只能升级为黄金
@@ -87,12 +81,12 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
upgradeRecord := &model.AgentUpgrade{ upgradeRecord := &model.AgentUpgrade{
AgentId: subordinateAgent.Id, AgentId: subordinateAgent.Id,
FromLevel: 1, // 普通 FromLevel: 1, // 普通
ToLevel: toLevel, ToLevel: toLevel,
UpgradeType: 2, // 钻石升级下级 UpgradeType: 2, // 钻石升级下级
UpgradeFee: 0, // 免费 UpgradeFee: 0, // 免费
RebateAmount: 0, // 无返佣 RebateAmount: 0, // 无返佣
OperatorAgentId: sql.NullInt64{Int64: operatorAgent.Id, Valid: true}, OperatorAgentId: sql.NullInt64{Int64: operatorAgent.Id, Valid: true},
Status: 1, // 待处理 Status: 1, // 待处理
} }
upgradeResult, err := l.svcCtx.AgentUpgradeModel.Insert(transCtx, session, upgradeRecord) upgradeResult, err := l.svcCtx.AgentUpgradeModel.Insert(transCtx, session, upgradeRecord)
@@ -125,3 +119,27 @@ func (l *UpgradeSubordinateLogic) UpgradeSubordinate(req *types.UpgradeSubordina
Success: true, Success: true,
}, nil }, 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) { func (l *GetAppVersionLogic) GetAppVersion() (resp *types.GetAppVersionResp, err error) {
return &types.GetAppVersionResp{ return &types.GetAppVersionResp{
Version: "1.0.0", 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 }, nil
} }

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