Compare commits
43 Commits
51d888c960
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a50b2823b3 | |||
| 079b5b39ef | |||
| 0dada87a54 | |||
| f9ec2e51fd | |||
| 35e9191981 | |||
| a85436950e | |||
| a79c464329 | |||
| 932f2773d4 | |||
| 7506ee718b | |||
| e2aab6af71 | |||
| effe82ce65 | |||
| d818a0a1c7 | |||
| 144d5507dd | |||
| 0a0ca7bf9b | |||
| 3399de0dc5 | |||
| 7f0383b7d6 | |||
| 2312f54e1e | |||
| 28c084f608 | |||
| c9b4c50540 | |||
| fdfb2d76f8 | |||
| 0dc053420a | |||
| 357ac705c7 | |||
| a8b4887571 | |||
| e8da86f1a6 | |||
| 6db59f1dea | |||
| a083fdca46 | |||
| 1ea5ff1d88 | |||
| 5eeb6888e6 | |||
| 6b7eaa6851 | |||
| 98e0c57067 | |||
| 7e0e027013 | |||
| 787f7a303f | |||
| 5e02a56e87 | |||
| e1fd1439d5 | |||
| cae488d88e | |||
| 9fe6a88670 | |||
| d40aa40544 | |||
| fbfcae6c42 | |||
| 4e77c570bf | |||
| 88231da31f | |||
| 888f921343 | |||
| 40afd27d23 | |||
| 7cb7d5b4f4 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,6 +22,9 @@ data/*
|
|||||||
/app/api
|
/app/api
|
||||||
/app/main/api/main
|
/app/main/api/main
|
||||||
/app/main/api/_*
|
/app/main/api/_*
|
||||||
|
__debug_bin.exe
|
||||||
|
**/.../
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
|
||||||
# 文档目录
|
# 文档目录
|
||||||
|
|||||||
BIN
app/main/api/api.exe
Normal file
BIN
app/main/api/api.exe
Normal file
Binary file not shown.
@@ -20,6 +20,10 @@ service main {
|
|||||||
@handler AdminGetAgentList
|
@handler AdminGetAgentList
|
||||||
get /list (AdminGetAgentListReq) returns (AdminGetAgentListResp)
|
get /list (AdminGetAgentListReq) returns (AdminGetAgentListResp)
|
||||||
|
|
||||||
|
// 代理团队树(同钻石团队 + 邀请关系)
|
||||||
|
@handler AdminGetAgentTeamTree
|
||||||
|
get /team/tree (AdminGetAgentTeamTreeReq) returns (AdminGetAgentTeamTreeResp)
|
||||||
|
|
||||||
// 代理审核
|
// 代理审核
|
||||||
@handler AdminAuditAgent
|
@handler AdminAuditAgent
|
||||||
post /audit (AdminAuditAgentReq) returns (AdminAuditAgentResp)
|
post /audit (AdminAuditAgentReq) returns (AdminAuditAgentResp)
|
||||||
@@ -32,6 +36,10 @@ service main {
|
|||||||
@handler AdminGetAgentOrderList
|
@handler AdminGetAgentOrderList
|
||||||
get /order/list (AdminGetAgentOrderListReq) returns (AdminGetAgentOrderListResp)
|
get /order/list (AdminGetAgentOrderListReq) returns (AdminGetAgentOrderListResp)
|
||||||
|
|
||||||
|
// 单笔订单分账视图:同一响应返回佣金 + 返佣(避免前端双请求重复走鉴权)
|
||||||
|
@handler AdminGetAgentOrderSettlement
|
||||||
|
get /order/settlement (AdminGetAgentOrderSettlementReq) returns (AdminGetAgentOrderSettlementResp)
|
||||||
|
|
||||||
// 代理佣金分页查询
|
// 代理佣金分页查询
|
||||||
@handler AdminGetAgentCommissionList
|
@handler AdminGetAgentCommissionList
|
||||||
get /commission/list (AdminGetAgentCommissionListReq) returns (AdminGetAgentCommissionListResp)
|
get /commission/list (AdminGetAgentCommissionListReq) returns (AdminGetAgentCommissionListResp)
|
||||||
@@ -82,6 +90,10 @@ service main {
|
|||||||
// 邀请码列表查询
|
// 邀请码列表查询
|
||||||
@handler AdminGetInviteCodeList
|
@handler AdminGetInviteCodeList
|
||||||
get /invite_code/list (AdminGetInviteCodeListReq) returns (AdminGetInviteCodeListResp)
|
get /invite_code/list (AdminGetInviteCodeListReq) returns (AdminGetInviteCodeListResp)
|
||||||
|
|
||||||
|
// 平台升级代理等级
|
||||||
|
@handler AdminUpgradeAgent
|
||||||
|
post /upgrade/agent (AdminUpgradeAgentReq) returns (AdminUpgradeAgentResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -90,9 +102,12 @@ type (
|
|||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
Mobile *string `form:"mobile,optional"` // 手机号(可选)
|
Mobile *string `form:"mobile,optional"` // 手机号(可选)
|
||||||
|
AgentCode *string `form:"agent_code,optional"` // 代理编码(可选)
|
||||||
Region *string `form:"region,optional"` // 区域(可选)
|
Region *string `form:"region,optional"` // 区域(可选)
|
||||||
Level *int64 `form:"level,optional"` // 等级(可选)
|
Level *int64 `form:"level,optional"` // 等级(可选)
|
||||||
TeamLeaderId *string `form:"team_leader_id,optional"` // 团队首领ID(可选)
|
TeamLeaderId *string `form:"team_leader_id,optional"` // 团队首领ID(可选)
|
||||||
|
AgentId *string `form:"agent_id,optional"` // 代理主键UUID,精确查询;传入时不再按 team_leader 与其它条件组合筛选
|
||||||
|
RealName *string `form:"real_name,optional"` // 实名姓名(可选,模糊匹配已三要素核验记录)
|
||||||
}
|
}
|
||||||
AgentListItem {
|
AgentListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
@@ -101,8 +116,11 @@ type (
|
|||||||
LevelName string `json:"level_name"` // 等级名称
|
LevelName string `json:"level_name"` // 等级名称
|
||||||
Region string `json:"region"` // 区域
|
Region string `json:"region"` // 区域
|
||||||
Mobile string `json:"mobile"` // 手机号
|
Mobile string `json:"mobile"` // 手机号
|
||||||
|
RealName string `json:"real_name"` // 姓名(来自实名表)
|
||||||
|
IdCard string `json:"id_card"` // 身份证(解密后明文返回)
|
||||||
WechatId string `json:"wechat_id"` // 微信号
|
WechatId string `json:"wechat_id"` // 微信号
|
||||||
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
TeamLeaderId string `json:"team_leader_id"` // 团队首领ID
|
||||||
|
SubordinateCount int64 `json:"subordinate_count"` // 直属下级代理数(team_leader_id 为本代理 id)
|
||||||
AgentCode int64 `json:"agent_code"`
|
AgentCode int64 `json:"agent_code"`
|
||||||
Balance float64 `json:"balance"` // 钱包余额
|
Balance float64 `json:"balance"` // 钱包余额
|
||||||
TotalEarnings float64 `json:"total_earnings"` // 累计收益
|
TotalEarnings float64 `json:"total_earnings"` // 累计收益
|
||||||
@@ -115,6 +133,21 @@ type (
|
|||||||
Total int64 `json:"total"` // 总数
|
Total int64 `json:"total"` // 总数
|
||||||
Items []AgentListItem `json:"items"` // 列表数据
|
Items []AgentListItem `json:"items"` // 列表数据
|
||||||
}
|
}
|
||||||
|
AdminGetAgentTeamTreeReq {
|
||||||
|
AgentId string `form:"agent_id"` // 锚点代理ID(UUID)
|
||||||
|
}
|
||||||
|
AgentTeamTreeNode {
|
||||||
|
Id string `json:"id"`
|
||||||
|
AgentCode int64 `json:"agent_code"`
|
||||||
|
Level int64 `json:"level"`
|
||||||
|
LevelName string `json:"level_name"`
|
||||||
|
Mobile string `json:"mobile"`
|
||||||
|
IsAnchor bool `json:"is_anchor"`
|
||||||
|
Children []AgentTeamTreeNode `json:"children"`
|
||||||
|
}
|
||||||
|
AdminGetAgentTeamTreeResp {
|
||||||
|
Root AgentTeamTreeNode `json:"root"`
|
||||||
|
}
|
||||||
// 代理审核
|
// 代理审核
|
||||||
AdminAuditAgentReq {
|
AdminAuditAgentReq {
|
||||||
AuditId int64 `json:"audit_id"` // 审核记录ID
|
AuditId int64 `json:"audit_id"` // 审核记录ID
|
||||||
@@ -129,12 +162,14 @@ type (
|
|||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||||
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
ProductId *string `form:"product_id,optional"` // 产品ID(可选)
|
ProductId *string `form:"product_id,optional"` // 产品ID(可选)
|
||||||
LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选)
|
LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选)
|
||||||
}
|
}
|
||||||
AgentLinkListItem {
|
AgentLinkListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
ProductId string `json:"product_id"` // 产品ID
|
ProductId string `json:"product_id"` // 产品ID
|
||||||
ProductName string `json:"product_name"` // 产品名称
|
ProductName string `json:"product_name"` // 产品名称
|
||||||
SetPrice float64 `json:"set_price"` // 设定价格
|
SetPrice float64 `json:"set_price"` // 设定价格
|
||||||
@@ -151,13 +186,18 @@ type (
|
|||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||||
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
||||||
|
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||||
ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选)
|
ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选)
|
||||||
|
OrderStatus *string `form:"order_status,optional"` // 订单状态(可选):pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||||
}
|
}
|
||||||
AgentOrderListItem {
|
AgentOrderListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
OrderId string `json:"order_id"` // 订单ID
|
OrderId string `json:"order_id"` // 订单ID
|
||||||
|
OrderNo string `json:"order_no"` // 商户订单号
|
||||||
ProductId string `json:"product_id"` // 产品ID
|
ProductId string `json:"product_id"` // 产品ID
|
||||||
ProductName string `json:"product_name"` // 产品名称
|
ProductName string `json:"product_name"` // 产品名称
|
||||||
OrderAmount float64 `json:"order_amount"` // 订单金额
|
OrderAmount float64 `json:"order_amount"` // 订单金额
|
||||||
@@ -165,25 +205,36 @@ type (
|
|||||||
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
|
ActualBasePrice float64 `json:"actual_base_price"` // 实际底价
|
||||||
PriceCost float64 `json:"price_cost"` // 提价成本
|
PriceCost float64 `json:"price_cost"` // 提价成本
|
||||||
AgentProfit float64 `json:"agent_profit"` // 代理收益
|
AgentProfit float64 `json:"agent_profit"` // 代理收益
|
||||||
ProcessStatus int64 `json:"process_status"` // 处理状态
|
ProcessStatus int64 `json:"process_status"` // 处理状态(保留用于筛选,前端不显示)
|
||||||
|
OrderStatus string `json:"order_status"` // 订单状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
}
|
}
|
||||||
AdminGetAgentOrderListResp {
|
AdminGetAgentOrderListResp {
|
||||||
Total int64 `json:"total"` // 总数
|
Total int64 `json:"total"` // 总数
|
||||||
Items []AgentOrderListItem `json:"items"` // 列表数据
|
Items []AgentOrderListItem `json:"items"` // 列表数据
|
||||||
}
|
}
|
||||||
|
AdminGetAgentOrderSettlementReq {
|
||||||
|
OrderNo string `form:"order_no"` // 商户订单号
|
||||||
|
}
|
||||||
|
AdminGetAgentOrderSettlementResp {
|
||||||
|
Commissions []AgentCommissionListItem `json:"commissions"` // 本单销售佣金
|
||||||
|
Rebates []AgentRebateListItem `json:"rebates"` // 本单返佣
|
||||||
|
}
|
||||||
// 代理佣金分页查询
|
// 代理佣金分页查询
|
||||||
AdminGetAgentCommissionListReq {
|
AdminGetAgentCommissionListReq {
|
||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||||
OrderId *string `form:"order_id,optional"` // 订单ID(可选)
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||||
|
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||||
}
|
}
|
||||||
AgentCommissionListItem {
|
AgentCommissionListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
OrderId string `json:"order_id"` // 订单ID
|
OrderId string `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"` // 状态
|
Status int64 `json:"status"` // 状态
|
||||||
@@ -195,19 +246,26 @@ type (
|
|||||||
}
|
}
|
||||||
// 代理返佣分页查询
|
// 代理返佣分页查询
|
||||||
AdminGetAgentRebateListReq {
|
AdminGetAgentRebateListReq {
|
||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||||
SourceAgentId *string `form:"source_agent_id,optional"` // 来源代理ID(可选)
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
SourceAgentCode *string `form:"source_agent_code,optional"` // 来源代理编号(可选)
|
||||||
|
OrderNo *string `form:"order_no,optional"` // 商户订单号(可选)
|
||||||
|
RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选)
|
||||||
|
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
||||||
}
|
}
|
||||||
AgentRebateListItem {
|
AgentRebateListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
AgentId string `json:"agent_id"` // 获得返佣的代理ID
|
||||||
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
AgentCode int64 `json:"agent_code"` // 获得返佣代理编号
|
||||||
OrderId string `json:"order_id"` // 订单ID
|
SourceAgentId string `json:"source_agent_id"` // 来源代理ID
|
||||||
|
SourceAgentCode int64 `json:"source_agent_code"` // 来源代理编号
|
||||||
|
OrderId string `json:"order_id"` // 订单ID
|
||||||
|
OrderNo string `json:"order_no"` // 商户订单号
|
||||||
RebateType int64 `json:"rebate_type"` // 返佣类型
|
RebateType int64 `json:"rebate_type"` // 返佣类型
|
||||||
Amount float64 `json:"amount"` // 金额
|
Amount float64 `json:"amount"` // 金额
|
||||||
|
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
}
|
}
|
||||||
AdminGetAgentRebateListResp {
|
AdminGetAgentRebateListResp {
|
||||||
@@ -219,12 +277,14 @@ type (
|
|||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
||||||
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
UpgradeType *int64 `form:"upgrade_type,optional"` // 升级类型(可选)
|
UpgradeType *int64 `form:"upgrade_type,optional"` // 升级类型(可选)
|
||||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
Status *int64 `form:"status,optional"` // 状态(可选)
|
||||||
}
|
}
|
||||||
AgentUpgradeListItem {
|
AgentUpgradeListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
FromLevel int64 `json:"from_level"` // 原等级
|
FromLevel int64 `json:"from_level"` // 原等级
|
||||||
ToLevel int64 `json:"to_level"` // 目标等级
|
ToLevel int64 `json:"to_level"` // 目标等级
|
||||||
UpgradeType int64 `json:"upgrade_type"` // 升级类型
|
UpgradeType int64 `json:"upgrade_type"` // 升级类型
|
||||||
@@ -239,24 +299,32 @@ type (
|
|||||||
}
|
}
|
||||||
// 代理提现分页查询
|
// 代理提现分页查询
|
||||||
AdminGetAgentWithdrawalListReq {
|
AdminGetAgentWithdrawalListReq {
|
||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
AgentId *string `form:"agent_id,optional"` // 代理ID(可选)
|
AgentId *string `form:"agent_id,optional"` // 代理主键 UUID(弹窗内按代理筛选时传入)
|
||||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
AgentCode *string `form:"agent_code,optional"` // 代理编号(可选)
|
||||||
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选)
|
WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选,模糊匹配)
|
||||||
|
Status string `form:"status,optional"` // 状态(可选):1=待审核,2=已通过,3=已拒绝,5=已打款
|
||||||
|
WithdrawalType string `form:"withdrawal_type,optional"` // 提现方式(可选):1=支付宝,2=银行卡
|
||||||
|
PayeeAccount *string `form:"payee_account,optional"` // 收款账户(可选,模糊)
|
||||||
|
PayeeName *string `form:"payee_name,optional"` // 收款人(可选,模糊)
|
||||||
}
|
}
|
||||||
AgentWithdrawalListItem {
|
AgentWithdrawalListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
Amount float64 `json:"amount"` // 金额
|
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
||||||
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
Amount float64 `json:"amount"` // 金额
|
||||||
ActualAmount float64 `json:"actual_amount"` // 实际到账金额
|
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
||||||
Status int64 `json:"status"` // 状态
|
ActualAmount float64 `json:"actual_amount"` // 实际到账金额
|
||||||
PayeeAccount string `json:"payee_account"` // 收款账户
|
Status int64 `json:"status"` // 状态
|
||||||
PayeeName string `json:"payee_name"` // 收款人姓名
|
WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡
|
||||||
Remark string `json:"remark"` // 备注
|
PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号)
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
PayeeName string `json:"payee_name"` // 收款人姓名
|
||||||
|
BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时使用)
|
||||||
|
BankName string `json:"bank_name"` // 开户行名称(银行卡提现时使用)
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
}
|
}
|
||||||
AdminGetAgentWithdrawalListResp {
|
AdminGetAgentWithdrawalListResp {
|
||||||
Total int64 `json:"total"` // 总数
|
Total int64 `json:"total"` // 总数
|
||||||
@@ -281,6 +349,7 @@ type (
|
|||||||
AgentRealNameListItem {
|
AgentRealNameListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
Name string `json:"name"` // 姓名
|
Name string `json:"name"` // 姓名
|
||||||
IdCard string `json:"id_card"` // 身份证号
|
IdCard string `json:"id_card"` // 身份证号
|
||||||
Mobile string `json:"mobile"` // 手机号
|
Mobile string `json:"mobile"` // 手机号
|
||||||
@@ -399,20 +468,21 @@ type (
|
|||||||
AdminGetInviteCodeListReq {
|
AdminGetInviteCodeListReq {
|
||||||
Page int64 `form:"page"` // 页码
|
Page int64 `form:"page"` // 页码
|
||||||
PageSize int64 `form:"pageSize"` // 每页数量
|
PageSize int64 `form:"pageSize"` // 每页数量
|
||||||
Code *string `form:"code,optional"` // 邀请码(可选)
|
Code *string `form:"code,optional"` // 邀请码(可选,模糊匹配)
|
||||||
AgentId *string `form:"agent_id,optional"` // 发放代理ID(可选,NULL表示平台发放)
|
|
||||||
TargetLevel *int64 `form:"target_level,optional"` // 目标等级(可选)
|
TargetLevel *int64 `form:"target_level,optional"` // 目标等级(可选)
|
||||||
Status *int64 `form:"status,optional"` // 状态(可选)
|
Status string `form:"status,optional"` // 状态(可选):0=未使用,1=已使用,2=已失效
|
||||||
}
|
}
|
||||||
InviteCodeListItem {
|
InviteCodeListItem {
|
||||||
Id string `json:"id"` // 主键
|
Id string `json:"id"` // 主键
|
||||||
Code string `json:"code"` // 邀请码
|
Code string `json:"code"` // 邀请码
|
||||||
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
AgentId string `json:"agent_id"` // 发放代理ID(0表示平台发放)
|
||||||
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
AgentCode int64 `json:"agent_code"` // 发放代理编号(平台为0)
|
||||||
TargetLevel int64 `json:"target_level"` // 目标等级
|
AgentMobile string `json:"agent_mobile"` // 发放代理手机号
|
||||||
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
TargetLevel int64 `json:"target_level"` // 目标等级
|
||||||
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
Status int64 `json:"status"` // 状态:0=未使用,1=已使用,2=已失效
|
||||||
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
UsedUserId string `json:"used_user_id"` // 使用用户ID
|
||||||
|
UsedAgentId string `json:"used_agent_id"` // 使用代理ID
|
||||||
|
UsedAgentCode int64 `json:"used_agent_code"` // 使用代理编号
|
||||||
UsedTime string `json:"used_time"` // 使用时间
|
UsedTime string `json:"used_time"` // 使用时间
|
||||||
ExpireTime string `json:"expire_time"` // 过期时间
|
ExpireTime string `json:"expire_time"` // 过期时间
|
||||||
Remark string `json:"remark"` // 备注
|
Remark string `json:"remark"` // 备注
|
||||||
@@ -422,5 +492,13 @@ type (
|
|||||||
Total int64 `json:"total"` // 总数
|
Total int64 `json:"total"` // 总数
|
||||||
Items []InviteCodeListItem `json:"items"` // 列表数据
|
Items []InviteCodeListItem `json:"items"` // 列表数据
|
||||||
}
|
}
|
||||||
|
// 平台升级代理等级
|
||||||
|
AdminUpgradeAgentReq {
|
||||||
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
|
ToLevel int64 `json:"to_level"` // 目标等级:2=黄金,3=钻石
|
||||||
|
}
|
||||||
|
AdminUpgradeAgentResp {
|
||||||
|
Success bool `json:"success"` // 是否成功
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ info (
|
|||||||
|
|
||||||
// 功能管理接口
|
// 功能管理接口
|
||||||
@server (
|
@server (
|
||||||
prefix: /api/v1/admin/feature
|
prefix: /api/v1/admin/feature
|
||||||
group: admin_feature
|
group: admin_feature
|
||||||
middleware: AdminAuthInterceptor
|
middleware: AdminAuthInterceptor
|
||||||
)
|
)
|
||||||
service main {
|
service main {
|
||||||
@@ -45,27 +45,31 @@ service main {
|
|||||||
type (
|
type (
|
||||||
// 创建功能请求
|
// 创建功能请求
|
||||||
AdminCreateFeatureReq {
|
AdminCreateFeatureReq {
|
||||||
ApiId string `json:"api_id"` // API标识
|
ApiId string `json:"api_id"` // API标识
|
||||||
Name string `json:"name"` // 描述
|
Name string `json:"name"` // 描述
|
||||||
|
WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元)
|
||||||
|
CostPrice *float64 `json:"cost_price,optional"` // 天远API调用成本价(单位:元)
|
||||||
}
|
}
|
||||||
// 创建功能响应
|
// 创建功能响应
|
||||||
AdminCreateFeatureResp {
|
AdminCreateFeatureResp {
|
||||||
Id string `json:"id"` // 功能ID
|
Id string `json:"id"` // 功能ID
|
||||||
}
|
}
|
||||||
// 更新功能请求
|
// 更新功能请求
|
||||||
AdminUpdateFeatureReq {
|
AdminUpdateFeatureReq {
|
||||||
Id string `path:"id"` // 功能ID
|
Id string `path:"id"` // 功能ID
|
||||||
ApiId *string `json:"api_id,optional"` // API标识
|
ApiId *string `json:"api_id,optional"` // API标识
|
||||||
Name *string `json:"name,optional"` // 描述
|
Name *string `json:"name,optional"` // 描述
|
||||||
}
|
WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元)
|
||||||
|
CostPrice *float64 `json:"cost_price,optional"` // 天远API调用成本价(单位:元)
|
||||||
|
}
|
||||||
// 更新功能响应
|
// 更新功能响应
|
||||||
AdminUpdateFeatureResp {
|
AdminUpdateFeatureResp {
|
||||||
Success bool `json:"success"` // 是否成功
|
Success bool `json:"success"` // 是否成功
|
||||||
}
|
}
|
||||||
// 删除功能请求
|
// 删除功能请求
|
||||||
AdminDeleteFeatureReq {
|
AdminDeleteFeatureReq {
|
||||||
Id string `path:"id"` // 功能ID
|
Id string `path:"id"` // 功能ID
|
||||||
}
|
}
|
||||||
// 删除功能响应
|
// 删除功能响应
|
||||||
AdminDeleteFeatureResp {
|
AdminDeleteFeatureResp {
|
||||||
Success bool `json:"success"` // 是否成功
|
Success bool `json:"success"` // 是否成功
|
||||||
@@ -78,12 +82,14 @@ type (
|
|||||||
Name *string `form:"name,optional"` // 描述
|
Name *string `form:"name,optional"` // 描述
|
||||||
}
|
}
|
||||||
// 功能列表项
|
// 功能列表项
|
||||||
FeatureListItem {
|
FeatureListItem {
|
||||||
Id string `json:"id"` // 功能ID
|
Id string `json:"id"` // 功能ID
|
||||||
ApiId string `json:"api_id"` // API标识
|
ApiId string `json:"api_id"` // API标识
|
||||||
Name string `json:"name"` // 描述
|
Name string `json:"name"` // 描述
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元)
|
||||||
UpdateTime string `json:"update_time"` // 更新时间
|
CostPrice float64 `json:"cost_price"` // 天远API调用成本价(单位:元)
|
||||||
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
}
|
}
|
||||||
// 获取功能列表响应
|
// 获取功能列表响应
|
||||||
AdminGetFeatureListResp {
|
AdminGetFeatureListResp {
|
||||||
@@ -91,38 +97,40 @@ type (
|
|||||||
Items []FeatureListItem `json:"items"` // 列表数据
|
Items []FeatureListItem `json:"items"` // 列表数据
|
||||||
}
|
}
|
||||||
// 获取功能详情请求
|
// 获取功能详情请求
|
||||||
AdminGetFeatureDetailReq {
|
AdminGetFeatureDetailReq {
|
||||||
Id string `path:"id"` // 功能ID
|
Id string `path:"id"` // 功能ID
|
||||||
}
|
}
|
||||||
// 获取功能详情响应
|
// 获取功能详情响应
|
||||||
AdminGetFeatureDetailResp {
|
AdminGetFeatureDetailResp {
|
||||||
Id string `json:"id"` // 功能ID
|
Id string `json:"id"` // 功能ID
|
||||||
ApiId string `json:"api_id"` // API标识
|
ApiId string `json:"api_id"` // API标识
|
||||||
Name string `json:"name"` // 描述
|
Name string `json:"name"` // 描述
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元)
|
||||||
UpdateTime string `json:"update_time"` // 更新时间
|
CostPrice float64 `json:"cost_price"` // 天远API调用成本价(单位:元)
|
||||||
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
}
|
}
|
||||||
// 配置功能示例数据请求
|
// 配置功能示例数据请求
|
||||||
AdminConfigFeatureExampleReq {
|
AdminConfigFeatureExampleReq {
|
||||||
FeatureId string `json:"feature_id"` // 功能ID
|
FeatureId string `json:"feature_id"` // 功能ID
|
||||||
Data string `json:"data"` // 示例数据JSON
|
Data string `json:"data"` // 示例数据JSON
|
||||||
}
|
}
|
||||||
// 配置功能示例数据响应
|
// 配置功能示例数据响应
|
||||||
AdminConfigFeatureExampleResp {
|
AdminConfigFeatureExampleResp {
|
||||||
Success bool `json:"success"` // 是否成功
|
Success bool `json:"success"` // 是否成功
|
||||||
}
|
}
|
||||||
// 查看功能示例数据请求
|
// 查看功能示例数据请求
|
||||||
AdminGetFeatureExampleReq {
|
AdminGetFeatureExampleReq {
|
||||||
FeatureId string `path:"feature_id"` // 功能ID
|
FeatureId string `path:"feature_id"` // 功能ID
|
||||||
}
|
}
|
||||||
// 查看功能示例数据响应
|
// 查看功能示例数据响应
|
||||||
AdminGetFeatureExampleResp {
|
AdminGetFeatureExampleResp {
|
||||||
Id string `json:"id"` // 示例数据ID
|
Id string `json:"id"` // 示例数据ID
|
||||||
FeatureId string `json:"feature_id"` // 功能ID
|
FeatureId string `json:"feature_id"` // 功能ID
|
||||||
ApiId string `json:"api_id"` // API标识
|
ApiId string `json:"api_id"` // API标识
|
||||||
Data string `json:"data"` // 示例数据JSON
|
Data string `json:"data"` // 示例数据JSON
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
UpdateTime string `json:"update_time"` // 更新时间
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ type (
|
|||||||
ProductEn string `json:"product_en"` // 英文名
|
ProductEn string `json:"product_en"` // 英文名
|
||||||
Description string `json:"description"` // 描述
|
Description string `json:"description"` // 描述
|
||||||
Notes string `json:"notes,optional"` // 备注
|
Notes string `json:"notes,optional"` // 备注
|
||||||
CostPrice float64 `json:"cost_price"` // 成本
|
|
||||||
SellPrice float64 `json:"sell_price"` // 售价
|
SellPrice float64 `json:"sell_price"` // 售价
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,6 @@ type (
|
|||||||
ProductEn *string `json:"product_en,optional"` // 英文名
|
ProductEn *string `json:"product_en,optional"` // 英文名
|
||||||
Description *string `json:"description,optional"` // 描述
|
Description *string `json:"description,optional"` // 描述
|
||||||
Notes *string `json:"notes,optional"` // 备注
|
Notes *string `json:"notes,optional"` // 备注
|
||||||
CostPrice *float64 `json:"cost_price,optional"` // 成本
|
|
||||||
SellPrice *float64 `json:"sell_price,optional"` // 售价
|
SellPrice *float64 `json:"sell_price,optional"` // 售价
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +97,7 @@ type (
|
|||||||
ProductEn string `json:"product_en"` // 英文名
|
ProductEn string `json:"product_en"` // 英文名
|
||||||
Description string `json:"description"` // 描述
|
Description string `json:"description"` // 描述
|
||||||
Notes string `json:"notes"` // 备注
|
Notes string `json:"notes"` // 备注
|
||||||
CostPrice float64 `json:"cost_price"` // 成本
|
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||||
SellPrice float64 `json:"sell_price"` // 售价
|
SellPrice float64 `json:"sell_price"` // 售价
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
UpdateTime string `json:"update_time"` // 更新时间
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
@@ -123,7 +121,7 @@ type (
|
|||||||
ProductEn string `json:"product_en"` // 英文名
|
ProductEn string `json:"product_en"` // 英文名
|
||||||
Description string `json:"description"` // 描述
|
Description string `json:"description"` // 描述
|
||||||
Notes string `json:"notes"` // 备注
|
Notes string `json:"notes"` // 备注
|
||||||
CostPrice float64 `json:"cost_price"` // 成本
|
CostPrice float64 `json:"cost_price"` // 成本(由关联模块成本汇总,只读)
|
||||||
SellPrice float64 `json:"sell_price"` // 售价
|
SellPrice float64 `json:"sell_price"` // 售价
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
UpdateTime string `json:"update_time"` // 更新时间
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
@@ -141,6 +139,7 @@ type (
|
|||||||
FeatureId string `json:"feature_id"` // 功能ID
|
FeatureId string `json:"feature_id"` // 功能ID
|
||||||
ApiId string `json:"api_id"` // API标识
|
ApiId string `json:"api_id"` // API标识
|
||||||
Name string `json:"name"` // 功能描述
|
Name string `json:"name"` // 功能描述
|
||||||
|
CostPrice float64 `json:"cost_price"` // 模块成本价(元,来自 feature)
|
||||||
Sort int64 `json:"sort"` // 排序
|
Sort int64 `json:"sort"` // 排序
|
||||||
Enable int64 `json:"enable"` // 是否启用
|
Enable int64 `json:"enable"` // 是否启用
|
||||||
IsImportant int64 `json:"is_important"` // 是否重要
|
IsImportant int64 `json:"is_important"` // 是否重要
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ service main {
|
|||||||
@handler AdminGetQueryDetailByOrderId
|
@handler AdminGetQueryDetailByOrderId
|
||||||
get /detail/:order_id (AdminGetQueryDetailByOrderIdReq) returns (AdminGetQueryDetailByOrderIdResp)
|
get /detail/:order_id (AdminGetQueryDetailByOrderIdReq) returns (AdminGetQueryDetailByOrderIdResp)
|
||||||
|
|
||||||
|
@doc "生成报告分享链接(供后台预览 H5)"
|
||||||
|
@handler AdminGenerateQueryShareLink
|
||||||
|
post /generate_share_link (AdminGenerateQueryShareLinkReq) returns (QueryGenerateShareLinkResp)
|
||||||
|
|
||||||
@doc "获取清理日志列表"
|
@doc "获取清理日志列表"
|
||||||
@handler AdminGetQueryCleanupLogList
|
@handler AdminGetQueryCleanupLogList
|
||||||
get /cleanup/logs (AdminGetQueryCleanupLogListReq) returns (AdminGetQueryCleanupLogListResp)
|
get /cleanup/logs (AdminGetQueryCleanupLogListReq) returns (AdminGetQueryCleanupLogListResp)
|
||||||
@@ -37,6 +41,10 @@ service main {
|
|||||||
OrderId string `path:"order_id"`
|
OrderId string `path:"order_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AdminGenerateQueryShareLinkReq {
|
||||||
|
OrderId string `json:"order_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type AdminGetQueryDetailByOrderIdResp {
|
type AdminGetQueryDetailByOrderIdResp {
|
||||||
Id string `json:"id"` // 主键ID
|
Id string `json:"id"` // 主键ID
|
||||||
OrderId string `json:"order_id"` // 订单ID
|
OrderId string `json:"order_id"` // 订单ID
|
||||||
|
|||||||
93
app/main/api/desc/admin/dashboard.api
Normal file
93
app/main/api/desc/admin/dashboard.api
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "后台统计面板服务"
|
||||||
|
desc: "后台统计面板相关接口"
|
||||||
|
author: "team"
|
||||||
|
version: "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 统计面板接口
|
||||||
|
// ============================================
|
||||||
|
@server (
|
||||||
|
prefix: /api/v1/admin/dashboard
|
||||||
|
group: admin_dashboard
|
||||||
|
middleware: AdminAuthInterceptor
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
// 获取统计面板数据
|
||||||
|
@handler AdminGetDashboardStatistics
|
||||||
|
get /statistics returns (AdminGetDashboardStatisticsResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// 统计面板响应
|
||||||
|
AdminGetDashboardStatisticsResp {
|
||||||
|
// 订单统计
|
||||||
|
OrderStats AdminOrderStatistics `json:"order_stats"`
|
||||||
|
// 营收统计
|
||||||
|
RevenueStats AdminRevenueStatistics `json:"revenue_stats"`
|
||||||
|
// 代理统计
|
||||||
|
AgentStats AdminAgentStatistics `json:"agent_stats"`
|
||||||
|
// 利润统计
|
||||||
|
ProfitStats AdminProfitStatistics `json:"profit_stats"`
|
||||||
|
// 订单趋势(最近7天)
|
||||||
|
OrderTrend []AdminTrendData `json:"order_trend"`
|
||||||
|
// 营收趋势(最近7天)
|
||||||
|
RevenueTrend []AdminTrendData `json:"revenue_trend"`
|
||||||
|
}
|
||||||
|
// 订单统计
|
||||||
|
AdminOrderStatistics {
|
||||||
|
TodayCount int64 `json:"today_count"` // 今日订单数
|
||||||
|
MonthCount int64 `json:"month_count"` // 当月订单数
|
||||||
|
TotalCount int64 `json:"total_count"` // 总订单数
|
||||||
|
YesterdayCount int64 `json:"yesterday_count"` // 昨日订单数
|
||||||
|
ChangeRate float64 `json:"change_rate"` // 变化率(百分比)
|
||||||
|
}
|
||||||
|
// 营收统计
|
||||||
|
AdminRevenueStatistics {
|
||||||
|
TodayAmount float64 `json:"today_amount"` // 今日营收
|
||||||
|
MonthAmount float64 `json:"month_amount"` // 当月营收
|
||||||
|
TotalAmount float64 `json:"total_amount"` // 总营收
|
||||||
|
YesterdayAmount float64 `json:"yesterday_amount"` // 昨日营收
|
||||||
|
ChangeRate float64 `json:"change_rate"` // 变化率(百分比)
|
||||||
|
}
|
||||||
|
// 代理统计
|
||||||
|
AdminAgentStatistics {
|
||||||
|
TotalCount int64 `json:"total_count"` // 代理总数
|
||||||
|
TodayNew int64 `json:"today_new"` // 今日新增
|
||||||
|
MonthNew int64 `json:"month_new"` // 当月新增
|
||||||
|
}
|
||||||
|
// 利润统计
|
||||||
|
AdminProfitStatistics {
|
||||||
|
TodayProfit float64 `json:"today_profit"` // 今日利润
|
||||||
|
MonthProfit float64 `json:"month_profit"` // 当月利润
|
||||||
|
TotalProfit float64 `json:"total_profit"` // 总利润
|
||||||
|
TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率
|
||||||
|
MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率
|
||||||
|
TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率
|
||||||
|
// 今日明细
|
||||||
|
TodayDetail AdminProfitDetail `json:"today_detail"`
|
||||||
|
// 当月明细
|
||||||
|
MonthDetail AdminProfitDetail `json:"month_detail"`
|
||||||
|
// 总计明细
|
||||||
|
TotalDetail AdminProfitDetail `json:"total_detail"`
|
||||||
|
}
|
||||||
|
// 利润明细
|
||||||
|
AdminProfitDetail {
|
||||||
|
Revenue float64 `json:"revenue"` // 营收
|
||||||
|
Commission float64 `json:"commission"` // 佣金
|
||||||
|
Rebate float64 `json:"rebate"` // 返利
|
||||||
|
CompanyTax float64 `json:"company_tax"` // 税务成本
|
||||||
|
ApiCost float64 `json:"api_cost"` // API调用成本
|
||||||
|
TaxIncome float64 `json:"tax_income"` // 提现收税
|
||||||
|
Profit float64 `json:"profit"` // 利润
|
||||||
|
ProfitRate float64 `json:"profit_rate"` // 利润率
|
||||||
|
}
|
||||||
|
// 趋势数据
|
||||||
|
AdminTrendData {
|
||||||
|
Date string `json:"date"` // 日期(格式:MM-DD)
|
||||||
|
Value float64 `json:"value"` // 数值
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -39,6 +39,10 @@ service main {
|
|||||||
@doc "重新执行代理处理"
|
@doc "重新执行代理处理"
|
||||||
@handler AdminRetryAgentProcess
|
@handler AdminRetryAgentProcess
|
||||||
post /retry-agent-process/:id (AdminRetryAgentProcessReq) returns (AdminRetryAgentProcessResp)
|
post /retry-agent-process/:id (AdminRetryAgentProcessReq) returns (AdminRetryAgentProcessResp)
|
||||||
|
|
||||||
|
@doc "xpay补发货(查微信单并到账)"
|
||||||
|
@handler AdminXpayDeliver
|
||||||
|
post /xpay-deliver/:id (AdminXpayDeliverReq) returns (AdminXpayDeliverResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -53,6 +57,8 @@ 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-支付失败
|
||||||
|
IsAgentOrder *bool `form:"is_agent_order,optional"` // 是否代理订单
|
||||||
|
AgentCode string `form:"agent_code,optional"` // 代理编号
|
||||||
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"` // 支付时间开始
|
||||||
@@ -79,7 +85,9 @@ 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"` // 退款时间
|
||||||
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
|
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
|
||||||
|
AgentCode string `json:"agent_code"` // 代理编号
|
||||||
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理
|
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理
|
||||||
}
|
}
|
||||||
// 详情请求
|
// 详情请求
|
||||||
@@ -165,4 +173,15 @@ type (
|
|||||||
Message string `json:"message"` // 执行结果消息
|
Message string `json:"message"` // 执行结果消息
|
||||||
ProcessedAt string `json:"processed_at"` // 处理时间
|
ProcessedAt string `json:"processed_at"` // 处理时间
|
||||||
}
|
}
|
||||||
|
// xpay 补发货
|
||||||
|
AdminXpayDeliverReq {
|
||||||
|
Id string `path:"id"` // 订单ID
|
||||||
|
}
|
||||||
|
AdminXpayDeliverResp {
|
||||||
|
Credited bool `json:"credited"`
|
||||||
|
Notified bool `json:"notified"`
|
||||||
|
WechatDetail string `json:"wechat_detail"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type (
|
|||||||
AdminGetPlatformUserListReq {
|
AdminGetPlatformUserListReq {
|
||||||
Page int64 `form:"page,default=1"` // 页码
|
Page int64 `form:"page,default=1"` // 页码
|
||||||
PageSize int64 `form:"pageSize,default=20"` // 每页数量
|
PageSize int64 `form:"pageSize,default=20"` // 每页数量
|
||||||
|
UserId string `form:"user_id,optional"` // 平台用户主键 ID,精确筛选
|
||||||
Mobile string `form:"mobile,optional"` // 手机号
|
Mobile string `form:"mobile,optional"` // 手机号
|
||||||
Nickname string `form:"nickname,optional"` // 昵称
|
Nickname string `form:"nickname,optional"` // 昵称
|
||||||
Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否
|
Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
|
|
||||||
|
import "product.api"
|
||||||
|
|
||||||
info (
|
info (
|
||||||
title: "代理服务"
|
title: "代理服务"
|
||||||
desc: "新代理系统接口"
|
desc: "新代理系统接口"
|
||||||
@@ -121,6 +123,14 @@ type (
|
|||||||
GetInviteLinkResp {
|
GetInviteLinkResp {
|
||||||
InviteLink string `json:"invite_link"` // 邀请链接
|
InviteLink string `json:"invite_link"` // 邀请链接
|
||||||
}
|
}
|
||||||
|
// 生成邀请海报请求
|
||||||
|
GenerateInvitePosterReq {
|
||||||
|
InviteLink string `form:"invite_link"` // 邀请链接(用于生成二维码)
|
||||||
|
}
|
||||||
|
// 生成邀请海报响应
|
||||||
|
GenerateInvitePosterResp {
|
||||||
|
PosterUrl string `json:"poster_url"` // 海报图片URL(base64编码的data URL)
|
||||||
|
}
|
||||||
// 获取代理等级特权信息
|
// 获取代理等级特权信息
|
||||||
GetLevelPrivilegeResp {
|
GetLevelPrivilegeResp {
|
||||||
Levels []LevelPrivilegeItem `json:"levels"`
|
Levels []LevelPrivilegeItem `json:"levels"`
|
||||||
@@ -224,6 +234,10 @@ service main {
|
|||||||
@handler ApplyWithdrawal
|
@handler ApplyWithdrawal
|
||||||
post /withdrawal/apply (ApplyWithdrawalReq) returns (ApplyWithdrawalResp)
|
post /withdrawal/apply (ApplyWithdrawalReq) returns (ApplyWithdrawalResp)
|
||||||
|
|
||||||
|
// 获取上次提现信息(用于前端预填)
|
||||||
|
@handler GetLastWithdrawalInfo
|
||||||
|
get /withdrawal/last_info (GetLastWithdrawalInfoReq) returns (GetLastWithdrawalInfoResp)
|
||||||
|
|
||||||
// 实名认证
|
// 实名认证
|
||||||
@handler RealNameAuth
|
@handler RealNameAuth
|
||||||
post /real_name (RealNameAuthReq) returns (RealNameAuthResp)
|
post /real_name (RealNameAuthReq) returns (RealNameAuthResp)
|
||||||
@@ -244,6 +258,10 @@ service main {
|
|||||||
@handler GetInviteLink
|
@handler GetInviteLink
|
||||||
get /invite_link (GetInviteLinkReq) returns (GetInviteLinkResp)
|
get /invite_link (GetInviteLinkReq) returns (GetInviteLinkResp)
|
||||||
|
|
||||||
|
// 生成邀请海报(带二维码的图片)
|
||||||
|
@handler GenerateInvitePoster
|
||||||
|
get /invite/poster (GenerateInvitePosterReq) returns (GenerateInvitePosterResp)
|
||||||
|
|
||||||
// 获取代理等级特权信息
|
// 获取代理等级特权信息
|
||||||
@handler GetLevelPrivilege
|
@handler GetLevelPrivilege
|
||||||
get /level/privilege returns (GetLevelPrivilegeResp)
|
get /level/privilege returns (GetLevelPrivilegeResp)
|
||||||
@@ -251,6 +269,32 @@ service main {
|
|||||||
// 获取推广查询报告列表
|
// 获取推广查询报告列表
|
||||||
@handler GetPromotionQueryList
|
@handler GetPromotionQueryList
|
||||||
get /promotion/query/list (GetPromotionQueryListReq) returns (GetPromotionQueryListResp)
|
get /promotion/query/list (GetPromotionQueryListReq) returns (GetPromotionQueryListResp)
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 用户模块白名单相关接口
|
||||||
|
// ============================================
|
||||||
|
@handler GetWhitelistFeatures
|
||||||
|
get /whitelist/features (GetWhitelistFeaturesReq) returns (GetWhitelistFeaturesResp)
|
||||||
|
|
||||||
|
// 创建白名单订单
|
||||||
|
@handler CreateWhitelistOrder
|
||||||
|
post /whitelist/order/create (CreateWhitelistOrderReq) returns (CreateWhitelistOrderResp)
|
||||||
|
|
||||||
|
// 获取用户白名单列表
|
||||||
|
@handler GetWhitelistList
|
||||||
|
get /whitelist/list (GetWhitelistListReq) returns (GetWhitelistListResp)
|
||||||
|
|
||||||
|
// 检查模块是否已下架(用于显示下架按钮状态)
|
||||||
|
@handler CheckFeatureWhitelistStatus
|
||||||
|
get /whitelist/check (CheckFeatureWhitelistStatusReq) returns (CheckFeatureWhitelistStatusResp)
|
||||||
|
|
||||||
|
// 下架单个模块(创建订单并支付)
|
||||||
|
@handler OfflineFeature
|
||||||
|
post /whitelist/offline (OfflineFeatureReq) returns (OfflineFeatureResp)
|
||||||
|
|
||||||
|
// 检查订单是否属于当前代理推广
|
||||||
|
@handler CheckOrderAgent
|
||||||
|
get /order/agent (CheckOrderAgentReq) returns (CheckOrderAgentResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -268,9 +312,9 @@ type (
|
|||||||
}
|
}
|
||||||
// 生成推广链接
|
// 生成推广链接
|
||||||
AgentGeneratingLinkReq {
|
AgentGeneratingLinkReq {
|
||||||
ProductId string `json:"product_id"` // 产品ID
|
ProductId string `json:"product_id"` // 产品ID
|
||||||
SetPrice float64 `json:"set_price"` // 设定价格
|
SetPrice string `json:"set_price"` // 设定价格
|
||||||
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
TargetPath string `json:"target_path,optional"` // 目标地址(可选,默认为推广报告页面)
|
||||||
}
|
}
|
||||||
AgentGeneratingLinkResp {
|
AgentGeneratingLinkResp {
|
||||||
LinkIdentifier string `json:"link_identifier"` // 推广链接标识
|
LinkIdentifier string `json:"link_identifier"` // 推广链接标识
|
||||||
@@ -408,31 +452,35 @@ type (
|
|||||||
MonthEarnings float64 `json:"month_earnings"` // 月收益
|
MonthEarnings float64 `json:"month_earnings"` // 月收益
|
||||||
}
|
}
|
||||||
TeamMemberItem {
|
TeamMemberItem {
|
||||||
AgentId string `json:"agent_id"` // 代理ID
|
AgentId string `json:"agent_id"` // 代理ID
|
||||||
Level int64 `json:"level"` // 等级
|
Level int64 `json:"level"` // 等级
|
||||||
LevelName string `json:"level_name"` // 等级名称
|
LevelName string `json:"level_name"` // 等级名称
|
||||||
Mobile string `json:"mobile"` // 手机号
|
Mobile string `json:"mobile"` // 手机号
|
||||||
CreateTime string `json:"create_time"` // 加入团队时间
|
CreateTime string `json:"create_time"` // 加入团队时间
|
||||||
TodayInvites int64 `json:"today_invites"` // 邀请加入团队的今日人数
|
TodayQueries int64 `json:"today_queries"` // 当日查询量
|
||||||
MonthInvites int64 `json:"month_invites"` // 邀请加入团队的本月人数
|
MonthQueries int64 `json:"month_queries"` // 本月查询量
|
||||||
TotalInvites int64 `json:"total_invites"` // 邀请加入团队的总人数
|
TotalQueries int64 `json:"total_queries"` // 总查询量
|
||||||
TodayQueries int64 `json:"today_queries"` // 当日查询量
|
TotalRebateAmount float64 `json:"total_rebate_amount"` // 返佣给我的总金额
|
||||||
MonthQueries int64 `json:"month_queries"` // 本月查询量
|
TodayRebateAmount float64 `json:"today_rebate_amount"` // 返佣给我的今日金额
|
||||||
TotalQueries int64 `json:"total_queries"` // 总查询量
|
TotalInvites int64 `json:"total_invites"` // 邀请加入团队的总人数
|
||||||
IsDirect bool `json:"is_direct"` // 是否直接下级
|
TodayInvites int64 `json:"today_invites"` // 邀请加入团队的今日人数
|
||||||
|
MonthInvites int64 `json:"month_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"` // 佣金累计总收益(推广订单获得的佣金)
|
CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金)
|
||||||
CommissionToday float64 `json:"commission_today"` // 佣金今日收益
|
CommissionToday float64 `json:"commission_today"` // 佣金今日收益
|
||||||
CommissionMonth float64 `json:"commission_month"` // 佣金本月收益
|
CommissionMonth float64 `json:"commission_month"` // 佣金本月收益
|
||||||
RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣)
|
RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣)
|
||||||
RebateToday float64 `json:"rebate_today"` // 返佣今日收益
|
RebateToday float64 `json:"rebate_today"` // 返佣今日收益
|
||||||
RebateMonth float64 `json:"rebate_month"` // 返佣本月收益
|
RebateMonth float64 `json:"rebate_month"` // 返佣本月收益
|
||||||
|
AlipayMonthQuota float64 `json:"alipay_month_quota"` // 支付宝每月提现总额度
|
||||||
|
AlipayMonthUsed float64 `json:"alipay_month_used"` // 本月已使用的支付宝提现额度
|
||||||
}
|
}
|
||||||
// 佣金记录
|
// 佣金记录
|
||||||
GetCommissionListReq {
|
GetCommissionListReq {
|
||||||
@@ -471,6 +519,7 @@ type (
|
|||||||
OrderNo string `json:"order_no"` // 订单号
|
OrderNo string `json:"order_no"` // 订单号
|
||||||
RebateType int64 `json:"rebate_type"` // 返佣类型:1=直接上级,2=钻石上级,3=黄金上级
|
RebateType int64 `json:"rebate_type"` // 返佣类型:1=直接上级,2=钻石上级,3=黄金上级
|
||||||
Amount float64 `json:"amount"` // 返佣金额
|
Amount float64 `json:"amount"` // 返佣金额
|
||||||
|
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
}
|
}
|
||||||
// 升级返佣记录
|
// 升级返佣记录
|
||||||
@@ -537,28 +586,46 @@ type (
|
|||||||
Total int64 `json:"total"` // 总数
|
Total int64 `json:"total"` // 总数
|
||||||
List []WithdrawalItem `json:"list"` // 列表
|
List []WithdrawalItem `json:"list"` // 列表
|
||||||
}
|
}
|
||||||
|
// 提现记录
|
||||||
WithdrawalItem {
|
WithdrawalItem {
|
||||||
Id string `json:"id"` // 记录ID
|
Id string `json:"id"` // 记录ID
|
||||||
WithdrawalNo string `json:"withdrawal_no"` // 提现单号
|
WithdrawalNo string `json:"withdrawal_no"` // 提现单号
|
||||||
Amount float64 `json:"amount"` // 提现金额
|
Amount float64 `json:"amount"` // 提现金额
|
||||||
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
||||||
ActualAmount float64 `json:"actual_amount"` // 实际到账金额
|
ActualAmount float64 `json:"actual_amount"` // 实际到账金额
|
||||||
Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败
|
Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败
|
||||||
PayeeAccount string `json:"payee_account"` // 收款账户
|
WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡
|
||||||
PayeeName string `json:"payee_name"` // 收款人姓名
|
PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号)
|
||||||
Remark string `json:"remark"` // 备注
|
PayeeName string `json:"payee_name"` // 收款人姓名
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时填写)
|
||||||
|
BankName string `json:"bank_name"` // 开户行名称(银行卡提现时填写)
|
||||||
|
Remark string `json:"remark"` // 备注
|
||||||
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
}
|
}
|
||||||
// 申请提现
|
// 申请提现
|
||||||
ApplyWithdrawalReq {
|
ApplyWithdrawalReq {
|
||||||
Amount float64 `json:"amount"` // 提现金额
|
Amount float64 `json:"amount"` // 提现金额
|
||||||
PayeeAccount string `json:"payee_account"` // 收款账户
|
WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡
|
||||||
PayeeName string `json:"payee_name"` // 收款人姓名
|
PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号)
|
||||||
|
PayeeName string `json:"payee_name"` // 收款人姓名
|
||||||
|
BankCardNo string `json:"bank_card_no,optional"` // 银行卡号(银行卡提现必填)
|
||||||
|
BankName string `json:"bank_name,optional"` // 开户行名称(银行卡提现必填)
|
||||||
}
|
}
|
||||||
ApplyWithdrawalResp {
|
ApplyWithdrawalResp {
|
||||||
WithdrawalId string `json:"withdrawal_id"` // 提现记录ID
|
WithdrawalId string `json:"withdrawal_id"` // 提现记录ID
|
||||||
WithdrawalNo string `json:"withdrawal_no"` // 提现单号
|
WithdrawalNo string `json:"withdrawal_no"` // 提现单号
|
||||||
}
|
}
|
||||||
|
// 获取上次提现信息(用于前端预填)
|
||||||
|
GetLastWithdrawalInfoReq {
|
||||||
|
WithdrawalType int64 `form:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡
|
||||||
|
}
|
||||||
|
GetLastWithdrawalInfoResp {
|
||||||
|
WithdrawalType int64 `json:"withdrawal_type"` // 提现方式
|
||||||
|
PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号)
|
||||||
|
PayeeName string `json:"payee_name"` // 收款人姓名
|
||||||
|
BankCardNo string `json:"bank_card_no"` // 银行卡号
|
||||||
|
BankName string `json:"bank_name"` // 开户行名称
|
||||||
|
}
|
||||||
// 实名认证
|
// 实名认证
|
||||||
RealNameAuthReq {
|
RealNameAuthReq {
|
||||||
Name string `json:"name"` // 姓名
|
Name string `json:"name"` // 姓名
|
||||||
@@ -579,11 +646,82 @@ type (
|
|||||||
List []PromotionQueryItem `json:"list"` // 列表
|
List []PromotionQueryItem `json:"list"` // 列表
|
||||||
}
|
}
|
||||||
PromotionQueryItem {
|
PromotionQueryItem {
|
||||||
Id string `json:"id"` // 查询ID
|
Id string `json:"id"` // 查询ID
|
||||||
OrderId string `json:"order_id"` // 订单ID
|
OrderId string `json:"order_id"` // 订单ID
|
||||||
ProductName string `json:"product_name"` // 产品名称
|
ProductName string `json:"product_name"` // 产品名称
|
||||||
CreateTime string `json:"create_time"` // 创建时间
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
QueryState string `json:"query_state"` // 查询状态
|
QueryState string `json:"query_state"` // 查询状态
|
||||||
|
Params map[string]interface{} `json:"params,optional"` // 查询参数(已脱敏)
|
||||||
|
Price float64 `json:"price"` // 查询价格
|
||||||
|
}
|
||||||
|
// ============================================
|
||||||
|
// 用户模块白名单相关类型
|
||||||
|
// ============================================
|
||||||
|
GetWhitelistFeaturesReq {}
|
||||||
|
GetWhitelistFeaturesResp {
|
||||||
|
List []WhitelistFeatureItem `json:"list"` // 可屏蔽的feature列表
|
||||||
|
}
|
||||||
|
WhitelistFeatureItem {
|
||||||
|
FeatureId string `json:"feature_id"` // Feature的UUID
|
||||||
|
FeatureApiId string `json:"feature_api_id"` // Feature的API标识
|
||||||
|
FeatureName string `json:"feature_name"` // Feature的名称
|
||||||
|
WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元)
|
||||||
|
}
|
||||||
|
CreateWhitelistOrderReq {
|
||||||
|
IdCard string `json:"id_card"` // 身份证号(查询对象标识)
|
||||||
|
FeatureIds []string `json:"feature_ids"` // 要屏蔽的feature ID列表
|
||||||
|
OrderId string `json:"order_id,optional"` // 关联的查询订单ID(可选)
|
||||||
|
}
|
||||||
|
CreateWhitelistOrderResp {
|
||||||
|
OrderId string `json:"order_id"` // 订单ID
|
||||||
|
OrderNo string `json:"order_no"` // 订单号
|
||||||
|
TotalAmount float64 `json:"total_amount"` // 总金额
|
||||||
|
}
|
||||||
|
GetWhitelistListReq {
|
||||||
|
Page int64 `form:"page"` // 页码
|
||||||
|
PageSize int64 `form:"page_size"` // 每页数量
|
||||||
|
IdCard string `form:"id_card,optional"` // 身份证号(可选,用于筛选)
|
||||||
|
}
|
||||||
|
GetWhitelistListResp {
|
||||||
|
Total int64 `json:"total"` // 总数
|
||||||
|
List []WhitelistItem `json:"list"` // 列表
|
||||||
|
}
|
||||||
|
WhitelistItem {
|
||||||
|
Id string `json:"id"` // 白名单记录ID
|
||||||
|
IdCard string `json:"id_card"` // 身份证号
|
||||||
|
FeatureId string `json:"feature_id"` // Feature的UUID
|
||||||
|
FeatureApiId string `json:"feature_api_id"` // Feature的API标识
|
||||||
|
FeatureName string `json:"feature_name"` // Feature的名称
|
||||||
|
Amount float64 `json:"amount"` // 费用
|
||||||
|
Status int64 `json:"status"` // 状态:1=生效,2=已失效
|
||||||
|
StatusText string `json:"status_text"` // 状态文本
|
||||||
|
CreateTime string `json:"create_time"` // 创建时间
|
||||||
|
}
|
||||||
|
CheckFeatureWhitelistStatusReq {
|
||||||
|
IdCard string `form:"id_card"` // 身份证号
|
||||||
|
FeatureApiId string `form:"feature_api_id"` // Feature的API标识
|
||||||
|
QueryId string `form:"query_id,optional"` // 查询记录ID(可选)
|
||||||
|
}
|
||||||
|
CheckFeatureWhitelistStatusResp {
|
||||||
|
IsWhitelisted bool `json:"is_whitelisted"` // 是否在白名单中
|
||||||
|
WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元),0表示不支持下架
|
||||||
|
FeatureId string `json:"feature_id"` // Feature的UUID
|
||||||
|
DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除
|
||||||
|
}
|
||||||
|
OfflineFeatureReq {
|
||||||
|
FeatureApiId string `json:"feature_api_id"` // Feature的API标识
|
||||||
|
QueryId string `json:"query_id"` // 查询记录ID(Query表的ID,必选)
|
||||||
|
}
|
||||||
|
OfflineFeatureResp {
|
||||||
|
Success bool `json:"success"` // 是否已完成下架
|
||||||
|
NeedPay bool `json:"need_pay"` // 是否需要发起支付
|
||||||
|
Amount float64 `json:"amount"` // 需要支付的金额(元),0表示无需支付
|
||||||
|
}
|
||||||
|
CheckOrderAgentReq {
|
||||||
|
OrderId string `form:"order_id"` // 订单ID
|
||||||
|
}
|
||||||
|
CheckOrderAgentResp {
|
||||||
|
IsAgentOrder bool `json:"is_agent_order"` // 是否是当前代理推广的订单
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,31 @@ service main {
|
|||||||
|
|
||||||
@handler getAppConfig
|
@handler getAppConfig
|
||||||
get /app/config returns (getAppConfigResp)
|
get /app/config returns (getAppConfigResp)
|
||||||
|
|
||||||
|
@handler getHomeDynamicData
|
||||||
|
get /app/home/dynamic (getHomeDynamicDataReq) returns (getHomeDynamicDataResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
getHomeDynamicDataReq {
|
||||||
|
LastId int64 `form:"lastId,optional"`
|
||||||
|
}
|
||||||
|
InquiryRecordItem {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Vin string `json:"vin"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
}
|
||||||
|
ReviewItem {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
getHomeDynamicDataResp {
|
||||||
|
Cases []InquiryRecordItem `json:"cases"`
|
||||||
|
Reviews []ReviewItem `json:"reviews"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// 心跳检测响应
|
// 心跳检测响应
|
||||||
HealthCheckResp {
|
HealthCheckResp {
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ service main {
|
|||||||
// 微信退款回调
|
// 微信退款回调
|
||||||
@handler WechatPayRefundCallback
|
@handler WechatPayRefundCallback
|
||||||
post /pay/wechat/refund_callback
|
post /pay/wechat/refund_callback
|
||||||
|
|
||||||
|
// 微信小程序虚拟支付发货推送(GET 验签 + POST 事件)
|
||||||
|
@handler XpayPush
|
||||||
|
get /pay/xpay/push
|
||||||
|
post /pay/xpay/push
|
||||||
}
|
}
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
@@ -40,6 +45,10 @@ service main {
|
|||||||
|
|
||||||
@handler PaymentCheck
|
@handler PaymentCheck
|
||||||
post /pay/check (PaymentCheckReq) returns (PaymentCheckResp)
|
post /pay/check (PaymentCheckReq) returns (PaymentCheckResp)
|
||||||
|
|
||||||
|
// 小程序虚拟支付:上报微信/Apple 客户端提示与步骤(写入服务端日志并可选查微信单)
|
||||||
|
@handler XpayClientEvent
|
||||||
|
post /pay/xpay/client-event (XpayClientEventReq) returns (XpayClientEventResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -47,6 +56,7 @@ type (
|
|||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式)
|
PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式)
|
||||||
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"`
|
PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"`
|
||||||
|
Code string `json:"code,optional"` // 微信小程序/H5授权码,用于自动绑定微信账号(当用户未绑定微信时)
|
||||||
}
|
}
|
||||||
PaymentResp {
|
PaymentResp {
|
||||||
PrepayData interface{} `json:"prepay_data"`
|
PrepayData interface{} `json:"prepay_data"`
|
||||||
@@ -57,8 +67,28 @@ type (
|
|||||||
OrderNo string `json:"order_no" validate:"required"`
|
OrderNo string `json:"order_no" validate:"required"`
|
||||||
}
|
}
|
||||||
PaymentCheckResp {
|
PaymentCheckResp {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
WxSyncError string `json:"wx_sync_error,optional"` // query_order 失败时微信返回
|
||||||
|
WxOrderStatus int `json:"wx_order_status,optional"` // 微信侧订单 status
|
||||||
|
WxOrderDetail string `json:"wx_order_detail,optional"` // query_order 原始 order 摘要(排查用)
|
||||||
|
}
|
||||||
|
XpayClientEventReq {
|
||||||
|
OrderNo string `json:"order_no,optional"`
|
||||||
|
Stage string `json:"stage" validate:"required"` // prepay_ok, invoke, success, fail, check
|
||||||
|
EventType string `json:"event_type" validate:"required,oneof=info tip fail success"`
|
||||||
|
Message string `json:"message,optional"` // 微信/Apple 展示给用户的话术(非 HTTP 报错)
|
||||||
|
ErrMsg string `json:"err_msg,optional"`
|
||||||
|
ErrCode int `json:"err_code,optional"`
|
||||||
|
Errno int `json:"errno,optional"`
|
||||||
|
SignData string `json:"sign_data,optional"`
|
||||||
|
Device string `json:"device,optional"`
|
||||||
|
Extra string `json:"extra,optional"`
|
||||||
|
}
|
||||||
|
XpayClientEventResp {
|
||||||
|
WxOrderStatus int `json:"wx_order_status,optional"`
|
||||||
|
WxOrderDetail string `json:"wx_order_detail,optional"`
|
||||||
|
WxSyncError string `json:"wx_sync_error,optional"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
29
app/main/api/desc/front/toolbox.api
Normal file
29
app/main/api/desc/front/toolbox.api
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "工具箱服务"
|
||||||
|
desc: "免费工具箱(天行聚合等)"
|
||||||
|
version: "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
//============================> toolbox v1 <============================
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: toolbox
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "工具箱统一查询"
|
||||||
|
@handler toolboxQuery
|
||||||
|
post /toolbox/query (ToolboxQueryReq) returns (ToolboxQueryResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
ToolboxQueryReq {
|
||||||
|
ToolKey string `json:"tool_key"`
|
||||||
|
Params map[string]interface{} `json:"params,optional"`
|
||||||
|
}
|
||||||
|
ToolboxQueryResp {
|
||||||
|
ToolKey string `json:"tool_key"`
|
||||||
|
Result map[string]interface{} `json:"result"`
|
||||||
|
}
|
||||||
|
)
|
||||||
44
app/main/api/desc/front/upload.api
Normal file
44
app/main/api/desc/front/upload.api
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "上传服务"
|
||||||
|
desc: "图片 Base64 上传与文件访问"
|
||||||
|
version: "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
//============================> upload v1 <============================
|
||||||
|
// 访问已上传图片(直接输出二进制,无 JSON 包装)
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: upload
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "访问已上传图片"
|
||||||
|
@handler serveUpload
|
||||||
|
get /upload/file/:name (ServeUploadFileReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServeUploadFileReq {
|
||||||
|
Name string `path:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: upload
|
||||||
|
jwt: JwtAuth
|
||||||
|
middleware: AuthInterceptor
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "图片 Base64 上传"
|
||||||
|
@handler uploadImage
|
||||||
|
post /upload/image (UploadImageReq) returns (UploadImageResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
UploadImageReq {
|
||||||
|
ImageBase64 string `json:"image_base64"`
|
||||||
|
}
|
||||||
|
UploadImageResp {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -156,8 +156,26 @@ service main {
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
sendSmsReq {
|
sendSmsReq {
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||||
ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"`
|
ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply realName bindMobile"`
|
||||||
|
CaptchaVerifyParam string `json:"captchaVerifyParam,optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//============================> captcha v1 <============================
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: captcha
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "get encrypted scene id for aliyun captcha"
|
||||||
|
@handler getEncryptedSceneId
|
||||||
|
post /captcha/encryptedSceneId returns (GetEncryptedSceneIdResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
GetEncryptedSceneIdResp {
|
||||||
|
EncryptedSceneId string `json:"encryptedSceneId"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import "./front/product.api"
|
|||||||
import "./front/agent.api"
|
import "./front/agent.api"
|
||||||
import "./front/app.api"
|
import "./front/app.api"
|
||||||
import "./front/authorization.api"
|
import "./front/authorization.api"
|
||||||
|
import "./front/toolbox.api"
|
||||||
|
import "./front/upload.api"
|
||||||
// 后台
|
// 后台
|
||||||
import "./admin/auth.api"
|
import "./admin/auth.api"
|
||||||
import "./admin/menu.api"
|
import "./admin/menu.api"
|
||||||
@@ -24,7 +26,11 @@ import "./admin/platform_user.api"
|
|||||||
import "./admin/notification.api"
|
import "./admin/notification.api"
|
||||||
import "./admin/admin_product.api"
|
import "./admin/admin_product.api"
|
||||||
import "./admin/admin_feature.api"
|
import "./admin/admin_feature.api"
|
||||||
|
import "./admin/dashboard.api"
|
||||||
import "./admin/admin_query.api"
|
import "./admin/admin_query.api"
|
||||||
import "./admin/admin_agent.api"
|
import "./admin/admin_agent.api"
|
||||||
import "./admin/admin_api.api"
|
import "./admin/admin_api.api"
|
||||||
import "./admin/admin_role_api.api"
|
import "./admin/admin_role_api.api"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ VerifyCode:
|
|||||||
SignName: "天远查"
|
SignName: "天远查"
|
||||||
TemplateCode: "SMS_302641455"
|
TemplateCode: "SMS_302641455"
|
||||||
ValidTime: 300
|
ValidTime: 300
|
||||||
|
Captcha:
|
||||||
|
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||||
|
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||||
|
EndpointURL: "captcha.cn-shanghai.aliyuncs.com"
|
||||||
|
SceneID: "wynt39to"
|
||||||
|
EKey: ""
|
||||||
Encrypt:
|
Encrypt:
|
||||||
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
||||||
WestConfig:
|
WestConfig:
|
||||||
@@ -42,13 +48,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/paywechat/refund_callback"
|
RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/paywechat/refund_callback"
|
||||||
Applepay:
|
Applepay:
|
||||||
@@ -67,10 +73,22 @@ WechatH5:
|
|||||||
AppID: "wx442ee1ac1ee75917"
|
AppID: "wx442ee1ac1ee75917"
|
||||||
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
||||||
WechatMini:
|
WechatMini:
|
||||||
AppID: "wx781abb66b3368963" # 小程序的AppID
|
AppID: "wx5bacc94add2da981" # 小程序的AppID
|
||||||
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
|
AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
|
||||||
TycAppID: "wxe74617f3dd56c196"
|
WechatXpay:
|
||||||
TycAppSecret: "c8207e54aef5689b2a7c1f91ed7ae8a0"
|
Enabled: true
|
||||||
|
Env: 1 # 本地联调用沙箱;现网单请改 0 + 现网 AppKey
|
||||||
|
OfferId: "1450552691"
|
||||||
|
AppKey: "n1SSzeMiitPjiQwLlBn1s4Hn7SpkG3qD" # 沙箱 AppKey(仅 env=1)
|
||||||
|
MessagePushToken: "qncXpayPush2026" # 与 mp 后台消息推送 Token 保持一致
|
||||||
|
SessionKeyTTL: 2592000 # 30 天,与 JwtAuth.AccessExpire 对齐
|
||||||
|
|
||||||
|
# Enabled: true
|
||||||
|
# Env: 0 # 0 现网 / 1 沙箱(须与 AppKey 配套)
|
||||||
|
# OfferId: "1450552691"
|
||||||
|
# AppKey: "oYv2wooYzRmPDLdXkpBpqaml8cFaY0Bb" # 现网 AppKey(env=0 必填现网密钥)
|
||||||
|
# MessagePushToken: "qncXpayPush2026" # 与 mp 后台消息推送 Token 保持一致
|
||||||
|
# SessionKeyTTL: 2592000 # 30 天,与 JwtAuth.AccessExpire 对齐
|
||||||
Query:
|
Query:
|
||||||
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
||||||
AdminConfig:
|
AdminConfig:
|
||||||
@@ -81,10 +99,14 @@ TaxConfig:
|
|||||||
TaxRate: 0.06
|
TaxRate: 0.06
|
||||||
TaxExemptionAmount: 0.00
|
TaxExemptionAmount: 0.00
|
||||||
Tianyuanapi:
|
Tianyuanapi:
|
||||||
AccessID: "7f8a9b2c4d5e6f1a"
|
AccessID: "9e60b34eb51f3827"
|
||||||
Key: "9e4f8a1b3c6d7e2f5a8b9c0d1e4f7a2b"
|
Key: "04c6b4c559be6d5ba5351c04c8713a64"
|
||||||
BaseURL: "https://api.tianyuanapi.com"
|
BaseURL: "https://api.tianyuanapi.com"
|
||||||
Timeout: 60
|
Timeout: 60
|
||||||
|
tianxingjuhe:
|
||||||
|
url: "https://apis.tianapi.com"
|
||||||
|
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
|
||||||
|
timeout: 30
|
||||||
Authorization:
|
Authorization:
|
||||||
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
|
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
|
||||||
Promotion:
|
Promotion:
|
||||||
|
|||||||
@@ -16,9 +16,15 @@ VerifyCode:
|
|||||||
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||||
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||||
EndpointURL: "dysmsapi.aliyuncs.com"
|
EndpointURL: "dysmsapi.aliyuncs.com"
|
||||||
SignName: "天远查"
|
SignName: "海南海宇大数据"
|
||||||
TemplateCode: "SMS_302641455"
|
TemplateCode: "SMS_302641455"
|
||||||
ValidTime: 300
|
ValidTime: 300
|
||||||
|
Captcha:
|
||||||
|
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||||
|
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||||
|
EndpointURL: "captcha.cn-shanghai.aliyuncs.com"
|
||||||
|
SceneID: "wynt39to"
|
||||||
|
EKey: ""
|
||||||
Encrypt:
|
Encrypt:
|
||||||
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
||||||
Alipay:
|
Alipay:
|
||||||
@@ -33,13 +39,13 @@ Alipay:
|
|||||||
ReturnURL: "https://www.quannengcha.com/payment/result"
|
ReturnURL: "https://www.quannengcha.com/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.quannengcha.com/api/v1/pay/wechat/callback"
|
||||||
RefundNotifyUrl: "https://www.quannengcha.com/api/v1/pay/wechat/refund_callback"
|
RefundNotifyUrl: "https://www.quannengcha.com/api/v1/pay/wechat/refund_callback"
|
||||||
Applepay:
|
Applepay:
|
||||||
@@ -58,6 +64,13 @@ WechatH5:
|
|||||||
WechatMini:
|
WechatMini:
|
||||||
AppID: "wx5bacc94add2da981" # 小程序的AppID
|
AppID: "wx5bacc94add2da981" # 小程序的AppID
|
||||||
AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
|
AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
|
||||||
|
WechatXpay:
|
||||||
|
Enabled: true
|
||||||
|
Env: 0 # 0 现网 / 1 沙箱(须与 AppKey 配套)
|
||||||
|
OfferId: "1450552691"
|
||||||
|
AppKey: "oYv2wooYzRmPDLdXkpBpqaml8cFaY0Bb" # 现网 AppKey(env=0 必填现网密钥)
|
||||||
|
MessagePushToken: "qncXpayPush2026" # 与 mp 后台消息推送 Token 保持一致
|
||||||
|
SessionKeyTTL: 2592000 # 30 天,与 JwtAuth.AccessExpire 对齐
|
||||||
Query:
|
Query:
|
||||||
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
||||||
AdminConfig:
|
AdminConfig:
|
||||||
@@ -68,10 +81,14 @@ TaxConfig:
|
|||||||
TaxRate: 0.06
|
TaxRate: 0.06
|
||||||
TaxExemptionAmount: 0.00
|
TaxExemptionAmount: 0.00
|
||||||
Tianyuanapi:
|
Tianyuanapi:
|
||||||
AccessID: "7f8a9b2c4d5e6f1a"
|
AccessID: "9e60b34eb51f3827"
|
||||||
Key: "9e4f8a1b3c6d7e2f5a8b9c0d1e4f7a2b"
|
Key: "04c6b4c559be6d5ba5351c04c8713a64"
|
||||||
BaseURL: "https://api.tianyuanapi.com"
|
BaseURL: "https://api.tianyuanapi.com"
|
||||||
Timeout: 60
|
Timeout: 60
|
||||||
|
tianxingjuhe:
|
||||||
|
url: "https://apis.tianapi.com"
|
||||||
|
key: "4ceffb1ffb95b83230b9a9c9df2467e1"
|
||||||
|
timeout: 30
|
||||||
Authorization:
|
Authorization:
|
||||||
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
|
FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL
|
||||||
Promotion:
|
Promotion:
|
||||||
|
|||||||
@@ -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-----
|
||||||
|
|||||||
@@ -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-----
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type Config struct {
|
|||||||
CacheRedis cache.CacheConf
|
CacheRedis cache.CacheConf
|
||||||
JwtAuth JwtAuth // JWT 鉴权相关配置
|
JwtAuth JwtAuth // JWT 鉴权相关配置
|
||||||
VerifyCode VerifyCode
|
VerifyCode VerifyCode
|
||||||
|
Captcha CaptchaConfig // 阿里云验证码配置
|
||||||
Encrypt Encrypt
|
Encrypt Encrypt
|
||||||
Alipay AlipayConfig
|
Alipay AlipayConfig
|
||||||
Wxpay WxpayConfig
|
Wxpay WxpayConfig
|
||||||
@@ -20,10 +21,12 @@ type Config struct {
|
|||||||
WechatH5 WechatH5Config
|
WechatH5 WechatH5Config
|
||||||
Authorization AuthorizationConfig // 授权书配置
|
Authorization AuthorizationConfig // 授权书配置
|
||||||
WechatMini WechatMiniConfig
|
WechatMini WechatMiniConfig
|
||||||
|
WechatXpay WechatXpayConfig
|
||||||
Query QueryConfig
|
Query QueryConfig
|
||||||
AdminConfig AdminConfig
|
AdminConfig AdminConfig
|
||||||
TaxConfig TaxConfig
|
TaxConfig TaxConfig
|
||||||
Promotion PromotionConfig // 推广链接配置
|
Promotion PromotionConfig // 推广链接配置
|
||||||
|
Tianxingjuhe TianxingjuheConfig // 天行聚合API配置
|
||||||
}
|
}
|
||||||
|
|
||||||
// JwtAuth 用于 JWT 鉴权配置
|
// JwtAuth 用于 JWT 鉴权配置
|
||||||
@@ -40,6 +43,15 @@ type VerifyCode struct {
|
|||||||
TemplateCode string
|
TemplateCode string
|
||||||
ValidTime int
|
ValidTime int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CaptchaConfig struct {
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
EndpointURL string
|
||||||
|
SceneID string
|
||||||
|
EKey string // 加密模式用的 ekey(Base64)
|
||||||
|
}
|
||||||
|
|
||||||
type Encrypt struct {
|
type Encrypt struct {
|
||||||
SecretKey string
|
SecretKey string
|
||||||
}
|
}
|
||||||
@@ -87,6 +99,16 @@ type WechatMiniConfig struct {
|
|||||||
AppID string
|
AppID string
|
||||||
AppSecret string
|
AppSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WechatXpayConfig 微信小程序虚拟支付(xpay)
|
||||||
|
type WechatXpayConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
Env int // 0 现网 / 1 沙箱
|
||||||
|
OfferId string
|
||||||
|
AppKey string // 与 Env 配套的 AppKey(沙箱/现网各自在 yaml 中配置)
|
||||||
|
MessagePushToken string
|
||||||
|
SessionKeyTTL int64
|
||||||
|
}
|
||||||
type QueryConfig struct {
|
type QueryConfig struct {
|
||||||
ShareLinkExpire int64
|
ShareLinkExpire int64
|
||||||
}
|
}
|
||||||
@@ -116,3 +138,10 @@ type PromotionConfig struct {
|
|||||||
PromotionDomain string // 推广域名(用于生成短链)
|
PromotionDomain string // 推广域名(用于生成短链)
|
||||||
OfficialDomain string // 正式站点域名(短链重定向的目标域名)
|
OfficialDomain string // 正式站点域名(短链重定向的目标域名)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TianxingjuheConfig 天行聚合API配置
|
||||||
|
type TianxingjuheConfig struct {
|
||||||
|
URL string // API基础URL
|
||||||
|
Key string // API密钥
|
||||||
|
Timeout int // 超时时间(秒),默认30秒
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminGetAgentOrderSettlementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminGetAgentOrderSettlementReq
|
||||||
|
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_agent.NewAdminGetAgentOrderSettlementLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminGetAgentOrderSettlement(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminGetAgentTeamTreeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminGetAgentTeamTreeReq
|
||||||
|
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_agent.NewAdminGetAgentTeamTreeLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminGetAgentTeamTree(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminUpgradeAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminUpgradeAgentReq
|
||||||
|
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_agent.NewAdminUpgradeAgentLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminUpgradeAgent(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package admin_dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_dashboard"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminGetDashboardStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := admin_dashboard.NewAdminGetDashboardStatisticsLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminGetDashboardStatistics()
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package admin_order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_order"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminXpayDeliverHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminXpayDeliverReq
|
||||||
|
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_order.NewAdminXpayDeliverLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminXpayDeliver(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package admin_query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/admin_query"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminGenerateQueryShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminGenerateQueryShareLinkReq
|
||||||
|
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_query.NewAdminGenerateQueryShareLinkLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AdminGenerateQueryShareLink(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckFeatureWhitelistStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.CheckFeatureWhitelistStatusReq
|
||||||
|
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.NewCheckFeatureWhitelistStatusLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.CheckFeatureWhitelistStatus(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckOrderAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.CheckOrderAgentReq
|
||||||
|
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.NewCheckOrderAgentLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.CheckOrderAgent(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateWhitelistOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.CreateWhitelistOrderReq
|
||||||
|
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.NewCreateWhitelistOrderLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.CreateWhitelistOrder(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateInvitePosterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GenerateInvitePosterReq
|
||||||
|
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.NewGenerateInvitePosterLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GenerateInvitePoster(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLastWithdrawalInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GetLastWithdrawalInfoReq
|
||||||
|
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.NewGetLastWithdrawalInfoLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetLastWithdrawalInfo(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetWhitelistFeaturesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GetWhitelistFeaturesReq
|
||||||
|
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.NewGetWhitelistFeaturesLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetWhitelistFeatures(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetWhitelistListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GetWhitelistListReq
|
||||||
|
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.NewGetWhitelistListLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetWhitelistList(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/main/api/internal/handler/agent/offlinefeaturehandler.go
Normal file
29
app/main/api/internal/handler/agent/offlinefeaturehandler.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/agent"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OfflineFeatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.OfflineFeatureReq
|
||||||
|
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.NewOfflineFeatureLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.OfflineFeature(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/app"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetHomeDynamicDataHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GetHomeDynamicDataReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := app.NewGetHomeDynamicDataLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetHomeDynamicData(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,27 @@ func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
result.ParamValidateErrorResult(r, w, err)
|
result.ParamValidateErrorResult(r, w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 获取客户端真实 IP
|
||||||
|
clientIP := getClientIP(r)
|
||||||
|
// 获取 User-Agent 用于判断微信环境
|
||||||
|
userAgent := r.Header.Get("User-Agent")
|
||||||
l := auth.NewSendSmsLogic(r.Context(), svcCtx)
|
l := auth.NewSendSmsLogic(r.Context(), svcCtx)
|
||||||
err := l.SendSms(&req)
|
err := l.SendSms(&req, clientIP, userAgent)
|
||||||
result.HttpResult(r, w, nil, err)
|
result.HttpResult(r, w, nil, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getClientIP 获取客户端真实 IP
|
||||||
|
func getClientIP(r *http.Request) string {
|
||||||
|
// 优先从 X-Forwarded-For 获取
|
||||||
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||||
|
// X-Forwarded-For 可能包含多个 IP,取第一个
|
||||||
|
return xff
|
||||||
|
}
|
||||||
|
// 其次从 X-Real-IP 获取
|
||||||
|
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||||
|
return xri
|
||||||
|
}
|
||||||
|
// 最后使用 RemoteAddr
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"qnc-server/app/main/api/internal/logic/captcha"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetEncryptedSceneIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := captcha.NewGetEncryptedSceneIdLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetEncryptedSceneId()
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/main/api/internal/handler/pay/xpayclienteventhandler.go
Normal file
30
app/main/api/internal/handler/pay/xpayclienteventhandler.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/pay"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func XpayClientEventHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.XpayClientEventReq
|
||||||
|
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 := pay.NewXpayClientEventLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.XpayClientEvent(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/main/api/internal/handler/pay/xpaypushhandler.go
Normal file
22
app/main/api/internal/handler/pay/xpaypushhandler.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/pay"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func XpayPushHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
l := pay.NewXpayPushLogic(svcCtx)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
l.HandleGET(w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
l.HandlePOST(w, r)
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
admin_agent "qnc-server/app/main/api/internal/handler/admin_agent"
|
admin_agent "qnc-server/app/main/api/internal/handler/admin_agent"
|
||||||
admin_api "qnc-server/app/main/api/internal/handler/admin_api"
|
admin_api "qnc-server/app/main/api/internal/handler/admin_api"
|
||||||
admin_auth "qnc-server/app/main/api/internal/handler/admin_auth"
|
admin_auth "qnc-server/app/main/api/internal/handler/admin_auth"
|
||||||
|
admin_dashboard "qnc-server/app/main/api/internal/handler/admin_dashboard"
|
||||||
admin_feature "qnc-server/app/main/api/internal/handler/admin_feature"
|
admin_feature "qnc-server/app/main/api/internal/handler/admin_feature"
|
||||||
admin_menu "qnc-server/app/main/api/internal/handler/admin_menu"
|
admin_menu "qnc-server/app/main/api/internal/handler/admin_menu"
|
||||||
admin_notification "qnc-server/app/main/api/internal/handler/admin_notification"
|
admin_notification "qnc-server/app/main/api/internal/handler/admin_notification"
|
||||||
@@ -21,10 +22,13 @@ import (
|
|||||||
app "qnc-server/app/main/api/internal/handler/app"
|
app "qnc-server/app/main/api/internal/handler/app"
|
||||||
auth "qnc-server/app/main/api/internal/handler/auth"
|
auth "qnc-server/app/main/api/internal/handler/auth"
|
||||||
authorization "qnc-server/app/main/api/internal/handler/authorization"
|
authorization "qnc-server/app/main/api/internal/handler/authorization"
|
||||||
|
captcha "qnc-server/app/main/api/internal/handler/captcha"
|
||||||
notification "qnc-server/app/main/api/internal/handler/notification"
|
notification "qnc-server/app/main/api/internal/handler/notification"
|
||||||
pay "qnc-server/app/main/api/internal/handler/pay"
|
pay "qnc-server/app/main/api/internal/handler/pay"
|
||||||
product "qnc-server/app/main/api/internal/handler/product"
|
product "qnc-server/app/main/api/internal/handler/product"
|
||||||
query "qnc-server/app/main/api/internal/handler/query"
|
query "qnc-server/app/main/api/internal/handler/query"
|
||||||
|
toolbox "qnc-server/app/main/api/internal/handler/toolbox"
|
||||||
|
upload "qnc-server/app/main/api/internal/handler/upload"
|
||||||
user "qnc-server/app/main/api/internal/handler/user"
|
user "qnc-server/app/main/api/internal/handler/user"
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
|
||||||
@@ -81,6 +85,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/order/list",
|
Path: "/order/list",
|
||||||
Handler: admin_agent.AdminGetAgentOrderListHandler(serverCtx),
|
Handler: admin_agent.AdminGetAgentOrderListHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/order/settlement",
|
||||||
|
Handler: admin_agent.AdminGetAgentOrderSettlementHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/product_config/list",
|
Path: "/product_config/list",
|
||||||
@@ -101,6 +110,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/rebate/list",
|
Path: "/rebate/list",
|
||||||
Handler: admin_agent.AdminGetAgentRebateListHandler(serverCtx),
|
Handler: admin_agent.AdminGetAgentRebateListHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/team/tree",
|
||||||
|
Handler: admin_agent.AdminGetAgentTeamTreeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/upgrade/agent",
|
||||||
|
Handler: admin_agent.AdminUpgradeAgentHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/upgrade/list",
|
Path: "/upgrade/list",
|
||||||
@@ -172,6 +191,20 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1/admin/auth"),
|
rest.WithPrefix("/api/v1/admin/auth"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/statistics",
|
||||||
|
Handler: admin_dashboard.AdminGetDashboardStatisticsHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/admin/dashboard"),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
|
[]rest.Middleware{serverCtx.AdminAuthInterceptor},
|
||||||
@@ -335,6 +368,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/retry-agent-process/:id",
|
Path: "/retry-agent-process/:id",
|
||||||
Handler: admin_order.AdminRetryAgentProcessHandler(serverCtx),
|
Handler: admin_order.AdminRetryAgentProcessHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// xpay补发货(查微信单并到账)
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/xpay-deliver/:id",
|
||||||
|
Handler: admin_order.AdminXpayDeliverHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 更新订单
|
// 更新订单
|
||||||
Method: http.MethodPut,
|
Method: http.MethodPut,
|
||||||
@@ -458,6 +497,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/detail/:order_id",
|
Path: "/detail/:order_id",
|
||||||
Handler: admin_query.AdminGetQueryDetailByOrderIdHandler(serverCtx),
|
Handler: admin_query.AdminGetQueryDetailByOrderIdHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// 生成报告分享链接(供后台预览 H5)
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/generate_share_link",
|
||||||
|
Handler: admin_query.AdminGenerateQueryShareLinkHandler(serverCtx),
|
||||||
|
},
|
||||||
}...,
|
}...,
|
||||||
),
|
),
|
||||||
rest.WithPrefix("/api/v1/admin/query"),
|
rest.WithPrefix("/api/v1/admin/query"),
|
||||||
@@ -641,6 +686,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/info",
|
Path: "/info",
|
||||||
Handler: agent.GetAgentInfoHandler(serverCtx),
|
Handler: agent.GetAgentInfoHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/invite/poster",
|
||||||
|
Handler: agent.GenerateInvitePosterHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Path: "/invite_code/delete",
|
Path: "/invite_code/delete",
|
||||||
@@ -666,6 +716,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/level/privilege",
|
Path: "/level/privilege",
|
||||||
Handler: agent.GetLevelPrivilegeHandler(serverCtx),
|
Handler: agent.GetLevelPrivilegeHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/order/agent",
|
||||||
|
Handler: agent.CheckOrderAgentHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/product_config",
|
Path: "/product_config",
|
||||||
@@ -731,11 +786,41 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/upgrade/subordinate",
|
Path: "/upgrade/subordinate",
|
||||||
Handler: agent.UpgradeSubordinateHandler(serverCtx),
|
Handler: agent.UpgradeSubordinateHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/whitelist/check",
|
||||||
|
Handler: agent.CheckFeatureWhitelistStatusHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/whitelist/features",
|
||||||
|
Handler: agent.GetWhitelistFeaturesHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/whitelist/list",
|
||||||
|
Handler: agent.GetWhitelistListHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/whitelist/offline",
|
||||||
|
Handler: agent.OfflineFeatureHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/whitelist/order/create",
|
||||||
|
Handler: agent.CreateWhitelistOrderHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Path: "/withdrawal/apply",
|
Path: "/withdrawal/apply",
|
||||||
Handler: agent.ApplyWithdrawalHandler(serverCtx),
|
Handler: agent.ApplyWithdrawalHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/withdrawal/last_info",
|
||||||
|
Handler: agent.GetLastWithdrawalInfoHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/withdrawal/list",
|
Path: "/withdrawal/list",
|
||||||
@@ -769,6 +854,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/app/version",
|
Path: "/app/version",
|
||||||
Handler: app.GetAppVersionHandler(serverCtx),
|
Handler: app.GetAppVersionHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/app/home/dynamic",
|
||||||
|
Handler: app.GetHomeDynamicDataHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 心跳检测接口
|
// 心跳检测接口
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
@@ -812,6 +902,18 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1"),
|
rest.WithPrefix("/api/v1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// get encrypted scene id for aliyun captcha
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/captcha/encryptedSceneId",
|
||||||
|
Handler: captcha.GetEncryptedSceneIdHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
@@ -841,6 +943,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/pay/wechat/refund_callback",
|
Path: "/pay/wechat/refund_callback",
|
||||||
Handler: pay.WechatPayRefundCallbackHandler(serverCtx),
|
Handler: pay.WechatPayRefundCallbackHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/pay/xpay/push",
|
||||||
|
Handler: pay.XpayPushHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/pay/xpay/push",
|
||||||
|
Handler: pay.XpayPushHandler(serverCtx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rest.WithPrefix("/api/v1"),
|
rest.WithPrefix("/api/v1"),
|
||||||
)
|
)
|
||||||
@@ -864,6 +976,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/pay/payment",
|
Path: "/pay/payment",
|
||||||
Handler: pay.PaymentHandler(serverCtx),
|
Handler: pay.PaymentHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/pay/xpay/client-event",
|
||||||
|
Handler: pay.XpayClientEventHandler(serverCtx),
|
||||||
|
},
|
||||||
}...,
|
}...,
|
||||||
),
|
),
|
||||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||||
@@ -1008,6 +1125,46 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1"),
|
rest.WithPrefix("/api/v1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 工具箱统一查询
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/toolbox/query",
|
||||||
|
Handler: toolbox.ToolboxQueryHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 访问已上传图片
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/upload/file/:name",
|
||||||
|
Handler: upload.ServeUploadHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthInterceptor},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 图片 Base64 上传
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/upload/image",
|
||||||
|
Handler: upload.UploadImageHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
|
|||||||
25
app/main/api/internal/handler/toolbox/toolboxqueryhandler.go
Normal file
25
app/main/api/internal/handler/toolbox/toolboxqueryhandler.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package toolbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/toolbox"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToolboxQueryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.ToolboxQueryReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
result.ParamErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := toolbox.NewToolboxQueryLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.ToolboxQuery(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/main/api/internal/handler/upload/serveuploadhandler.go
Normal file
26
app/main/api/internal/handler/upload/serveuploadhandler.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
uploadlogic "qnc-server/app/main/api/internal/logic/upload"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServeUploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.ServeUploadFileReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
result.ParamErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := uploadlogic.NewServeUploadLogic(r.Context(), svcCtx)
|
||||||
|
if err := l.ServeUpload(req.Name, w); err != nil {
|
||||||
|
result.HttpResult(r, w, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/main/api/internal/handler/upload/uploadimagehandler.go
Normal file
25
app/main/api/internal/handler/upload/uploadimagehandler.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
uploadlogic "qnc-server/app/main/api/internal/logic/upload"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/result"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.UploadImageReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
result.ParamErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := uploadlogic.NewUploadImageLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.UploadImage(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"os"
|
||||||
"qnc-server/common/globalkey"
|
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/lzUtils"
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
@@ -32,6 +32,13 @@ func NewAdminAuditWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseFloat 解析配置中的浮点数
|
||||||
|
func (l *AdminAuditWithdrawalLogic) parseFloat(s string) (float64, error) {
|
||||||
|
var result float64
|
||||||
|
_, err := fmt.Sscanf(s, "%f", &result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWithdrawalReq) (resp *types.AdminAuditWithdrawalResp, err error) {
|
func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWithdrawalReq) (resp *types.AdminAuditWithdrawalResp, err error) {
|
||||||
// 1. 查询提现记录
|
// 1. 查询提现记录
|
||||||
withdrawal, err := l.svcCtx.AgentWithdrawalModel.FindOne(l.ctx, req.WithdrawalId)
|
withdrawal, err := l.svcCtx.AgentWithdrawalModel.FindOne(l.ctx, req.WithdrawalId)
|
||||||
@@ -46,39 +53,126 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi
|
|||||||
|
|
||||||
// 4. 使用事务处理审核
|
// 4. 使用事务处理审核
|
||||||
err = l.svcCtx.AgentWithdrawalModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
|
err = l.svcCtx.AgentWithdrawalModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||||
if req.Status == 2 { // 审核通过
|
switch req.Status {
|
||||||
// 4.1 更新提现记录状态为提现中
|
case 2: // 审核通过
|
||||||
withdrawal.Status = 4 // 提现中
|
// 4.1 根据提现方式处理
|
||||||
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true}
|
switch withdrawal.WithdrawalType {
|
||||||
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
case 1:
|
||||||
return errors.Wrapf(err, "更新提现记录失败")
|
// 支付宝提现:审核通过前再次校验月度额度,避免一次性通过多笔超限
|
||||||
}
|
now := time.Now()
|
||||||
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||||
|
nextMonthStart := monthStart.AddDate(0, 1, 0)
|
||||||
|
|
||||||
// 4.2 调用支付宝转账接口
|
// 获取支付宝月度额度配置(默认 800 元)
|
||||||
outBizNo := withdrawal.WithdrawNo
|
alipayQuota := 800.0
|
||||||
transferResp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo)
|
if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(transCtx, "alipay_month_quota"); cfgErr == nil {
|
||||||
if err != nil {
|
if parsed, parseErr := l.parseFloat(cfg.ConfigValue); parseErr == nil && parsed > 0 {
|
||||||
// 转账失败,更新状态为失败
|
alipayQuota = parsed
|
||||||
withdrawal.Status = 6 // 提现失败
|
}
|
||||||
withdrawal.Remark = sql.NullString{String: fmt.Sprintf("转账失败: %v", err), Valid: true}
|
|
||||||
l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal)
|
|
||||||
|
|
||||||
// 解冻余额
|
|
||||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
|
|
||||||
if err == nil {
|
|
||||||
wallet.FrozenBalance -= withdrawal.Amount
|
|
||||||
wallet.Balance += withdrawal.Amount
|
|
||||||
l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrapf(err, "支付宝转账失败")
|
// 统计本月已成功的支付宝提现金额(status=5)
|
||||||
}
|
withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().
|
||||||
|
Where("agent_id = ? AND withdrawal_type = ? AND status = ? AND create_time >= ? AND create_time < ?",
|
||||||
|
withdrawal.AgentId, 1, 5, monthStart, nextMonthStart)
|
||||||
|
usedAmount, sumErr := l.svcCtx.AgentWithdrawalModel.FindSum(transCtx, withdrawBuilder, "amount")
|
||||||
|
if sumErr != nil {
|
||||||
|
return errors.Wrapf(sumErr, "查询本月支付宝提现额度使用情况失败")
|
||||||
|
}
|
||||||
|
|
||||||
// 4.3 根据转账结果更新状态
|
if usedAmount+withdrawal.Amount > alipayQuota {
|
||||||
switch transferResp.Status {
|
// 超出额度,不允许通过,保持待审核状态并提示原因
|
||||||
case "SUCCESS":
|
withdrawal.Status = 1
|
||||||
// 转账成功
|
withdrawal.Remark = sql.NullString{
|
||||||
|
String: fmt.Sprintf("超过本月支付宝提现额度(限额:%.2f 元,已用:%.2f 元),请使用银行卡提现或调整金额", alipayQuota, usedAmount),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新提现记录失败")
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("超过本月支付宝提现额度,无法通过该笔提现"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付宝提现:开发环境下做模拟,不调用真实支付宝转账
|
||||||
|
mockTransferStatus := "SUCCESS"
|
||||||
|
var transferResp struct {
|
||||||
|
Status string
|
||||||
|
SubCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("ENV") == "development" {
|
||||||
|
transferResp.Status = mockTransferStatus
|
||||||
|
logx.Infof("【DEV】模拟支付宝转账成功,withdrawNo=%s, amount=%.2f, payee=%s",
|
||||||
|
withdrawal.WithdrawNo, withdrawal.ActualAmount, withdrawal.PayeeAccount)
|
||||||
|
} else {
|
||||||
|
// 生产环境:同步调用支付宝转账接口
|
||||||
|
outBizNo := withdrawal.WithdrawNo
|
||||||
|
resp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo)
|
||||||
|
if err != nil {
|
||||||
|
// 调用失败:保持状态为待审核(1),只记录备注,方便管理员重试
|
||||||
|
withdrawal.Status = 1 // 待审核
|
||||||
|
withdrawal.Remark = sql.NullString{String: fmt.Sprintf("支付宝转账调用失败: %v", err), Valid: true}
|
||||||
|
_ = l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal)
|
||||||
|
|
||||||
|
return errors.Wrapf(err, "支付宝转账失败")
|
||||||
|
}
|
||||||
|
transferResp.Status = resp.Status
|
||||||
|
transferResp.SubCode = resp.SubCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2 根据转账结果更新状态
|
||||||
|
switch transferResp.Status {
|
||||||
|
case "SUCCESS":
|
||||||
|
// 转账成功
|
||||||
|
withdrawal.Status = 5 // 提现成功
|
||||||
|
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新提现记录失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新钱包(解冻并扣除)
|
||||||
|
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "查询钱包失败")
|
||||||
|
}
|
||||||
|
wallet.FrozenBalance -= withdrawal.Amount
|
||||||
|
wallet.WithdrawnAmount += withdrawal.Amount
|
||||||
|
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新钱包失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新扣税记录状态
|
||||||
|
taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
||||||
|
Where("withdrawal_id = ?", withdrawal.Id)
|
||||||
|
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "")
|
||||||
|
if err == nil && len(taxRecords) > 0 {
|
||||||
|
taxRecord := taxRecords[0]
|
||||||
|
taxRecord.TaxStatus = 2 // 已扣税
|
||||||
|
taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now())
|
||||||
|
l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "FAIL":
|
||||||
|
// 转账失败:保持待审核状态,方便人工处理或重试
|
||||||
|
withdrawal.Status = 1 // 待审核
|
||||||
|
errorMsg := l.mapAlipayError(transferResp.SubCode)
|
||||||
|
withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true}
|
||||||
|
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新提现记录失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "DEALING":
|
||||||
|
// 处理中:同样保持待审核状态(1),由管理员后续确认
|
||||||
|
withdrawal.Status = 1
|
||||||
|
withdrawal.Remark = sql.NullString{String: "支付宝处理中,请稍后重试或联系平台", Valid: true}
|
||||||
|
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新提现记录失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// 银行卡提现:审核通过即视为提现成功(线下已/将立即打款)
|
||||||
withdrawal.Status = 5 // 提现成功
|
withdrawal.Status = 5 // 提现成功
|
||||||
|
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true}
|
||||||
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
||||||
return errors.Wrapf(err, "更新提现记录失败")
|
return errors.Wrapf(err, "更新提现记录失败")
|
||||||
}
|
}
|
||||||
@@ -96,7 +190,7 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi
|
|||||||
|
|
||||||
// 更新扣税记录状态
|
// 更新扣税记录状态
|
||||||
taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
||||||
Where("withdrawal_id = ? AND del_state = ?", withdrawal.Id, globalkey.DelStateNo)
|
Where("withdrawal_id = ?", withdrawal.Id)
|
||||||
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "")
|
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "")
|
||||||
if err == nil && len(taxRecords) > 0 {
|
if err == nil && len(taxRecords) > 0 {
|
||||||
taxRecord := taxRecords[0]
|
taxRecord := taxRecords[0]
|
||||||
@@ -104,30 +198,9 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi
|
|||||||
taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now())
|
taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now())
|
||||||
l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord)
|
l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "FAIL":
|
|
||||||
// 转账失败
|
|
||||||
withdrawal.Status = 6 // 提现失败
|
|
||||||
errorMsg := l.mapAlipayError(transferResp.SubCode)
|
|
||||||
withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true}
|
|
||||||
if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil {
|
|
||||||
return errors.Wrapf(err, "更新提现记录失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解冻余额
|
|
||||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId)
|
|
||||||
if err == nil {
|
|
||||||
wallet.FrozenBalance -= withdrawal.Amount
|
|
||||||
wallet.Balance += withdrawal.Amount
|
|
||||||
l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "DEALING":
|
|
||||||
// 处理中,保持提现中状态,后续通过轮询更新
|
|
||||||
// 状态已经是4(提现中),无需更新
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if req.Status == 3 { // 审核拒绝
|
case 3: // 审核拒绝
|
||||||
// 4.1 更新提现记录状态为拒绝
|
// 4.1 更新提现记录状态为拒绝
|
||||||
withdrawal.Status = 3 // 审核拒绝
|
withdrawal.Status = 3 // 审核拒绝
|
||||||
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true}
|
withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
@@ -27,20 +28,46 @@ func NewAdminGetAgentCommissionListLogic(ctx context.Context, svcCtx *svc.Servic
|
|||||||
|
|
||||||
func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *types.AdminGetAgentCommissionListReq) (resp *types.AdminGetAgentCommissionListResp, err error) {
|
func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *types.AdminGetAgentCommissionListReq) (resp *types.AdminGetAgentCommissionListResp, err error) {
|
||||||
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
|
builder := l.svcCtx.AgentCommissionModel.SelectBuilder()
|
||||||
|
|
||||||
|
// 按 agent_code 筛选:先查出 agent_id
|
||||||
|
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||||
|
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||||
|
if parseErr == nil {
|
||||||
|
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||||
|
if findErr == nil && agent != nil {
|
||||||
|
builder = builder.Where(squirrel.Eq{"agent_id": agent.Id})
|
||||||
|
} else {
|
||||||
|
// 找不到对应代理,返回空列表
|
||||||
|
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 order_no 筛选:先查出 order_id
|
||||||
|
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||||
|
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||||
|
if findErr == nil && order != nil {
|
||||||
|
builder = builder.Where(squirrel.Eq{"order_id": order.Id})
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.AgentId != nil {
|
if req.AgentId != nil {
|
||||||
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
|
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
|
||||||
}
|
}
|
||||||
if req.Status != nil {
|
if req.Status != nil {
|
||||||
builder = builder.Where(squirrel.Eq{"status": *req.Status})
|
builder = builder.Where(squirrel.Eq{"status": *req.Status})
|
||||||
}
|
}
|
||||||
// 产品名称筛选功能已移除,如需可按product_id筛选
|
|
||||||
|
|
||||||
list, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
list, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量查product_name
|
// 批量查 product_name
|
||||||
productIds := make(map[string]struct{})
|
productIds := make(map[string]struct{})
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
productIds[v.ProductId] = struct{}{}
|
productIds[v.ProductId] = struct{}{}
|
||||||
@@ -58,11 +85,38 @@ func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量查 agent_code
|
||||||
|
agentIds := make([]string, 0, len(list))
|
||||||
|
for _, v := range list {
|
||||||
|
if v.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, v.AgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
|
// 批量查 order_no
|
||||||
|
orderIds := make([]string, 0, len(list))
|
||||||
|
for _, v := range list {
|
||||||
|
if v.OrderId != "" {
|
||||||
|
orderIds = append(orderIds, v.OrderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderNoMap := make(map[string]string)
|
||||||
|
if len(orderIds) > 0 {
|
||||||
|
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": orderIds})
|
||||||
|
orders, _ := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
|
||||||
|
for _, o := range orders {
|
||||||
|
orderNoMap[o.Id] = o.OrderNo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items := make([]types.AgentCommissionListItem, 0, len(list))
|
items := make([]types.AgentCommissionListItem, 0, len(list))
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
item := types.AgentCommissionListItem{}
|
item := types.AgentCommissionListItem{}
|
||||||
_ = copier.Copy(&item, v)
|
_ = copier.Copy(&item, v)
|
||||||
|
item.AgentCode = agentCodeMap[v.AgentId]
|
||||||
item.ProductName = productNameMap[v.ProductId]
|
item.ProductName = productNameMap[v.ProductId]
|
||||||
|
item.OrderNo = orderNoMap[v.OrderId]
|
||||||
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
|
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
@@ -29,6 +32,16 @@ func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAg
|
|||||||
if req.AgentId != nil {
|
if req.AgentId != nil {
|
||||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||||
}
|
}
|
||||||
|
if req.AgentCode != nil && strings.TrimSpace(*req.AgentCode) != "" {
|
||||||
|
var agentCode int64
|
||||||
|
if _, err := fmt.Sscanf(strings.TrimSpace(*req.AgentCode), "%d", &agentCode); err == nil {
|
||||||
|
builder = builder.Where(
|
||||||
|
"agent_id IN (SELECT id FROM agent WHERE agent_code = ? AND del_state = ?)",
|
||||||
|
agentCode,
|
||||||
|
globalkey.DelStateNo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
if req.LinkIdentifier != nil && *req.LinkIdentifier != "" {
|
if req.LinkIdentifier != nil && *req.LinkIdentifier != "" {
|
||||||
builder = builder.Where("link_identifier = ?", *req.LinkIdentifier)
|
builder = builder.Where("link_identifier = ?", *req.LinkIdentifier)
|
||||||
}
|
}
|
||||||
@@ -60,11 +73,20 @@ func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agentIds := make([]string, 0, len(links))
|
||||||
|
for _, link := range links {
|
||||||
|
if link.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, link.AgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
items := make([]types.AgentLinkListItem, 0, len(links))
|
items := make([]types.AgentLinkListItem, 0, len(links))
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
items = append(items, types.AgentLinkListItem{
|
items = append(items, types.AgentLinkListItem{
|
||||||
Id: link.Id,
|
Id: link.Id,
|
||||||
AgentId: link.AgentId,
|
AgentId: link.AgentId,
|
||||||
|
AgentCode: agentCodeMap[link.AgentId],
|
||||||
ProductId: link.ProductId,
|
ProductId: link.ProductId,
|
||||||
ProductName: productNameMap[link.ProductId],
|
ProductName: productNameMap[link.ProductId],
|
||||||
SetPrice: link.SetPrice,
|
SetPrice: link.SetPrice,
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
@@ -30,26 +34,52 @@ func NewAdminGetAgentListLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
|||||||
func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListReq) (resp *types.AdminGetAgentListResp, err error) {
|
func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListReq) (resp *types.AdminGetAgentListResp, err error) {
|
||||||
builder := l.svcCtx.AgentModel.SelectBuilder()
|
builder := l.svcCtx.AgentModel.SelectBuilder()
|
||||||
|
|
||||||
// 如果传入TeamLeaderId,则查找该团队首领下的所有代理
|
exactByAgentId := req.AgentId != nil && *req.AgentId != ""
|
||||||
if req.TeamLeaderId != nil {
|
if exactByAgentId {
|
||||||
builder = builder.Where(squirrel.Eq{"team_leader_id": *req.TeamLeaderId})
|
builder = builder.Where(squirrel.Eq{"id": *req.AgentId})
|
||||||
}
|
} else {
|
||||||
if req.Level != nil {
|
// 如果传入TeamLeaderId,则查找该团队首领下的直属代理(不含首领本人:钻石等业务会把 team_leader_id 写成自己)
|
||||||
builder = builder.Where(squirrel.Eq{"level": *req.Level})
|
if req.TeamLeaderId != nil && *req.TeamLeaderId != "" {
|
||||||
}
|
tl := *req.TeamLeaderId
|
||||||
if req.Mobile != nil && *req.Mobile != "" {
|
builder = builder.Where(squirrel.Eq{"team_leader_id": tl})
|
||||||
// 加密手机号进行查询
|
builder = builder.Where(squirrel.NotEq{"id": tl})
|
||||||
encryptedMobile, err := crypto.EncryptMobile(*req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
}
|
||||||
if err != nil {
|
if req.Level != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
builder = builder.Where(squirrel.Eq{"level": *req.Level})
|
||||||
|
}
|
||||||
|
if req.Mobile != nil && *req.Mobile != "" {
|
||||||
|
// 加密手机号进行查询
|
||||||
|
encryptedMobile, err := crypto.EncryptMobile(*req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||||
|
}
|
||||||
|
builder = builder.Where(squirrel.Eq{"mobile": encryptedMobile})
|
||||||
|
}
|
||||||
|
if req.Region != nil && *req.Region != "" {
|
||||||
|
// 注意:region字段现在是可选的,查询时需要处理NULL值
|
||||||
|
// 如果region字段是NULL,使用IS NULL查询;否则使用等值查询
|
||||||
|
// 这里简化处理,直接使用等值查询(如果数据库中有NULL值,需要特殊处理)
|
||||||
|
builder = builder.Where(squirrel.Eq{"region": *req.Region})
|
||||||
|
}
|
||||||
|
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||||
|
// 将字符串转换为int64
|
||||||
|
var agentCode int64
|
||||||
|
if _, err := fmt.Sscanf(*req.AgentCode, "%d", &agentCode); err == nil {
|
||||||
|
builder = builder.Where(squirrel.Eq{"agent_code": agentCode})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder = builder.Where(squirrel.Eq{"mobile": encryptedMobile})
|
|
||||||
}
|
}
|
||||||
if req.Region != nil && *req.Region != "" {
|
|
||||||
// 注意:region字段现在是可选的,查询时需要处理NULL值
|
if req.RealName != nil && strings.TrimSpace(*req.RealName) != "" {
|
||||||
// 如果region字段是NULL,使用IS NULL查询;否则使用等值查询
|
name := strings.TrimSpace(*req.RealName)
|
||||||
// 这里简化处理,直接使用等值查询(如果数据库中有NULL值,需要特殊处理)
|
likePattern := "%" + name + "%"
|
||||||
builder = builder.Where(squirrel.Eq{"region": *req.Region})
|
builder = builder.Where(`EXISTS (
|
||||||
|
SELECT 1 FROM agent_real_name arn
|
||||||
|
WHERE arn.agent_id = agent.id
|
||||||
|
AND arn.del_state = ?
|
||||||
|
AND arn.verify_time IS NOT NULL
|
||||||
|
AND arn.name LIKE ?
|
||||||
|
)`, globalkey.DelStateNo, likePattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
agents, total, err := l.svcCtx.AgentModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
|
agents, total, err := l.svcCtx.AgentModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC")
|
||||||
@@ -57,6 +87,17 @@ func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListR
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leaderIds := make([]string, 0, len(agents))
|
||||||
|
for _, a := range agents {
|
||||||
|
if a.Id != "" {
|
||||||
|
leaderIds = append(leaderIds, a.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subCountByLeader, err := l.svcCtx.AgentModel.CountSubordinatesByTeamLeaderIds(l.ctx, leaderIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "统计直属下级数量失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
items := make([]types.AgentListItem, 0, len(agents))
|
items := make([]types.AgentListItem, 0, len(agents))
|
||||||
|
|
||||||
for _, agent := range agents {
|
for _, agent := range agents {
|
||||||
@@ -79,11 +120,25 @@ func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListR
|
|||||||
// 查询钱包信息
|
// 查询钱包信息
|
||||||
wallet, _ := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
|
wallet, _ := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
|
||||||
|
|
||||||
// 查询实名认证信息
|
// 查询实名认证信息(数据库姓名明文、身份证密文,解密后明文返回不脱敏)
|
||||||
realNameInfo, _ := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
|
realNameInfo, _ := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
|
||||||
isRealName := false
|
isRealName := false
|
||||||
if realNameInfo != nil && realNameInfo.VerifyTime.Valid {
|
realName := ""
|
||||||
isRealName = true // verify_time不为空表示已通过三要素核验
|
idCardPlain := "" // 解密后明文返回
|
||||||
|
if realNameInfo != nil {
|
||||||
|
if realNameInfo.VerifyTime.Valid {
|
||||||
|
isRealName = true // verify_time不为空表示已通过三要素核验
|
||||||
|
}
|
||||||
|
realName = realNameInfo.Name
|
||||||
|
if realNameInfo.IdCard != "" {
|
||||||
|
key, keyErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey)
|
||||||
|
if keyErr == nil {
|
||||||
|
decrypted, err := crypto.DecryptIDCard(realNameInfo.IdCard, key)
|
||||||
|
if err == nil {
|
||||||
|
idCardPlain = decrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wechatId := ""
|
wechatId := ""
|
||||||
@@ -102,15 +157,18 @@ func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListR
|
|||||||
}
|
}
|
||||||
|
|
||||||
item := types.AgentListItem{
|
item := types.AgentListItem{
|
||||||
Id: agent.Id,
|
Id: agent.Id,
|
||||||
UserId: agent.UserId,
|
UserId: agent.UserId,
|
||||||
Level: agent.Level,
|
Level: agent.Level,
|
||||||
LevelName: levelName,
|
LevelName: levelName,
|
||||||
Region: region,
|
Region: region,
|
||||||
Mobile: agent.Mobile,
|
Mobile: agent.Mobile,
|
||||||
WechatId: wechatId,
|
RealName: realName,
|
||||||
TeamLeaderId: teamLeaderId,
|
IdCard: idCardPlain,
|
||||||
AgentCode: agent.AgentCode,
|
WechatId: wechatId,
|
||||||
|
TeamLeaderId: teamLeaderId,
|
||||||
|
SubordinateCount: subCountByLeader[agent.Id],
|
||||||
|
AgentCode: agent.AgentCode,
|
||||||
Balance: 0,
|
Balance: 0,
|
||||||
TotalEarnings: 0,
|
TotalEarnings: 0,
|
||||||
FrozenBalance: 0,
|
FrozenBalance: 0,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
@@ -28,21 +30,57 @@ func NewAdminGetAgentOrderListLogic(ctx context.Context, svcCtx *svc.ServiceCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func emptyAgentOrderListResp() *types.AdminGetAgentOrderListResp {
|
||||||
|
return &types.AdminGetAgentOrderListResp{
|
||||||
|
Total: 0,
|
||||||
|
Items: []types.AgentOrderListItem{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) {
|
func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) {
|
||||||
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
|
builder := l.svcCtx.AgentOrderModel.SelectBuilder().
|
||||||
Where("del_state = ?", globalkey.DelStateNo)
|
Where("del_state = ?", globalkey.DelStateNo)
|
||||||
|
|
||||||
if req.AgentId != nil {
|
if req.AgentId != nil && *req.AgentId != "" {
|
||||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||||
}
|
}
|
||||||
if req.OrderId != nil {
|
|
||||||
|
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||||
|
code, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||||
|
if parseErr != nil {
|
||||||
|
return emptyAgentOrderListResp(), nil
|
||||||
|
}
|
||||||
|
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, code)
|
||||||
|
if findErr != nil || agent == nil {
|
||||||
|
return emptyAgentOrderListResp(), nil
|
||||||
|
}
|
||||||
|
builder = builder.Where("agent_id = ?", agent.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.OrderId != nil && *req.OrderId != "" {
|
||||||
builder = builder.Where("order_id = ?", *req.OrderId)
|
builder = builder.Where("order_id = ?", *req.OrderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||||
|
o, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||||
|
if findErr != nil || o == nil {
|
||||||
|
return emptyAgentOrderListResp(), nil
|
||||||
|
}
|
||||||
|
builder = builder.Where("order_id = ?", o.Id)
|
||||||
|
}
|
||||||
|
|
||||||
if req.ProcessStatus != nil {
|
if req.ProcessStatus != nil {
|
||||||
builder = builder.Where("process_status = ?", *req.ProcessStatus)
|
builder = builder.Where("process_status = ?", *req.ProcessStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页查询
|
if req.OrderStatus != nil && *req.OrderStatus != "" {
|
||||||
|
builder = builder.Where(squirrel.Expr(
|
||||||
|
"order_id IN (SELECT id FROM `order` WHERE status = ? AND del_state = ?)",
|
||||||
|
*req.OrderStatus,
|
||||||
|
globalkey.DelStateNo,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
page := req.Page
|
page := req.Page
|
||||||
if page <= 0 {
|
if page <= 0 {
|
||||||
page = 1
|
page = 1
|
||||||
@@ -57,7 +95,6 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理订单列表失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理订单列表失败, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量查询产品名称
|
|
||||||
productIdSet := make(map[string]struct{})
|
productIdSet := make(map[string]struct{})
|
||||||
for _, order := range orders {
|
for _, order := range orders {
|
||||||
productIdSet[order.ProductId] = struct{}{}
|
productIdSet[order.ProductId] = struct{}{}
|
||||||
@@ -74,13 +111,50 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装响应
|
agentIds := make([]string, 0, len(orders))
|
||||||
|
orderIds := make([]string, 0, len(orders))
|
||||||
|
for _, order := range orders {
|
||||||
|
if order.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, order.AgentId)
|
||||||
|
}
|
||||||
|
if order.OrderId != "" {
|
||||||
|
orderIds = append(orderIds, order.OrderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
|
orderNoMap := make(map[string]string)
|
||||||
|
orderStatusMap := make(map[string]string)
|
||||||
|
if len(orderIds) > 0 {
|
||||||
|
uniq := make([]string, 0, len(orderIds))
|
||||||
|
seen := make(map[string]struct{}, len(orderIds))
|
||||||
|
for _, id := range orderIds {
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
uniq = append(uniq, id)
|
||||||
|
}
|
||||||
|
if len(uniq) > 0 {
|
||||||
|
os, _ := l.svcCtx.OrderModel.FindAll(l.ctx, l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": uniq}), "")
|
||||||
|
for _, o := range os {
|
||||||
|
orderNoMap[o.Id] = o.OrderNo
|
||||||
|
orderStatusMap[o.Id] = o.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items := make([]types.AgentOrderListItem, 0, len(orders))
|
items := make([]types.AgentOrderListItem, 0, len(orders))
|
||||||
for _, order := range orders {
|
for _, order := range orders {
|
||||||
items = append(items, types.AgentOrderListItem{
|
items = append(items, types.AgentOrderListItem{
|
||||||
Id: order.Id,
|
Id: order.Id,
|
||||||
AgentId: order.AgentId,
|
AgentId: order.AgentId,
|
||||||
|
AgentCode: agentCodeMap[order.AgentId],
|
||||||
OrderId: order.OrderId,
|
OrderId: order.OrderId,
|
||||||
|
OrderNo: orderNoMap[order.OrderId],
|
||||||
ProductId: order.ProductId,
|
ProductId: order.ProductId,
|
||||||
ProductName: productNameMap[order.ProductId],
|
ProductName: productNameMap[order.ProductId],
|
||||||
OrderAmount: order.OrderAmount,
|
OrderAmount: order.OrderAmount,
|
||||||
@@ -89,6 +163,7 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet
|
|||||||
PriceCost: order.PriceCost,
|
PriceCost: order.PriceCost,
|
||||||
AgentProfit: order.AgentProfit,
|
AgentProfit: order.AgentProfit,
|
||||||
ProcessStatus: order.ProcessStatus,
|
ProcessStatus: order.ProcessStatus,
|
||||||
|
OrderStatus: orderStatusMap[order.OrderId],
|
||||||
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
|
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminGetAgentOrderSettlementLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminGetAgentOrderSettlementLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentOrderSettlementLogic {
|
||||||
|
return &AdminGetAgentOrderSettlementLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetAgentOrderSettlementLogic) AdminGetAgentOrderSettlement(req *types.AdminGetAgentOrderSettlementReq) (resp *types.AdminGetAgentOrderSettlementResp, err error) {
|
||||||
|
empty := &types.AdminGetAgentOrderSettlementResp{
|
||||||
|
Commissions: []types.AgentCommissionListItem{},
|
||||||
|
Rebates: []types.AgentRebateListItem{},
|
||||||
|
}
|
||||||
|
if req.OrderNo == "" {
|
||||||
|
return empty, nil
|
||||||
|
}
|
||||||
|
on := req.OrderNo
|
||||||
|
page := int64(1)
|
||||||
|
pageSize := int64(200)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
var cResp *types.AdminGetAgentCommissionListResp
|
||||||
|
var cErr error
|
||||||
|
var rResp *types.AdminGetAgentRebateListResp
|
||||||
|
var rErr error
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
resp, e := NewAdminGetAgentCommissionListLogic(l.ctx, l.svcCtx).AdminGetAgentCommissionList(
|
||||||
|
&types.AdminGetAgentCommissionListReq{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
OrderNo: &on,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mu.Lock()
|
||||||
|
cResp, cErr = resp, e
|
||||||
|
mu.Unlock()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
resp, e := NewAdminGetAgentRebateListLogic(l.ctx, l.svcCtx).AdminGetAgentRebateList(
|
||||||
|
&types.AdminGetAgentRebateListReq{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
OrderNo: &on,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mu.Lock()
|
||||||
|
rResp, rErr = resp, e
|
||||||
|
mu.Unlock()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
if cErr != nil {
|
||||||
|
return nil, cErr
|
||||||
|
}
|
||||||
|
if rErr != nil {
|
||||||
|
return nil, rErr
|
||||||
|
}
|
||||||
|
if cResp == nil {
|
||||||
|
cResp = &types.AdminGetAgentCommissionListResp{Items: []types.AgentCommissionListItem{}}
|
||||||
|
}
|
||||||
|
if rResp == nil {
|
||||||
|
rResp = &types.AdminGetAgentRebateListResp{Items: []types.AgentRebateListItem{}}
|
||||||
|
}
|
||||||
|
commissions := cResp.Items
|
||||||
|
if commissions == nil {
|
||||||
|
commissions = []types.AgentCommissionListItem{}
|
||||||
|
}
|
||||||
|
rebates := rResp.Items
|
||||||
|
if rebates == nil {
|
||||||
|
rebates = []types.AgentRebateListItem{}
|
||||||
|
}
|
||||||
|
return &types.AdminGetAgentOrderSettlementResp{
|
||||||
|
Commissions: commissions,
|
||||||
|
Rebates: rebates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -2,15 +2,15 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
|
||||||
"qnc-server/app/main/api/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,6 +60,14 @@ func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.Ad
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询实名认证列表失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询实名认证列表失败, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agentIds := make([]string, 0, len(realNames))
|
||||||
|
for _, realName := range realNames {
|
||||||
|
if realName.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, realName.AgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
// 组装响应
|
// 组装响应
|
||||||
items := make([]types.AgentRealNameListItem, 0, len(realNames))
|
items := make([]types.AgentRealNameListItem, 0, len(realNames))
|
||||||
for _, realName := range realNames {
|
for _, realName := range realNames {
|
||||||
@@ -72,16 +80,19 @@ func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.Ad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密身份证号(仅显示部分)
|
// 解密身份证号(仅显示部分,密钥与实名认证写入时一致:hex 解码后使用)
|
||||||
idCard := ""
|
idCard := ""
|
||||||
if realName.IdCard != "" {
|
if realName.IdCard != "" {
|
||||||
decrypted, err := crypto.DecryptIDCard(realName.IdCard, []byte(l.svcCtx.Config.Encrypt.SecretKey))
|
key, keyErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey)
|
||||||
if err == nil {
|
if keyErr == nil {
|
||||||
// 脱敏显示
|
decrypted, err := crypto.DecryptIDCard(realName.IdCard, key)
|
||||||
if len(decrypted) > 10 {
|
if err == nil {
|
||||||
idCard = decrypted[:3] + "***********" + decrypted[len(decrypted)-4:]
|
// 脱敏显示
|
||||||
} else {
|
if len(decrypted) > 10 {
|
||||||
idCard = decrypted
|
idCard = decrypted[:3] + "***********" + decrypted[len(decrypted)-4:]
|
||||||
|
} else {
|
||||||
|
idCard = decrypted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +108,7 @@ func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.Ad
|
|||||||
item := types.AgentRealNameListItem{
|
item := types.AgentRealNameListItem{
|
||||||
Id: realName.Id,
|
Id: realName.Id,
|
||||||
AgentId: realName.AgentId,
|
AgentId: realName.AgentId,
|
||||||
|
AgentCode: agentCodeMap[realName.AgentId],
|
||||||
Name: realName.Name,
|
Name: realName.Name,
|
||||||
IdCard: idCard,
|
IdCard: idCard,
|
||||||
Mobile: mobile,
|
Mobile: mobile,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
@@ -35,9 +37,47 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG
|
|||||||
if req.AgentId != nil {
|
if req.AgentId != nil {
|
||||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||||
}
|
}
|
||||||
if req.SourceAgentId != nil {
|
|
||||||
builder = builder.Where("source_agent_id = ?", *req.SourceAgentId)
|
// 按 agent_code 筛选:先查出 agent_id
|
||||||
|
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||||
|
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||||
|
if parseErr == nil {
|
||||||
|
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||||
|
if findErr == nil && agent != nil {
|
||||||
|
builder = builder.Where("agent_id = ?", agent.Id)
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按 source_agent_code 筛选:先查出 source_agent_id
|
||||||
|
if req.SourceAgentCode != nil && *req.SourceAgentCode != "" {
|
||||||
|
sourceAgentCodeInt, parseErr := strconv.ParseInt(*req.SourceAgentCode, 10, 64)
|
||||||
|
if parseErr == nil {
|
||||||
|
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, sourceAgentCodeInt)
|
||||||
|
if findErr == nil && agent != nil {
|
||||||
|
builder = builder.Where("source_agent_id = ?", agent.Id)
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 order_no 筛选:先查出 order_id
|
||||||
|
if req.OrderNo != nil && *req.OrderNo != "" {
|
||||||
|
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo)
|
||||||
|
if findErr == nil && order != nil {
|
||||||
|
builder = builder.Where("order_id = ?", order.Id)
|
||||||
|
} else {
|
||||||
|
return &types.AdminGetAgentRebateListResp{Total: 0, Items: []types.AgentRebateListItem{}}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.RebateType != nil {
|
if req.RebateType != nil {
|
||||||
builder = builder.Where("rebate_type = ?", *req.RebateType)
|
builder = builder.Where("rebate_type = ?", *req.RebateType)
|
||||||
}
|
}
|
||||||
@@ -74,17 +114,48 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agentIds := make([]string, 0, len(rebates)*2)
|
||||||
|
for _, rebate := range rebates {
|
||||||
|
if rebate.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, rebate.AgentId)
|
||||||
|
}
|
||||||
|
if rebate.SourceAgentId != "" {
|
||||||
|
agentIds = append(agentIds, rebate.SourceAgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
|
// 批量查 order_no
|
||||||
|
orderIds := make([]string, 0, len(rebates))
|
||||||
|
for _, rebate := range rebates {
|
||||||
|
if rebate.OrderId != "" {
|
||||||
|
orderIds = append(orderIds, rebate.OrderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderNoMap := make(map[string]string)
|
||||||
|
if len(orderIds) > 0 {
|
||||||
|
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": orderIds})
|
||||||
|
orders, _ := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
|
||||||
|
for _, o := range orders {
|
||||||
|
orderNoMap[o.Id] = o.OrderNo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组装响应
|
// 组装响应
|
||||||
items := make([]types.AgentRebateListItem, 0, len(rebates))
|
items := make([]types.AgentRebateListItem, 0, len(rebates))
|
||||||
for _, rebate := range rebates {
|
for _, rebate := range rebates {
|
||||||
items = append(items, types.AgentRebateListItem{
|
items = append(items, types.AgentRebateListItem{
|
||||||
Id: rebate.Id,
|
Id: rebate.Id,
|
||||||
AgentId: rebate.AgentId,
|
AgentId: rebate.AgentId,
|
||||||
SourceAgentId: rebate.SourceAgentId,
|
AgentCode: agentCodeMap[rebate.AgentId],
|
||||||
OrderId: rebate.OrderId,
|
SourceAgentId: rebate.SourceAgentId,
|
||||||
RebateType: rebate.RebateType,
|
SourceAgentCode: agentCodeMap[rebate.SourceAgentId],
|
||||||
Amount: rebate.RebateAmount,
|
OrderId: rebate.OrderId,
|
||||||
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
|
OrderNo: orderNoMap[rebate.OrderId],
|
||||||
|
RebateType: rebate.RebateType,
|
||||||
|
Amount: rebate.RebateAmount,
|
||||||
|
Status: rebate.Status,
|
||||||
|
CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminGetAgentTeamTreeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminGetAgentTeamTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentTeamTreeLogic {
|
||||||
|
return &AdminGetAgentTeamTreeLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDiamondTeamRootId(agent *model.Agent) string {
|
||||||
|
if agent.TeamLeaderId.Valid && agent.TeamLeaderId.String != "" && agent.TeamLeaderId.String != agent.Id {
|
||||||
|
return agent.TeamLeaderId.String
|
||||||
|
}
|
||||||
|
return agent.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func levelName(lv int64) string {
|
||||||
|
switch lv {
|
||||||
|
case 1:
|
||||||
|
return "普通"
|
||||||
|
case 2:
|
||||||
|
return "黄金"
|
||||||
|
case 3:
|
||||||
|
return "钻石"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetAgentTeamTreeLogic) AdminGetAgentTeamTree(req *types.AdminGetAgentTeamTreeReq) (resp *types.AdminGetAgentTeamTreeResp, err error) {
|
||||||
|
if req.AgentId == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("agent_id 不能为空"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.AgentId)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
teamRootId := resolveDiamondTeamRootId(anchor)
|
||||||
|
|
||||||
|
builder := l.svcCtx.AgentModel.SelectBuilder().
|
||||||
|
Where("del_state = ?", globalkey.DelStateNo).
|
||||||
|
Where(squirrel.Or{
|
||||||
|
squirrel.Eq{"id": teamRootId},
|
||||||
|
squirrel.And{
|
||||||
|
squirrel.Eq{"team_leader_id": teamRootId},
|
||||||
|
squirrel.NotEq{"id": teamRootId},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
members, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询团队成员失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agentById := make(map[string]*model.Agent, len(members)+1)
|
||||||
|
memberIDs := make([]string, 0, len(members)+1)
|
||||||
|
anchorInTeam := false
|
||||||
|
for _, m := range members {
|
||||||
|
agentById[m.Id] = m
|
||||||
|
memberIDs = append(memberIDs, m.Id)
|
||||||
|
if m.Id == anchor.Id {
|
||||||
|
anchorInTeam = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !anchorInTeam {
|
||||||
|
agentById[anchor.Id] = anchor
|
||||||
|
memberIDs = append(memberIDs, anchor.Id)
|
||||||
|
members = append(members, anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
relBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
|
||||||
|
Where("relation_type = ? AND del_state = ?", 1, globalkey.DelStateNo).
|
||||||
|
Where(squirrel.Eq{"parent_id": memberIDs}).
|
||||||
|
Where(squirrel.Eq{"child_id": memberIDs})
|
||||||
|
|
||||||
|
relations, err := l.svcCtx.AgentRelationModel.FindAll(l.ctx, relBuilder, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请关系失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
childSet := make(map[string]map[string]struct{})
|
||||||
|
addEdge := func(parentId, childId string) {
|
||||||
|
if parentId == "" || childId == "" || parentId == childId {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := agentById[parentId]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := agentById[childId]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if childSet[parentId] == nil {
|
||||||
|
childSet[parentId] = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
childSet[parentId][childId] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range relations {
|
||||||
|
addEdge(r.ParentId, r.ChildId)
|
||||||
|
}
|
||||||
|
|
||||||
|
reachable := map[string]bool{teamRootId: true}
|
||||||
|
var walkReach func(string)
|
||||||
|
walkReach = func(id string) {
|
||||||
|
for cid := range childSet[id] {
|
||||||
|
if reachable[cid] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reachable[cid] = true
|
||||||
|
walkReach(cid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walkReach(teamRootId)
|
||||||
|
|
||||||
|
for _, m := range members {
|
||||||
|
if m.Id == teamRootId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reachable[m.Id] {
|
||||||
|
addEdge(teamRootId, m.Id)
|
||||||
|
if !reachable[m.Id] {
|
||||||
|
reachable[m.Id] = true
|
||||||
|
walkReach(m.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
childrenMap := make(map[string][]string)
|
||||||
|
for pid, set := range childSet {
|
||||||
|
slice := make([]string, 0, len(set))
|
||||||
|
for cid := range set {
|
||||||
|
slice = append(slice, cid)
|
||||||
|
}
|
||||||
|
sort.Slice(slice, func(i, j int) bool {
|
||||||
|
return agentById[slice[i]].AgentCode < agentById[slice[j]].AgentCode
|
||||||
|
})
|
||||||
|
childrenMap[pid] = slice
|
||||||
|
}
|
||||||
|
|
||||||
|
var build func(string) (types.AgentTeamTreeNode, error)
|
||||||
|
build = func(agentId string) (types.AgentTeamTreeNode, error) {
|
||||||
|
ag := agentById[agentId]
|
||||||
|
if ag == nil {
|
||||||
|
return types.AgentTeamTreeNode{}, errors.New("missing agent in tree")
|
||||||
|
}
|
||||||
|
mobile := ag.Mobile
|
||||||
|
mobile, err = crypto.DecryptMobile(mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return types.AgentTeamTreeNode{}, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
node := types.AgentTeamTreeNode{
|
||||||
|
Id: ag.Id,
|
||||||
|
AgentCode: ag.AgentCode,
|
||||||
|
Level: ag.Level,
|
||||||
|
LevelName: levelName(ag.Level),
|
||||||
|
Mobile: mobile,
|
||||||
|
IsAnchor: ag.Id == anchor.Id,
|
||||||
|
Children: []types.AgentTeamTreeNode{},
|
||||||
|
}
|
||||||
|
|
||||||
|
kids := childrenMap[agentId]
|
||||||
|
for _, cid := range kids {
|
||||||
|
childNode, err := build(cid)
|
||||||
|
if err != nil {
|
||||||
|
return types.AgentTeamTreeNode{}, err
|
||||||
|
}
|
||||||
|
node.Children = append(node.Children, childNode)
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := build(teamRootId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AdminGetAgentTeamTreeResp{Root: root}, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
@@ -31,9 +33,26 @@ func (l *AdminGetAgentUpgradeListLogic) AdminGetAgentUpgradeList(req *types.Admi
|
|||||||
builder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
|
builder := l.svcCtx.AgentUpgradeModel.SelectBuilder().
|
||||||
Where("del_state = ?", globalkey.DelStateNo)
|
Where("del_state = ?", globalkey.DelStateNo)
|
||||||
|
|
||||||
if req.AgentId != nil {
|
if req.AgentCode != nil && *req.AgentCode != "" {
|
||||||
|
agentCodeInt, parseErr := strconv.ParseInt(*req.AgentCode, 10, 64)
|
||||||
|
if parseErr != nil {
|
||||||
|
return &types.AdminGetAgentUpgradeListResp{
|
||||||
|
Total: 0,
|
||||||
|
Items: []types.AgentUpgradeListItem{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
agent, findErr := l.svcCtx.AgentModel.FindOneByAgentCode(l.ctx, agentCodeInt)
|
||||||
|
if findErr != nil || agent == nil {
|
||||||
|
return &types.AdminGetAgentUpgradeListResp{
|
||||||
|
Total: 0,
|
||||||
|
Items: []types.AgentUpgradeListItem{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
builder = builder.Where("agent_id = ?", agent.Id)
|
||||||
|
} else if req.AgentId != nil && *req.AgentId != "" {
|
||||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
builder = builder.Where("agent_id = ?", *req.AgentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.UpgradeType != nil {
|
if req.UpgradeType != nil {
|
||||||
builder = builder.Where("upgrade_type = ?", *req.UpgradeType)
|
builder = builder.Where("upgrade_type = ?", *req.UpgradeType)
|
||||||
}
|
}
|
||||||
@@ -56,12 +75,21 @@ func (l *AdminGetAgentUpgradeListLogic) AdminGetAgentUpgradeList(req *types.Admi
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录列表失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录列表失败, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agentIds := make([]string, 0, len(upgrades))
|
||||||
|
for _, upgrade := range upgrades {
|
||||||
|
if upgrade.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, upgrade.AgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
// 组装响应
|
// 组装响应
|
||||||
items := make([]types.AgentUpgradeListItem, 0, len(upgrades))
|
items := make([]types.AgentUpgradeListItem, 0, len(upgrades))
|
||||||
for _, upgrade := range upgrades {
|
for _, upgrade := range upgrades {
|
||||||
items = append(items, types.AgentUpgradeListItem{
|
items = append(items, types.AgentUpgradeListItem{
|
||||||
Id: upgrade.Id,
|
Id: upgrade.Id,
|
||||||
AgentId: upgrade.AgentId,
|
AgentId: upgrade.AgentId,
|
||||||
|
AgentCode: agentCodeMap[upgrade.AgentId],
|
||||||
FromLevel: upgrade.FromLevel,
|
FromLevel: upgrade.FromLevel,
|
||||||
ToLevel: upgrade.ToLevel,
|
ToLevel: upgrade.ToLevel,
|
||||||
UpgradeType: upgrade.UpgradeType,
|
UpgradeType: upgrade.UpgradeType,
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
@@ -27,28 +31,76 @@ func NewAdminGetAgentWithdrawalListLogic(ctx context.Context, svcCtx *svc.Servic
|
|||||||
|
|
||||||
func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *types.AdminGetAgentWithdrawalListReq) (resp *types.AdminGetAgentWithdrawalListResp, err error) {
|
func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *types.AdminGetAgentWithdrawalListReq) (resp *types.AdminGetAgentWithdrawalListResp, err error) {
|
||||||
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder()
|
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder()
|
||||||
if req.AgentId != nil {
|
if req.AgentId != nil && strings.TrimSpace(*req.AgentId) != "" {
|
||||||
builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId})
|
builder = builder.Where(squirrel.Eq{"agent_id": strings.TrimSpace(*req.AgentId)})
|
||||||
}
|
}
|
||||||
if req.Status != nil {
|
if req.AgentCode != nil && strings.TrimSpace(*req.AgentCode) != "" {
|
||||||
builder = builder.Where(squirrel.Eq{"status": *req.Status})
|
var code int64
|
||||||
|
if _, scanErr := fmt.Sscanf(strings.TrimSpace(*req.AgentCode), "%d", &code); scanErr == nil {
|
||||||
|
builder = builder.Where(
|
||||||
|
"agent_id IN (SELECT id FROM agent WHERE agent_code = ? AND del_state = ?)",
|
||||||
|
code,
|
||||||
|
globalkey.DelStateNo,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.WithdrawNo != nil && *req.WithdrawNo != "" {
|
if strings.TrimSpace(req.Status) != "" {
|
||||||
builder = builder.Where(squirrel.Eq{"withdraw_no": *req.WithdrawNo})
|
st, perr := strconv.ParseInt(strings.TrimSpace(req.Status), 10, 64)
|
||||||
|
if perr == nil {
|
||||||
|
builder = builder.Where(squirrel.Eq{"status": st})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(req.WithdrawalType) != "" {
|
||||||
|
wt, perr := strconv.ParseInt(strings.TrimSpace(req.WithdrawalType), 10, 64)
|
||||||
|
if perr == nil {
|
||||||
|
builder = builder.Where(squirrel.Eq{"withdrawal_type": wt})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.WithdrawNo != nil && strings.TrimSpace(*req.WithdrawNo) != "" {
|
||||||
|
pat := "%" + strings.TrimSpace(*req.WithdrawNo) + "%"
|
||||||
|
builder = builder.Where("withdraw_no LIKE ?", pat)
|
||||||
|
}
|
||||||
|
if req.PayeeAccount != nil && strings.TrimSpace(*req.PayeeAccount) != "" {
|
||||||
|
pat := "%" + strings.TrimSpace(*req.PayeeAccount) + "%"
|
||||||
|
builder = builder.Where("payee_account LIKE ?", pat)
|
||||||
|
}
|
||||||
|
if req.PayeeName != nil && strings.TrimSpace(*req.PayeeName) != "" {
|
||||||
|
pat := "%" + strings.TrimSpace(*req.PayeeName) + "%"
|
||||||
|
builder = builder.Where("payee_name LIKE ?", pat)
|
||||||
}
|
}
|
||||||
list, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
list, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
agentIds := make([]string, 0, len(list))
|
||||||
|
for _, v := range list {
|
||||||
|
if v.AgentId != "" {
|
||||||
|
agentIds = append(agentIds, v.AgentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentCodeMap := batchAgentCodesByIds(l.ctx, l.svcCtx, agentIds)
|
||||||
|
|
||||||
items := make([]types.AgentWithdrawalListItem, 0, len(list))
|
items := make([]types.AgentWithdrawalListItem, 0, len(list))
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
item := types.AgentWithdrawalListItem{}
|
item := types.AgentWithdrawalListItem{}
|
||||||
_ = copier.Copy(&item, v)
|
_ = copier.Copy(&item, v)
|
||||||
|
item.AgentCode = agentCodeMap[v.AgentId]
|
||||||
item.Remark = ""
|
item.Remark = ""
|
||||||
if v.Remark.Valid {
|
if v.Remark.Valid {
|
||||||
item.Remark = v.Remark.String
|
item.Remark = v.Remark.String
|
||||||
}
|
}
|
||||||
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
|
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
// 如果是银行卡提现,填充银行卡信息
|
||||||
|
if v.WithdrawalType == 2 {
|
||||||
|
if v.BankCardNo.Valid {
|
||||||
|
item.BankCardNo = v.BankCardNo.String
|
||||||
|
}
|
||||||
|
if v.BankName.Valid {
|
||||||
|
item.BankName = v.BankName.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
resp = &types.AdminGetAgentWithdrawalListResp{
|
resp = &types.AdminGetAgentWithdrawalListResp{
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package admin_agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
@@ -34,22 +37,18 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
|||||||
builder := l.svcCtx.AgentInviteCodeModel.SelectBuilder().
|
builder := l.svcCtx.AgentInviteCodeModel.SelectBuilder().
|
||||||
Where("del_state = ?", globalkey.DelStateNo)
|
Where("del_state = ?", globalkey.DelStateNo)
|
||||||
|
|
||||||
if req.Code != nil && *req.Code != "" {
|
if req.Code != nil && strings.TrimSpace(*req.Code) != "" {
|
||||||
builder = builder.Where("code = ?", *req.Code)
|
pat := "%" + strings.TrimSpace(*req.Code) + "%"
|
||||||
}
|
builder = builder.Where("code LIKE ?", pat)
|
||||||
if req.AgentId != nil && *req.AgentId != "" {
|
|
||||||
if *req.AgentId == "0" {
|
|
||||||
// agent_id = 0 表示查询平台发放的邀请码(agent_id为NULL)
|
|
||||||
builder = builder.Where("agent_id IS NULL")
|
|
||||||
} else {
|
|
||||||
builder = builder.Where("agent_id = ?", *req.AgentId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if req.TargetLevel != nil {
|
if req.TargetLevel != nil {
|
||||||
builder = builder.Where("target_level = ?", *req.TargetLevel)
|
builder = builder.Where("target_level = ?", *req.TargetLevel)
|
||||||
}
|
}
|
||||||
if req.Status != nil {
|
if strings.TrimSpace(req.Status) != "" {
|
||||||
builder = builder.Where("status = ?", *req.Status)
|
st, perr := strconv.ParseInt(strings.TrimSpace(req.Status), 10, 64)
|
||||||
|
if perr == nil {
|
||||||
|
builder = builder.Where("status = ?", st)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 分页查询
|
// 2. 分页查询
|
||||||
@@ -58,15 +57,19 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码列表失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码列表失败, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 批量查询代理信息(用于显示代理手机号)
|
// 3. 批量查询代理信息(用于显示代理手机号、代理编号)
|
||||||
agentIds := make(map[string]struct{})
|
agentIds := make(map[string]struct{})
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
if v.AgentId.Valid && v.AgentId.String != "" {
|
if v.AgentId.Valid && v.AgentId.String != "" {
|
||||||
agentIds[v.AgentId.String] = struct{}{}
|
agentIds[v.AgentId.String] = struct{}{}
|
||||||
}
|
}
|
||||||
|
if v.UsedAgentId.Valid && v.UsedAgentId.String != "" {
|
||||||
|
agentIds[v.UsedAgentId.String] = struct{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
agentMobileMap := make(map[string]string)
|
agentMobileMap := make(map[string]string)
|
||||||
|
agentCodeMap := make(map[string]int64)
|
||||||
if len(agentIds) > 0 {
|
if len(agentIds) > 0 {
|
||||||
agentIdList := make([]string, 0, len(agentIds))
|
agentIdList := make([]string, 0, len(agentIds))
|
||||||
for id := range agentIds {
|
for id := range agentIds {
|
||||||
@@ -75,6 +78,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
|||||||
agents, _ := l.svcCtx.AgentModel.FindAll(l.ctx, l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": agentIdList}), "")
|
agents, _ := l.svcCtx.AgentModel.FindAll(l.ctx, l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": agentIdList}), "")
|
||||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
for _, agent := range agents {
|
for _, agent := range agents {
|
||||||
|
agentCodeMap[agent.Id] = agent.AgentCode
|
||||||
mobile, decErr := crypto.DecryptMobile(agent.Mobile, secretKey)
|
mobile, decErr := crypto.DecryptMobile(agent.Mobile, secretKey)
|
||||||
if decErr == nil {
|
if decErr == nil {
|
||||||
agentMobileMap[agent.Id] = mobile
|
agentMobileMap[agent.Id] = mobile
|
||||||
@@ -100,6 +104,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
|||||||
if v.AgentId.Valid {
|
if v.AgentId.Valid {
|
||||||
item.AgentId = v.AgentId.String
|
item.AgentId = v.AgentId.String
|
||||||
item.AgentMobile = agentMobileMap[v.AgentId.String]
|
item.AgentMobile = agentMobileMap[v.AgentId.String]
|
||||||
|
item.AgentCode = agentCodeMap[v.AgentId.String]
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.UsedUserId.Valid {
|
if v.UsedUserId.Valid {
|
||||||
@@ -107,6 +112,7 @@ func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGet
|
|||||||
}
|
}
|
||||||
if v.UsedAgentId.Valid {
|
if v.UsedAgentId.Valid {
|
||||||
item.UsedAgentId = v.UsedAgentId.String
|
item.UsedAgentId = v.UsedAgentId.String
|
||||||
|
item.UsedAgentCode = agentCodeMap[v.UsedAgentId.String]
|
||||||
}
|
}
|
||||||
if v.UsedTime.Valid {
|
if v.UsedTime.Valid {
|
||||||
item.UsedTime = v.UsedTime.Time.Format("2006-01-02 15:04:05")
|
item.UsedTime = v.UsedTime.Time.Format("2006-01-02 15:04:05")
|
||||||
|
|||||||
@@ -163,12 +163,9 @@ func (l *AdminUpdateAgentConfigLogic) AdminUpdateAgentConfig(req *types.AdminUpd
|
|||||||
if err := updateConfig("commission_freeze_threshold", &req.CommissionFreeze.Threshold); err != nil {
|
if err := updateConfig("commission_freeze_threshold", &req.CommissionFreeze.Threshold); err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结阈值失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结阈值失败, %v", err)
|
||||||
}
|
}
|
||||||
// 更新解冻天数(整数类型)
|
daysFloat := float64(req.CommissionFreeze.Days)
|
||||||
if req.CommissionFreeze.Days > 0 {
|
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
|
||||||
daysFloat := float64(req.CommissionFreeze.Days)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
|
||||||
if err := updateConfig("commission_freeze_days", &daysFloat); err != nil {
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新佣金冻结解冻天数失败, %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 升级类型:1=自主付费,2=钻石升级下级,3=平台升级
|
||||||
|
const upgradeTypePlatform = 3
|
||||||
|
|
||||||
|
type AdminUpgradeAgentLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminUpgradeAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpgradeAgentLogic {
|
||||||
|
return &AdminUpgradeAgentLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminUpgradeAgentLogic) AdminUpgradeAgent(req *types.AdminUpgradeAgentReq) (resp *types.AdminUpgradeAgentResp, err error) {
|
||||||
|
// 1. 参数校验
|
||||||
|
if req.AgentId == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("代理ID不能为空"), "")
|
||||||
|
}
|
||||||
|
if req.ToLevel != 2 && req.ToLevel != 3 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("目标等级只能为黄金(2)或钻石(3)"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取代理信息
|
||||||
|
agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.AgentId)
|
||||||
|
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 req.ToLevel <= agent.Level {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("目标等级必须高于当前等级,当前为"+l.levelName(agent.Level)), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用事务执行升级(遵守代理系统逻辑:脱离关系、团队首领、升级记录等)
|
||||||
|
err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||||
|
// 4.1 创建升级记录(upgrade_type=3 平台升级,不收费不返佣)
|
||||||
|
upgradeRecord := &model.AgentUpgrade{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
AgentId: agent.Id,
|
||||||
|
FromLevel: agent.Level,
|
||||||
|
ToLevel: req.ToLevel,
|
||||||
|
UpgradeType: upgradeTypePlatform,
|
||||||
|
UpgradeFee: 0,
|
||||||
|
RebateAmount: 0,
|
||||||
|
Status: 1, // 待处理,执行完成后更新为已完成
|
||||||
|
}
|
||||||
|
if _, err := l.svcCtx.AgentUpgradeModel.Insert(transCtx, session, upgradeRecord); err != nil {
|
||||||
|
return errors.Wrapf(err, "创建升级记录失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2 执行升级(ProcessUpgrade:更新等级、脱离/重连上级、团队首领等,upgradeType!=1 时不返佣)
|
||||||
|
if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, agent.Id, req.ToLevel, upgradeTypePlatform, 0, 0, "", ""); err != nil {
|
||||||
|
return errors.Wrapf(err, "执行升级操作失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.3 更新升级记录为已完成
|
||||||
|
upgradeRecord.Status = 2 // 已完成
|
||||||
|
upgradeRecord.Remark = lzUtils.StringToNullString("平台升级完成")
|
||||||
|
if err := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新升级记录失败")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AdminUpgradeAgentResp{Success: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminUpgradeAgentLogic) levelName(level int64) string {
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
return "普通代理"
|
||||||
|
case 2:
|
||||||
|
return "黄金代理"
|
||||||
|
case 3:
|
||||||
|
return "钻石代理"
|
||||||
|
default:
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package admin_agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// batchAgentCodesByIds 按代理主键批量查询 agent_code,用于列表软关联展示。
|
||||||
|
func batchAgentCodesByIds(ctx context.Context, svcCtx *svc.ServiceContext, ids []string) map[string]int64 {
|
||||||
|
out := make(map[string]int64)
|
||||||
|
uniq := make([]string, 0, len(ids))
|
||||||
|
seen := make(map[string]struct{}, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
uniq = append(uniq, id)
|
||||||
|
}
|
||||||
|
if len(uniq) == 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
agents, err := svcCtx.AgentModel.FindAll(ctx, svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": uniq}), "")
|
||||||
|
if err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for _, a := range agents {
|
||||||
|
out[a.Id] = a.AgentCode
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package admin_dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"qnc-server/app/main/api/internal/service"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminGetDashboardStatisticsLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminGetDashboardStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetDashboardStatisticsLogic {
|
||||||
|
return &AdminGetDashboardStatisticsLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) AdminGetDashboardStatistics() (resp *types.AdminGetDashboardStatisticsResp, err error) {
|
||||||
|
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
|
||||||
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
|
||||||
|
monthEnd := monthStart.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
orderStats, err := l.calculateOrderStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算订单统计失败, %v", err)
|
||||||
|
}
|
||||||
|
revenueStats, err := l.calculateRevenueStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算营收统计失败, %v", err)
|
||||||
|
}
|
||||||
|
agentStats, err := l.calculateAgentStatistics(todayStart, monthStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算代理统计失败, %v", err)
|
||||||
|
}
|
||||||
|
profitStats, err := l.calculateProfitStatistics(todayStart, todayEnd, monthStart, monthEnd, revenueStats)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算利润统计失败, %v", err)
|
||||||
|
}
|
||||||
|
orderTrend, err := l.calculateOrderTrend(now, loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算订单趋势失败, %v", err)
|
||||||
|
}
|
||||||
|
revenueTrend, err := l.calculateRevenueTrend(now, loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算营收趋势失败, %v", err)
|
||||||
|
}
|
||||||
|
return &types.AdminGetDashboardStatisticsResp{
|
||||||
|
OrderStats: orderStats,
|
||||||
|
RevenueStats: revenueStats,
|
||||||
|
AgentStats: agentStats,
|
||||||
|
ProfitStats: profitStats,
|
||||||
|
OrderTrend: orderTrend,
|
||||||
|
RevenueTrend: revenueTrend,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateOrderStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd time.Time) (types.AdminOrderStatistics, error) {
|
||||||
|
var stats types.AdminOrderStatistics
|
||||||
|
todayBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", todayStart, todayEnd)
|
||||||
|
todayCount, err := l.svcCtx.OrderModel.FindCount(l.ctx, todayBuilder, "id")
|
||||||
|
if err != nil {
|
||||||
|
return stats, err
|
||||||
|
}
|
||||||
|
stats.TodayCount = todayCount
|
||||||
|
yesterdayBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", yesterdayStart, yesterdayEnd)
|
||||||
|
yesterdayCount, _ := l.svcCtx.OrderModel.FindCount(l.ctx, yesterdayBuilder, "id")
|
||||||
|
stats.YesterdayCount = yesterdayCount
|
||||||
|
monthBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", monthStart, monthEnd)
|
||||||
|
monthCount, _ := l.svcCtx.OrderModel.FindCount(l.ctx, monthBuilder, "id")
|
||||||
|
stats.MonthCount = monthCount
|
||||||
|
totalBuilder := l.svcCtx.OrderModel.SelectBuilder().Where("status = ?", "paid")
|
||||||
|
totalCount, _ := l.svcCtx.OrderModel.FindCount(l.ctx, totalBuilder, "id")
|
||||||
|
stats.TotalCount = totalCount
|
||||||
|
if stats.YesterdayCount > 0 {
|
||||||
|
stats.ChangeRate = float64(stats.TodayCount-stats.YesterdayCount) / float64(stats.YesterdayCount) * 100
|
||||||
|
} else if stats.TodayCount > 0 {
|
||||||
|
stats.ChangeRate = 100
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateRevenueStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd time.Time) (types.AdminRevenueStatistics, error) {
|
||||||
|
var stats types.AdminRevenueStatistics
|
||||||
|
todayBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", todayStart, todayEnd)
|
||||||
|
todayAmount, _ := l.svcCtx.OrderModel.FindSum(l.ctx, todayBuilder, "amount")
|
||||||
|
stats.TodayAmount = todayAmount
|
||||||
|
yesterdayBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", yesterdayStart, yesterdayEnd)
|
||||||
|
yesterdayAmount, _ := l.svcCtx.OrderModel.FindSum(l.ctx, yesterdayBuilder, "amount")
|
||||||
|
stats.YesterdayAmount = yesterdayAmount
|
||||||
|
monthBuilder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", monthStart, monthEnd)
|
||||||
|
monthAmount, _ := l.svcCtx.OrderModel.FindSum(l.ctx, monthBuilder, "amount")
|
||||||
|
stats.MonthAmount = monthAmount
|
||||||
|
totalBuilder := l.svcCtx.OrderModel.SelectBuilder().Where("status = ?", "paid")
|
||||||
|
totalAmount, _ := l.svcCtx.OrderModel.FindSum(l.ctx, totalBuilder, "amount")
|
||||||
|
stats.TotalAmount = totalAmount
|
||||||
|
if stats.YesterdayAmount > 0 {
|
||||||
|
stats.ChangeRate = (stats.TodayAmount - stats.YesterdayAmount) / stats.YesterdayAmount * 100
|
||||||
|
} else if stats.TodayAmount > 0 {
|
||||||
|
stats.ChangeRate = 100
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateAgentStatistics(todayStart, monthStart time.Time) (types.AdminAgentStatistics, error) {
|
||||||
|
var stats types.AdminAgentStatistics
|
||||||
|
totalCount, _ := l.svcCtx.AgentModel.FindCount(l.ctx, l.svcCtx.AgentModel.SelectBuilder(), "id")
|
||||||
|
stats.TotalCount = totalCount
|
||||||
|
todayBuilder := l.svcCtx.AgentModel.SelectBuilder().Where("create_time >= ?", todayStart)
|
||||||
|
todayNew, _ := l.svcCtx.AgentModel.FindCount(l.ctx, todayBuilder, "id")
|
||||||
|
stats.TodayNew = todayNew
|
||||||
|
monthBuilder := l.svcCtx.AgentModel.SelectBuilder().Where("create_time >= ?", monthStart)
|
||||||
|
monthNew, _ := l.svcCtx.AgentModel.FindCount(l.ctx, monthBuilder, "id")
|
||||||
|
stats.MonthNew = monthNew
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, todayEnd, monthStart, monthEnd time.Time, revenueStats types.AdminRevenueStatistics) (types.AdminProfitStatistics, error) {
|
||||||
|
var stats types.AdminProfitStatistics
|
||||||
|
const companyTaxRate = 0.06
|
||||||
|
todayRevenue := revenueStats.TodayAmount
|
||||||
|
todayCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, todayStart, todayEnd)
|
||||||
|
todayCommission, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, todayCommissionBuilder, "amount")
|
||||||
|
todayRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, todayStart, todayEnd)
|
||||||
|
todayRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, todayRebateBuilder, "rebate_amount")
|
||||||
|
todayCompanyTax := todayRevenue * companyTaxRate
|
||||||
|
todayTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, todayStart, todayEnd)
|
||||||
|
todayTaxIncome, _ := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, todayTaxIncomeBuilder, "tax_amount")
|
||||||
|
todayApiCost := 0.0
|
||||||
|
if l.svcCtx.TianyuanapiCallLogService != nil {
|
||||||
|
todayApiStats, e := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{StartDate: todayStart, EndDate: todayEnd})
|
||||||
|
if e == nil {
|
||||||
|
todayApiCost = todayApiStats.TotalCost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax - todayApiCost + todayTaxIncome
|
||||||
|
if todayRevenue > 0 {
|
||||||
|
stats.TodayProfitRate = stats.TodayProfit / todayRevenue * 100
|
||||||
|
}
|
||||||
|
stats.TodayDetail = types.AdminProfitDetail{
|
||||||
|
Revenue: todayRevenue, Commission: todayCommission, Rebate: todayRebate,
|
||||||
|
CompanyTax: todayCompanyTax, ApiCost: todayApiCost, TaxIncome: todayTaxIncome,
|
||||||
|
Profit: stats.TodayProfit, ProfitRate: stats.TodayProfitRate,
|
||||||
|
}
|
||||||
|
monthRevenue := revenueStats.MonthAmount
|
||||||
|
monthCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, monthStart, monthEnd)
|
||||||
|
monthCommission, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, monthCommissionBuilder, "amount")
|
||||||
|
monthRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, monthStart, monthEnd)
|
||||||
|
monthRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, monthRebateBuilder, "rebate_amount")
|
||||||
|
monthCompanyTax := monthRevenue * companyTaxRate
|
||||||
|
monthTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
||||||
|
Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, monthStart, monthEnd)
|
||||||
|
monthTaxIncome, _ := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, monthTaxIncomeBuilder, "tax_amount")
|
||||||
|
monthApiCost := 0.0
|
||||||
|
if l.svcCtx.TianyuanapiCallLogService != nil {
|
||||||
|
monthApiStats, e := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{StartDate: monthStart, EndDate: monthEnd})
|
||||||
|
if e == nil {
|
||||||
|
monthApiCost = monthApiStats.TotalCost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax - monthApiCost + monthTaxIncome
|
||||||
|
if monthRevenue > 0 {
|
||||||
|
stats.MonthProfitRate = stats.MonthProfit / monthRevenue * 100
|
||||||
|
}
|
||||||
|
stats.MonthDetail = types.AdminProfitDetail{
|
||||||
|
Revenue: monthRevenue, Commission: monthCommission, Rebate: monthRebate,
|
||||||
|
CompanyTax: monthCompanyTax, ApiCost: monthApiCost, TaxIncome: monthTaxIncome,
|
||||||
|
Profit: stats.MonthProfit, ProfitRate: stats.MonthProfitRate,
|
||||||
|
}
|
||||||
|
totalRevenue := revenueStats.TotalAmount
|
||||||
|
totalCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where("del_state = ? AND status != ?", globalkey.DelStateNo, 3)
|
||||||
|
totalCommission, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, totalCommissionBuilder, "amount")
|
||||||
|
totalRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().Where("status != ?", 3)
|
||||||
|
totalRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, totalRebateBuilder, "rebate_amount")
|
||||||
|
totalCompanyTax := totalRevenue * companyTaxRate
|
||||||
|
totalTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().Where("tax_status = ?", 2)
|
||||||
|
totalTaxIncome, _ := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, totalTaxIncomeBuilder, "tax_amount")
|
||||||
|
totalApiCost := 0.0
|
||||||
|
if l.svcCtx.TianyuanapiCallLogService != nil {
|
||||||
|
totalApiStats, e := l.svcCtx.TianyuanapiCallLogService.GetStatistics(l.ctx, service.StatisticsFilter{})
|
||||||
|
if e == nil {
|
||||||
|
totalApiCost = totalApiStats.TotalCost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax - totalApiCost + totalTaxIncome
|
||||||
|
if totalRevenue > 0 {
|
||||||
|
stats.TotalProfitRate = stats.TotalProfit / totalRevenue * 100
|
||||||
|
}
|
||||||
|
stats.TotalDetail = types.AdminProfitDetail{
|
||||||
|
Revenue: totalRevenue, Commission: totalCommission, Rebate: totalRebate,
|
||||||
|
CompanyTax: totalCompanyTax, ApiCost: totalApiCost, TaxIncome: totalTaxIncome,
|
||||||
|
Profit: stats.TotalProfit, ProfitRate: stats.TotalProfitRate,
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateOrderTrend(now time.Time, loc *time.Location) ([]types.AdminTrendData, error) {
|
||||||
|
var trend []types.AdminTrendData
|
||||||
|
for i := 6; i >= 0; i-- {
|
||||||
|
date := now.AddDate(0, 0, -i)
|
||||||
|
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc)
|
||||||
|
dateEnd := dateStart.AddDate(0, 0, 1)
|
||||||
|
builder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", dateStart, dateEnd)
|
||||||
|
count, err := l.svcCtx.OrderModel.FindCount(l.ctx, builder, "id")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
trend = append(trend, types.AdminTrendData{Date: date.Format("01-02"), Value: float64(count)})
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGetDashboardStatisticsLogic) calculateRevenueTrend(now time.Time, loc *time.Location) ([]types.AdminTrendData, error) {
|
||||||
|
var trend []types.AdminTrendData
|
||||||
|
for i := 6; i >= 0; i-- {
|
||||||
|
date := now.AddDate(0, 0, -i)
|
||||||
|
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc)
|
||||||
|
dateEnd := dateStart.AddDate(0, 0, 1)
|
||||||
|
builder := l.svcCtx.OrderModel.SelectBuilder().
|
||||||
|
Where("status = ? AND create_time >= ? AND create_time < ?", "paid", dateStart, dateEnd)
|
||||||
|
amount, err := l.svcCtx.OrderModel.FindSum(l.ctx, builder, "amount")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
trend = append(trend, types.AdminTrendData{Date: date.Format("01-02"), Value: amount})
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
@@ -34,6 +34,12 @@ func (l *AdminCreateFeatureLogic) AdminCreateFeature(req *types.AdminCreateFeatu
|
|||||||
ApiId: req.ApiId,
|
ApiId: req.ApiId,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
}
|
}
|
||||||
|
if req.WhitelistPrice != nil {
|
||||||
|
data.WhitelistPrice = *req.WhitelistPrice
|
||||||
|
}
|
||||||
|
if req.CostPrice != nil {
|
||||||
|
data.CostPrice = *req.CostPrice
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 数据库操作
|
// 2. 数据库操作
|
||||||
result, err := l.svcCtx.FeatureModel.Insert(l.ctx, nil, data)
|
result, err := l.svcCtx.FeatureModel.Insert(l.ctx, nil, data)
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ func (l *AdminGetFeatureDetailLogic) AdminGetFeatureDetail(req *types.AdminGetFe
|
|||||||
|
|
||||||
// 2. 构建响应
|
// 2. 构建响应
|
||||||
resp = &types.AdminGetFeatureDetailResp{
|
resp = &types.AdminGetFeatureDetailResp{
|
||||||
Id: record.Id,
|
Id: record.Id,
|
||||||
ApiId: record.ApiId,
|
ApiId: record.ApiId,
|
||||||
Name: record.Name,
|
Name: record.Name,
|
||||||
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
|
WhitelistPrice: record.WhitelistPrice,
|
||||||
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
|
CostPrice: record.CostPrice,
|
||||||
|
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
|||||||
@@ -49,11 +49,13 @@ func (l *AdminGetFeatureListLogic) AdminGetFeatureList(req *types.AdminGetFeatur
|
|||||||
items := make([]types.FeatureListItem, 0, len(list))
|
items := make([]types.FeatureListItem, 0, len(list))
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
listItem := types.FeatureListItem{
|
listItem := types.FeatureListItem{
|
||||||
Id: item.Id,
|
Id: item.Id,
|
||||||
ApiId: item.ApiId,
|
ApiId: item.ApiId,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
|
WhitelistPrice: item.WhitelistPrice,
|
||||||
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
|
CostPrice: item.CostPrice,
|
||||||
|
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
}
|
}
|
||||||
items = append(items, listItem)
|
items = append(items, listItem)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package admin_feature
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/productcost"
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
@@ -46,6 +47,12 @@ func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatu
|
|||||||
if req.Name != nil && *req.Name != "" {
|
if req.Name != nil && *req.Name != "" {
|
||||||
record.Name = *req.Name
|
record.Name = *req.Name
|
||||||
}
|
}
|
||||||
|
if req.WhitelistPrice != nil {
|
||||||
|
record.WhitelistPrice = *req.WhitelistPrice
|
||||||
|
}
|
||||||
|
if req.CostPrice != nil {
|
||||||
|
record.CostPrice = *req.CostPrice
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 执行更新操作
|
// 4. 执行更新操作
|
||||||
err = l.svcCtx.FeatureModel.UpdateWithVersion(l.ctx, nil, record)
|
err = l.svcCtx.FeatureModel.UpdateWithVersion(l.ctx, nil, record)
|
||||||
@@ -54,6 +61,10 @@ func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatu
|
|||||||
"更新功能失败, err: %v, id: %d", err, req.Id)
|
"更新功能失败, err: %v, id: %d", err, req.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := productcost.SyncAllProductsUsingFeature(l.ctx, l.svcCtx, req.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// 5. 返回成功结果
|
// 5. 返回成功结果
|
||||||
return &types.AdminUpdateFeatureResp{Success: true}, nil
|
return &types.AdminUpdateFeatureResp{Success: true}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package admin_notification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
@@ -32,7 +33,10 @@ func (l *AdminGetNotificationListLogic) AdminGetNotificationList(req *types.Admi
|
|||||||
builder = builder.Where("title LIKE ?", "%"+*req.Title+"%")
|
builder = builder.Where("title LIKE ?", "%"+*req.Title+"%")
|
||||||
}
|
}
|
||||||
if req.NotificationPage != nil {
|
if req.NotificationPage != nil {
|
||||||
builder = builder.Where("notification_page = ?", *req.NotificationPage)
|
s := strings.TrimSpace(*req.NotificationPage)
|
||||||
|
if s != "" {
|
||||||
|
builder = builder.Where("notification_page LIKE ?", "%"+s+"%")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.Status != nil {
|
if req.Status != nil {
|
||||||
builder = builder.Where("status = ?", *req.Status)
|
builder = builder.Where("status = ?", *req.Status)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package admin_notification
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
@@ -33,12 +34,28 @@ func (l *AdminUpdateNotificationLogic) AdminUpdateNotification(req *types.AdminU
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id)
|
||||||
}
|
}
|
||||||
if req.StartDate != nil {
|
if req.StartDate != nil {
|
||||||
startDate, _ := time.Parse("2006-01-02", *req.StartDate)
|
s := strings.TrimSpace(*req.StartDate)
|
||||||
notification.StartDate = sql.NullTime{Time: startDate, Valid: true}
|
if s == "" {
|
||||||
|
notification.StartDate = sql.NullTime{Valid: false}
|
||||||
|
} else {
|
||||||
|
startDate, err := time.Parse("2006-01-02", s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "start_date 格式错误: %v", err)
|
||||||
|
}
|
||||||
|
notification.StartDate = sql.NullTime{Time: startDate, Valid: true}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.EndDate != nil {
|
if req.EndDate != nil {
|
||||||
endDate, _ := time.Parse("2006-01-02", *req.EndDate)
|
s := strings.TrimSpace(*req.EndDate)
|
||||||
notification.EndDate = sql.NullTime{Time: endDate, Valid: true}
|
if s == "" {
|
||||||
|
notification.EndDate = sql.NullTime{Valid: false}
|
||||||
|
} else {
|
||||||
|
endDate, err := time.Parse("2006-01-02", s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "end_date 格式错误: %v", err)
|
||||||
|
}
|
||||||
|
notification.EndDate = sql.NullTime{Time: endDate, Valid: true}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.Title != nil {
|
if req.Title != nil {
|
||||||
notification.Title = *req.Title
|
notification.Title = *req.Title
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package admin_order
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
@@ -54,6 +55,24 @@ 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.IsAgentOrder != nil {
|
||||||
|
if *req.IsAgentOrder {
|
||||||
|
// 筛选代理订单:订单ID在agent_order表中
|
||||||
|
builder = builder.Where("id IN (SELECT order_id FROM agent_order WHERE del_state = 0)")
|
||||||
|
} else {
|
||||||
|
// 筛选非代理订单:订单ID不在agent_order表中
|
||||||
|
builder = builder.Where("id NOT IN (SELECT order_id FROM agent_order WHERE del_state = 0)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 代理编号筛选
|
||||||
|
if req.AgentCode != "" {
|
||||||
|
// 将字符串转换为int64进行查询
|
||||||
|
var agentCode int64
|
||||||
|
if _, err := fmt.Sscanf(req.AgentCode, "%d", &agentCode); err == nil {
|
||||||
|
builder = builder.Where("id IN (SELECT order_id FROM agent_order ao JOIN agent a ON ao.agent_id = a.id WHERE a.agent_code = ? AND ao.del_state = 0)", agentCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 时间范围查询
|
// 时间范围查询
|
||||||
if req.CreateTimeStart != "" {
|
if req.CreateTimeStart != "" {
|
||||||
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
|
builder = builder.Where("create_time >= ?", req.CreateTimeStart)
|
||||||
@@ -86,7 +105,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
return nil
|
return nil
|
||||||
}, func() error {
|
}, func() error {
|
||||||
var err error
|
var err error
|
||||||
orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC")
|
orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "update_time DESC")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单列表失败 err: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单列表失败 err: %v", err)
|
||||||
}
|
}
|
||||||
@@ -101,6 +120,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
queryStateMap := make(map[string]string)
|
queryStateMap := make(map[string]string)
|
||||||
agentOrderMap := make(map[string]bool) // 代理订单映射
|
agentOrderMap := make(map[string]bool) // 代理订单映射
|
||||||
agentProcessStatusMap := make(map[string]string) // 代理处理状态映射
|
agentProcessStatusMap := make(map[string]string) // 代理处理状态映射
|
||||||
|
agentCodeMap := make(map[string]string) // 代理编号映射
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
@@ -169,8 +189,32 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录代理订单
|
// 记录代理订单
|
||||||
|
agentIds := make([]string, 0, len(agentOrders))
|
||||||
for _, agentOrder := range agentOrders {
|
for _, agentOrder := range agentOrders {
|
||||||
agentOrderMap[agentOrder.OrderId] = true
|
agentOrderMap[agentOrder.OrderId] = true
|
||||||
|
agentIds = append(agentIds, agentOrder.AgentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量获取代理编号
|
||||||
|
if len(agentIds) > 0 {
|
||||||
|
agents, err := l.svcCtx.AgentModel.FindAll(l.ctx,
|
||||||
|
l.svcCtx.AgentModel.SelectBuilder().Where(squirrel.Eq{"id": agentIds}), "")
|
||||||
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理信息失败 err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建代理ID到代理编号的映射
|
||||||
|
agentIdToCodeMap := make(map[string]string)
|
||||||
|
for _, agent := range agents {
|
||||||
|
agentIdToCodeMap[agent.Id] = fmt.Sprintf("%d", agent.AgentCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个代理订单设置代理编号
|
||||||
|
for _, agentOrder := range agentOrders {
|
||||||
|
if code, exists := agentIdToCodeMap[agentOrder.AgentId]; exists {
|
||||||
|
agentCodeMap[agentOrder.OrderId] = code
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于代理订单,查询代理处理状态
|
// 对于代理订单,查询代理处理状态
|
||||||
@@ -264,6 +308,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
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"),
|
||||||
QueryState: queryStateMap[order.Id],
|
QueryState: queryStateMap[order.Id],
|
||||||
|
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
}
|
}
|
||||||
if order.PayTime.Valid {
|
if order.PayTime.Valid {
|
||||||
item.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05")
|
item.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05")
|
||||||
@@ -276,6 +321,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
if agentOrderMap[order.Id] {
|
if agentOrderMap[order.Id] {
|
||||||
item.IsAgentOrder = true
|
item.IsAgentOrder = true
|
||||||
item.AgentProcessStatus = agentProcessStatusMap[order.Id]
|
item.AgentProcessStatus = agentProcessStatusMap[order.Id]
|
||||||
|
item.AgentCode = agentCodeMap[order.Id]
|
||||||
} else {
|
} else {
|
||||||
item.IsAgentOrder = false
|
item.IsAgentOrder = false
|
||||||
item.AgentProcessStatus = "not_agent"
|
item.AgentProcessStatus = "not_agent"
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
package admin_order
|
package admin_order
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
paylogic "qnc-server/app/main/api/internal/logic/pay"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/api/internal/types"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/google/uuid"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/google/uuid"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -44,6 +45,10 @@ func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if model.IsXpayOrder(order) {
|
||||||
|
return l.handleXpayRefund(order, req)
|
||||||
|
}
|
||||||
|
|
||||||
// 根据支付平台处理退款
|
// 根据支付平台处理退款
|
||||||
switch order.PaymentPlatform {
|
switch order.PaymentPlatform {
|
||||||
case PaymentPlatformAlipay:
|
case PaymentPlatformAlipay:
|
||||||
@@ -107,6 +112,24 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleXpayRefund 处理小程序虚拟支付退款
|
||||||
|
func (l *AdminRefundOrderLogic) handleXpayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
||||||
|
if req.RefundAmount != order.Amount {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("虚拟支付订单仅支持全额退款"), "")
|
||||||
|
}
|
||||||
|
if err := paylogic.RefundXpayQueryOrder(l.ctx, l.svcCtx, order); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "虚拟支付退款失败: %v", err)
|
||||||
|
}
|
||||||
|
refundNo := l.generateRefundNo(order.OrderNo)
|
||||||
|
_ = l.createRefundRecordOnly(order, req, refundNo, "", model.OrderRefundStatusSuccess)
|
||||||
|
l.Infof("[xpay] admin refund OK order_no=%s amount=%.2f", order.OrderNo, req.RefundAmount)
|
||||||
|
return &types.AdminRefundOrderResp{
|
||||||
|
Status: model.OrderStatusRefunded,
|
||||||
|
RefundNo: refundNo,
|
||||||
|
Amount: req.RefundAmount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleWechatRefund 处理微信退款
|
// handleWechatRefund 处理微信退款
|
||||||
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
|
||||||
// 调用微信退款接口
|
// 调用微信退款接口
|
||||||
@@ -133,18 +156,18 @@ func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *type
|
|||||||
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
|
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
|
||||||
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
// 创建退款记录
|
// 创建退款记录
|
||||||
refund := &model.OrderRefund{
|
refund := &model.OrderRefund{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
RefundNo: refundNo,
|
RefundNo: refundNo,
|
||||||
PlatformRefundId: l.createNullString(platformRefundId),
|
PlatformRefundId: l.createNullString(platformRefundId),
|
||||||
OrderId: order.Id,
|
OrderId: order.Id,
|
||||||
UserId: order.UserId,
|
UserId: order.UserId,
|
||||||
ProductId: order.ProductId,
|
ProductId: order.ProductId,
|
||||||
RefundAmount: req.RefundAmount,
|
RefundAmount: req.RefundAmount,
|
||||||
RefundReason: l.createNullString(req.RefundReason),
|
RefundReason: l.createNullString(req.RefundReason),
|
||||||
Status: refundStatus, // 使用传入的状态,不再硬编码
|
Status: refundStatus, // 使用传入的状态,不再硬编码
|
||||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
|
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
|
||||||
return fmt.Errorf("创建退款记录失败: %v", err)
|
return fmt.Errorf("创建退款记录失败: %v", err)
|
||||||
@@ -156,24 +179,31 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or
|
|||||||
return fmt.Errorf("更新订单状态失败: %v", err)
|
return fmt.Errorf("更新订单状态失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 退款成功落库后冲正代理佣金/返佣(部分退款与全额退款均全额冲回本单分账;仅已退款时执行,不含「退款中」)
|
||||||
|
if orderStatus == model.OrderStatusRefunded {
|
||||||
|
if revErr := l.svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(ctx, session, order.Id); revErr != nil {
|
||||||
|
return revErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
|
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
|
||||||
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
|
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
|
||||||
refund := &model.OrderRefund{
|
refund := &model.OrderRefund{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
RefundNo: refundNo,
|
RefundNo: refundNo,
|
||||||
PlatformRefundId: l.createNullString(platformRefundId),
|
PlatformRefundId: l.createNullString(platformRefundId),
|
||||||
OrderId: order.Id,
|
OrderId: order.Id,
|
||||||
UserId: order.UserId,
|
UserId: order.UserId,
|
||||||
ProductId: order.ProductId,
|
ProductId: order.ProductId,
|
||||||
RefundAmount: req.RefundAmount,
|
RefundAmount: req.RefundAmount,
|
||||||
RefundReason: l.createNullString(req.RefundReason),
|
RefundReason: l.createNullString(req.RefundReason),
|
||||||
Status: refundStatus,
|
Status: refundStatus,
|
||||||
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
|
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package admin_order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/logic/pay"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminXpayDeliverLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminXpayDeliverLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminXpayDeliverLogic {
|
||||||
|
return &AdminXpayDeliverLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminXpayDeliverLogic) AdminXpayDeliver(req *types.AdminXpayDeliverReq) (resp *types.AdminXpayDeliverResp, err error) {
|
||||||
|
order, findErr := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
|
||||||
|
if findErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "%v", findErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.IsXpayOrder(order) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("仅支持小程序虚拟支付订单"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.svcCtx.XpayService == nil || !l.svcCtx.XpayService.Enabled() {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("虚拟支付未启用"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, deliverErr := pay.DeliverXpayQueryOrder(l.ctx, l.svcCtx, order.OrderNo)
|
||||||
|
if deliverErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "补发货失败: %v", deliverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AdminXpayDeliverResp{
|
||||||
|
Credited: result.Credited,
|
||||||
|
Notified: result.Notified,
|
||||||
|
WechatDetail: result.WechatDetail,
|
||||||
|
Errors: result.Errors,
|
||||||
|
Message: result.Message,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -30,8 +30,17 @@ func NewAdminGetPlatformUserListLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
|||||||
|
|
||||||
func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) {
|
func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) {
|
||||||
builder := l.svcCtx.UserModel.SelectBuilder()
|
builder := l.svcCtx.UserModel.SelectBuilder()
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
if req.UserId != "" {
|
||||||
|
builder = builder.Where("id = ?", req.UserId)
|
||||||
|
}
|
||||||
if req.Mobile != "" {
|
if req.Mobile != "" {
|
||||||
builder = builder.Where("mobile = ?", req.Mobile)
|
// 数据库存密文,搜索时把明文手机号加密后再查询
|
||||||
|
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机号加密失败: %v", err)
|
||||||
|
}
|
||||||
|
builder = builder.Where("mobile = ?", encryptedMobile)
|
||||||
}
|
}
|
||||||
if req.Nickname != "" {
|
if req.Nickname != "" {
|
||||||
builder = builder.Where("nickname = ?", req.Nickname)
|
builder = builder.Where("nickname = ?", req.Nickname)
|
||||||
@@ -55,16 +64,15 @@ func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.Admi
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户分页失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户分页失败: %v", err)
|
||||||
}
|
}
|
||||||
var items []types.PlatformUserListItem
|
var items []types.PlatformUserListItem
|
||||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
mobile := user.Mobile
|
mobile := user.Mobile
|
||||||
if mobile.Valid {
|
if mobile.Valid {
|
||||||
encryptedMobile, err := crypto.DecryptMobile(mobile.String, secretKey)
|
DecryptMobile, err := crypto.DecryptMobile(mobile.String, secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 解密手机号失败: %+v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 解密手机号失败: %+v", err)
|
||||||
}
|
}
|
||||||
mobile = sql.NullString{String: encryptedMobile, Valid: true}
|
mobile = sql.NullString{String: DecryptMobile, Valid: true}
|
||||||
}
|
}
|
||||||
itemData := types.PlatformUserListItem{
|
itemData := types.PlatformUserListItem{
|
||||||
Id: user.Id,
|
Id: user.Id,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func NewAdminCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminCreateProduct 创建产品后,在同一事务内写入一条 agent_product_config(与代理产品配置列表一致)。
|
||||||
func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) {
|
func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) {
|
||||||
// 1. 数据转换
|
// 1. 数据转换
|
||||||
data := &model.Product{
|
data := &model.Product{
|
||||||
@@ -36,7 +37,7 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
|
|||||||
ProductEn: req.ProductEn,
|
ProductEn: req.ProductEn,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Notes: sql.NullString{String: req.Notes, Valid: req.Notes != ""},
|
Notes: sql.NullString{String: req.Notes, Valid: req.Notes != ""},
|
||||||
CostPrice: req.CostPrice,
|
CostPrice: 0, // 成本由关联模块汇总,创建后为 0,保存模块关联后写入
|
||||||
SellPrice: req.SellPrice,
|
SellPrice: req.SellPrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,14 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
|
|||||||
}
|
}
|
||||||
productId = data.Id
|
productId = data.Id
|
||||||
|
|
||||||
// 2.2 同步创建代理产品配置(使用默认值)
|
// 2.2 同步创建代理产品配置(使用默认值);已存在则跳过(幂等)
|
||||||
|
if _, findErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, productId); findErr == nil {
|
||||||
|
return nil
|
||||||
|
} else if findErr != model.ErrNotFound {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"检查代理产品配置失败, err: %v, productId: %s", findErr, productId)
|
||||||
|
}
|
||||||
|
|
||||||
agentProductConfig := &model.AgentProductConfig{
|
agentProductConfig := &model.AgentProductConfig{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
ProductId: productId,
|
ProductId: productId,
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package admin_product
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -25,12 +27,13 @@ func NewAdminDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminDeleteProduct 软删除产品后,在同一事务内软删除对应 agent_product_config(与产品列表一致)。
|
||||||
func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) {
|
func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) {
|
||||||
// 1. 查询记录是否存在
|
// 1. 查询记录是否存在
|
||||||
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
|
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
"查找产品失败, err: %v, id: %d", err, req.Id)
|
"查找产品失败, err: %v, id: %s", err, req.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
|
// 2. 执行软删除(使用事务确保产品表和代理产品配置表同步)
|
||||||
@@ -39,20 +42,22 @@ func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProdu
|
|||||||
err = l.svcCtx.ProductModel.DeleteSoft(ctx, session, record)
|
err = l.svcCtx.ProductModel.DeleteSoft(ctx, session, record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
"删除产品失败, err: %v, id: %d", err, req.Id)
|
"删除产品失败, err: %v, id: %s", err, record.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.2 同步软删除代理产品配置
|
// 2.2 同步软删除代理产品配置(按产品主键 product_id)
|
||||||
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, req.Id)
|
agentProductConfig, findErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, record.Id)
|
||||||
if err != nil {
|
switch {
|
||||||
// 如果代理产品配置不存在,记录日志但不影响主流程
|
case findErr == nil:
|
||||||
l.Infof("同步删除代理产品配置失败:代理产品配置不存在, productId: %d, err: %v", req.Id, err)
|
if err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig); err != nil {
|
||||||
} else {
|
|
||||||
err = l.svcCtx.AgentProductConfigModel.DeleteSoft(ctx, session, agentProductConfig)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
"同步删除代理产品配置失败, err: %v, productId: %d", err, req.Id)
|
"同步删除代理产品配置失败, err: %v, productId: %s", err, record.Id)
|
||||||
}
|
}
|
||||||
|
case findErr == model.ErrNotFound:
|
||||||
|
l.Infof("产品无对应代理产品配置,跳过同步删除, productId: %s", record.Id)
|
||||||
|
default:
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"查询代理产品配置失败, err: %v, productId: %s", findErr, record.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -32,14 +32,21 @@ func (l *AdminGetProductDetailLogic) AdminGetProductDetail(req *types.AdminGetPr
|
|||||||
"查找产品失败, err: %v, id: %d", err, req.Id)
|
"查找产品失败, err: %v, id: %d", err, req.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 构建响应
|
// 2. 成本价为关联模块 cost_price 之和
|
||||||
|
costSum, err := l.svcCtx.ProductFeatureModel.SumFeatureCostPriceByProductId(l.ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"汇总产品模块成本失败, err: %v, id: %s", err, req.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建响应
|
||||||
resp = &types.AdminGetProductDetailResp{
|
resp = &types.AdminGetProductDetailResp{
|
||||||
Id: record.Id,
|
Id: record.Id,
|
||||||
ProductName: record.ProductName,
|
ProductName: record.ProductName,
|
||||||
ProductEn: record.ProductEn,
|
ProductEn: record.ProductEn,
|
||||||
Description: record.Description,
|
Description: record.Description,
|
||||||
Notes: record.Notes.String,
|
Notes: record.Notes.String,
|
||||||
CostPrice: record.CostPrice,
|
CostPrice: costSum,
|
||||||
SellPrice: record.SellPrice,
|
SellPrice: record.SellPrice,
|
||||||
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
|
CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
|
UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ func (l *AdminGetProductFeatureListLogic) AdminGetProductFeatureList(req *types.
|
|||||||
FeatureId: item.FeatureId,
|
FeatureId: item.FeatureId,
|
||||||
ApiId: feature.ApiId,
|
ApiId: feature.ApiId,
|
||||||
Name: feature.Name,
|
Name: feature.Name,
|
||||||
|
CostPrice: feature.CostPrice,
|
||||||
Sort: item.Sort,
|
Sort: item.Sort,
|
||||||
Enable: item.Enable,
|
Enable: item.Enable,
|
||||||
IsImportant: item.IsImportant,
|
IsImportant: item.IsImportant,
|
||||||
|
|||||||
@@ -44,16 +44,26 @@ func (l *AdminGetProductListLogic) AdminGetProductList(req *types.AdminGetProduc
|
|||||||
"查询产品列表失败, err: %v, req: %+v", err, req)
|
"查询产品列表失败, err: %v, req: %+v", err, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 构建响应列表
|
// 4. 构建响应列表(成本价为关联模块 cost_price 之和,非库表字段直读)
|
||||||
items := make([]types.ProductListItem, 0, len(list))
|
items := make([]types.ProductListItem, 0, len(list))
|
||||||
|
productIds := make([]string, 0, len(list))
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
|
productIds = append(productIds, item.Id)
|
||||||
|
}
|
||||||
|
costByProduct, err := l.svcCtx.ProductFeatureModel.SumFeatureCostPriceByProductIds(l.ctx, productIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"汇总产品模块成本失败, err: %v, req: %+v", err, req)
|
||||||
|
}
|
||||||
|
for _, item := range list {
|
||||||
|
cost := costByProduct[item.Id]
|
||||||
listItem := types.ProductListItem{
|
listItem := types.ProductListItem{
|
||||||
Id: item.Id,
|
Id: item.Id,
|
||||||
ProductName: item.ProductName,
|
ProductName: item.ProductName,
|
||||||
ProductEn: item.ProductEn,
|
ProductEn: item.ProductEn,
|
||||||
Description: item.Description,
|
Description: item.Description,
|
||||||
Notes: item.Notes.String,
|
Notes: item.Notes.String,
|
||||||
CostPrice: item.CostPrice,
|
CostPrice: cost,
|
||||||
SellPrice: item.SellPrice,
|
SellPrice: item.SellPrice,
|
||||||
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
|
CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
|
UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
package admin_product
|
package admin_product
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"qnc-server/app/main/api/internal/svc"
|
|
||||||
"qnc-server/app/main/api/internal/types"
|
|
||||||
"qnc-server/app/main/model"
|
|
||||||
"qnc-server/common/xerr"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"qnc-server/app/main/api/internal/logic/productcost"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"github.com/zeromicro/go-zero/core/mr"
|
"qnc-server/app/main/api/internal/types"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"qnc-server/app/main/model"
|
||||||
"github.com/google/uuid"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/mr"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminUpdateProductFeaturesLogic struct {
|
type AdminUpdateProductFeaturesLogic struct {
|
||||||
@@ -120,15 +122,15 @@ func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增关联
|
// 新增关联
|
||||||
newFeature := &model.ProductFeature{
|
newFeature := &model.ProductFeature{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
ProductId: req.ProductId,
|
ProductId: req.ProductId,
|
||||||
FeatureId: data.featureId,
|
FeatureId: data.featureId,
|
||||||
Sort: data.newItem.Sort,
|
Sort: data.newItem.Sort,
|
||||||
Enable: data.newItem.Enable,
|
Enable: data.newItem.Enable,
|
||||||
IsImportant: data.newItem.IsImportant,
|
IsImportant: data.newItem.IsImportant,
|
||||||
}
|
}
|
||||||
_, err = l.svcCtx.ProductFeatureModel.Insert(ctx, session, newFeature)
|
_, err = l.svcCtx.ProductFeatureModel.Insert(ctx, session, newFeature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateErr = errors.Wrapf(err, "新增产品功能关联失败, product_id: %d, feature_id: %d",
|
updateErr = errors.Wrapf(err, "新增产品功能关联失败, product_id: %d, feature_id: %d",
|
||||||
@@ -156,6 +158,10 @@ func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.
|
|||||||
"更新产品功能关联失败, err: %v, req: %+v", err, req)
|
"更新产品功能关联失败, err: %v, req: %+v", err, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := productcost.SyncProductCostFromModules(l.ctx, l.svcCtx, req.ProductId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// 5. 返回结果
|
// 5. 返回结果
|
||||||
return &types.AdminUpdateProductFeaturesResp{Success: true}, nil
|
return &types.AdminUpdateProductFeaturesResp{Success: true}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProdu
|
|||||||
if req.Notes != nil {
|
if req.Notes != nil {
|
||||||
record.Notes = sql.NullString{String: *req.Notes, Valid: *req.Notes != ""}
|
record.Notes = sql.NullString{String: *req.Notes, Valid: *req.Notes != ""}
|
||||||
}
|
}
|
||||||
if req.CostPrice != nil {
|
|
||||||
record.CostPrice = *req.CostPrice
|
|
||||||
}
|
|
||||||
if req.SellPrice != nil {
|
if req.SellPrice != nil {
|
||||||
record.SellPrice = *req.SellPrice
|
record.SellPrice = *req.SellPrice
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package admin_query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminGenerateQueryShareLinkLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminGenerateQueryShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGenerateQueryShareLinkLogic {
|
||||||
|
return &AdminGenerateQueryShareLinkLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminGenerateQueryShareLinkLogic) AdminGenerateQueryShareLink(req *types.AdminGenerateQueryShareLinkReq) (resp *types.QueryGenerateShareLinkResp, err error) {
|
||||||
|
if req.OrderId == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("订单ID不能为空"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "")
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取订单失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Status != model.OrderStatusPaid {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 订单未支付")
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取查询失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.QueryState != model.QueryStateSuccess {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 查询未成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
expireAt := time.Now().Add(time.Duration(l.svcCtx.Config.Query.ShareLinkExpire) * time.Second)
|
||||||
|
payload := types.QueryShareLinkPayload{
|
||||||
|
OrderId: order.Id,
|
||||||
|
ExpireAt: expireAt.Unix(),
|
||||||
|
}
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, err := hex.DecodeString(secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 解密失败: %v", err)
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
encryptedPayload, err := crypto.AesEncryptURL(payloadBytes, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 加密失败: %v", err)
|
||||||
|
}
|
||||||
|
return &types.QueryGenerateShareLinkResp{
|
||||||
|
ShareLink: encryptedPayload,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
@@ -53,8 +54,12 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
|
|||||||
if req.Referrer == "" {
|
if req.Referrer == "" {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
|
||||||
}
|
}
|
||||||
// 2. 校验验证码(开发环境下跳过验证码校验)
|
// 2. 校验验证码:开发环境跳过;审核预留号段 + 固定码;其余 Redis
|
||||||
if os.Getenv("ENV") != "development" {
|
if os.Getenv("ENV") == "development" {
|
||||||
|
// skip
|
||||||
|
} else if reviewphone.IsAppReviewDemoMobile(req.Mobile) && req.Code == reviewphone.DemoVerifyCode {
|
||||||
|
l.Infof("[ApplyForAgent] 审核体验号段固定验证码通过, mobile: %s", req.Mobile)
|
||||||
|
} else {
|
||||||
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 {
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/lzUtils"
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -62,53 +61,158 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
return nil, errors.Wrapf(xerr.NewErrMsg("请先完成实名认证"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("请先完成实名认证"), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 验证提现金额
|
// 3. 验证提现方式
|
||||||
if req.Amount <= 0 {
|
if req.WithdrawalType != 1 && req.WithdrawalType != 2 {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("提现方式无效"), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 获取钱包信息
|
// 4. 验证提现信息
|
||||||
|
if req.WithdrawalType == 1 {
|
||||||
|
// 支付宝提现:验证支付宝账号和姓名
|
||||||
|
if req.PayeeAccount == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请输入支付宝账号"), "")
|
||||||
|
}
|
||||||
|
if req.PayeeName == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请输入收款人姓名"), "")
|
||||||
|
}
|
||||||
|
} else if req.WithdrawalType == 2 {
|
||||||
|
// 银行卡提现:验证银行卡号、开户行和姓名
|
||||||
|
if req.BankCardNo == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请输入银行卡号"), "")
|
||||||
|
}
|
||||||
|
if req.BankName == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请输入开户行名称"), "")
|
||||||
|
}
|
||||||
|
if req.PayeeName == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请输入收款人姓名"), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 获取钱包信息
|
||||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id)
|
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 验证余额
|
// 6. 提现金额与可用余额:仅可提「可用余额」内金额;可用余额须大于 0
|
||||||
if wallet.Balance < req.Amount {
|
withdrawAmount := lzUtils.RoundMoney(req.Amount)
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("余额不足,当前余额:%.2f", wallet.Balance)), "")
|
if withdrawAmount <= 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
|
||||||
|
}
|
||||||
|
bal := lzUtils.RoundMoney(wallet.Balance)
|
||||||
|
if bal < 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("账户存在欠款,请先补足欠款后再申请提现,当前可用余额:%.2f", bal)), "")
|
||||||
|
}
|
||||||
|
if bal <= 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("可用余额须大于0才能申请提现"), "")
|
||||||
|
}
|
||||||
|
if withdrawAmount > bal {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("提现金额不能超过可用余额(不含冻结),当前可用:%.2f", bal)), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 计算税费
|
// 7. 支付宝月度提现额度校验(仅针对支付宝提现)
|
||||||
|
if req.WithdrawalType == 1 {
|
||||||
|
now := time.Now()
|
||||||
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||||
|
nextMonthStart := monthStart.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
// 8.1 获取支付宝月度额度配置(默认 800 元)
|
||||||
|
alipayQuota := 800.0
|
||||||
|
if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, "alipay_month_quota"); cfgErr == nil {
|
||||||
|
if parsed, parseErr := l.parseFloat(cfg.ConfigValue); parseErr == nil && parsed > 0 {
|
||||||
|
alipayQuota = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.2 统计本月已申请/成功的支付宝提现金额(status IN (1,5)),避免多次申请占用超额
|
||||||
|
withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().
|
||||||
|
Where("agent_id = ? AND withdrawal_type = ? AND status IN (1,5) AND create_time >= ? AND create_time < ?",
|
||||||
|
agent.Id, 1, monthStart, nextMonthStart)
|
||||||
|
usedAmount, sumErr := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, withdrawBuilder, "amount")
|
||||||
|
if sumErr != nil {
|
||||||
|
return nil, errors.Wrapf(sumErr, "查询本月支付宝提现额度使用情况失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
remainQuota := alipayQuota - usedAmount
|
||||||
|
if remainQuota <= 0 {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
xerr.NewErrMsg(fmt.Sprintf("本月支付宝提现额度已用完(额度:%.2f 元),请使用银行卡提现", alipayQuota)),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if withdrawAmount > remainQuota {
|
||||||
|
return nil, errors.Wrapf(
|
||||||
|
xerr.NewErrMsg(fmt.Sprintf("本月支付宝最高可提现 %.2f 元,请调整提现金额或使用银行卡提现", remainQuota)),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 计算税费
|
||||||
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
||||||
taxInfo, err := l.calculateTax(l.ctx, agent.Id, req.Amount, yearMonth)
|
taxInfo, err := l.calculateTax(l.ctx, agent.Id, withdrawAmount, yearMonth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "计算税费失败")
|
return nil, errors.Wrapf(err, "计算税费失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 生成提现单号
|
// 10. 生成提现单号(WD开头 + GenerateOutTradeNo生成的订单号,确保总长度不超过32个字符)
|
||||||
withdrawNo := fmt.Sprintf("WD%d%d", time.Now().Unix(), agent.Id)
|
orderNo := l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||||
|
withdrawNo := "WD" + orderNo
|
||||||
|
// 确保总长度不超过32个字符
|
||||||
|
if len(withdrawNo) > 32 {
|
||||||
|
withdrawNo = withdrawNo[:32]
|
||||||
|
}
|
||||||
|
|
||||||
// 8. 使用事务处理提现申请
|
// 11. 使用事务处理提现申请
|
||||||
var withdrawalId string
|
var withdrawalId string
|
||||||
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 {
|
||||||
// 8.1 冻结余额
|
// 提交前再次校验可用余额(防止并发入账/冲正与申请提现竞态)
|
||||||
wallet.FrozenBalance += req.Amount
|
fresh, wErr := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, agent.Id)
|
||||||
wallet.Balance -= req.Amount
|
if wErr != nil {
|
||||||
|
return errors.Wrapf(wErr, "事务内查询钱包失败")
|
||||||
|
}
|
||||||
|
freshBal := lzUtils.RoundMoney(fresh.Balance)
|
||||||
|
if freshBal < 0 {
|
||||||
|
return errors.New("账户存在欠款,无法申请提现")
|
||||||
|
}
|
||||||
|
if freshBal <= 0 {
|
||||||
|
return errors.New("可用余额须大于0才能申请提现")
|
||||||
|
}
|
||||||
|
if withdrawAmount > freshBal {
|
||||||
|
return fmt.Errorf("提现金额不能超过当前可用余额(%.2f)", freshBal)
|
||||||
|
}
|
||||||
|
wallet = fresh
|
||||||
|
|
||||||
|
// 11.1 冻结余额
|
||||||
|
wallet.FrozenBalance += withdrawAmount
|
||||||
|
wallet.Balance -= withdrawAmount
|
||||||
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
|
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil {
|
||||||
return errors.Wrapf(err, "冻结余额失败")
|
return errors.Wrapf(err, "冻结余额失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8.2 创建提现记录
|
// 11.2 创建提现记录
|
||||||
withdrawal := &model.AgentWithdrawal{
|
withdrawal := &model.AgentWithdrawal{
|
||||||
Id: uuid.New().String(),
|
Id: uuid.New().String(),
|
||||||
AgentId: agent.Id,
|
AgentId: agent.Id,
|
||||||
WithdrawNo: withdrawNo,
|
WithdrawNo: withdrawNo,
|
||||||
PayeeAccount: req.PayeeAccount,
|
WithdrawalType: req.WithdrawalType,
|
||||||
PayeeName: req.PayeeName,
|
PayeeAccount: req.PayeeAccount,
|
||||||
Amount: req.Amount,
|
PayeeName: req.PayeeName,
|
||||||
ActualAmount: taxInfo.ActualAmount,
|
Amount: withdrawAmount,
|
||||||
TaxAmount: taxInfo.TaxAmount,
|
ActualAmount: taxInfo.ActualAmount,
|
||||||
Status: 1, // 处理中(待审核)
|
TaxAmount: taxInfo.TaxAmount,
|
||||||
|
Status: 1, // 待审核
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是银行卡提现,设置银行卡相关字段
|
||||||
|
if req.WithdrawalType == 2 {
|
||||||
|
withdrawal.BankCardNo = lzUtils.StringToNullString(req.BankCardNo)
|
||||||
|
withdrawal.BankName = lzUtils.StringToNullString(req.BankName)
|
||||||
|
// 银行卡提现时,payee_account 可以存储银行卡号(便于查询),也可以留空
|
||||||
|
if req.PayeeAccount == "" {
|
||||||
|
withdrawal.PayeeAccount = req.BankCardNo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := l.svcCtx.AgentWithdrawalModel.Insert(transCtx, session, withdrawal)
|
_, err := l.svcCtx.AgentWithdrawalModel.Insert(transCtx, session, withdrawal)
|
||||||
@@ -117,12 +221,12 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
}
|
}
|
||||||
withdrawalId = withdrawal.Id
|
withdrawalId = withdrawal.Id
|
||||||
|
|
||||||
// 8.3 创建扣税记录
|
// 11.3 创建扣税记录
|
||||||
taxRecord := &model.AgentWithdrawalTax{
|
taxRecord := &model.AgentWithdrawalTax{
|
||||||
AgentId: agent.Id,
|
AgentId: agent.Id,
|
||||||
WithdrawalId: withdrawalId,
|
WithdrawalId: withdrawalId,
|
||||||
YearMonth: yearMonth,
|
YearMonth: yearMonth,
|
||||||
WithdrawalAmount: req.Amount,
|
WithdrawalAmount: withdrawAmount,
|
||||||
TaxableAmount: taxInfo.TaxableAmount,
|
TaxableAmount: taxInfo.TaxableAmount,
|
||||||
TaxRate: taxInfo.TaxRate,
|
TaxRate: taxInfo.TaxRate,
|
||||||
TaxAmount: taxInfo.TaxAmount,
|
TaxAmount: taxInfo.TaxAmount,
|
||||||
@@ -167,8 +271,10 @@ func (l *ApplyWithdrawalLogic) calculateTax(ctx context.Context, agentId string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询本月已提现金额
|
// 查询本月已提现金额
|
||||||
|
// 注意:FindAll 方法会自动添加 del_state = ? 条件,所以这里不需要手动添加
|
||||||
|
// 这里对 year_month 使用反引号包裹,避免与某些数据库版本/SQL 模式下的关键字冲突
|
||||||
builder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
builder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder().
|
||||||
Where("agent_id = ? AND year_month = ? AND del_state = ?", agentId, yearMonth, globalkey.DelStateNo)
|
Where("agent_id = ? AND `year_month` = ?", agentId, yearMonth)
|
||||||
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(ctx, builder, "")
|
taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(ctx, builder, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "查询月度提现记录失败")
|
return nil, errors.Wrapf(err, "查询月度提现记录失败")
|
||||||
@@ -207,8 +313,8 @@ func (l *ApplyWithdrawalLogic) calculateTax(ctx context.Context, agentId string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 计算税费
|
// 计算税费
|
||||||
taxAmount := taxableAmount * taxRate
|
taxAmount := lzUtils.RoundMoney(taxableAmount * taxRate)
|
||||||
actualAmount := amount - taxAmount
|
actualAmount := lzUtils.RoundMoney(amount - taxAmount)
|
||||||
|
|
||||||
return &TaxInfo{
|
return &TaxInfo{
|
||||||
TaxableAmount: taxableAmount,
|
TaxableAmount: taxableAmount,
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckFeatureWhitelistStatusLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckFeatureWhitelistStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckFeatureWhitelistStatusLogic {
|
||||||
|
return &CheckFeatureWhitelistStatusLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CheckFeatureWhitelistStatusLogic) CheckFeatureWhitelistStatus(req *types.CheckFeatureWhitelistStatusReq) (resp *types.CheckFeatureWhitelistStatusResp, err error) {
|
||||||
|
if req.IdCard == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("身份证号不能为空"), "")
|
||||||
|
}
|
||||||
|
if req.FeatureApiId == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("模块API标识不能为空"), "")
|
||||||
|
}
|
||||||
|
mainApiId := req.FeatureApiId
|
||||||
|
if idx := strings.Index(req.FeatureApiId, "_"); idx > 0 {
|
||||||
|
mainApiId = req.FeatureApiId[:idx]
|
||||||
|
}
|
||||||
|
feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, mainApiId)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
whitelistBuilder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder().
|
||||||
|
Where("id_card = ? AND feature_api_id = ? AND status = ?", req.IdCard, mainApiId, 1)
|
||||||
|
whitelists, err := l.svcCtx.UserFeatureWhitelistModel.FindAll(l.ctx, whitelistBuilder, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单记录失败, %v", err)
|
||||||
|
}
|
||||||
|
isWhitelisted := len(whitelists) > 0
|
||||||
|
dataDeleted := false
|
||||||
|
if req.QueryId != "" {
|
||||||
|
containsFeature, err := l.svcCtx.WhitelistService.CheckQueryDataContainsFeature(l.ctx, req.QueryId, req.FeatureApiId)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("检查报告数据是否包含模块失败:%v", err)
|
||||||
|
dataDeleted = true
|
||||||
|
} else {
|
||||||
|
dataDeleted = !containsFeature
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataDeleted = true
|
||||||
|
}
|
||||||
|
return &types.CheckFeatureWhitelistStatusResp{
|
||||||
|
IsWhitelisted: isWhitelisted,
|
||||||
|
WhitelistPrice: feature.WhitelistPrice,
|
||||||
|
FeatureId: feature.Id,
|
||||||
|
DataDeleted: dataDeleted,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
54
app/main/api/internal/logic/agent/checkorderagentlogic.go
Normal file
54
app/main/api/internal/logic/agent/checkorderagentlogic.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckOrderAgentLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckOrderAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckOrderAgentLogic {
|
||||||
|
return &CheckOrderAgentLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckOrderAgent 判断订单是否为当前代理推广的订单(通过 agent_order 关联)
|
||||||
|
func (l *CheckOrderAgentLogic) CheckOrderAgent(req *types.CheckOrderAgentReq) (resp *types.CheckOrderAgentResp, err error) {
|
||||||
|
if req.OrderId == "" {
|
||||||
|
return &types.CheckOrderAgentResp{IsAgentOrder: false}, nil
|
||||||
|
}
|
||||||
|
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 &types.CheckOrderAgentResp{IsAgentOrder: false}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
|
||||||
|
}
|
||||||
|
agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, req.OrderId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, model.ErrNotFound) {
|
||||||
|
return &types.CheckOrderAgentResp{IsAgentOrder: false}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单关联失败, %v", err)
|
||||||
|
}
|
||||||
|
return &types.CheckOrderAgentResp{IsAgentOrder: agentOrder.AgentId == agent.Id}, nil
|
||||||
|
}
|
||||||
128
app/main/api/internal/logic/agent/createwhitelistorderlogic.go
Normal file
128
app/main/api/internal/logic/agent/createwhitelistorderlogic.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateWhitelistOrderLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateWhitelistOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateWhitelistOrderLogic {
|
||||||
|
return &CreateWhitelistOrderLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CreateWhitelistOrderLogic) CreateWhitelistOrder(req *types.CreateWhitelistOrderReq) (resp *types.CreateWhitelistOrderResp, err error) {
|
||||||
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
|
||||||
|
}
|
||||||
|
_, 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)
|
||||||
|
}
|
||||||
|
if req.IdCard == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("身份证号不能为空"), "")
|
||||||
|
}
|
||||||
|
if len(req.FeatureIds) == 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请至少选择一个模块"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalAmount float64
|
||||||
|
var orderItems []struct {
|
||||||
|
FeatureId string
|
||||||
|
FeatureApiId string
|
||||||
|
FeatureName string
|
||||||
|
Price float64
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, featureId := range req.FeatureIds {
|
||||||
|
feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, featureId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("模块不存在: %s", featureId)), "")
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询模块信息失败, %v", err)
|
||||||
|
}
|
||||||
|
whitelistPrice := feature.WhitelistPrice
|
||||||
|
if whitelistPrice <= 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("模块 %s 不支持白名单屏蔽", feature.Name)), "")
|
||||||
|
}
|
||||||
|
whitelistBuilder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder().
|
||||||
|
Where("id_card = ? AND feature_id = ?", req.IdCard, featureId)
|
||||||
|
existing, err := l.svcCtx.UserFeatureWhitelistModel.FindAll(l.ctx, whitelistBuilder, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单记录失败, %v", err)
|
||||||
|
}
|
||||||
|
for _, item := range existing {
|
||||||
|
if item.Status == 1 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("身份证号 %s 的模块 %s 已经加入白名单", req.IdCard, feature.Name)), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalAmount += whitelistPrice
|
||||||
|
orderItems = append(orderItems, struct {
|
||||||
|
FeatureId string
|
||||||
|
FeatureApiId string
|
||||||
|
FeatureName string
|
||||||
|
Price float64
|
||||||
|
}{
|
||||||
|
FeatureId: feature.Id, FeatureApiId: feature.ApiId, FeatureName: feature.Name, Price: whitelistPrice,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
base := l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||||
|
orderNo := "W_" + base
|
||||||
|
if len(orderNo) > 32 {
|
||||||
|
orderNo = orderNo[:32]
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderId string
|
||||||
|
err = l.svcCtx.WhitelistOrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
|
order := &model.WhitelistOrder{
|
||||||
|
Id: uuid.NewString(), OrderNo: orderNo, UserId: userID, IdCard: req.IdCard,
|
||||||
|
TotalAmount: totalAmount, Status: 1,
|
||||||
|
}
|
||||||
|
_, err := l.svcCtx.WhitelistOrderModel.Insert(ctx, session, order)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建订单失败, %v", err)
|
||||||
|
}
|
||||||
|
orderId = order.Id
|
||||||
|
for _, item := range orderItems {
|
||||||
|
orderItem := &model.WhitelistOrderItem{
|
||||||
|
Id: uuid.NewString(), OrderId: orderId, FeatureId: item.FeatureId,
|
||||||
|
FeatureApiId: item.FeatureApiId, FeatureName: item.FeatureName, Price: item.Price,
|
||||||
|
}
|
||||||
|
if _, err := l.svcCtx.WhitelistOrderItemModel.Insert(ctx, session, orderItem); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建订单明细失败, %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.CreateWhitelistOrderResp{
|
||||||
|
OrderId: orderId, OrderNo: orderNo, TotalAmount: totalAmount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenerateInvitePosterLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerateInvitePosterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateInvitePosterLogic {
|
||||||
|
return &GenerateInvitePosterLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GenerateInvitePosterLogic) GenerateInvitePoster(req *types.GenerateInvitePosterReq) (resp *types.GenerateInvitePosterResp, err error) {
|
||||||
|
if req.InviteLink == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("邀请链接不能为空"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用ImageService生成海报
|
||||||
|
imageData, mimeType, err := l.svcCtx.ImageService.ProcessImageWithQRCode("invitation", req.InviteLink)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("生成邀请海报失败: %v", err)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成海报失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将图片数据转换为base64编码的data URL
|
||||||
|
base64Data := base64.StdEncoding.EncodeToString(imageData)
|
||||||
|
posterUrl := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
|
||||||
|
|
||||||
|
return &types.GenerateInvitePosterResp{
|
||||||
|
PosterUrl: posterUrl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"qnc-server/common/tool"
|
"qnc-server/common/tool"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -41,6 +42,12 @@ func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Ge
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
|
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
|
||||||
|
// 将 set_price 从 string 转为 float64
|
||||||
|
setPrice, err := strconv.ParseFloat(req.SetPrice, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格格式不正确"), "")
|
||||||
|
}
|
||||||
|
|
||||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广链接失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广链接失败, %v", err)
|
||||||
@@ -71,14 +78,14 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
basePrice := productConfig.BasePrice
|
basePrice := productConfig.BasePrice
|
||||||
actualBasePrice := basePrice + float64(levelBonus)
|
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||||
systemMaxPrice := productConfig.SystemMaxPrice
|
systemMaxPrice := productConfig.SystemMaxPrice
|
||||||
upliftAmount, err := l.getLevelMaxUpliftAmount(agentModel.Level)
|
upliftAmount, err := l.getLevelMaxUpliftAmount(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)
|
||||||
}
|
}
|
||||||
levelMaxPrice := systemMaxPrice + upliftAmount
|
levelMaxPrice := lzUtils.RoundMoney(systemMaxPrice + upliftAmount)
|
||||||
if req.SetPrice < actualBasePrice || req.SetPrice > levelMaxPrice {
|
if setPrice < actualBasePrice || setPrice > levelMaxPrice {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格必须在 %.2f 到 %.2f 之间"), "设定价格必须在 %.2f 到 %.2f 之间", actualBasePrice, levelMaxPrice)
|
return nil, errors.Wrapf(xerr.NewErrMsg("设定价格必须在 %.2f 到 %.2f 之间"), "设定价格必须在 %.2f 到 %.2f 之间", actualBasePrice, levelMaxPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +93,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
|||||||
builder := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
|
builder := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
|
||||||
squirrel.Eq{"agent_id": agentModel.Id},
|
squirrel.Eq{"agent_id": agentModel.Id},
|
||||||
squirrel.Eq{"product_id": req.ProductId},
|
squirrel.Eq{"product_id": req.ProductId},
|
||||||
squirrel.Eq{"set_price": req.SetPrice},
|
squirrel.Eq{"set_price": setPrice},
|
||||||
squirrel.Eq{"del_state": globalkey.DelStateNo},
|
squirrel.Eq{"del_state": globalkey.DelStateNo},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -115,7 +122,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
|||||||
var agentIdentifier types.AgentIdentifier
|
var agentIdentifier types.AgentIdentifier
|
||||||
agentIdentifier.AgentID = agentModel.Id
|
agentIdentifier.AgentID = agentModel.Id
|
||||||
agentIdentifier.ProductID = req.ProductId
|
agentIdentifier.ProductID = req.ProductId
|
||||||
agentIdentifier.SetPrice = req.SetPrice
|
agentIdentifier.SetPrice = setPrice
|
||||||
agentIdentifierByte, err := json.Marshal(agentIdentifier)
|
agentIdentifierByte, err := json.Marshal(agentIdentifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化标识失败, %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化标识失败, %v", err)
|
||||||
@@ -140,7 +147,7 @@ func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq)
|
|||||||
UserId: userID,
|
UserId: userID,
|
||||||
ProductId: req.ProductId,
|
ProductId: req.ProductId,
|
||||||
LinkIdentifier: encrypted,
|
LinkIdentifier: encrypted,
|
||||||
SetPrice: req.SetPrice,
|
SetPrice: setPrice,
|
||||||
ActualBasePrice: actualBasePrice,
|
ActualBasePrice: actualBasePrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@@ -76,11 +77,11 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
|
|||||||
productBasePrice := productConfig.BasePrice
|
productBasePrice := productConfig.BasePrice
|
||||||
|
|
||||||
// 计算该产品的实际底价
|
// 计算该产品的实际底价
|
||||||
productActualBasePrice := productBasePrice + float64(levelBonus)
|
productActualBasePrice := lzUtils.RoundMoney(productBasePrice + float64(levelBonus))
|
||||||
|
|
||||||
priceRangeMin := productActualBasePrice
|
priceRangeMin := productActualBasePrice
|
||||||
upliftAmount, _ := l.getLevelMaxUpliftAmount(agentModel.Level)
|
upliftAmount, _ := l.getLevelMaxUpliftAmount(agentModel.Level)
|
||||||
priceRangeMax := productConfig.SystemMaxPrice + upliftAmount
|
priceRangeMax := lzUtils.RoundMoney(productConfig.SystemMaxPrice + upliftAmount)
|
||||||
|
|
||||||
// 使用产品配置的提价阈值和手续费比例,如果为NULL则使用0
|
// 使用产品配置的提价阈值和手续费比例,如果为NULL则使用0
|
||||||
productPriceThreshold := 0.0
|
productPriceThreshold := 0.0
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/common/tool"
|
"qnc-server/common/tool"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@@ -124,7 +124,18 @@ func (l *GetInviteLinkLogic) createInviteShortLink(inviteCodeId string, inviteCo
|
|||||||
// 先查询是否已存在短链
|
// 先查询是否已存在短链
|
||||||
existingShortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullString{String: inviteCodeId, Valid: inviteCodeId != ""}, 2, globalkey.DelStateNo)
|
existingShortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullString{String: inviteCodeId, Valid: inviteCodeId != ""}, 2, globalkey.DelStateNo)
|
||||||
if err == nil && existingShortLink != nil {
|
if err == nil && existingShortLink != nil {
|
||||||
// 已存在短链,直接返回
|
// 已存在短链,检查 target_path 是否需要更新
|
||||||
|
if existingShortLink.TargetPath != targetPath {
|
||||||
|
// target_path 已变化,更新短链记录
|
||||||
|
oldTargetPath := existingShortLink.TargetPath
|
||||||
|
existingShortLink.TargetPath = targetPath
|
||||||
|
if updateErr := l.svcCtx.AgentShortLinkModel.UpdateWithVersion(l.ctx, nil, existingShortLink); updateErr != nil {
|
||||||
|
l.Errorf("更新短链 target_path 失败: %v", updateErr)
|
||||||
|
// 即使更新失败,也返回旧短链,避免影响用户体验
|
||||||
|
} else {
|
||||||
|
l.Infof("短链 target_path 已更新: shortCode=%s, old=%s, new=%s", existingShortLink.ShortCode, oldTargetPath, targetPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingShortLink.ShortCode), nil
|
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingShortLink.ShortCode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +147,18 @@ func (l *GetInviteLinkLogic) createInviteShortLink(inviteCodeId string, inviteCo
|
|||||||
if inviteCodeId == "" && inviteCode != "" {
|
if inviteCodeId == "" && inviteCode != "" {
|
||||||
existingByCode, err2 := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeTypeDelState(l.ctx, sql.NullString{String: inviteCode, Valid: true}, 2, globalkey.DelStateNo)
|
existingByCode, err2 := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeTypeDelState(l.ctx, sql.NullString{String: inviteCode, Valid: true}, 2, globalkey.DelStateNo)
|
||||||
if err2 == nil && existingByCode != nil {
|
if err2 == nil && existingByCode != nil {
|
||||||
|
// 已存在短链,检查 target_path 是否需要更新
|
||||||
|
if existingByCode.TargetPath != targetPath {
|
||||||
|
// target_path 已变化,更新短链记录
|
||||||
|
oldTargetPath := existingByCode.TargetPath
|
||||||
|
existingByCode.TargetPath = targetPath
|
||||||
|
if updateErr := l.svcCtx.AgentShortLinkModel.UpdateWithVersion(l.ctx, nil, existingByCode); updateErr != nil {
|
||||||
|
l.Errorf("更新短链 target_path 失败: %v", updateErr)
|
||||||
|
// 即使更新失败,也返回旧短链,避免影响用户体验
|
||||||
|
} else {
|
||||||
|
l.Infof("短链 target_path 已更新: shortCode=%s, old=%s, new=%s", existingByCode.ShortCode, oldTargetPath, targetPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingByCode.ShortCode), nil
|
return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingByCode.ShortCode), nil
|
||||||
}
|
}
|
||||||
if err2 != nil && !errors.Is(err2, model.ErrNotFound) {
|
if err2 != nil && !errors.Is(err2, model.ErrNotFound) {
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetLastWithdrawalInfoLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetLastWithdrawalInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLastWithdrawalInfoLogic {
|
||||||
|
return &GetLastWithdrawalInfoLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetLastWithdrawalInfoLogic) GetLastWithdrawalInfo(req *types.GetLastWithdrawalInfoReq) (resp *types.GetLastWithdrawalInfoResp, 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. 验证提现方式
|
||||||
|
if req.WithdrawalType != 1 && req.WithdrawalType != 2 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("提现方式无效"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 查询该代理最近一次该类型的提现记录
|
||||||
|
// 注意:FindAll 方法会自动添加 del_state = ? 条件,所以这里不需要手动添加
|
||||||
|
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().
|
||||||
|
Where("agent_id = ? AND withdrawal_type = ?", agent.Id, req.WithdrawalType).
|
||||||
|
OrderBy("create_time DESC").
|
||||||
|
Limit(1)
|
||||||
|
|
||||||
|
withdrawals, err := l.svcCtx.AgentWithdrawalModel.FindAll(l.ctx, builder, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上次提现记录失败, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果没有找到记录,返回空信息
|
||||||
|
if len(withdrawals) == 0 {
|
||||||
|
return &types.GetLastWithdrawalInfoResp{
|
||||||
|
WithdrawalType: req.WithdrawalType,
|
||||||
|
PayeeAccount: "",
|
||||||
|
PayeeName: "",
|
||||||
|
BankCardNo: "",
|
||||||
|
BankName: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 组装响应
|
||||||
|
lastWithdrawal := withdrawals[0]
|
||||||
|
resp = &types.GetLastWithdrawalInfoResp{
|
||||||
|
WithdrawalType: lastWithdrawal.WithdrawalType,
|
||||||
|
PayeeAccount: lastWithdrawal.PayeeAccount,
|
||||||
|
PayeeName: lastWithdrawal.PayeeName,
|
||||||
|
BankCardNo: "",
|
||||||
|
BankName: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是银行卡提现,填充银行卡信息
|
||||||
|
if lastWithdrawal.WithdrawalType == 2 {
|
||||||
|
if lastWithdrawal.BankCardNo.Valid {
|
||||||
|
resp.BankCardNo = lastWithdrawal.BankCardNo.String
|
||||||
|
}
|
||||||
|
if lastWithdrawal.BankName.Valid {
|
||||||
|
resp.BankName = lastWithdrawal.BankName.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -314,7 +314,8 @@ func (l *GetTeamListLogic) buildTeamMemberItem(agentId string, member *model.Age
|
|||||||
monthQueries := l.countDistinctOrdersForMember(l.ctx, monthRebateBuilder)
|
monthQueries := l.countDistinctOrdersForMember(l.ctx, monthRebateBuilder)
|
||||||
|
|
||||||
// 统计返佣给我的金额(从agent_rebate表,source_agent_id = member.Id, agent_id = agentId)
|
// 统计返佣给我的金额(从agent_rebate表,source_agent_id = member.Id, agent_id = agentId)
|
||||||
// 原返佣统计字段已在新需求中不再下发给前端,这里暂不计算金额,后续如有需要可在 .api 中重新定义并补充实现
|
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)
|
// 统计邀请人数(从agent_relation表,parent_id = member.Id)
|
||||||
inviteBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
|
inviteBuilder := l.svcCtx.AgentRelationModel.SelectBuilder().
|
||||||
@@ -327,17 +328,19 @@ func (l *GetTeamListLogic) buildTeamMemberItem(agentId string, member *model.Age
|
|||||||
isDirect := directSubordinateIds[member.Id]
|
isDirect := directSubordinateIds[member.Id]
|
||||||
|
|
||||||
return types.TeamMemberItem{
|
return types.TeamMemberItem{
|
||||||
AgentId: member.Id,
|
AgentId: member.Id,
|
||||||
Level: member.Level,
|
Level: member.Level,
|
||||||
LevelName: levelName,
|
LevelName: levelName,
|
||||||
Mobile: mobile,
|
Mobile: mobile,
|
||||||
CreateTime: member.CreateTime.Format("2006-01-02 15:04:05"),
|
CreateTime: member.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
TodayInvites: todayInvites,
|
TodayInvites: todayInvites,
|
||||||
MonthInvites: monthInvites,
|
MonthInvites: monthInvites,
|
||||||
TotalInvites: totalInvites,
|
TotalInvites: totalInvites,
|
||||||
TodayQueries: todayQueries,
|
TodayQueries: todayQueries,
|
||||||
MonthQueries: monthQueries,
|
MonthQueries: monthQueries,
|
||||||
TotalQueries: totalQueries,
|
TotalQueries: totalQueries,
|
||||||
IsDirect: isDirect,
|
TotalRebateAmount: totalRebateAmount,
|
||||||
|
TodayRebateAmount: todayRebateAmount,
|
||||||
|
IsDirect: isDirect,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetWhitelistFeaturesLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetWhitelistFeaturesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhitelistFeaturesLogic {
|
||||||
|
return &GetWhitelistFeaturesLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWhitelistFeatures 返回支持白名单屏蔽的 feature 列表(whitelist_price > 0)
|
||||||
|
func (l *GetWhitelistFeaturesLogic) GetWhitelistFeatures(req *types.GetWhitelistFeaturesReq) (resp *types.GetWhitelistFeaturesResp, err error) {
|
||||||
|
builder := l.svcCtx.FeatureModel.SelectBuilder().Where("whitelist_price > ?", 0)
|
||||||
|
list, err := l.svcCtx.FeatureModel.FindAll(l.ctx, builder, "id ASC")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询功能列表失败, %v", err)
|
||||||
|
}
|
||||||
|
items := make([]types.WhitelistFeatureItem, 0, len(list))
|
||||||
|
for _, f := range list {
|
||||||
|
items = append(items, types.WhitelistFeatureItem{
|
||||||
|
FeatureId: f.Id,
|
||||||
|
FeatureApiId: f.ApiId,
|
||||||
|
FeatureName: f.Name,
|
||||||
|
WhitelistPrice: f.WhitelistPrice,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &types.GetWhitelistFeaturesResp{List: items}, nil
|
||||||
|
}
|
||||||
78
app/main/api/internal/logic/agent/getwhitelistlistlogic.go
Normal file
78
app/main/api/internal/logic/agent/getwhitelistlistlogic.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetWhitelistListLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetWhitelistListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhitelistListLogic {
|
||||||
|
return &GetWhitelistListLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusToText(status int64) string {
|
||||||
|
switch status {
|
||||||
|
case 1:
|
||||||
|
return "生效"
|
||||||
|
case 2:
|
||||||
|
return "已失效"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetWhitelistListLogic) GetWhitelistList(req *types.GetWhitelistListReq) (resp *types.GetWhitelistListResp, err error) {
|
||||||
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
|
||||||
|
}
|
||||||
|
if req.Page <= 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
if req.PageSize <= 0 || req.PageSize > 100 {
|
||||||
|
req.PageSize = 10
|
||||||
|
}
|
||||||
|
builder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder().Where("user_id = ?", userID)
|
||||||
|
if req.IdCard != "" {
|
||||||
|
builder = builder.Where("id_card = ?", req.IdCard)
|
||||||
|
}
|
||||||
|
list, total, err := l.svcCtx.UserFeatureWhitelistModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单列表失败, %v", err)
|
||||||
|
}
|
||||||
|
items := make([]types.WhitelistItem, 0, len(list))
|
||||||
|
for _, r := range list {
|
||||||
|
featureName := r.FeatureApiId
|
||||||
|
if f, e := l.svcCtx.FeatureModel.FindOne(l.ctx, r.FeatureId); e == nil {
|
||||||
|
featureName = f.Name
|
||||||
|
}
|
||||||
|
items = append(items, types.WhitelistItem{
|
||||||
|
Id: r.Id,
|
||||||
|
IdCard: r.IdCard,
|
||||||
|
FeatureId: r.FeatureId,
|
||||||
|
FeatureApiId: r.FeatureApiId,
|
||||||
|
FeatureName: featureName,
|
||||||
|
Amount: r.Amount,
|
||||||
|
Status: r.Status,
|
||||||
|
StatusText: statusToText(r.Status),
|
||||||
|
CreateTime: r.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &types.GetWhitelistListResp{Total: total, List: items}, nil
|
||||||
|
}
|
||||||
@@ -81,18 +81,31 @@ func (l *GetWithdrawalListLogic) GetWithdrawalList(req *types.GetWithdrawalListR
|
|||||||
remark = withdrawal.Remark.String
|
remark = withdrawal.Remark.String
|
||||||
}
|
}
|
||||||
|
|
||||||
list = append(list, types.WithdrawalItem{
|
item := types.WithdrawalItem{
|
||||||
Id: withdrawal.Id,
|
Id: withdrawal.Id,
|
||||||
WithdrawalNo: withdrawal.WithdrawNo,
|
WithdrawalNo: withdrawal.WithdrawNo,
|
||||||
Amount: withdrawal.Amount,
|
WithdrawalType: withdrawal.WithdrawalType,
|
||||||
TaxAmount: withdrawal.TaxAmount,
|
Amount: withdrawal.Amount,
|
||||||
ActualAmount: withdrawal.ActualAmount,
|
TaxAmount: withdrawal.TaxAmount,
|
||||||
Status: withdrawal.Status,
|
ActualAmount: withdrawal.ActualAmount,
|
||||||
PayeeAccount: withdrawal.PayeeAccount,
|
Status: withdrawal.Status,
|
||||||
PayeeName: withdrawal.PayeeName,
|
PayeeAccount: withdrawal.PayeeAccount,
|
||||||
Remark: remark,
|
PayeeName: withdrawal.PayeeName,
|
||||||
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
|
Remark: remark,
|
||||||
})
|
CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是银行卡提现,填充银行卡信息
|
||||||
|
if withdrawal.WithdrawalType == 2 {
|
||||||
|
if withdrawal.BankCardNo.Valid {
|
||||||
|
item.BankCardNo = withdrawal.BankCardNo.String
|
||||||
|
}
|
||||||
|
if withdrawal.BankName.Valid {
|
||||||
|
item.BankName = withdrawal.BankName.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.GetWithdrawalListResp{
|
return &types.GetWithdrawalListResp{
|
||||||
|
|||||||
71
app/main/api/internal/logic/agent/offlinefeaturelogic.go
Normal file
71
app/main/api/internal/logic/agent/offlinefeaturelogic.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OfflineFeatureLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOfflineFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OfflineFeatureLogic {
|
||||||
|
return &OfflineFeatureLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OfflineFeatureLogic) OfflineFeature(req *types.OfflineFeatureReq) (resp *types.OfflineFeatureResp, err error) {
|
||||||
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
|
||||||
|
}
|
||||||
|
if req.FeatureApiId == "" || req.QueryId == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("模块标识和查询记录ID不能为空"), "")
|
||||||
|
}
|
||||||
|
// 从 Query 获取 id_card
|
||||||
|
query, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.QueryId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询记录不存在或查询失败, %v", err)
|
||||||
|
}
|
||||||
|
idCard := ""
|
||||||
|
if query.QueryParams != "" {
|
||||||
|
var params map[string]interface{}
|
||||||
|
if e := json.Unmarshal([]byte(query.QueryParams), ¶ms); e == nil {
|
||||||
|
if v, ok := params["id_card"]; ok {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
idCard = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idCard == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("无法从查询记录中获取身份证号"), "")
|
||||||
|
}
|
||||||
|
needPay, amount, whitelistCreated, err := l.svcCtx.WhitelistService.ProcessOfflineFeature(l.ctx, nil, idCard, req.FeatureApiId, userID, req.QueryId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if whitelistCreated && !needPay {
|
||||||
|
if err := l.svcCtx.WhitelistService.DeleteFeatureFromQueryData(l.ctx, nil, req.QueryId, req.FeatureApiId); err != nil {
|
||||||
|
logx.Errorf("从报告数据中删除模块失败: %v", err)
|
||||||
|
}
|
||||||
|
return &types.OfflineFeatureResp{Success: true, NeedPay: false, Amount: 0}, nil
|
||||||
|
}
|
||||||
|
if needPay {
|
||||||
|
return &types.OfflineFeatureResp{Success: false, NeedPay: true, Amount: amount}, nil
|
||||||
|
}
|
||||||
|
return &types.OfflineFeatureResp{Success: true, NeedPay: false, Amount: 0}, nil
|
||||||
|
}
|
||||||
@@ -5,14 +5,16 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
@@ -24,6 +26,36 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isMySQLDuplicateEntry(err error) bool {
|
||||||
|
var me *mysql.MySQLError
|
||||||
|
return errors.As(err, &me) && me.Number == 1062
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertMobileUserAuthOrSkip 写入 mobile 认证;若并发重复插入触发唯一键冲突,且该手机号已绑定同一用户则视为成功(幂等)。
|
||||||
|
func (l *RegisterByInviteCodeLogic) insertMobileUserAuthOrSkip(ctx context.Context, session sqlx.Session, userID string, encryptedMobile string) error {
|
||||||
|
_, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
UserId: userID,
|
||||||
|
AuthType: model.UserAuthTypeMobile,
|
||||||
|
AuthKey: encryptedMobile,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !isMySQLDuplicateEntry(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exist, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(ctx, model.UserAuthTypeMobile, encryptedMobile)
|
||||||
|
if findErr != nil {
|
||||||
|
return findErr
|
||||||
|
}
|
||||||
|
if exist != nil && exist.UserId == userID {
|
||||||
|
l.Infof("[insertMobileUserAuthOrSkip] mobile 认证已由并发请求创建,跳过: userId=%s", userID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("该手机号已被占用"), "")
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterByInviteCodeLogic struct {
|
type RegisterByInviteCodeLogic struct {
|
||||||
logx.Logger
|
logx.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -48,8 +80,12 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
|
|||||||
}
|
}
|
||||||
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
|
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
|
||||||
|
|
||||||
// 校验验证码(开发环境下跳过验证码校验)
|
// 校验验证码:开发环境跳过;审核预留号段 + 固定码走专用通道;其余走 Redis
|
||||||
if os.Getenv("ENV") != "development" && req.Code != "143838" {
|
if os.Getenv("ENV") == "development" {
|
||||||
|
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
|
||||||
|
} else if reviewphone.IsAppReviewDemoMobile(req.Mobile) && req.Code == reviewphone.DemoVerifyCode {
|
||||||
|
l.Infof("[RegisterByInviteCode] 审核体验号段固定验证码通过, mobile: %s", req.Mobile)
|
||||||
|
} else {
|
||||||
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 {
|
||||||
@@ -64,8 +100,6 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
|
|||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
|
||||||
}
|
}
|
||||||
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
|
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
|
||||||
} else {
|
|
||||||
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前登录态(可能为空)
|
// 获取当前登录态(可能为空)
|
||||||
@@ -471,14 +505,8 @@ func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, s
|
|||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 用户创建成功, userId: %s", newUser.Id)
|
l.Infof("[handleMobileNotExists] 用户创建成功, userId: %s", newUser.Id)
|
||||||
// 创建 mobile 认证
|
if err := l.insertMobileUserAuthOrSkip(ctx, session, newUser.Id, encryptedMobile); err != nil {
|
||||||
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
return "", errors.Wrap(err, "创建手机号认证失败")
|
||||||
Id: uuid.NewString(),
|
|
||||||
UserId: newUser.Id,
|
|
||||||
AuthType: model.UserAuthTypeMobile,
|
|
||||||
AuthKey: encryptedMobile,
|
|
||||||
}); err != nil {
|
|
||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", newUser.Id)
|
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", newUser.Id)
|
||||||
return newUser.Id, nil
|
return newUser.Id, nil
|
||||||
@@ -496,14 +524,8 @@ func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, s
|
|||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 用户升级为正式用户成功, userId: %s", currentUserID)
|
l.Infof("[handleMobileNotExists] 用户升级为正式用户成功, userId: %s", currentUserID)
|
||||||
// 创建 mobile 认证
|
if err := l.insertMobileUserAuthOrSkip(ctx, session, currentUserID, encryptedMobile); err != nil {
|
||||||
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
return "", errors.Wrap(err, "创建手机号认证失败")
|
||||||
Id: uuid.NewString(),
|
|
||||||
UserId: currentUserID,
|
|
||||||
AuthType: model.UserAuthTypeMobile,
|
|
||||||
AuthKey: encryptedMobile,
|
|
||||||
}); err != nil {
|
|
||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", currentUserID)
|
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", currentUserID)
|
||||||
return currentUserID, nil
|
return currentUserID, nil
|
||||||
|
|||||||
91
app/main/api/internal/logic/app/gethomedynamicdatalogic.go
Normal file
91
app/main/api/internal/logic/app/gethomedynamicdatalogic.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetHomeDynamicDataLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetHomeDynamicDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetHomeDynamicDataLogic {
|
||||||
|
return &GetHomeDynamicDataLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetHomeDynamicDataLogic) GetHomeDynamicData(req *types.GetHomeDynamicDataReq) (resp *types.GetHomeDynamicDataResp, err error) {
|
||||||
|
// 1. 获取真实查询案例 (支持增量查询)
|
||||||
|
// 增加日志输出,方便调试
|
||||||
|
l.Infof("获取首页动态数据, lastId: %d", req.LastId)
|
||||||
|
|
||||||
|
builder := l.svcCtx.InquiryRecordModel.SelectBuilder().Where("status = ?", 1)
|
||||||
|
|
||||||
|
var records []*model.InquiryRecord
|
||||||
|
if req.LastId > 0 {
|
||||||
|
// 增量获取大于 lastId 的记录
|
||||||
|
records, err = l.svcCtx.InquiryRecordModel.FindPageListByIdASC(l.ctx, builder, req.LastId, 50)
|
||||||
|
} else {
|
||||||
|
// 首次加载获取最新的 10 条
|
||||||
|
records, err = l.svcCtx.InquiryRecordModel.FindPageListByPage(l.ctx, builder, 1, 10, "id DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("查询查询记录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := make([]types.InquiryRecordItem, 0)
|
||||||
|
if err == nil && len(records) > 0 {
|
||||||
|
for _, r := range records {
|
||||||
|
cases = append(cases, types.InquiryRecordItem{
|
||||||
|
Id: r.Id,
|
||||||
|
Tag: r.InquiryTag,
|
||||||
|
Vin: r.VinMasked,
|
||||||
|
Model: r.CarModel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果数据库没有数据或查询失败,返回一些兜底的 Mock 数据,确保前端有展示
|
||||||
|
if req.LastId == 0 {
|
||||||
|
l.Info("使用兜底 Mock 案例数据")
|
||||||
|
mockCases := []struct {
|
||||||
|
Id int64
|
||||||
|
Tag string
|
||||||
|
Vin string
|
||||||
|
Model string
|
||||||
|
}{
|
||||||
|
{1, "查询成功", "湘A·12345", "奔驰GLC"},
|
||||||
|
{2, "查询成功", "粤B·67890", "丰田凯美瑞"},
|
||||||
|
{3, "查询成功", "京A·00011", "特斯拉ModelY"},
|
||||||
|
{4, "查询成功", "苏E·88888", "大众迈腾"},
|
||||||
|
{5, "查询成功", "沪A·87654", "宝马325"},
|
||||||
|
}
|
||||||
|
for _, m := range mockCases {
|
||||||
|
cases = append(cases, types.InquiryRecordItem{
|
||||||
|
Id: m.Id,
|
||||||
|
Tag: m.Tag,
|
||||||
|
Vin: m.Vin,
|
||||||
|
Model: m.Model,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 评价数据不再从后端返回,改为前端硬编码
|
||||||
|
reviews := make([]types.ReviewItem, 0)
|
||||||
|
|
||||||
|
return &types.GetHomeDynamicDataResp{
|
||||||
|
Cases: cases,
|
||||||
|
Reviews: reviews,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"qnc-server/common/xerr"
|
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
"qnc-server/pkg/captcha"
|
||||||
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -34,13 +36,72 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
|
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq, clientIP string, userAgent string) error {
|
||||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
// 检查手机号是否在一分钟内已发送过验证码
|
|
||||||
|
// 审核体验:预留号段申请代理时不发短信、不校验滑块,仅写入与注册校验一致的固定验证码
|
||||||
|
if req.ActionType == "agentApply" && reviewphone.IsAppReviewDemoMobile(req.Mobile) {
|
||||||
|
codeKey := fmt.Sprintf("%s:%s", req.ActionType, encryptedMobile)
|
||||||
|
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
||||||
|
if err := l.svcCtx.Redis.Setex(codeKey, reviewphone.DemoVerifyCode, l.svcCtx.Config.VerifyCode.ValidTime); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 审核号写入验证码失败: %v", err)
|
||||||
|
}
|
||||||
|
if err := l.svcCtx.Redis.Setex(limitCodeKey, "1", 60); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 审核号限流标记失败: %v", err)
|
||||||
|
}
|
||||||
|
l.Infof("短信发送, 审核体验号段跳过真实短信, mobile=%s", req.Mobile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 滑块验证码校验(可选,支持微信环境跳过验证)
|
||||||
|
cfg := l.svcCtx.Config.Captcha
|
||||||
|
captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{
|
||||||
|
AccessKeyID: cfg.AccessKeyID,
|
||||||
|
AccessKeySecret: cfg.AccessKeySecret,
|
||||||
|
EndpointURL: cfg.EndpointURL,
|
||||||
|
SceneID: cfg.SceneID,
|
||||||
|
}, req.CaptchaVerifyParam, userAgent)
|
||||||
|
|
||||||
|
if captchaResult.VerifyErr != nil {
|
||||||
|
return captchaResult.VerifyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 防刷策略
|
||||||
|
if captchaResult.Skipped {
|
||||||
|
// 没有滑块验证码,使用更严格的限流策略
|
||||||
|
// 2.1 IP 限流:同一 IP 每小时最多发送 10 次
|
||||||
|
ipLimitKey := fmt.Sprintf("ip_limit:%s", clientIP)
|
||||||
|
ipCount, err := l.svcCtx.Redis.Incr(ipLimitKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取IP限流缓存失败: %v", err)
|
||||||
|
}
|
||||||
|
if ipCount == 1 {
|
||||||
|
// 第一次访问,设置 1 小时过期
|
||||||
|
l.svcCtx.Redis.Expire(ipLimitKey, 3600)
|
||||||
|
}
|
||||||
|
if ipCount > 10 {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("请求过于频繁,请稍后再试"), "短信发送, IP限流: %s, count: %d", clientIP, ipCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2 手机号限流:同一手机号每小时最多发送 5 次(无滑块时更严格)
|
||||||
|
hourLimitKey := fmt.Sprintf("hour_limit:%s:%s", req.ActionType, encryptedMobile)
|
||||||
|
hourCount, err := l.svcCtx.Redis.Incr(hourLimitKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取小时限流缓存失败: %v", err)
|
||||||
|
}
|
||||||
|
if hourCount == 1 {
|
||||||
|
l.svcCtx.Redis.Expire(hourLimitKey, 3600)
|
||||||
|
}
|
||||||
|
if hourCount > 5 {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("该手机号请求过于频繁,请稍后再试"), "短信发送, 手机号小时限流: %s, count: %d", encryptedMobile, hourCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查手机号是否在一分钟内已发送过验证码(通用)
|
||||||
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
||||||
exists, err := l.svcCtx.Redis.Exists(limitCodeKey)
|
exists, err := l.svcCtx.Redis.Exists(limitCodeKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,7 +109,6 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
// 如果 Redis 中已经存在标记,说明在 1 分钟内请求过,返回错误
|
|
||||||
return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "短信发送, 手机号1分钟内重复请求发送验证码: %s", encryptedMobile)
|
return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "短信发送, 手机号1分钟内重复请求发送验证码: %s", encryptedMobile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/pkg/captcha"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetEncryptedSceneIdLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetEncryptedSceneIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetEncryptedSceneIdLogic {
|
||||||
|
return &GetEncryptedSceneIdLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetEncryptedSceneIdLogic) GetEncryptedSceneId() (*types.GetEncryptedSceneIdResp, error) {
|
||||||
|
cfg := l.svcCtx.Config.Captcha
|
||||||
|
encrypted, err := captcha.GenerateEncryptedSceneID(cfg.SceneID, cfg.EKey, 3600)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("generate encrypted scene id error: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.GetEncryptedSceneIdResp{
|
||||||
|
EncryptedSceneId: encrypted,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -3,8 +3,12 @@ package pay
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/service"
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -26,26 +30,98 @@ func NewPaymentCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Paym
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *types.PaymentCheckResp, err error) {
|
func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *types.PaymentCheckResp, err error) {
|
||||||
// 根据订单号前缀判断订单类型
|
|
||||||
if strings.HasPrefix(req.OrderNo, "U_") {
|
if strings.HasPrefix(req.OrderNo, "U_") {
|
||||||
// 升级订单
|
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||||
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
if findErr != nil {
|
||||||
if err != nil {
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级订单失败: %v", findErr)
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级订单失败: %v", err)
|
|
||||||
}
|
}
|
||||||
return &types.PaymentCheckResp{
|
return &types.PaymentCheckResp{Type: "agent_upgrade", Status: order.Status}, nil
|
||||||
Type: "agent_upgrade",
|
|
||||||
Status: order.Status,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询订单(包括代理订单)
|
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||||
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
if findErr != nil {
|
||||||
if err != nil {
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", findErr)
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", err)
|
|
||||||
}
|
}
|
||||||
return &types.PaymentCheckResp{
|
|
||||||
Type: "query",
|
resp = &types.PaymentCheckResp{Type: "query", Status: order.Status}
|
||||||
Status: order.Status,
|
|
||||||
}, nil
|
// xpay 轮询:pending 时主动查微信单并到账(不通知微信发货)
|
||||||
|
if order.Status == model.OrderStatusPending && model.IsXpayOrder(order) &&
|
||||||
|
l.svcCtx.XpayService != nil && l.svcCtx.XpayService.Enabled() {
|
||||||
|
wxInfo, syncErr := l.syncXpayOrderStatus(order)
|
||||||
|
resp.WxOrderStatus = wxInfo.Status
|
||||||
|
resp.WxOrderDetail = wxInfo.RawOrder
|
||||||
|
if syncErr != nil {
|
||||||
|
resp.WxSyncError = syncErr.Error()
|
||||||
|
l.Errorf("[xpay] 轮询查单失败 order_no=%s local_status=%s wx_status=%d wx_detail=%s err=%v",
|
||||||
|
req.OrderNo, order.Status, wxInfo.Status, wxInfo.RawOrder, syncErr)
|
||||||
|
} else if wxInfo.Status > 0 && !service.IsXpayPaidStatus(wxInfo.Status) {
|
||||||
|
l.Infof("[xpay] 轮询查单未到账 order_no=%s wx_status=%d wx_detail=%s",
|
||||||
|
req.OrderNo, wxInfo.Status, wxInfo.RawOrder)
|
||||||
|
}
|
||||||
|
order, _ = l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||||
|
resp.Status = order.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type xpaySyncInfo struct {
|
||||||
|
Status int
|
||||||
|
RawOrder string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *PaymentCheckLogic) syncXpayOrderStatus(order *model.Order) (xpaySyncInfo, error) {
|
||||||
|
info := xpaySyncInfo{}
|
||||||
|
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
if order.UserId != userID {
|
||||||
|
return info, errors.New("无权查询此订单")
|
||||||
|
}
|
||||||
|
|
||||||
|
openid, err := l.svcCtx.XpayService.GetWxMiniOpenID(l.ctx, l.svcCtx.UserAuthModel, userID)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infof("[xpay] check sync start order_no=%s openid=%s env=%d", order.OrderNo, openid, l.svcCtx.XpayService.Env())
|
||||||
|
|
||||||
|
status, err := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, order.OrderNo, "")
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
info.Status = status.Status
|
||||||
|
info.RawOrder = status.RawOrder
|
||||||
|
|
||||||
|
if service.IsXpayPaidStatus(status.Status) {
|
||||||
|
_, fulfillErr := fulfillQueryOrderPaid(l.ctx, l.svcCtx, order, status.WxOrderID, status.PaidFee)
|
||||||
|
if fulfillErr != nil {
|
||||||
|
return info, fulfillErr
|
||||||
|
}
|
||||||
|
l.Infof("[xpay] check sync paid order_no=%s wx_order_id=%s paid_fee=%d",
|
||||||
|
order.OrderNo, status.WxOrderID, status.PaidFee)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.IsXpayRefundedStatus(status.Status) || service.IsXpayAlreadyRefunded(status) {
|
||||||
|
order.Status = model.OrderStatusRefunded
|
||||||
|
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||||
|
return info, updateErr
|
||||||
|
}
|
||||||
|
l.Infof("[xpay] check sync refunded order_no=%s wx_status=%d left_fee=%d", order.OrderNo, status.Status, status.LeftFee)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.IsXpayClosedStatus(status.Status) {
|
||||||
|
order.Status = model.OrderStatusClosed
|
||||||
|
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||||
|
return info, updateErr
|
||||||
|
}
|
||||||
|
l.Infof("[xpay] check sync closed order_no=%s wx_status=%d", order.OrderNo, status.Status)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
"qnc-server/app/main/api/internal/types"
|
"qnc-server/app/main/api/internal/types"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/lzUtils"
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -33,8 +33,12 @@ type PaymentTypeResp struct {
|
|||||||
outTradeNo string
|
outTradeNo string
|
||||||
description string
|
description string
|
||||||
orderID string // 订单ID,用于开发环境测试支付模式
|
orderID string // 订单ID,用于开发环境测试支付模式
|
||||||
|
productEn string // 产品英文名,xpay 道具 ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enableDevTestPayment 测试支付功能开关,设置为 true 以启用测试支付功能
|
||||||
|
const enableDevTestPayment = false
|
||||||
|
|
||||||
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
|
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
|
||||||
return &PaymentLogic{
|
return &PaymentLogic{
|
||||||
Logger: logx.WithContext(ctx),
|
Logger: logx.WithContext(ctx),
|
||||||
@@ -47,11 +51,12 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
var paymentTypeResp *PaymentTypeResp
|
var paymentTypeResp *PaymentTypeResp
|
||||||
var prepayData interface{}
|
var prepayData interface{}
|
||||||
var orderID string
|
var orderID string
|
||||||
|
useXpay := l.shouldUseXpay(req)
|
||||||
|
|
||||||
// 检查是否为开发环境的测试支付模式
|
// 检查是否为开发环境的测试支付模式
|
||||||
env := os.Getenv("ENV")
|
env := os.Getenv("ENV")
|
||||||
isDevTestPayment := env == "development" && (req.PayMethod == "test" || req.PayMethod == "test_empty")
|
isDevTestPayment := enableDevTestPayment && env == "development" && (req.PayMethod == "test" || req.PayMethod == "test_empty")
|
||||||
isEmptyReportMode := env == "development" && req.PayMethod == "test_empty"
|
isEmptyReportMode := enableDevTestPayment && env == "development" && req.PayMethod == "test_empty"
|
||||||
|
|
||||||
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
switch req.PayType {
|
switch req.PayType {
|
||||||
@@ -62,7 +67,7 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "query":
|
case "query":
|
||||||
paymentTypeResp, err = l.QueryOrderPayment(req, session)
|
paymentTypeResp, err = l.QueryOrderPayment(req, session, useXpay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -92,10 +97,12 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 正常支付流程
|
// 正常支付流程(微信小程序 xpay 在事务外构造支付参数)
|
||||||
var createOrderErr error
|
var createOrderErr error
|
||||||
if req.PayMethod == "wechat" {
|
if useXpay {
|
||||||
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
// 订单已在 QueryOrderPayment 中创建,此处跳过 JSAPI 预下单
|
||||||
|
} else if req.PayMethod == "wechat" {
|
||||||
|
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo, req.Code)
|
||||||
} else if req.PayMethod == "alipay" {
|
} else if req.PayMethod == "alipay" {
|
||||||
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||||
} else if req.PayMethod == "appleiap" {
|
} else if req.PayMethod == "appleiap" {
|
||||||
@@ -110,6 +117,26 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 微信小程序虚拟支付:构造 signData / paySig / signature
|
||||||
|
if useXpay && paymentTypeResp != nil {
|
||||||
|
userID, uidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
if uidErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败")
|
||||||
|
}
|
||||||
|
xpayParams, buildErr := l.svcCtx.XpayService.BuildPayParams(l.ctx, userID, paymentTypeResp.outTradeNo, paymentTypeResp.productEn, paymentTypeResp.amount)
|
||||||
|
if buildErr != nil {
|
||||||
|
logx.WithContext(l.ctx).Errorf("[xpay] BuildPayParams FAIL user=%s order_no=%s amount=%.2f product_en=%s env=%d err=%v",
|
||||||
|
userID, paymentTypeResp.outTradeNo, paymentTypeResp.amount, paymentTypeResp.productEn, l.svcCtx.XpayService.Env(), buildErr)
|
||||||
|
if strings.Contains(buildErr.Error(), "过期") || strings.Contains(buildErr.Error(), "会话") {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, buildErr.Error()), "")
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "构造虚拟支付参数失败: %v", buildErr)
|
||||||
|
}
|
||||||
|
logx.WithContext(l.ctx).Infof("[xpay] BuildPayParams OK user=%s order_no=%s env=%d signData=%s",
|
||||||
|
userID, paymentTypeResp.outTradeNo, l.svcCtx.XpayService.Env(), xpayParams.SignData)
|
||||||
|
prepayData = xpayParams
|
||||||
|
}
|
||||||
|
|
||||||
// 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程
|
// 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程
|
||||||
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != "" {
|
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != "" {
|
||||||
// 使用 goroutine 异步处理,确保事务已完全提交
|
// 使用 goroutine 异步处理,确保事务已完全提交
|
||||||
@@ -202,7 +229,7 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
|
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session, useXpay bool) (resp *PaymentTypeResp, err error) {
|
||||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
if getUidErr != nil {
|
if getUidErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
|
||||||
@@ -248,13 +275,44 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
if user.Inside == 1 {
|
if user.Inside == 1 {
|
||||||
amount = 0.01
|
amount = 0.01
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同一 queryId 重复发起支付:复用已有订单,避免 order_no 唯一键冲突导致二次支付失败
|
||||||
|
if existingOrder, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, outTradeNo); findOrderErr == nil {
|
||||||
|
switch existingOrder.Status {
|
||||||
|
case model.OrderStatusPaid:
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("订单已支付,请前往报告查看"), "订单号: %s", outTradeNo)
|
||||||
|
case model.OrderStatusPending:
|
||||||
|
if existingOrder.Status != model.OrderStatusPending {
|
||||||
|
existingOrder.Status = model.OrderStatusPending
|
||||||
|
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, session, existingOrder); updateErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "重置订单状态失败: %+v", updateErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &PaymentTypeResp{
|
||||||
|
amount: existingOrder.Amount,
|
||||||
|
outTradeNo: outTradeNo,
|
||||||
|
description: product.ProductName,
|
||||||
|
orderID: existingOrder.Id,
|
||||||
|
productEn: product.ProductEn,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态异常,请重新发起查询"), "status=%s", existingOrder.Status)
|
||||||
|
}
|
||||||
|
} else if !errors.Is(findOrderErr, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %+v", findOrderErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentPlatform := req.PayMethod
|
||||||
|
if useXpay {
|
||||||
|
paymentPlatform = model.PaymentPlatformWechatXpay
|
||||||
|
}
|
||||||
order := model.Order{
|
order := model.Order{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
OrderNo: outTradeNo,
|
OrderNo: outTradeNo,
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
ProductId: product.Id,
|
ProductId: product.Id,
|
||||||
PaymentPlatform: req.PayMethod,
|
PaymentPlatform: paymentPlatform,
|
||||||
PaymentScene: "app",
|
PaymentScene: resolvePaymentScene(l.ctx),
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
}
|
}
|
||||||
@@ -289,7 +347,7 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
|
|
||||||
// 使用产品配置的底价计算实际底价
|
// 使用产品配置的底价计算实际底价
|
||||||
basePrice := productConfig.BasePrice
|
basePrice := productConfig.BasePrice
|
||||||
actualBasePrice := basePrice + float64(levelBonus)
|
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||||
|
|
||||||
// 计算提价成本(使用产品配置)
|
// 计算提价成本(使用产品配置)
|
||||||
priceThreshold := 0.0
|
priceThreshold := 0.0
|
||||||
@@ -303,11 +361,11 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
|
|
||||||
priceCost := 0.0
|
priceCost := 0.0
|
||||||
if agentLinkModel.SetPrice > priceThreshold {
|
if agentLinkModel.SetPrice > priceThreshold {
|
||||||
priceCost = (agentLinkModel.SetPrice - priceThreshold) * priceFeeRate
|
priceCost = lzUtils.RoundMoney((agentLinkModel.SetPrice - priceThreshold) * priceFeeRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算代理收益
|
// 计算代理收益
|
||||||
agentProfit := agentLinkModel.SetPrice - actualBasePrice - priceCost
|
agentProfit := lzUtils.RoundMoney(agentLinkModel.SetPrice - actualBasePrice - priceCost)
|
||||||
|
|
||||||
// 创建代理订单记录
|
// 创建代理订单记录
|
||||||
agentOrder := model.AgentOrder{
|
agentOrder := model.AgentOrder{
|
||||||
@@ -327,7 +385,35 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil
|
return &PaymentTypeResp{
|
||||||
|
amount: amount,
|
||||||
|
outTradeNo: outTradeNo,
|
||||||
|
description: product.ProductName,
|
||||||
|
orderID: orderID,
|
||||||
|
productEn: product.ProductEn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *PaymentLogic) shouldUseXpay(req *types.PaymentReq) bool {
|
||||||
|
if req.PayMethod != "wechat" || req.PayType != "query" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l.svcCtx.XpayService == nil || !l.svcCtx.XpayService.Enabled() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
platform, err := ctxdata.GetPlatformFromCtx(l.ctx)
|
||||||
|
if err != nil || platform != model.PlatformWxMini {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePaymentScene(ctx context.Context) string {
|
||||||
|
platform, err := ctxdata.GetPlatformFromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return model.PaymentSceneApp
|
||||||
|
}
|
||||||
|
return model.PaymentSceneFromPlatform(platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentVipOrderPayment 代理会员充值订单(已废弃,新系统使用升级功能替代)
|
// AgentVipOrderPayment 代理会员充值订单(已废弃,新系统使用升级功能替代)
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st
|
|||||||
return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo)
|
return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if status == refunddomestic.STATUS_SUCCESS {
|
||||||
|
if err := l.svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(ctx, session, order.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user