Compare commits
22 Commits
a8b4887571
...
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 |
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)
|
||||||
@@ -94,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"` // 主键
|
||||||
@@ -105,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"` // 累计收益
|
||||||
@@ -119,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
|
||||||
@@ -133,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"` // 设定价格
|
||||||
@@ -155,14 +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-支付失败
|
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"` // 订单金额
|
||||||
@@ -178,18 +213,28 @@ type (
|
|||||||
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"` // 状态
|
||||||
@@ -201,18 +246,23 @@ 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"` // 来源代理编号(可选)
|
||||||
Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款)
|
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=已取消(已退款)
|
Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款)
|
||||||
@@ -227,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"` // 升级类型
|
||||||
@@ -247,15 +299,20 @@ 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
|
||||||
|
AgentCode int64 `json:"agent_code"` // 代理编号
|
||||||
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
WithdrawNo string `json:"withdraw_no"` // 提现单号
|
||||||
Amount float64 `json:"amount"` // 金额
|
Amount float64 `json:"amount"` // 金额
|
||||||
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
TaxAmount float64 `json:"tax_amount"` // 税费金额
|
||||||
@@ -292,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"` // 手机号
|
||||||
@@ -410,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"` // 备注
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"` // 支付时间开始
|
||||||
@@ -81,6 +87,7 @@ type (
|
|||||||
RefundTime string `json:"refund_time"` // 退款时间
|
RefundTime string `json:"refund_time"` // 退款时间
|
||||||
UpdateTime string `json:"update_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-待处理
|
||||||
}
|
}
|
||||||
// 详情请求
|
// 详情请求
|
||||||
@@ -166,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-否
|
||||||
|
|||||||
@@ -295,32 +295,6 @@ service main {
|
|||||||
// 检查订单是否属于当前代理推广
|
// 检查订单是否属于当前代理推广
|
||||||
@handler CheckOrderAgent
|
@handler CheckOrderAgent
|
||||||
get /order/agent (CheckOrderAgentReq) returns (CheckOrderAgentResp)
|
get /order/agent (CheckOrderAgentReq) returns (CheckOrderAgentResp)
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// 用户模块白名单相关接口
|
|
||||||
// ============================================
|
|
||||||
@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 (
|
||||||
@@ -338,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"` // 推广链接标识
|
||||||
|
|||||||
@@ -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 (
|
||||||
@@ -58,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"`
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -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"
|
||||||
@@ -29,3 +31,6 @@ 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"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,20 @@ WechatH5:
|
|||||||
WechatMini:
|
WechatMini:
|
||||||
AppID: "wx5bacc94add2da981" # 小程序的AppID
|
AppID: "wx5bacc94add2da981" # 小程序的AppID
|
||||||
AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
|
AppSecret: "48a2c1e8ff1b7d4c0ff82fbefa64d2d0" # 小程序的AppSecret
|
||||||
|
WechatXpay:
|
||||||
|
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:
|
||||||
@@ -89,6 +103,10 @@ Tianyuanapi:
|
|||||||
Key: "04c6b4c559be6d5ba5351c04c8713a64"
|
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:
|
||||||
|
|||||||
@@ -64,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:
|
||||||
@@ -78,6 +85,10 @@ Tianyuanapi:
|
|||||||
Key: "04c6b4c559be6d5ba5351c04c8713a64"
|
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:
|
||||||
|
|||||||
@@ -21,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 鉴权配置
|
||||||
@@ -97,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
|
||||||
}
|
}
|
||||||
@@ -126,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,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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ import (
|
|||||||
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"
|
||||||
|
|
||||||
@@ -83,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",
|
||||||
@@ -103,6 +110,11 @@ 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,
|
Method: http.MethodPost,
|
||||||
Path: "/upgrade/agent",
|
Path: "/upgrade/agent",
|
||||||
@@ -356,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,
|
||||||
@@ -479,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"),
|
||||||
@@ -830,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,
|
||||||
@@ -914,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"),
|
||||||
)
|
)
|
||||||
@@ -937,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),
|
||||||
@@ -1081,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,23 +31,60 @@ 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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -60,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)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于代理订单,查询代理处理状态
|
// 对于代理订单,查询代理处理状态
|
||||||
@@ -277,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 {
|
||||||
|
|||||||
@@ -88,26 +88,29 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 验证提现金额
|
// 5. 获取钱包信息
|
||||||
if req.Amount <= 0 {
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 获取钱包信息
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 验证余额(包括检查是否为负数)
|
// 6. 提现金额与可用余额:仅可提「可用余额」内金额;可用余额须大于 0
|
||||||
if wallet.Balance < 0 {
|
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"), "")
|
||||||
}
|
}
|
||||||
if wallet.Balance < req.Amount {
|
bal := lzUtils.RoundMoney(wallet.Balance)
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("余额不足,当前余额:%.2f", 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)), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 支付宝月度提现额度校验(仅针对支付宝提现)
|
// 7. 支付宝月度提现额度校验(仅针对支付宝提现)
|
||||||
if req.WithdrawalType == 1 {
|
if req.WithdrawalType == 1 {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||||
@@ -138,7 +141,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Amount > remainQuota {
|
if withdrawAmount > remainQuota {
|
||||||
return nil, errors.Wrapf(
|
return nil, errors.Wrapf(
|
||||||
xerr.NewErrMsg(fmt.Sprintf("本月支付宝最高可提现 %.2f 元,请调整提现金额或使用银行卡提现", remainQuota)),
|
xerr.NewErrMsg(fmt.Sprintf("本月支付宝最高可提现 %.2f 元,请调整提现金额或使用银行卡提现", remainQuota)),
|
||||||
"",
|
"",
|
||||||
@@ -148,7 +151,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
|
|
||||||
// 9. 计算税费
|
// 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, "计算税费失败")
|
||||||
}
|
}
|
||||||
@@ -164,9 +167,26 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
// 11. 使用事务处理提现申请
|
// 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 {
|
||||||
|
// 提交前再次校验可用余额(防止并发入账/冲正与申请提现竞态)
|
||||||
|
fresh, wErr := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, agent.Id)
|
||||||
|
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 冻结余额
|
// 11.1 冻结余额
|
||||||
wallet.FrozenBalance += req.Amount
|
wallet.FrozenBalance += withdrawAmount
|
||||||
wallet.Balance -= req.Amount
|
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, "冻结余额失败")
|
||||||
}
|
}
|
||||||
@@ -179,7 +199,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
WithdrawalType: req.WithdrawalType,
|
WithdrawalType: req.WithdrawalType,
|
||||||
PayeeAccount: req.PayeeAccount,
|
PayeeAccount: req.PayeeAccount,
|
||||||
PayeeName: req.PayeeName,
|
PayeeName: req.PayeeName,
|
||||||
Amount: req.Amount,
|
Amount: withdrawAmount,
|
||||||
ActualAmount: taxInfo.ActualAmount,
|
ActualAmount: taxInfo.ActualAmount,
|
||||||
TaxAmount: taxInfo.TaxAmount,
|
TaxAmount: taxInfo.TaxAmount,
|
||||||
Status: 1, // 待审核
|
Status: 1, // 待审核
|
||||||
@@ -206,7 +226,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r
|
|||||||
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,
|
||||||
@@ -293,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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ 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"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"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
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/captcha"
|
"qnc-server/pkg/captcha"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
@@ -38,22 +39,36 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
|
|||||||
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq, clientIP string, userAgent string) 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 滑块验证码校验(可选,支持微信环境跳过验证)
|
// 审核体验:预留号段申请代理时不发短信、不校验滑块,仅写入与注册校验一致的固定验证码
|
||||||
cfg := l.svcCtx.Config.Captcha
|
if req.ActionType == "agentApply" && reviewphone.IsAppReviewDemoMobile(req.Mobile) {
|
||||||
captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{
|
codeKey := fmt.Sprintf("%s:%s", req.ActionType, encryptedMobile)
|
||||||
AccessKeyID: cfg.AccessKeyID,
|
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
||||||
AccessKeySecret: cfg.AccessKeySecret,
|
if err := l.svcCtx.Redis.Setex(codeKey, reviewphone.DemoVerifyCode, l.svcCtx.Config.VerifyCode.ValidTime); err != nil {
|
||||||
EndpointURL: cfg.EndpointURL,
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 审核号写入验证码失败: %v", err)
|
||||||
SceneID: cfg.SceneID,
|
}
|
||||||
}, req.CaptchaVerifyParam, userAgent)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if captchaResult.VerifyErr != nil {
|
// 1. 滑块验证码校验(可选,支持微信环境跳过验证)
|
||||||
return captchaResult.VerifyErr
|
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. 防刷策略
|
// 2. 防刷策略
|
||||||
if captchaResult.Skipped {
|
if captchaResult.Skipped {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type PaymentTypeResp struct {
|
|||||||
outTradeNo string
|
outTradeNo string
|
||||||
description string
|
description string
|
||||||
orderID string // 订单ID,用于开发环境测试支付模式
|
orderID string // 订单ID,用于开发环境测试支付模式
|
||||||
|
productEn string // 产品英文名,xpay 道具 ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableDevTestPayment 测试支付功能开关,设置为 true 以启用测试支付功能
|
// enableDevTestPayment 测试支付功能开关,设置为 true 以启用测试支付功能
|
||||||
@@ -50,6 +51,7 @@ 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")
|
||||||
@@ -65,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
|
||||||
}
|
}
|
||||||
@@ -95,9 +97,11 @@ 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 {
|
||||||
|
// 订单已在 QueryOrderPayment 中创建,此处跳过 JSAPI 预下单
|
||||||
|
} else if req.PayMethod == "wechat" {
|
||||||
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo, req.Code)
|
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)
|
||||||
@@ -113,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 异步处理,确保事务已完全提交
|
||||||
@@ -205,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)
|
||||||
@@ -251,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",
|
||||||
}
|
}
|
||||||
@@ -292,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
|
||||||
@@ -306,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{
|
||||||
@@ -330,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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
87
app/main/api/internal/logic/pay/xpay_deliver.go
Normal file
87
app/main/api/internal/logic/pay/xpay_deliver.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/service"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XpayDeliverResult 同步 xpay 支付状态(查微信单 → 本地到账 → 异步查报告)
|
||||||
|
type XpayDeliverResult struct {
|
||||||
|
Credited bool `json:"credited"`
|
||||||
|
Notified bool `json:"notified"`
|
||||||
|
WechatDetail string `json:"wechat_detail"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeliverXpayQueryOrder 按商户订单号同步微信虚拟支付到账(不通知微信发货,发货在报告成功后)
|
||||||
|
func DeliverXpayQueryOrder(ctx context.Context, svcCtx *svc.ServiceContext, orderNo string) (*XpayDeliverResult, error) {
|
||||||
|
order, err := svcCtx.OrderModel.FindOneByOrderNo(ctx, orderNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &XpayDeliverResult{WechatDetail: "ok"}
|
||||||
|
|
||||||
|
if order.Status == model.OrderStatusPaid {
|
||||||
|
resp.Message = "订单已支付,无需同步"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.IsXpayOrder(order) {
|
||||||
|
resp.Errors = append(resp.Errors, "非小程序虚拟支付订单")
|
||||||
|
resp.Message = "非小程序虚拟支付订单"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||||
|
resp.Errors = append(resp.Errors, "虚拟支付未启用")
|
||||||
|
resp.Message = "虚拟支付未启用"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||||
|
if err != nil {
|
||||||
|
resp.Errors = append(resp.Errors, err.Error())
|
||||||
|
resp.Message = "获取用户 openid 失败"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status, qErr := svcCtx.XpayService.QueryOrder(ctx, openid, order.OrderNo, "")
|
||||||
|
if qErr != nil {
|
||||||
|
resp.Errors = append(resp.Errors, qErr.Error())
|
||||||
|
resp.WechatDetail = qErr.Error()
|
||||||
|
resp.Message = "查询微信订单失败"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !service.IsXpayPaidStatus(status.Status) {
|
||||||
|
resp.Errors = append(resp.Errors, "微信侧订单未支付")
|
||||||
|
resp.Message = "微信侧订单未支付"
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
platformOrderID := status.WxOrderID
|
||||||
|
credited, fulfillErr := fulfillQueryOrderPaid(ctx, svcCtx, order, platformOrderID, status.PaidFee)
|
||||||
|
resp.Credited = credited
|
||||||
|
if fulfillErr != nil {
|
||||||
|
resp.Errors = append(resp.Errors, fulfillErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Message = buildXpayDeliverMessage(resp)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildXpayDeliverMessage(r *XpayDeliverResult) string {
|
||||||
|
if len(r.Errors) > 0 {
|
||||||
|
return strings.Join(r.Errors, ";")
|
||||||
|
}
|
||||||
|
if r.Credited {
|
||||||
|
return "同步成功,订单已到账并开始查询报告"
|
||||||
|
}
|
||||||
|
return "未执行到账"
|
||||||
|
}
|
||||||
48
app/main/api/internal/logic/pay/xpay_notify.go
Normal file
48
app/main/api/internal/logic/pay/xpay_notify.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotifyXpayGoodsAfterReport 报告生成成功后通知微信虚拟支付已发货(合规/结算)
|
||||||
|
func NotifyXpayGoodsAfterReport(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) {
|
||||||
|
if order == nil || !model.IsXpayOrder(order) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
already, err := svcCtx.XpayService.AlreadyNotified(ctx, order.OrderNo)
|
||||||
|
if err != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] 检查发货通知状态失败 order_no=%s err=%v", order.OrderNo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if already {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||||
|
if err != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] 报告成功后通知发货:获取 openid 失败 order_no=%s err=%v", order.OrderNo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey, _ := svcCtx.XpayService.GetSessionKey(ctx, order.UserId)
|
||||||
|
wxOrderID := ""
|
||||||
|
if order.PlatformOrderId.Valid {
|
||||||
|
wxOrderID = order.PlatformOrderId.String
|
||||||
|
}
|
||||||
|
|
||||||
|
if notifyErr := svcCtx.XpayService.NotifyProvideGoods(ctx, openid, order.OrderNo, wxOrderID, sessionKey); notifyErr != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] 报告成功后 notify_provide_goods 失败 order_no=%s err=%v", order.OrderNo, notifyErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = svcCtx.XpayService.MarkNotified(ctx, order.OrderNo)
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] 报告成功后已通知微信发货 order_no=%s", order.OrderNo)
|
||||||
|
}
|
||||||
84
app/main/api/internal/logic/pay/xpay_refund.go
Normal file
84
app/main/api/internal/logic/pay/xpay_refund.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RefundXpayQueryOrder 对 xpay 查询订单发起全额退款并更新本地订单状态
|
||||||
|
func RefundXpayQueryOrder(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) error {
|
||||||
|
if svcCtx.XpayService == nil || !svcCtx.XpayService.Enabled() {
|
||||||
|
return fmt.Errorf("虚拟支付未启用")
|
||||||
|
}
|
||||||
|
openid, err := svcCtx.XpayService.GetWxMiniOpenID(ctx, svcCtx.UserAuthModel, order.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取 openid 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refundFeeFen := lzUtils.ToWechatAmount(order.Amount)
|
||||||
|
refundOrderID := buildXpayRefundOrderID(order.OrderNo)
|
||||||
|
if refundErr := svcCtx.XpayService.RefundOrder(ctx, openid, order.OrderNo, refundOrderID, refundFeeFen); refundErr != nil {
|
||||||
|
return refundErr
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] refund local sync order_no=%s", order.OrderNo)
|
||||||
|
|
||||||
|
return svcCtx.OrderModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||||
|
order.Status = model.OrderStatusRefunded
|
||||||
|
if updateErr := svcCtx.OrderModel.UpdateWithVersion(transCtx, session, order); updateErr != nil {
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
|
return svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(transCtx, session, order.Id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildXpayRefundOrderID(orderNo string) string {
|
||||||
|
const prefix = "RF_"
|
||||||
|
id := prefix + orderNo
|
||||||
|
if len(id) > 32 {
|
||||||
|
id = id[:32]
|
||||||
|
}
|
||||||
|
if len(id) < 8 {
|
||||||
|
id = id + "00000000"
|
||||||
|
id = id[:8]
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryRefundOnQueryFailure 查询失败时按支付渠道退款
|
||||||
|
func TryRefundOnQueryFailure(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order) {
|
||||||
|
var refundErr error
|
||||||
|
switch {
|
||||||
|
case model.IsXpayOrder(order):
|
||||||
|
refundErr = RefundXpayQueryOrder(ctx, svcCtx, order)
|
||||||
|
case order.PaymentPlatform == model.PaymentPlatformWechat:
|
||||||
|
refundErr = svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
|
||||||
|
default:
|
||||||
|
refund, err := svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
|
||||||
|
if err != nil {
|
||||||
|
refundErr = err
|
||||||
|
} else if !refund.IsSuccess() {
|
||||||
|
refundErr = fmt.Errorf("支付宝退款失败: %s", refund.Msg)
|
||||||
|
} else {
|
||||||
|
transErr := svcCtx.OrderModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||||
|
order.Status = model.OrderStatusRefunded
|
||||||
|
if err := svcCtx.OrderModel.UpdateWithVersion(transCtx, session, order); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return svcCtx.AgentService.ReverseAgentSettlementOnOrderRefund(transCtx, session, order.Id)
|
||||||
|
})
|
||||||
|
if transErr != nil {
|
||||||
|
refundErr = transErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if refundErr != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[refund] 查询失败自动退款未成功 order_no=%s platform=%s err=%v", order.OrderNo, order.PaymentPlatform, refundErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/main/api/internal/logic/pay/xpayclienteventlogic.go
Normal file
65
app/main/api/internal/logic/pay/xpayclienteventlogic.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/ctxdata"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XpayClientEventLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXpayClientEventLogic(ctx context.Context, svcCtx *svc.ServiceContext) *XpayClientEventLogic {
|
||||||
|
return &XpayClientEventLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XpayClientEvent 记录小程序虚拟支付各步骤及微信/Apple 客户端提示(非 HTTP 异常)
|
||||||
|
func (l *XpayClientEventLogic) XpayClientEvent(req *types.XpayClientEventReq) (*types.XpayClientEventResp, error) {
|
||||||
|
userID, _ := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
|
||||||
|
l.Infof("[xpay:client-event] user=%s order_no=%s stage=%s event=%s message=%q err_msg=%q err_code=%d errno=%d sign_data=%s device=%s extra=%s",
|
||||||
|
userID, req.OrderNo, req.Stage, req.EventType, req.Message, req.ErrMsg, req.ErrCode, req.Errno,
|
||||||
|
req.SignData, req.Device, req.Extra)
|
||||||
|
|
||||||
|
resp := &types.XpayClientEventResp{}
|
||||||
|
|
||||||
|
if req.OrderNo == "" || l.svcCtx.XpayService == nil || !l.svcCtx.XpayService.Enabled() {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端 fail/tip 时主动 query_order,把微信服务端侧状态一并记入日志
|
||||||
|
if req.EventType == "fail" || req.EventType == "tip" {
|
||||||
|
openid, err := l.svcCtx.XpayService.GetWxMiniOpenID(l.ctx, l.svcCtx.UserAuthModel, userID)
|
||||||
|
if err != nil {
|
||||||
|
resp.WxSyncError = "获取 openid 失败: " + err.Error()
|
||||||
|
l.Errorf("[xpay:client-event] query_order skip order_no=%s reason=%v", req.OrderNo, err)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status, qErr := l.svcCtx.XpayService.QueryOrder(l.ctx, openid, req.OrderNo, "")
|
||||||
|
if qErr != nil {
|
||||||
|
resp.WxSyncError = qErr.Error()
|
||||||
|
l.Errorf("[xpay:client-event] query_order FAIL order_no=%s client_msg=%q wx_err=%v",
|
||||||
|
req.OrderNo, req.Message, qErr)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WxOrderStatus = status.Status
|
||||||
|
resp.WxOrderDetail = status.RawOrder
|
||||||
|
l.Infof("[xpay:client-event] query_order SNAPSHOT order_no=%s client_msg=%q wx_status=%d wx_order_id=%s paid_fee=%d err_msg=%s raw=%s",
|
||||||
|
req.OrderNo, req.Message, status.Status, status.WxOrderID, status.PaidFee, status.ErrMsg, status.RawOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
54
app/main/api/internal/logic/pay/xpayorderfulfilllogic.go
Normal file
54
app/main/api/internal/logic/pay/xpayorderfulfilllogic.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fulfillQueryOrderPaid 幂等将查询订单标记为已支付并触发报告生成
|
||||||
|
// wechatPaidFen:微信侧实付金额(分),>0 时须与订单 amount 一致
|
||||||
|
func fulfillQueryOrderPaid(ctx context.Context, svcCtx *svc.ServiceContext, order *model.Order, platformOrderID string, wechatPaidFen int64) (credited bool, err error) {
|
||||||
|
if order.Status == "paid" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
orderFen := lzUtils.ToWechatAmount(order.Amount)
|
||||||
|
if wechatPaidFen > 0 && wechatPaidFen != orderFen {
|
||||||
|
if model.IsXpayOrder(order) {
|
||||||
|
order.Amount = lzUtils.RoundMoney(float64(wechatPaidFen) / 100)
|
||||||
|
orderFen = wechatPaidFen
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] 同步微信实付金额 order_no=%s amount=%.2f fen=%d", order.OrderNo, order.Amount, wechatPaidFen)
|
||||||
|
} else {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] 金额不一致 order_no=%s order_fen=%d wechat_fen=%d", order.OrderNo, orderFen, wechatPaidFen)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Status = "paid"
|
||||||
|
order.PayTime = sql.NullTime{Time: time.Now(), Valid: true}
|
||||||
|
if platformOrderID != "" {
|
||||||
|
order.PlatformOrderId = sql.NullString{String: platformOrderID, Valid: true}
|
||||||
|
}
|
||||||
|
if order.PaymentScene == "" || order.PaymentScene == model.PaymentSceneApp {
|
||||||
|
order.PaymentScene = model.PaymentSceneMiniProgram
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateErr := svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order); updateErr != nil {
|
||||||
|
return false, updateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if asyncErr := svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] SendQueryTask 失败 order_id=%s err=%v", order.Id, asyncErr)
|
||||||
|
return true, asyncErr
|
||||||
|
}
|
||||||
|
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] 订单到账 order_no=%s order_id=%s", order.OrderNo, order.Id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
101
app/main/api/internal/logic/pay/xpaypushlogic.go
Normal file
101
app/main/api/internal/logic/pay/xpaypushlogic.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package pay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/service"
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XpayPushLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXpayPushLogic(svcCtx *svc.ServiceContext) *XpayPushLogic {
|
||||||
|
return &XpayPushLogic{svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleGET mp 后台配置消息推送时的验签
|
||||||
|
func (l *XpayPushLogic) HandleGET(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
signature := q.Get("signature")
|
||||||
|
timestamp := q.Get("timestamp")
|
||||||
|
nonce := q.Get("nonce")
|
||||||
|
echostr := q.Get("echostr")
|
||||||
|
|
||||||
|
if l.svcCtx.XpayService.VerifyPushSignature(signature, timestamp, nonce) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(echostr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePOST 接收 xpay_goods_deliver_notify
|
||||||
|
func (l *XpayPushLogic) HandlePOST(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
l.writePushResp(w, 1, "read body failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var notify service.XpayDeliverNotify
|
||||||
|
if err := json.Unmarshal(body, ¬ify); err != nil {
|
||||||
|
l.writePushResp(w, 1, "parse failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if notify.Event != "" && notify.Event != "xpay_goods_deliver_notify" {
|
||||||
|
l.writePushResp(w, 0, "ignored")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderNo := notify.OutTradeNo
|
||||||
|
if orderNo == "" {
|
||||||
|
l.writePushResp(w, 1, "missing OutTradeNo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
logx.WithContext(ctx).Infof("[xpay:push] recv event=%s order_no=%s openid=%s env=%d body=%s",
|
||||||
|
notify.Event, orderNo, notify.OpenId, notify.Env, string(body))
|
||||||
|
|
||||||
|
already, _ := l.svcCtx.XpayService.AlreadyNotified(ctx, orderNo)
|
||||||
|
if already {
|
||||||
|
l.writePushResp(w, 0, "already notified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
order, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(ctx, orderNo)
|
||||||
|
if findErr != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay push] 订单不存在 order_no=%s err=%v", orderNo, findErr)
|
||||||
|
l.writePushResp(w, 1, "order not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wxOrderID := notify.WeChatPayInfo.TransactionId
|
||||||
|
credited, fulfillErr := fulfillQueryOrderPaid(ctx, l.svcCtx, order, wxOrderID, notify.GoodsInfo.ActualPrice)
|
||||||
|
if fulfillErr != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay push] 到账失败 order_no=%s err=%v", orderNo, fulfillErr)
|
||||||
|
l.writePushResp(w, 1, fulfillErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = l.svcCtx.XpayService.MarkNotified(ctx, orderNo)
|
||||||
|
logx.WithContext(ctx).Infof("[xpay push] event=xpay_goods_deliver_notify order_no=%s credited=%v", orderNo, credited)
|
||||||
|
l.writePushResp(w, 0, "success")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *XpayPushLogic) writePushResp(w http.ResponseWriter, errCode int, errMsg string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"ErrCode": errCode,
|
||||||
|
"ErrMsg": errMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
51
app/main/api/internal/logic/productcost/sync.go
Normal file
51
app/main/api/internal/logic/productcost/sync.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package productcost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyncProductCostFromModules 将产品 cost_price 更新为当前关联模块的 feature.cost_price 之和
|
||||||
|
func SyncProductCostFromModules(ctx context.Context, c *svc.ServiceContext, productId string) error {
|
||||||
|
sum, err := c.ProductFeatureModel.SumFeatureCostPriceByProductId(ctx, productId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"汇总产品模块成本失败, err: %v, productId: %s", err, productId)
|
||||||
|
}
|
||||||
|
record, err := c.ProductModel.FindOne(ctx, productId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"查找产品失败, err: %v, productId: %s", err, productId)
|
||||||
|
}
|
||||||
|
record.CostPrice = sum
|
||||||
|
if _, err = c.ProductModel.Update(ctx, nil, record); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"同步产品成本失败, err: %v, productId: %s", err, productId)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncAllProductsUsingFeature 在模块成本等变更后,刷新所有关联了该模块的产品的 cost_price
|
||||||
|
func SyncAllProductsUsingFeature(ctx context.Context, c *svc.ServiceContext, featureId string) error {
|
||||||
|
builder := c.ProductFeatureModel.SelectBuilder().Where("feature_id = ?", featureId)
|
||||||
|
list, err := c.ProductFeatureModel.FindAll(ctx, builder, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
|
||||||
|
"查询产品模块关联失败, err: %v, featureId: %s", err, featureId)
|
||||||
|
}
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, pf := range list {
|
||||||
|
if _, ok := seen[pf.ProductId]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[pf.ProductId] = struct{}{}
|
||||||
|
if err := SyncProductCostFromModules(ctx, c, pf.ProductId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
"qnc-server/app/main/api/internal/service"
|
"qnc-server/app/main/api/internal/service"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
"qnc-server/pkg/lzkit/validator"
|
"qnc-server/pkg/lzkit/validator"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
@@ -55,6 +55,21 @@ var productProcessors = map[string]func(*QueryServiceLogic, *types.QueryServiceR
|
|||||||
"backgroundcheck": (*QueryServiceLogic).ProcessBackgroundCheckLogic,
|
"backgroundcheck": (*QueryServiceLogic).ProcessBackgroundCheckLogic,
|
||||||
"personalData": (*QueryServiceLogic).ProcessPersonalDataLogic,
|
"personalData": (*QueryServiceLogic).ProcessPersonalDataLogic,
|
||||||
"consumerFinanceReport": (*QueryServiceLogic).ProcessConsumerFinanceReportLogic,
|
"consumerFinanceReport": (*QueryServiceLogic).ProcessConsumerFinanceReportLogic,
|
||||||
|
// 车辆类产品
|
||||||
|
"toc_VehiclesUnderNameCount": (*QueryServiceLogic).ProcessVehiclesUnderNameCountLogic,
|
||||||
|
"toc_VehicleStaticInfo": (*QueryServiceLogic).ProcessVehicleVinCodeLogic,
|
||||||
|
"toc_VehicleMileageMixed": (*QueryServiceLogic).ProcessVehicleMileageMixedLogic,
|
||||||
|
"toc_VehicleVinValuation": (*QueryServiceLogic).ProcessVehicleVinValuationLogic,
|
||||||
|
"toc_VehicleTransferSimple": (*QueryServiceLogic).ProcessVehicleTransferSimpleLogic,
|
||||||
|
"toc_VehicleTransferDetail": (*QueryServiceLogic).ProcessVehicleVinCodeLogic,
|
||||||
|
"toc_VehicleMaintenanceSimple": (*QueryServiceLogic).ProcessVehicleMaintenanceSimpleLogic,
|
||||||
|
"toc_VehicleMaintenanceDetail": (*QueryServiceLogic).ProcessVehicleMaintenanceDetailLogic,
|
||||||
|
"toc_VehicleClaimDetail": (*QueryServiceLogic).ProcessVehicleClaimDetailLogic,
|
||||||
|
"toc_VehicleClaimVerify": (*QueryServiceLogic).ProcessVehicleClaimVerifyLogic,
|
||||||
|
"toc_VehiclesUnderName": (*QueryServiceLogic).ProcessMarriageLogic,
|
||||||
|
"toc_VehiclesUnderNamePlate": (*QueryServiceLogic).ProcessMarriageLogic,
|
||||||
|
"toc_PersonVehicleVerification": (*QueryServiceLogic).ProcessPersonVehicleVerificationLogic,
|
||||||
|
"toc_PersonVehicleVerificationDetail": (*QueryServiceLogic).ProcessPersonVehicleVerificationLogic,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
|
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
|
||||||
@@ -709,6 +724,435 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str
|
|||||||
return outTradeNo, nil
|
return outTradeNo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 车辆类产品处理方法 ----
|
||||||
|
|
||||||
|
// ProcessVehiclesUnderNameCountLogic 名下车辆(数量):仅 user_type + id_card
|
||||||
|
func (l *QueryServiceLogic) ProcessVehiclesUnderNameCountLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehiclesUnderNameCountReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
userType := data.UserType
|
||||||
|
if userType == "" {
|
||||||
|
userType = "1"
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"user_type": userType,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehiclesUnderNameCount", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleVinCodeLogic 仅 vin_code(车辆静态信息、过户详版等)
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleVinCodeLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleVinCodeReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}
|
||||||
|
product := req.Product
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, product, userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleMileageMixedLogic 车辆里程记录(混合) QCXG1U4U
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleMileageMixedLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleMileageMixedReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"image_url": data.ImageURL,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleMileageMixed", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleVinValuationLogic 二手车VIN估值 QCXGY7F2
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleVinValuationLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleVinValuationReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"vehicle_location": data.VehicleLocation,
|
||||||
|
"first_registrationdate": data.FirstRegistrationDate,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleVinValuation", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleTransferSimpleLogic 车辆过户简版 QCXG1H7Y
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleTransferSimpleLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleTransferSimpleReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleTransferSimple", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleMaintenanceSimpleLogic 车辆维保简版 QCXG3Y6B
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleMaintenanceSimpleLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleMaintenanceSimpleReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleMaintenanceSimple", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleMaintenanceDetailLogic 车辆维保详细版 QCXG3Z3L
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleMaintenanceDetailLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleMaintenanceDetailReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleMaintenanceDetail", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleClaimDetailLogic 车辆出险详版 QCXGP00W
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleClaimDetailLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleClaimDetailReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"vlphoto_data": data.VlphotoData,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleClaimDetail", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVehicleClaimVerifyLogic 车辆出险记录核验 QCXG6B4E
|
||||||
|
func (l *QueryServiceLogic) ProcessVehicleClaimVerifyLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocVehicleClaimVerifyReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auth := data.Authorized
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"vin_code": data.VINCode,
|
||||||
|
"authorized": auth,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, "toc_VehicleClaimVerify", userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessPersonVehicleVerificationLogic 人车核验简版:姓名+号牌类型+车牌号
|
||||||
|
func (l *QueryServiceLogic) ProcessPersonVehicleVerificationLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
|
||||||
|
decryptData, DecryptDataErr := l.DecryptData(req.Data)
|
||||||
|
if DecryptDataErr != nil {
|
||||||
|
return nil, DecryptDataErr
|
||||||
|
}
|
||||||
|
var data types.TocPersonVehicleVerification
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"plate_no": data.CarLicense,
|
||||||
|
"carplate_type": data.CarType,
|
||||||
|
}
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, req.Product, userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrCreateUser 获取或创建用户
|
// GetOrCreateUser 获取或创建用户
|
||||||
// 1. 如果已登录,使用当前登录用户
|
// 1. 如果已登录,使用当前登录用户
|
||||||
// 2. 如果未登录,创建临时用户(UUID用户)
|
// 2. 如果未登录,创建临时用户(UUID用户)
|
||||||
|
|||||||
921
app/main/api/internal/logic/query/queryservicelogic.md
Normal file
921
app/main/api/internal/logic/query/queryservicelogic.md
Normal file
@@ -0,0 +1,921 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
"tyc-server/app/main/api/internal/service"
|
||||||
|
"tyc-server/app/main/model"
|
||||||
|
"tyc-server/common/ctxdata"
|
||||||
|
"tyc-server/common/globalkey"
|
||||||
|
"tyc-server/common/xerr"
|
||||||
|
"tyc-server/pkg/captcha"
|
||||||
|
"tyc-server/pkg/lzkit/crypto"
|
||||||
|
"tyc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/api/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryServiceLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryServiceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryServiceLogic {
|
||||||
|
return &QueryServiceLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryServiceLogic) QueryService(req *types.QueryServiceReq) (resp *types.QueryServiceResp, err error) {
|
||||||
|
if req.AgentIdentifier != "" {
|
||||||
|
l.ctx = context.WithValue(l.ctx, "agentIdentifier", req.AgentIdentifier)
|
||||||
|
} else if req.App {
|
||||||
|
l.ctx = context.WithValue(l.ctx, "app", req.App)
|
||||||
|
}
|
||||||
|
return l.PreprocessLogic(req, req.Product)
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryHandlerFunc 通用查询 handler:解密后的数据 + 产品名 -> 校验并返回待缓存的 params
|
||||||
|
// 新增产品时只需在 productHandlers 里加一行并选用已有 handler 类型即可,无需再写 ProcessXxx
|
||||||
|
type queryHandlerFunc func(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error)
|
||||||
|
|
||||||
|
var productHandlers = map[string]queryHandlerFunc{
|
||||||
|
"marriage": runMarriageReq,
|
||||||
|
"homeservice": runMarriageReq,
|
||||||
|
"riskassessment": runMarriageReq,
|
||||||
|
"companyinfo": runMarriageReq,
|
||||||
|
"rentalinfo": runMarriageReq,
|
||||||
|
"preloanbackgroundcheck": runMarriageReq,
|
||||||
|
"backgroundcheck": runBackgroundCheckReq,
|
||||||
|
"personalData": runMarriageReq,
|
||||||
|
"toc_PersonalBadRecord": runPersonalBadRecordReq,
|
||||||
|
"toc_PersonalLawsuit": runMarriageReq,
|
||||||
|
"toc_EnterpriseLawsuit": runEntLawsuitReq,
|
||||||
|
// 人企关系加强版:仅身份证号
|
||||||
|
"toc_PersonEnterprisePro": runPersonEnterpriseProReq,
|
||||||
|
// 新司法涉诉类产品
|
||||||
|
"toc_EnterpriseLawsuitQYGL66SL": runEnterpriseLawsuitSimpleReq,
|
||||||
|
"toc_LimitHighExecuted": runLimitHighExecutedReq,
|
||||||
|
"toc_DishonestExecutedPerson": runDishonestExecutedReq,
|
||||||
|
"toc_Marriage": runMarriageReq,
|
||||||
|
"toc_PersonalMarriageStatus": runMarriageReq,
|
||||||
|
"toc_MarriageStatusRegisterTime": runMarriageReq,
|
||||||
|
"toc_MarriageStatusSupplement": runMarriageReq,
|
||||||
|
"toc_MarriageStatusVerify": runMarriageReq,
|
||||||
|
"toc_DualMarriageStatusRegisterTime": runDualMarriageReq,
|
||||||
|
"toc_VehiclesUnderName": runMarriageReq,
|
||||||
|
"toc_VehiclesUnderNamePlate": runMarriageReq,
|
||||||
|
"toc_PersonVehicleVerification": runPersonVehicleVerificationReq,
|
||||||
|
"toc_PersonVehicleVerificationDetail": runPersonVehicleVerificationReq,
|
||||||
|
// 车辆类产品(按 md 传参)
|
||||||
|
"toc_VehiclesUnderNameCount": runVehiclesUnderNameCountReq,
|
||||||
|
"toc_VehicleStaticInfo": runVehicleVinCodeReq,
|
||||||
|
"toc_VehicleMileageMixed": runVehicleMileageMixedReq,
|
||||||
|
"toc_VehicleVinValuation": runVehicleVinValuationReq,
|
||||||
|
"toc_VehicleTransferSimple": runVehicleTransferSimpleReq,
|
||||||
|
"toc_VehicleTransferDetail": runVehicleVinCodeReq,
|
||||||
|
"toc_VehicleMaintenanceSimple": runVehicleMaintenanceSimpleReq,
|
||||||
|
"toc_VehicleMaintenanceDetail": runVehicleMaintenanceDetailReq,
|
||||||
|
"toc_VehicleClaimDetail": runVehicleClaimDetailReq,
|
||||||
|
"toc_VehicleClaimVerify": runVehicleClaimVerifyReq,
|
||||||
|
// 核验工具(verify feature.md)
|
||||||
|
"toc_PoliceTwoFactors": runVerifyAuthTwoReq,
|
||||||
|
"toc_PoliceThreeFactors": runVerifyAuthThreeReq,
|
||||||
|
"toc_ProfessionalCertificate": runVerifyCertReq,
|
||||||
|
"toc_PersonalConsumptionCapacityLevel": runVerifyConsumptionReq, // 个人消费能力(沿用现有 product_en)
|
||||||
|
"toc_OperatorTwoFactors": runVerifyYysTwoReq,
|
||||||
|
"toc_MobileThreeFactors": runVerifyYysThreeReq,
|
||||||
|
"toc_NumberRecycle": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileEmptyCheck": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobilePortability": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileOnlineStatus": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileOnlineDuration": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileAttribution": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileConsumptionRange": runVerifyYysConsumptionReq,
|
||||||
|
"toc_EnterpriseRelation": runVerifyEntRelationReq,
|
||||||
|
"toc_BankcardFourFactors": runVerifyBankFourReq,
|
||||||
|
"toc_BankcardBlacklist": runVerifyBankBlackReq,
|
||||||
|
}
|
||||||
|
|
||||||
|
// productHasSmsCode 表示该 product 解密后的请求结构体中是否包含必填短信验证码 Code。
|
||||||
|
// 有 Code 的产品在「获取验证码」时已经做了滑块,这里不再强制要求 CaptchaVerifyParam。
|
||||||
|
// 其他产品(无 Code)在查询时必须传并校验 CaptchaVerifyParam,防止跳过图形验证;
|
||||||
|
// 微信小程序(X-Platform: wxmini,见 ctxdata.GetPlatformFromCtx)不嵌入 H5 滑块,由 PreprocessLogic 跳过图形校验。
|
||||||
|
func productHasSmsCode(product string) bool {
|
||||||
|
switch product {
|
||||||
|
case "marriage",
|
||||||
|
"homeservice",
|
||||||
|
"riskassessment",
|
||||||
|
"companyinfo",
|
||||||
|
"rentalinfo",
|
||||||
|
"preloanbackgroundcheck",
|
||||||
|
"personalData",
|
||||||
|
"toc_PersonalLawsuit",
|
||||||
|
"toc_EnterpriseLawsuit",
|
||||||
|
"toc_Marriage",
|
||||||
|
"toc_PersonalMarriageStatus",
|
||||||
|
"toc_MarriageStatusRegisterTime",
|
||||||
|
"toc_MarriageStatusSupplement",
|
||||||
|
"toc_MarriageStatusVerify",
|
||||||
|
"toc_DualMarriageStatusRegisterTime",
|
||||||
|
"toc_VehiclesUnderName",
|
||||||
|
"toc_VehiclesUnderNamePlate":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
|
||||||
|
// 无短信验证码 Code 的 product:查询前必须传并校验滑块;微信小程序端跳过(依赖登录态与 X-Platform)
|
||||||
|
requireCaptcha := !productHasSmsCode(product)
|
||||||
|
if requireCaptcha {
|
||||||
|
if plat, platErr := ctxdata.GetPlatformFromCtx(l.ctx); platErr == nil && plat == model.PlatformWxMini {
|
||||||
|
requireCaptcha = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if requireCaptcha {
|
||||||
|
if req.CaptchaVerifyParam == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("请完成图形验证"), "product %s requires captcha", product)
|
||||||
|
}
|
||||||
|
cfg := l.svcCtx.Config.Captcha
|
||||||
|
if err := captcha.Verify(captcha.Config{
|
||||||
|
AccessKeyID: cfg.AccessKeyID,
|
||||||
|
AccessKeySecret: cfg.AccessKeySecret,
|
||||||
|
EndpointURL: cfg.EndpointURL,
|
||||||
|
SceneID: cfg.SceneID,
|
||||||
|
}, req.CaptchaVerifyParam); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptData, err := l.DecryptData(req.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handler, exists := productHandlers[product]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New("未找到相应的处理程序")
|
||||||
|
}
|
||||||
|
params, err := handler(l, decryptData, product)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l.commonQueryTail(params, product)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonQueryTail 通用收尾:写缓存、生成 token 并返回
|
||||||
|
func (l *QueryServiceLogic) commonQueryTail(params map[string]interface{}, product string) (*types.QueryServiceResp, error) {
|
||||||
|
userID, err := l.GetOrCreateUser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
|
||||||
|
}
|
||||||
|
cacheNo, cacheDataErr := l.CacheData(params, product, userID)
|
||||||
|
if cacheDataErr != nil {
|
||||||
|
return nil, cacheDataErr
|
||||||
|
}
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.QueryServiceResp{
|
||||||
|
Id: cacheNo,
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runMarriageReq 姓名+身份证+手机+验证码 类产品(婚恋、家政、司法涉诉、婚姻状况、名下车辆等)
|
||||||
|
func runMarriageReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.MarriageReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
if verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile); verifyErr != nil {
|
||||||
|
return nil, verifyErr
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"mobile": data.Mobile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBackgroundCheckReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.BackgroundCheckReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile); verifyErr != nil {
|
||||||
|
return nil, verifyErr
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"mobile": data.Mobile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runEntLawsuitReq 企业司法涉诉:企业名称+统一社会信用代码+手机+验证码
|
||||||
|
func runEntLawsuitReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.EntLawsuitReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"ent_name": data.EntName,
|
||||||
|
"ent_code": data.EntCode,
|
||||||
|
"mobile": data.Mobile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业司法涉诉简版 QYGL66SL:仅企业名称,auth_date 与授权文件编码由后端自动生成
|
||||||
|
func runEnterpriseLawsuitSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.EnterpriseLawsuitSimpleReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
start := now.AddDate(0, 0, -7)
|
||||||
|
end := now.AddDate(0, 0, 7)
|
||||||
|
authDate := fmt.Sprintf("%s-%s", start.Format("20060102"), end.Format("20060102"))
|
||||||
|
return map[string]interface{}{
|
||||||
|
"ent_name": data.EntName,
|
||||||
|
"auth_date": authDate,
|
||||||
|
"auth_authorize_file_code": "AUTHTYC0001",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限高被执行人 FLXG3A9B
|
||||||
|
func runLimitHighExecutedReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.LimitHighExecutedReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
auth := data.Authorized
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"mobile_no": data.Mobile,
|
||||||
|
"authorized": auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本人不良 FLXGDEA9:姓名 + 身份证(授权由 ApiRequest 默认传 1)
|
||||||
|
func runPersonalBadRecordReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.PersonalBadRecordReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 失信被执行人 QYGL2S0W
|
||||||
|
func runDishonestExecutedReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.DishonestExecutedReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
tp := data.Type
|
||||||
|
if tp == "" {
|
||||||
|
tp = "per"
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"type": tp,
|
||||||
|
"name": data.Name,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runDualMarriageReq 双人婚姻状态:男方/女方姓名+身份证+手机+验证码
|
||||||
|
func runDualMarriageReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocDualMarriageReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
if verifyErr := l.VerifyTwo(data.NameMan, data.IDCardMan); verifyErr != nil {
|
||||||
|
return nil, verifyErr
|
||||||
|
}
|
||||||
|
if verifyErr := l.VerifyTwo(data.NameWoman, data.IDCardWoman); verifyErr != nil {
|
||||||
|
return nil, verifyErr
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.NameMan,
|
||||||
|
"id_card": data.IDCardMan,
|
||||||
|
"mobile": data.Mobile,
|
||||||
|
"name_man": data.NameMan,
|
||||||
|
"id_card_man": data.IDCardMan,
|
||||||
|
"name_woman": data.NameWoman,
|
||||||
|
"id_card_woman": data.IDCardWoman,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runPersonVehicleVerificationReq 人车核验简版:姓名+号牌类型+车牌号
|
||||||
|
func runPersonVehicleVerificationReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocPersonVehicleVerification
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"name": data.Name,
|
||||||
|
"plate_no": data.CarLicense,
|
||||||
|
"carplate_type": data.CarType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehiclesUnderNameCountReq 名下车辆(数量) QCXG4D2E:仅 user_type + id_card
|
||||||
|
func runVehiclesUnderNameCountReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehiclesUnderNameCountReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
userType := data.UserType
|
||||||
|
if userType == "" {
|
||||||
|
userType = "1"
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"user_type": userType,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleVinCodeReq 仅 vin_code(车辆静态信息、过户详版等)
|
||||||
|
func runVehicleVinCodeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleVinCodeReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleMileageMixedReq 车辆里程记录(混合) QCXG1U4U
|
||||||
|
func runVehicleMileageMixedReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleMileageMixedReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"image_url": data.ImageURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleVinValuationReq 二手车VIN估值 QCXGY7F2(仅必填)
|
||||||
|
func runVehicleVinValuationReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleVinValuationReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"vehicle_location": data.VehicleLocation,
|
||||||
|
"first_registrationdate": data.FirstRegistrationDate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleTransferSimpleReq 车辆过户简版 QCXG1H7Y(仅必填 vin_code)
|
||||||
|
func runVehicleTransferSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleTransferSimpleReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"vin_code": data.VinCode}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B(仅必填 vin_code;回调地址后端自动生成)
|
||||||
|
func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleMaintenanceSimpleReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L(仅必填 vin_code;回调地址后端自动生成)
|
||||||
|
func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleMaintenanceDetailReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleClaimDetailReq 车辆出险详版 QCXGP00W(仅必填 vin_code, vlphoto_data;回调地址后端自动生成),vlphoto_data 由 API 层加密为 data
|
||||||
|
func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleClaimDetailReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VinCode,
|
||||||
|
"vlphoto_data": data.VlphotoData,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runVehicleClaimVerifyReq 车辆出险记录核验 QCXG6B4E
|
||||||
|
func runVehicleClaimVerifyReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVehicleClaimVerifyReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
|
return nil, verifyCodeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auth := data.Authorized
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"vin_code": data.VINCode,
|
||||||
|
"authorized": auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runPersonEnterpriseProReq 人企关系加强版预查询:仅身份证号
|
||||||
|
func runPersonEnterpriseProReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocPersonEnterpriseProReq
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
|
||||||
|
}
|
||||||
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- 核验工具 handlers ---------------
|
||||||
|
func runVerifyAuthTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyAuthTwoReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyAuthThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyAuthThreeReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"photo_data": data.PhotoData, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyCertReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyCertReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyConsumptionReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysTwoReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysThreeReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyMobileOnlyReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyMobileOnlyReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysConsumptionReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "authorized": data.Authorized}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyEntRelationReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyEntRelationReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
out := map[string]interface{}{}
|
||||||
|
if data.IDCard != "" {
|
||||||
|
out["id_card"] = data.IDCard
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyBankFourReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyBankFourReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"mobile_no": data.MobileNo,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"bank_card": data.BankCard,
|
||||||
|
"name": data.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyBankBlackReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyBankBlackReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"mobile_no": data.MobileNo,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"name": data.Name,
|
||||||
|
"bank_card": data.BankCard,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryServiceLogic) DecryptData(data string) ([]byte, error) {
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, decodeErr := hex.DecodeString(secretKey)
|
||||||
|
if decodeErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "密钥获取失败: %+v", decodeErr)
|
||||||
|
}
|
||||||
|
decryptData, aesDecryptErr := crypto.AesDecrypt(data, key)
|
||||||
|
if aesDecryptErr != nil || len(decryptData) == 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密失败: %+v", aesDecryptErr)
|
||||||
|
}
|
||||||
|
return decryptData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验验证码(开发环境 ENV=development 可跳过)
|
||||||
|
func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error {
|
||||||
|
if os.Getenv("ENV") == "development" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %+v", err)
|
||||||
|
}
|
||||||
|
codeRedisKey := fmt.Sprintf("%s:%s", "query", encryptedMobile)
|
||||||
|
cacheCode, err := l.svcCtx.Redis.Get(codeRedisKey)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "验证码过期: %s", mobile)
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码redis缓存失败, mobile: %s, err: %+v", mobile, err)
|
||||||
|
}
|
||||||
|
if cacheCode != code {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "验证码不正确: %s", mobile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryServiceLogic) IsAgentQuery() bool {
|
||||||
|
agentID, _ := l.ctx.Value("agentIdentifier").(string)
|
||||||
|
return agentID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二要素验证(仅姓名+身份证号)(开发环境不调用验证 API)
|
||||||
|
func (l *QueryServiceLogic) VerifyTwo(Name string, IDCard string) error {
|
||||||
|
if os.Getenv("ENV") == "development" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
twoVerification := service.TwoFactorVerificationRequest{
|
||||||
|
Name: Name,
|
||||||
|
IDCard: IDCard,
|
||||||
|
}
|
||||||
|
verification, err := l.svcCtx.VerificationService.TwoFactorVerification(twoVerification)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "二要素验证失败: %v", err)
|
||||||
|
}
|
||||||
|
if !verification.Passed {
|
||||||
|
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "二要素验证不通过: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按代理/非代理切换要素验证:代理走三要素;非代理走二要素(开发环境不调用验证 API)
|
||||||
|
func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error {
|
||||||
|
if os.Getenv("ENV") == "development" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !l.IsAgentQuery() {
|
||||||
|
twoVerification := service.TwoFactorVerificationRequest{
|
||||||
|
Name: Name,
|
||||||
|
IDCard: IDCard,
|
||||||
|
}
|
||||||
|
verification, err := l.svcCtx.VerificationService.TwoFactorVerification(twoVerification)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "二要素验证失败: %v", err)
|
||||||
|
}
|
||||||
|
if !verification.Passed {
|
||||||
|
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "二要素验证不通过: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 三要素验证
|
||||||
|
if Mobile == "" {
|
||||||
|
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, "手机号不能为空"), "三要素验证参数不正确: mobile为空")
|
||||||
|
}
|
||||||
|
threeVerification := service.ThreeFactorVerificationRequest{
|
||||||
|
Name: Name,
|
||||||
|
IDCard: IDCard,
|
||||||
|
Mobile: Mobile,
|
||||||
|
}
|
||||||
|
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err)
|
||||||
|
}
|
||||||
|
if !verification.Passed {
|
||||||
|
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存
|
||||||
|
func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product string, userID int64) (string, error) {
|
||||||
|
agentIdentifier, _ := l.ctx.Value("agentIdentifier").(string)
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, decodeErr := hex.DecodeString(secretKey)
|
||||||
|
if decodeErr != nil {
|
||||||
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 获取AES密钥失败: %+v", decodeErr)
|
||||||
|
}
|
||||||
|
paramsMarshal, marshalErr := json.Marshal(params)
|
||||||
|
if marshalErr != nil {
|
||||||
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr)
|
||||||
|
}
|
||||||
|
encryptParams, aesEncryptErr := crypto.AesEncrypt(paramsMarshal, key)
|
||||||
|
if aesEncryptErr != nil {
|
||||||
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 加密参数失败: %+v", aesEncryptErr)
|
||||||
|
}
|
||||||
|
queryCache := types.QueryCacheLoad{
|
||||||
|
Params: encryptParams,
|
||||||
|
Product: Product,
|
||||||
|
AgentIdentifier: agentIdentifier,
|
||||||
|
}
|
||||||
|
jsonData, marshalErr := json.Marshal(queryCache)
|
||||||
|
if marshalErr != nil {
|
||||||
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr)
|
||||||
|
}
|
||||||
|
outTradeNo := "Q_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||||
|
redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo)
|
||||||
|
cacheErr := l.svcCtx.Redis.SetexCtx(l.ctx, redisKey, string(jsonData), int(2*time.Hour))
|
||||||
|
if cacheErr != nil {
|
||||||
|
return "", cacheErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入 query_user_record,用于后台按被查询人姓名/身份证/手机号追溯订单(见 query_user_record.sql 说明 1)
|
||||||
|
nameStr, _ := params["name"].(string)
|
||||||
|
idCardStr, _ := params["id_card"].(string)
|
||||||
|
mobileStr, _ := params["mobile"].(string)
|
||||||
|
if nameStr != "" || idCardStr != "" || mobileStr != "" {
|
||||||
|
var encName, encIdCard, encMobile string
|
||||||
|
if nameStr != "" {
|
||||||
|
encName, _ = crypto.AesEcbEncrypt([]byte(nameStr), key)
|
||||||
|
}
|
||||||
|
if idCardStr != "" {
|
||||||
|
encIdCard, _ = crypto.EncryptIDCard(idCardStr, key)
|
||||||
|
}
|
||||||
|
if mobileStr != "" {
|
||||||
|
encMobile, _ = crypto.EncryptMobile(mobileStr, secretKey)
|
||||||
|
}
|
||||||
|
agentIdent := sql.NullString{}
|
||||||
|
if agentIdentifier != "" {
|
||||||
|
agentIdent = sql.NullString{String: agentIdentifier, Valid: true}
|
||||||
|
}
|
||||||
|
rec := &model.QueryUserRecord{
|
||||||
|
DeleteTime: sql.NullTime{},
|
||||||
|
DelState: globalkey.DelStateNo,
|
||||||
|
Version: 0,
|
||||||
|
UserId: userID,
|
||||||
|
Name: encName,
|
||||||
|
IdCard: encIdCard,
|
||||||
|
Mobile: encMobile,
|
||||||
|
Product: Product,
|
||||||
|
QueryNo: outTradeNo,
|
||||||
|
OrderId: 0,
|
||||||
|
PlatformOrderId: sql.NullString{},
|
||||||
|
AgentIdentifier: agentIdent,
|
||||||
|
}
|
||||||
|
_, insertErr := l.svcCtx.QueryUserRecordModel.Insert(l.ctx, nil, rec)
|
||||||
|
if insertErr != nil {
|
||||||
|
logx.WithContext(l.ctx).Errorf("CacheData 写入 query_user_record 失败: %v", insertErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outTradeNo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrCreateUser 获取或创建用户
|
||||||
|
// 1. 如果上下文中已有用户ID,直接返回
|
||||||
|
// 2. 如果是代理查询或APP请求,创建新用户
|
||||||
|
// 3. 其他情况返回未登录错误
|
||||||
|
func (l *QueryServiceLogic) GetOrCreateUser() (int64, error) {
|
||||||
|
// 尝试获取用户ID
|
||||||
|
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
userID := claims.UserId
|
||||||
|
return userID, nil
|
||||||
|
|
||||||
|
// // 如果不是未登录错误,说明是其他错误,直接返回
|
||||||
|
// if !ctxdata.IsNoUserIdError(err) {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 检查是否是代理查询或APP请求
|
||||||
|
// isAgentQuery := false
|
||||||
|
// if agentID, ok := l.ctx.Value("agentIdentifier").(string); ok && agentID != "" {
|
||||||
|
// isAgentQuery = true
|
||||||
|
// }
|
||||||
|
// if app, ok := l.ctx.Value("app").(bool); ok && app {
|
||||||
|
// isAgentQuery = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 如果不是代理查询或APP请求,返回未登录错误
|
||||||
|
// if !isAgentQuery {
|
||||||
|
// return 0, ctxdata.ErrNoUserIdInCtx
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 创建新用户
|
||||||
|
// return l.svcCtx.UserService.RegisterUUIDUser(l.ctx)
|
||||||
|
}
|
||||||
48
app/main/api/internal/logic/toolbox/toolboxquerylogic.go
Normal file
48
app/main/api/internal/logic/toolbox/toolboxquerylogic.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package toolbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"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 ToolboxQueryLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToolboxQueryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToolboxQueryLogic {
|
||||||
|
return &ToolboxQueryLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ToolboxQueryLogic) ToolboxQuery(req *types.ToolboxQueryReq) (*types.ToolboxQueryResp, error) {
|
||||||
|
if req.ToolKey == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("tool_key 不能为空"), "")
|
||||||
|
}
|
||||||
|
if l.svcCtx.ToolboxService == nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "工具箱服务未初始化")
|
||||||
|
}
|
||||||
|
params := req.Params
|
||||||
|
if params == nil {
|
||||||
|
params = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
result, err := l.svcCtx.ToolboxService.Query(l.ctx, req.ToolKey, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.ToolboxQueryResp{
|
||||||
|
ToolKey: req.ToolKey,
|
||||||
|
Result: result,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
67
app/main/api/internal/logic/upload/serveuploadlogic.go
Normal file
67
app/main/api/internal/logic/upload/serveuploadlogic.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServeUploadLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServeUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadLogic {
|
||||||
|
return &ServeUploadLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ServeUploadLogic) ServeUpload(name string, w http.ResponseWriter) error {
|
||||||
|
name = filepath.Base(strings.TrimSpace(name))
|
||||||
|
if name == "" || name == "." || name == ".." || !isSafeUploadFileName(name) {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("无效的文件名"), "")
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(uploadImageDir, name)
|
||||||
|
data, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("文件不存在"), "")
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取文件失败: %v", err)
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=86400")
|
||||||
|
switch filepath.Ext(name) {
|
||||||
|
case ".png":
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
case ".gif":
|
||||||
|
w.Header().Set("Content-Type", "image/gif")
|
||||||
|
case ".webp":
|
||||||
|
w.Header().Set("Content-Type", "image/webp")
|
||||||
|
default:
|
||||||
|
w.Header().Set("Content-Type", "image/jpeg")
|
||||||
|
}
|
||||||
|
_, err = w.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSafeUploadFileName(name string) bool {
|
||||||
|
for _, c := range name {
|
||||||
|
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
93
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
93
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/svc"
|
||||||
|
"qnc-server/app/main/api/internal/types"
|
||||||
|
"qnc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const uploadImageDir = "uploads/images"
|
||||||
|
|
||||||
|
type UploadImageLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
|
||||||
|
return &UploadImageLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (*types.UploadImageResp, error) {
|
||||||
|
raw := strings.TrimSpace(req.ImageBase64)
|
||||||
|
if raw == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("image_base64 不能为空"), "")
|
||||||
|
}
|
||||||
|
if idx := strings.Index(raw, ","); idx >= 0 {
|
||||||
|
raw = raw[idx+1:]
|
||||||
|
}
|
||||||
|
data, err := base64.StdEncoding.DecodeString(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("图片 Base64 解析失败"), "")
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("图片内容为空"), "")
|
||||||
|
}
|
||||||
|
if len(data) > 5*1024*1024 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 5MB"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := detectImageExt(data)
|
||||||
|
fileName := fmt.Sprintf("%s%s", uuid.NewString(), ext)
|
||||||
|
dir := uploadImageDir
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err)
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(dir, fileName)
|
||||||
|
if err := os.WriteFile(fullPath, data, 0o644); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
base := publicAPIBase(l.svcCtx.Config.Promotion.OfficialDomain)
|
||||||
|
url := fmt.Sprintf("%s/api/v1/upload/file/%s", base, fileName)
|
||||||
|
return &types.UploadImageResp{Url: url}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectImageExt(data []byte) string {
|
||||||
|
if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
|
||||||
|
return ".jpg"
|
||||||
|
}
|
||||||
|
if len(data) >= 8 && string(data[0:8]) == "\x89PNG\r\n\x1a\n" {
|
||||||
|
return ".png"
|
||||||
|
}
|
||||||
|
if len(data) >= 6 && string(data[0:6]) == "GIF87a" || string(data[0:6]) == "GIF89a" {
|
||||||
|
return ".gif"
|
||||||
|
}
|
||||||
|
if len(data) >= 12 && string(data[8:12]) == "WEBP" {
|
||||||
|
return ".webp"
|
||||||
|
}
|
||||||
|
return ".jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicAPIBase(officialDomain string) string {
|
||||||
|
base := strings.TrimRight(officialDomain, "/")
|
||||||
|
if base != "" {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
return "http://127.0.0.1:8888"
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过加密后的手机号查找目标用户(手机号用户视为正式用户)
|
// 通过加密后的手机号查找目标用户(手机号用户视为正式用户;可能命中 go-zero 二级缓存)
|
||||||
targetUser, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
targetUser, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找用户失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找用户失败: %v", err)
|
||||||
@@ -90,6 +90,13 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
if _, err := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{Id: uuid.NewString(), UserId: finalUserID, AuthType: model.UserAuthTypeMobile, AuthKey: encryptedMobile}); err != nil {
|
if _, err := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{Id: uuid.NewString(), UserId: finalUserID, AuthType: model.UserAuthTypeMobile, AuthKey: encryptedMobile}); err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
||||||
}
|
}
|
||||||
|
// 与 userModel_gen 中 FindOneByMobile 的 QueryRowIndexCtx key 规则一致,避免此前「未命中」缓存导致后续按手机号仍查不到
|
||||||
|
userMobileCacheKey := fmt.Sprintf("cache:qnc:user:mobile:%v", sql.NullString{String: encryptedMobile, Valid: true})
|
||||||
|
if _, delErr := l.svcCtx.Redis.DelCtx(l.ctx, userMobileCacheKey); delErr != nil {
|
||||||
|
l.Errorf("[BindMobile] 失效 FindOneByMobile 缓存失败 | key: %s | err: %v", userMobileCacheKey, delErr)
|
||||||
|
} else {
|
||||||
|
l.Infof("[BindMobile] 已失效 FindOneByMobile 缓存 | key: %s", userMobileCacheKey)
|
||||||
|
}
|
||||||
// 发放token(userType会根据mobile字段动态计算)
|
// 发放token(userType会根据mobile字段动态计算)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
|
|||||||
var userID string
|
var userID string
|
||||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||||
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findUserErr)
|
||||||
}
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
// 用户不存在,自动注册新用户
|
// 用户不存在,自动注册新用户
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"qnc-server/app/main/api/internal/svc"
|
"qnc-server/app/main/api/internal/svc"
|
||||||
@@ -33,8 +35,14 @@ func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMini
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
||||||
|
code := strings.TrimSpace(req.Code)
|
||||||
|
if code == "" || code == "the code is a mock one" || strings.Contains(code, " ") {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("微信登录凭证无效,请重新打开小程序"),
|
||||||
|
"无效的微信 code: %q", req.Code)
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 获取session_key和openid
|
// 1. 获取session_key和openid
|
||||||
sessionKeyResp, err := l.GetSessionKey(req.Code)
|
sessionKeyResp, err := l.GetSessionKey(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||||
}
|
}
|
||||||
@@ -65,13 +73,20 @@ func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMi
|
|||||||
userID = user.Id
|
userID = user.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 生成JWT Token(动态计算userType)
|
// 4. 缓存 session_key(xpay 虚拟支付签名需要)
|
||||||
|
if l.svcCtx.XpayService != nil && l.svcCtx.XpayService.Enabled() {
|
||||||
|
if saveErr := l.svcCtx.XpayService.SaveSessionKey(l.ctx, userID, sessionKeyResp.SessionKey); saveErr != nil {
|
||||||
|
l.Errorf("缓存 xpay session_key 失败 userID=%s err=%v", userID, saveErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 生成JWT Token(动态计算userType)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 返回登录结果
|
// 6. 返回登录结果
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
return &types.WXMiniAuthResp{
|
return &types.WXMiniAuthResp{
|
||||||
AccessToken: token,
|
AccessToken: token,
|
||||||
@@ -97,10 +112,10 @@ func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) {
|
|||||||
appID = l.svcCtx.Config.WechatMini.AppID
|
appID = l.svcCtx.Config.WechatMini.AppID
|
||||||
appSecret = l.svcCtx.Config.WechatMini.AppSecret
|
appSecret = l.svcCtx.Config.WechatMini.AppSecret
|
||||||
|
|
||||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||||
appID, appSecret, code)
|
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(apiURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"qnc-server/app/main/api/internal/logic/pay"
|
||||||
"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/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
"qnc-server/pkg/lzkit/lzUtils"
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
@@ -158,7 +159,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
encryptData = encryptedEmptyData
|
encryptData = encryptedEmptyData
|
||||||
} else {
|
} else {
|
||||||
// 正常模式:调用API请求服务
|
// 正常模式:调用API请求服务
|
||||||
combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
|
combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id, order.OrderNo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return l.handleError(ctx, err, order, query)
|
return l.handleError(ctx, err, order, query)
|
||||||
}
|
}
|
||||||
@@ -185,12 +186,18 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
return l.handleError(ctx, updateQueryErr, order, query)
|
return l.handleError(ctx, updateQueryErr, order, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录动态查询记录 (真实案例展示)
|
||||||
|
go l.recordInquiryRecord(context.Background(), decryptData, product, encryptData)
|
||||||
|
|
||||||
// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
|
// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
|
||||||
if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
|
if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
|
||||||
// 代理处理任务发送失败,只记录日志,不影响报告流程
|
// 代理处理任务发送失败,只记录日志,不影响报告流程
|
||||||
logx.Errorf("发送代理处理任务失败,订单ID: %s, 错误: %v", order.Id, asyncErr)
|
logx.Errorf("发送代理处理任务失败,订单ID: %s, 错误: %v", order.Id, asyncErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// xpay:报告成功后再通知微信虚拟支付已发货
|
||||||
|
pay.NotifyXpayGoodsAfterReport(ctx, l.svcCtx, order)
|
||||||
|
|
||||||
_, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey)
|
_, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey)
|
||||||
if delErr != nil {
|
if delErr != nil {
|
||||||
logx.Errorf("删除Redis缓存失败,但任务已成功处理,订单ID: %s, 错误: %v", order.Id, delErr)
|
logx.Errorf("删除Redis缓存失败,但任务已成功处理,订单ID: %s, 错误: %v", order.Id, delErr)
|
||||||
@@ -218,36 +225,7 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error
|
|||||||
return asynq.SkipRetry
|
return asynq.SkipRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退款
|
pay.TryRefundOnQueryFailure(ctx, l.svcCtx, order)
|
||||||
if order.PaymentPlatform == "wechat" {
|
|
||||||
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
|
|
||||||
if refundErr != nil {
|
|
||||||
logx.Error(refundErr)
|
|
||||||
return asynq.SkipRetry
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
|
|
||||||
if refundErr != nil {
|
|
||||||
logx.Error(refundErr)
|
|
||||||
return asynq.SkipRetry
|
|
||||||
}
|
|
||||||
if refund.IsSuccess() {
|
|
||||||
logx.Errorf("支付宝退款成功, orderID: %s", order.Id)
|
|
||||||
// 更新订单状态为退款
|
|
||||||
order.Status = "refunded"
|
|
||||||
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
|
|
||||||
if updateOrderErr != nil {
|
|
||||||
logx.Errorf("更新订单状态失败,订单ID: %s, 错误: %v", order.Id, updateOrderErr)
|
|
||||||
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
|
|
||||||
}
|
|
||||||
return asynq.SkipRetry
|
|
||||||
} else {
|
|
||||||
logx.Errorf("支付宝退款失败:%v", refundErr)
|
|
||||||
return asynq.SkipRetry
|
|
||||||
}
|
|
||||||
// 直接成功
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return asynq.SkipRetry
|
return asynq.SkipRetry
|
||||||
@@ -372,6 +350,84 @@ func maskPhone(phone string) string {
|
|||||||
return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:]
|
return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordInquiryRecord 记录成功的查询到动态展示表
|
||||||
|
func (l *PaySuccessNotifyUserHandler) recordInquiryRecord(ctx context.Context, decryptParams []byte, product *model.Product, encryptedResp string) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logx.Errorf("记录查询记录异常: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, _ := hex.DecodeString(secretKey)
|
||||||
|
|
||||||
|
// 1. 解析参数获取手机号和VIN
|
||||||
|
var params map[string]interface{}
|
||||||
|
_ = json.Unmarshal(decryptParams, ¶ms)
|
||||||
|
|
||||||
|
mobile, _ := params["mobile"].(string)
|
||||||
|
vin, _ := params["vin_code"].(string)
|
||||||
|
if vin == "" {
|
||||||
|
vin, _ = params["vin"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mobile == "" || vin == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解密响应数据获取车型
|
||||||
|
decryptResp, err := crypto.AesDecrypt(encryptedResp, key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从响应中寻找车型信息 (这里根据实际响应结构寻找)
|
||||||
|
// 通常在车辆信息接口的 data 字段中
|
||||||
|
carModel := "未知车型"
|
||||||
|
respStr := string(decryptResp)
|
||||||
|
|
||||||
|
// 简单的规则寻找车型字段
|
||||||
|
if strings.Contains(respStr, "model_name") {
|
||||||
|
re := regexp.MustCompile(`"model_name"\s*:\s*"([^"]+)"`)
|
||||||
|
match := re.FindStringSubmatch(respStr)
|
||||||
|
if len(match) > 1 {
|
||||||
|
carModel = match[1]
|
||||||
|
}
|
||||||
|
} else if strings.Contains(respStr, "brand_name") {
|
||||||
|
re := regexp.MustCompile(`"brand_name"\s*:\s*"([^"]+)"`)
|
||||||
|
match := re.FindStringSubmatch(respStr)
|
||||||
|
if len(match) > 1 {
|
||||||
|
carModel = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 脱敏处理
|
||||||
|
phoneTail := ""
|
||||||
|
if len(mobile) >= 4 {
|
||||||
|
phoneTail = mobile[len(mobile)-4:]
|
||||||
|
}
|
||||||
|
displayName := "用户*" + phoneTail
|
||||||
|
|
||||||
|
maskedVin := ""
|
||||||
|
if len(vin) >= 8 {
|
||||||
|
maskedVin = vin[:4] + "********" + vin[len(vin)-4:]
|
||||||
|
} else {
|
||||||
|
maskedVin = vin
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 保存记录
|
||||||
|
record := &model.InquiryRecord{
|
||||||
|
UserPhoneTail: phoneTail,
|
||||||
|
DisplayName: displayName,
|
||||||
|
VinMasked: maskedVin,
|
||||||
|
CarModel: carModel,
|
||||||
|
InquiryTag: product.ProductName,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = l.svcCtx.InquiryRecordModel.Insert(ctx, nil, record)
|
||||||
|
}
|
||||||
|
|
||||||
// 通用敏感信息脱敏 - 根据字符串长度比例进行脱敏
|
// 通用敏感信息脱敏 - 根据字符串长度比例进行脱敏
|
||||||
func maskGeneral(value string) string {
|
func maskGeneral(value string) string {
|
||||||
length := len(value)
|
length := len(value)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
"qnc-server/pkg/lzkit/lzUtils"
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
@@ -120,7 +121,7 @@ func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) err
|
|||||||
|
|
||||||
// 5.2 使用产品配置的底价计算实际底价
|
// 5.2 使用产品配置的底价计算实际底价
|
||||||
basePrice := productConfig.BasePrice
|
basePrice := productConfig.BasePrice
|
||||||
actualBasePrice := basePrice + float64(levelBonus)
|
actualBasePrice := lzUtils.RoundMoney(basePrice + float64(levelBonus))
|
||||||
|
|
||||||
// 5.3 计算提价成本(使用产品配置)
|
// 5.3 计算提价成本(使用产品配置)
|
||||||
priceThreshold := 0.0
|
priceThreshold := 0.0
|
||||||
@@ -134,7 +135,7 @@ func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) err
|
|||||||
priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
|
priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
|
||||||
|
|
||||||
// 5.4 计算代理收益
|
// 5.4 计算代理收益
|
||||||
agentProfit := agentOrder.SetPrice - actualBasePrice - priceCost
|
agentProfit := lzUtils.RoundMoney(agentOrder.SetPrice - actualBasePrice - priceCost)
|
||||||
|
|
||||||
// 5.5 更新代理订单记录
|
// 5.5 更新代理订单记录
|
||||||
agentOrder.ProcessStatus = 1
|
agentOrder.ProcessStatus = 1
|
||||||
@@ -200,7 +201,7 @@ func (s *AgentService) calculatePriceCost(setPrice, priceThreshold, priceFeeRate
|
|||||||
if setPrice <= priceThreshold {
|
if setPrice <= priceThreshold {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return (setPrice - priceThreshold) * priceFeeRate
|
return lzUtils.RoundMoney((setPrice - priceThreshold) * priceFeeRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// giveAgentCommission 发放代理佣金
|
// giveAgentCommission 发放代理佣金
|
||||||
@@ -244,7 +245,7 @@ func (s *AgentService) giveAgentCommission(ctx context.Context, session sqlx.Ses
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 计算冻结金额:订单单价的10%
|
// 计算冻结金额:订单单价的10%
|
||||||
freezeAmountByPrice := orderPrice * freezeRatio
|
freezeAmountByPrice := lzUtils.RoundMoney(orderPrice * freezeRatio)
|
||||||
|
|
||||||
// 冻结金额不能超过佣金金额
|
// 冻结金额不能超过佣金金额
|
||||||
if freezeAmountByPrice > amount {
|
if freezeAmountByPrice > amount {
|
||||||
@@ -295,7 +296,7 @@ func (s *AgentService) giveAgentCommission(ctx context.Context, session sqlx.Ses
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 实际到账金额 = 佣金金额 - 冻结金额
|
// 实际到账金额 = 佣金金额 - 冻结金额
|
||||||
actualAmount := amount - freezeAmount
|
actualAmount := lzUtils.RoundMoney(amount - freezeAmount)
|
||||||
|
|
||||||
wallet.Balance += actualAmount
|
wallet.Balance += actualAmount
|
||||||
wallet.FrozenBalance += freezeAmount
|
wallet.FrozenBalance += freezeAmount
|
||||||
@@ -431,7 +432,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
|||||||
// ========== 步骤2:计算剩余金额并分配给钻石上级 ==========
|
// ========== 步骤2:计算剩余金额并分配给钻石上级 ==========
|
||||||
// 剩余金额 = 总等级加成 - 已给黄金上级的金额
|
// 剩余金额 = 总等级加成 - 已给黄金上级的金额
|
||||||
// 例如:等级加成6元 - 给黄金上级3元 = 剩余3元
|
// 例如:等级加成6元 - 给黄金上级3元 = 剩余3元
|
||||||
remaining := amount - goldAmount
|
remaining := lzUtils.RoundMoney(amount - goldAmount)
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
// 从黄金上级开始向上查找钻石上级
|
// 从黄金上级开始向上查找钻石上级
|
||||||
// 场景示例:
|
// 场景示例:
|
||||||
@@ -481,7 +482,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
|||||||
// ========== 步骤2:计算剩余金额 ==========
|
// ========== 步骤2:计算剩余金额 ==========
|
||||||
// 剩余金额 = 总等级加成 - 已给直接上级的金额
|
// 剩余金额 = 总等级加成 - 已给直接上级的金额
|
||||||
// 例如:等级加成6元 - 给直接上级2元 = 剩余4元
|
// 例如:等级加成6元 - 给直接上级2元 = 剩余4元
|
||||||
remaining := amount - directAmount
|
remaining := lzUtils.RoundMoney(amount - directAmount)
|
||||||
if remaining <= 0 {
|
if remaining <= 0 {
|
||||||
// 如果没有剩余,直接返回(所有金额已分配给直接上级)
|
// 如果没有剩余,直接返回(所有金额已分配给直接上级)
|
||||||
return nil
|
return nil
|
||||||
@@ -524,8 +525,8 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
|||||||
logx.Errorf("获取黄金等级加成配置失败,使用默认值3元: %v", err)
|
logx.Errorf("获取黄金等级加成配置失败,使用默认值3元: %v", err)
|
||||||
goldBonus = 3 // 默认3元
|
goldBonus = 3 // 默认3元
|
||||||
}
|
}
|
||||||
// 计算等级加成的差
|
// 计算等级加成的差
|
||||||
bonusDiff := normalBonus - float64(goldBonus) // 例如:6元 - 3元 = 3元
|
bonusDiff := lzUtils.RoundMoney(normalBonus - float64(goldBonus)) // 例如:6元 - 3元 = 3元
|
||||||
|
|
||||||
// 步骤A2:如果有黄金上级,给黄金上级一部分返佣
|
// 步骤A2:如果有黄金上级,给黄金上级一部分返佣
|
||||||
// 规则说明:
|
// 规则说明:
|
||||||
@@ -536,7 +537,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
|||||||
if goldParent != nil && bonusDiff > 0 {
|
if goldParent != nil && bonusDiff > 0 {
|
||||||
// 计算给黄金上级的金额 = 等级加成差 - 已给普通上级的金额
|
// 计算给黄金上级的金额 = 等级加成差 - 已给普通上级的金额
|
||||||
// 例如:等级加成差3元 - 已给普通2元 = 给黄金1元
|
// 例如:等级加成差3元 - 已给普通2元 = 给黄金1元
|
||||||
goldRebateAmount := bonusDiff - directAmount
|
goldRebateAmount := lzUtils.RoundMoney(bonusDiff - directAmount)
|
||||||
|
|
||||||
// 如果计算出的金额小于等于0,说明差已经被普通代理全部占用了,不给黄金
|
// 如果计算出的金额小于等于0,说明差已经被普通代理全部占用了,不给黄金
|
||||||
// 例如:如果差是2元,已给普通2元,则 goldRebateAmount = 0,不给黄金
|
// 例如:如果差是2元,已给普通2元,则 goldRebateAmount = 0,不给黄金
|
||||||
@@ -554,7 +555,7 @@ func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session s
|
|||||||
|
|
||||||
// 更新剩余金额(用于后续分配给钻石)
|
// 更新剩余金额(用于后续分配给钻石)
|
||||||
// 例如:剩余4元 - 给黄金1元 = 剩余3元(给钻石)
|
// 例如:剩余4元 - 给黄金1元 = 剩余3元(给钻石)
|
||||||
remaining = remaining - goldRebateAmount
|
remaining = lzUtils.RoundMoney(remaining - goldRebateAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1073,3 +1074,150 @@ func (s *AgentService) GetUpgradeRebate(ctx context.Context, fromLevel, toLevel
|
|||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReverseAgentSettlementOnOrderRefund 订单退款成功后冲正本单代理销售佣金、冻结任务与返佣(幂等)。
|
||||||
|
// 与退款是否部分/全额无关:凡触发本方法,均按本单佣金与返佣记录全额冲回。
|
||||||
|
// session 非 nil 时与外层共用事务;为 nil 时在独立事务中执行。
|
||||||
|
func (s *AgentService) ReverseAgentSettlementOnOrderRefund(ctx context.Context, session sqlx.Session, orderID string) error {
|
||||||
|
run := func(c context.Context, sess sqlx.Session) error {
|
||||||
|
return s.reverseAgentSettlementInTx(c, sess, orderID)
|
||||||
|
}
|
||||||
|
if session != nil {
|
||||||
|
return run(ctx, session)
|
||||||
|
}
|
||||||
|
return s.AgentWalletModel.Trans(ctx, run)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AgentService) reverseAgentSettlementInTx(ctx context.Context, session sqlx.Session, orderID string) error {
|
||||||
|
cb := s.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{
|
||||||
|
"order_id": orderID,
|
||||||
|
"del_state": globalkey.DelStateNo,
|
||||||
|
})
|
||||||
|
commissions, err := s.AgentCommissionModel.FindAll(ctx, cb, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "查询订单佣金失败 orderId=%s", orderID)
|
||||||
|
}
|
||||||
|
for _, c := range commissions {
|
||||||
|
if err := s.reverseOneCommissionForRefund(ctx, session, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := s.AgentRebateModel.SelectBuilder().Where(squirrel.Eq{
|
||||||
|
"order_id": orderID,
|
||||||
|
"del_state": globalkey.DelStateNo,
|
||||||
|
})
|
||||||
|
rebates, err := s.AgentRebateModel.FindAll(ctx, rb, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "查询订单返佣失败 orderId=%s", orderID)
|
||||||
|
}
|
||||||
|
for _, r := range rebates {
|
||||||
|
if err := s.reverseOneRebateForRefund(ctx, session, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AgentService) reverseOneCommissionForRefund(ctx context.Context, session sqlx.Session, commission *model.AgentCommission) error {
|
||||||
|
if commission.Status == 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
A := lzUtils.RoundMoney(commission.Amount)
|
||||||
|
if A <= 0 {
|
||||||
|
commission.Status = 3
|
||||||
|
return s.AgentCommissionModel.UpdateWithVersion(ctx, session, commission)
|
||||||
|
}
|
||||||
|
|
||||||
|
tb := s.AgentFreezeTaskModel.SelectBuilder().Where(squirrel.Eq{
|
||||||
|
"commission_id": commission.Id,
|
||||||
|
"del_state": globalkey.DelStateNo,
|
||||||
|
})
|
||||||
|
tasks, err := s.AgentFreezeTaskModel.FindAll(ctx, tb, "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "查询佣金冻结任务失败 commissionId=%s", commission.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var takeFromFrozen float64
|
||||||
|
for _, ft := range tasks {
|
||||||
|
if ft.Status == 1 {
|
||||||
|
takeFromFrozen = lzUtils.RoundMoney(takeFromFrozen + ft.FreezeAmount)
|
||||||
|
ft.Status = 3
|
||||||
|
if err := s.AgentFreezeTaskModel.UpdateWithVersion(ctx, session, ft); err != nil {
|
||||||
|
return errors.Wrapf(err, "取消冻结任务失败 freezeTaskId=%s", ft.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
takeFromFrozen = lzUtils.RoundMoney(takeFromFrozen)
|
||||||
|
|
||||||
|
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "冲正佣金时查询钱包失败 agentId=%s", commission.AgentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if takeFromFrozen > 0 {
|
||||||
|
if wallet.FrozenBalance < takeFromFrozen {
|
||||||
|
takeFromFrozen = lzUtils.RoundMoney(wallet.FrozenBalance)
|
||||||
|
}
|
||||||
|
wallet.FrozenBalance = lzUtils.RoundMoney(wallet.FrozenBalance - takeFromFrozen)
|
||||||
|
}
|
||||||
|
|
||||||
|
takeFromBalance := lzUtils.RoundMoney(A - takeFromFrozen)
|
||||||
|
if takeFromBalance < 0 {
|
||||||
|
takeFromBalance = 0
|
||||||
|
}
|
||||||
|
// 可用余额不足时允许扣成负数(例如代理已提现),退款侧事务仍可完成
|
||||||
|
wallet.Balance = lzUtils.RoundMoney(wallet.Balance - takeFromBalance)
|
||||||
|
if wallet.Balance < 0 {
|
||||||
|
logx.Infof("退款冲正佣金后代理可用余额为负: agentId=%s, orderId=%s, balance=%.2f",
|
||||||
|
commission.AgentId, commission.OrderId, wallet.Balance)
|
||||||
|
}
|
||||||
|
wallet.TotalEarnings = lzUtils.RoundMoney(wallet.TotalEarnings - A)
|
||||||
|
if wallet.TotalEarnings < 0 {
|
||||||
|
wallet.TotalEarnings = 0
|
||||||
|
}
|
||||||
|
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||||
|
return errors.Wrapf(err, "冲正佣金更新钱包失败 agentId=%s", commission.AgentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
commission.Status = 3
|
||||||
|
if err := s.AgentCommissionModel.UpdateWithVersion(ctx, session, commission); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新佣金状态为已取消失败 commissionId=%s", commission.Id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AgentService) reverseOneRebateForRefund(ctx context.Context, session sqlx.Session, rebate *model.AgentRebate) error {
|
||||||
|
if rebate.Status == 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
amt := lzUtils.RoundMoney(rebate.RebateAmount)
|
||||||
|
if amt <= 0 {
|
||||||
|
rebate.Status = 3
|
||||||
|
return s.AgentRebateModel.UpdateWithVersion(ctx, session, rebate)
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, rebate.AgentId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "冲正返佣时查询钱包失败 agentId=%s", rebate.AgentId)
|
||||||
|
}
|
||||||
|
// 可用余额不足时允许扣成负数(例如代理已提现)
|
||||||
|
wallet.Balance = lzUtils.RoundMoney(wallet.Balance - amt)
|
||||||
|
if wallet.Balance < 0 {
|
||||||
|
logx.Infof("退款冲正返佣后代理可用余额为负: agentId=%s, orderId=%s, balance=%.2f",
|
||||||
|
rebate.AgentId, rebate.OrderId, wallet.Balance)
|
||||||
|
}
|
||||||
|
wallet.TotalEarnings = lzUtils.RoundMoney(wallet.TotalEarnings - amt)
|
||||||
|
if wallet.TotalEarnings < 0 {
|
||||||
|
wallet.TotalEarnings = 0
|
||||||
|
}
|
||||||
|
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||||
|
return errors.Wrapf(err, "冲正返佣更新钱包失败 agentId=%s", rebate.AgentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
rebate.Status = 3
|
||||||
|
if err := s.AgentRebateModel.UpdateWithVersion(ctx, session, rebate); err != nil {
|
||||||
|
return errors.Wrapf(err, "更新返佣状态为已取消失败 rebateId=%s", rebate.Id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"qnc-server/app/main/api/internal/config"
|
"qnc-server/app/main/api/internal/config"
|
||||||
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -40,6 +42,7 @@ type ApiRequestService struct {
|
|||||||
whitelistService *WhitelistService
|
whitelistService *WhitelistService
|
||||||
tianyuanapi *tianyuanapi.Client
|
tianyuanapi *tianyuanapi.Client
|
||||||
tianyuanapiCallLogService *TianyuanapiCallLogService
|
tianyuanapiCallLogService *TianyuanapiCallLogService
|
||||||
|
authService *AuthorizationService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApiRequestService 构造函数
|
// NewApiRequestService 构造函数
|
||||||
@@ -51,6 +54,7 @@ func NewApiRequestService(
|
|||||||
tianyuanapi *tianyuanapi.Client,
|
tianyuanapi *tianyuanapi.Client,
|
||||||
tianyuanapiCallLogService *TianyuanapiCallLogService,
|
tianyuanapiCallLogService *TianyuanapiCallLogService,
|
||||||
whitelistService *WhitelistService,
|
whitelistService *WhitelistService,
|
||||||
|
authService *AuthorizationService,
|
||||||
) *ApiRequestService {
|
) *ApiRequestService {
|
||||||
return &ApiRequestService{
|
return &ApiRequestService{
|
||||||
config: c,
|
config: c,
|
||||||
@@ -60,6 +64,7 @@ func NewApiRequestService(
|
|||||||
tianyuanapi: tianyuanapi,
|
tianyuanapi: tianyuanapi,
|
||||||
tianyuanapiCallLogService: tianyuanapiCallLogService,
|
tianyuanapiCallLogService: tianyuanapiCallLogService,
|
||||||
whitelistService: whitelistService,
|
whitelistService: whitelistService,
|
||||||
|
authService: authService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +77,8 @@ type APIResponseData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessRequests 处理请求
|
// ProcessRequests 处理请求
|
||||||
func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) {
|
// orderNo: 当前查询订单号,供异步车辆类接口生成 return_url 回调地址
|
||||||
|
func (a *ApiRequestService) ProcessRequests(params []byte, productID string, orderNo string) ([]byte, error) {
|
||||||
var ctx, cancel = context.WithCancel(context.Background())
|
var ctx, cancel = context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -109,6 +115,25 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]
|
|||||||
if len(featureList) == 0 {
|
if len(featureList) == 0 {
|
||||||
return nil, errors.New("处理请求错误,产品无对应接口功能")
|
return nil, errors.New("处理请求错误,产品无对应接口功能")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在原始 params 上附加 order_no,供异步车辆类接口自动生成回调地址
|
||||||
|
var baseParams map[string]interface{}
|
||||||
|
if err := json.Unmarshal(params, &baseParams); err != nil {
|
||||||
|
logx.Errorf("解析查询参数失败, Params: %s, Error: %v", string(params), err)
|
||||||
|
return nil, fmt.Errorf("解析查询参数失败: %w", err)
|
||||||
|
}
|
||||||
|
if mobile, exists := baseParams["mobile"]; exists {
|
||||||
|
baseParams["mobile_no"] = mobile
|
||||||
|
}
|
||||||
|
if orderNo != "" {
|
||||||
|
baseParams["order_no"] = orderNo
|
||||||
|
}
|
||||||
|
paramsWithOrder, err := json.Marshal(baseParams)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("序列化查询参数失败, Params: %s, Error: %v", string(params), err)
|
||||||
|
return nil, fmt.Errorf("序列化查询参数失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
resultsCh = make(chan APIResponseData, len(featureList))
|
resultsCh = make(chan APIResponseData, len(featureList))
|
||||||
@@ -149,7 +174,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]
|
|||||||
tryCount := 0
|
tryCount := 0
|
||||||
for {
|
for {
|
||||||
tryCount++
|
tryCount++
|
||||||
resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId)
|
resp, preprocessErr = a.PreprocessRequestApi(paramsWithOrder, feature.ApiId)
|
||||||
if preprocessErr == nil {
|
if preprocessErr == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -221,7 +246,21 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
|
|||||||
"QYGL6F2D": (*ApiRequestService).ProcessQYGL6F2DRequest,
|
"QYGL6F2D": (*ApiRequestService).ProcessQYGL6F2DRequest,
|
||||||
"JRZQ8203": (*ApiRequestService).ProcessJRZQ8203Request,
|
"JRZQ8203": (*ApiRequestService).ProcessJRZQ8203Request,
|
||||||
"JRZQ4AA8": (*ApiRequestService).ProcessJRZQ4AA8Request,
|
"JRZQ4AA8": (*ApiRequestService).ProcessJRZQ4AA8Request,
|
||||||
|
"QCXGGB2Q": (*ApiRequestService).ProcessQCXGGB2QRequest,
|
||||||
|
"QCXGYTS2": (*ApiRequestService).ProcessQCXGYTS2Request,
|
||||||
|
"QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest,
|
||||||
"QCXG7A2B": (*ApiRequestService).ProcessQCXG7A2BRequest,
|
"QCXG7A2B": (*ApiRequestService).ProcessQCXG7A2BRequest,
|
||||||
|
"QCXG9P1C": (*ApiRequestService).ProcessQCXG9P1CFRequest,
|
||||||
|
"QCXG4D2E": (*ApiRequestService).ProcessQCXG4D2ERequest,
|
||||||
|
"QCXG5U0Z": (*ApiRequestService).ProcessQCXG5U0ZRequest,
|
||||||
|
"QCXG1U4U": (*ApiRequestService).ProcessQCXG1U4URequest,
|
||||||
|
"QCXGY7F2": (*ApiRequestService).ProcessQCXGY7F2Request,
|
||||||
|
"QCXG1H7Y": (*ApiRequestService).ProcessQCXG1H7YRequest,
|
||||||
|
"QCXG4I1Z": (*ApiRequestService).ProcessQCXG4I1ZRequest,
|
||||||
|
"QCXG3Y6B": (*ApiRequestService).ProcessQCXG3Y6BRequest,
|
||||||
|
"QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest,
|
||||||
|
"QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest,
|
||||||
|
"QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest,
|
||||||
"DWBG8B4D": (*ApiRequestService).ProcessDWBG8B4DRequest,
|
"DWBG8B4D": (*ApiRequestService).ProcessDWBG8B4DRequest,
|
||||||
"DWBG6A2C": (*ApiRequestService).ProcessDWBG6A2CRequest,
|
"DWBG6A2C": (*ApiRequestService).ProcessDWBG6A2CRequest,
|
||||||
"JRZQ4B6C": (*ApiRequestService).ProcessJRZQ4B6CRequest,
|
"JRZQ4B6C": (*ApiRequestService).ProcessJRZQ4B6CRequest,
|
||||||
@@ -238,6 +277,7 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
|
|||||||
"IVYZ8I9J": (*ApiRequestService).ProcessIVYZ8I9JRequest,
|
"IVYZ8I9J": (*ApiRequestService).ProcessIVYZ8I9JRequest,
|
||||||
"JRZQ7F1A": (*ApiRequestService).ProcessJRZQ7F1ARequest,
|
"JRZQ7F1A": (*ApiRequestService).ProcessJRZQ7F1ARequest,
|
||||||
"IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest,
|
"IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest,
|
||||||
|
"IVYZ4Y27": (*ApiRequestService).ProcessIVYZ4Y27Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreprocessRequestApi 调用指定的请求处理函数
|
// PreprocessRequestApi 调用指定的请求处理函数
|
||||||
@@ -1122,24 +1162,290 @@ func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error
|
|||||||
return nil, fmt.Errorf("响应code错误%s", code.String())
|
return nil, fmt.Errorf("响应code错误%s", code.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessQCXGGB2QRequest 人车核验简版
|
||||||
|
func (a *ApiRequestService) ProcessQCXGGB2QRequest(params []byte) ([]byte, error) {
|
||||||
|
plateNo := gjson.GetBytes(params, "plate_no")
|
||||||
|
carplateType := gjson.GetBytes(params, "carplate_type")
|
||||||
|
name := gjson.GetBytes(params, "name")
|
||||||
|
if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() {
|
||||||
|
return nil, errors.New("api请求, QCXGGB2Q, 获取相关参数失败")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXGGB2Q", map[string]interface{}{
|
||||||
|
"plate_no": plateNo.String(),
|
||||||
|
"carplate_type": carplateType.String(),
|
||||||
|
"name": name.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessQCXGYTS2Request 人车核验详版
|
||||||
|
func (a *ApiRequestService) ProcessQCXGYTS2Request(params []byte) ([]byte, error) {
|
||||||
|
plateNo := gjson.GetBytes(params, "plate_no")
|
||||||
|
carplateType := gjson.GetBytes(params, "carplate_type")
|
||||||
|
name := gjson.GetBytes(params, "name")
|
||||||
|
if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() {
|
||||||
|
return nil, errors.New("api请求, QCXGYTS2, 获取相关参数失败")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXGYTS2", map[string]interface{}{
|
||||||
|
"plate_no": plateNo.String(),
|
||||||
|
"carplate_type": carplateType.String(),
|
||||||
|
"name": name.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessQCXG5F3ARequest 名下车辆(车牌)
|
||||||
|
func (a *ApiRequestService) ProcessQCXG5F3ARequest(params []byte) ([]byte, error) {
|
||||||
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
|
name := gjson.GetBytes(params, "name")
|
||||||
|
if !idCard.Exists() || !name.Exists() {
|
||||||
|
return nil, errors.New("api请求, QCXG5F3A, 获取相关参数失败")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG5F3A", map[string]interface{}{
|
||||||
|
"id_card": idCard.String(),
|
||||||
|
"name": name.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessQCXG7A2BRequest 名下车辆
|
// ProcessQCXG7A2BRequest 名下车辆
|
||||||
func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) {
|
||||||
idCard := gjson.GetBytes(params, "id_card")
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
if !idCard.Exists() {
|
if !idCard.Exists() {
|
||||||
return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败")
|
return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{
|
resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{
|
||||||
"id_card": idCard.String(),
|
"id_card": idCard.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertTianyuanResponse(resp)
|
return convertTianyuanResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessQCXG9P1CFRequest 名下车辆车牌查询 A
|
||||||
|
func (a *ApiRequestService) ProcessQCXG9P1CFRequest(params []byte) ([]byte, error) {
|
||||||
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
|
name := gjson.GetBytes(params, "name")
|
||||||
|
mobile := gjson.GetBytes(params, "mobile")
|
||||||
|
if !idCard.Exists() || !name.Exists() || !mobile.Exists() {
|
||||||
|
return nil, errors.New("api请求, QCXG9P1C, 获取相关参数失败")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG9P1C", map[string]interface{}{
|
||||||
|
"id_card": idCard.String(),
|
||||||
|
"authorized": "1",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG4D2ERequest(params []byte) ([]byte, error) {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(params, &m); err != nil {
|
||||||
|
return nil, fmt.Errorf("api请求, QCXG4D2E, 解析参数失败: %w", err)
|
||||||
|
}
|
||||||
|
body := map[string]interface{}{}
|
||||||
|
if v, ok := m["user_type"].(string); ok && v != "" {
|
||||||
|
body["user_type"] = v
|
||||||
|
} else {
|
||||||
|
body["user_type"] = "1"
|
||||||
|
}
|
||||||
|
if v, ok := m["id_card"].(string); ok {
|
||||||
|
body["id_card"] = v
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("api请求, QCXG4D2E, 缺少 id_card")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG4D2E", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG5U0ZRequest(params []byte) ([]byte, error) {
|
||||||
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
|
if !vin.Exists() || vin.String() == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG5U0Z, 缺少 vin_code")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG5U0Z", map[string]interface{}{"vin_code": vin.String()})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) {
|
||||||
|
body := buildVehicleBody(params, []string{"vin_code", "image_url"}, nil)
|
||||||
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
|
if body["vin_code"] == nil || body["image_url"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/image_url/order_no")
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXG1U4U, order_no=%s, vin_code=%v, image_url=%v", orderNo, body["vin_code"], body["image_url"])
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG1U4U")
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXGY7F2Request(params []byte) ([]byte, error) {
|
||||||
|
body := buildVehicleBody(params, []string{"vin_code", "vehicle_location", "first_registrationdate"}, nil)
|
||||||
|
if body["vin_code"] == nil || body["vehicle_location"] == nil || body["first_registrationdate"] == nil {
|
||||||
|
return nil, errors.New("api请求, QCXGY7F2, 缺少必填参数 vin_code/vehicle_location/first_registrationdate")
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXGY7F2, vin_code=%v, vehicle_location=%v, first_registrationdate=%v", body["vin_code"], body["vehicle_location"], body["first_registrationdate"])
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXGY7F2", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG1H7YRequest(params []byte) ([]byte, error) {
|
||||||
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
|
plate := gjson.GetBytes(params, "car_license")
|
||||||
|
if !vin.Exists() || vin.String() == "" || !plate.Exists() || plate.String() == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG1H7Y, 缺少 vin_code 或 car_license")
|
||||||
|
}
|
||||||
|
body := map[string]interface{}{
|
||||||
|
"vin_code": vin.String(),
|
||||||
|
"plate_no": plate.String(),
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXG1H7Y, vin_code=%s, plate_no=%s", vin.String(), plate.String())
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG1H7Y", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG4I1ZRequest(params []byte) ([]byte, error) {
|
||||||
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
|
if !vin.Exists() || vin.String() == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG4I1Z, 缺少 vin_code")
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG4I1Z", map[string]interface{}{"vin_code": vin.String()})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) {
|
||||||
|
body := buildVehicleBody(params, []string{"vin_code"}, nil)
|
||||||
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
|
if body["vin_code"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/order_no")
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXG3Y6B, order_no=%s, vin_code=%v", orderNo, body["vin_code"])
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Y6B")
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) {
|
||||||
|
body := buildVehicleBody(params, []string{"vin_code"}, nil)
|
||||||
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
|
if body["vin_code"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/order_no")
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXG3Z3L, order_no=%s, vin_code=%v", orderNo, body["vin_code"])
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Z3L")
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) {
|
||||||
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
|
vlphoto := gjson.GetBytes(params, "vlphoto_data")
|
||||||
|
if !vin.Exists() || vin.String() == "" || orderNo == "" || !vlphoto.Exists() || vlphoto.String() == "" {
|
||||||
|
return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/order_no/vlphoto_data")
|
||||||
|
}
|
||||||
|
logx.Infof("vehicle api request QCXGP00W, order_no=%s, vin_code=%s, vlphoto_data_len=%d", orderNo, vin.String(), len(vlphoto.String()))
|
||||||
|
key, err := hex.DecodeString(a.config.Encrypt.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("api请求, QCXGP00W, 密钥解析失败: %w", err)
|
||||||
|
}
|
||||||
|
encData, err := crypto.AesEncrypt([]byte(vlphoto.String()), key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("api请求, QCXGP00W, 加密行驶证数据失败: %w", err)
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{
|
||||||
|
"vin_code": vin.String(),
|
||||||
|
"return_url": a.buildVehicleCallbackURL(orderNo, "QCXGP00W"),
|
||||||
|
"data": encData,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) {
|
||||||
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
|
if !vin.Exists() || vin.String() == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code")
|
||||||
|
}
|
||||||
|
auth := gjson.GetBytes(params, "authorized").String()
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{
|
||||||
|
"vin_code": vin.String(),
|
||||||
|
"authorized": auth,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body
|
||||||
|
func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} {
|
||||||
|
body := make(map[string]interface{})
|
||||||
|
for _, k := range required {
|
||||||
|
v := gjson.GetBytes(params, k)
|
||||||
|
if v.Exists() {
|
||||||
|
body[k] = v.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, k := range optional {
|
||||||
|
v := gjson.GetBytes(params, k)
|
||||||
|
if v.Exists() && v.String() != "" {
|
||||||
|
body[k] = v.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildVehicleCallbackURL 生成车辆类接口的异步回调地址
|
||||||
|
func (a *ApiRequestService) buildVehicleCallbackURL(orderNo, apiID string) string {
|
||||||
|
base := strings.TrimRight(a.config.Promotion.OfficialDomain, "/")
|
||||||
|
if base == "" {
|
||||||
|
return fmt.Sprintf("/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", orderNo, apiID)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", base, orderNo, apiID)
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessYYSY09CDRequest 三要素
|
// ProcessYYSY09CDRequest 三要素
|
||||||
func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) {
|
||||||
name := gjson.GetBytes(params, "name")
|
name := gjson.GetBytes(params, "name")
|
||||||
@@ -1602,6 +1908,35 @@ func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error
|
|||||||
"name": name.String(),
|
"name": name.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessIVYZ4Y27Request 学历信息查询(需生成专用授权书PDF并Base64编码传入)
|
||||||
|
func (a *ApiRequestService) ProcessIVYZ4Y27Request(params []byte) ([]byte, error) {
|
||||||
|
name := gjson.GetBytes(params, "name")
|
||||||
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
|
if !name.Exists() || !idCard.Exists() {
|
||||||
|
return nil, errors.New("api请求, IVYZ4Y27, 获取相关参数失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成专用授权书PDF并Base64编码
|
||||||
|
userInfo := map[string]interface{}{
|
||||||
|
"name": name.String(),
|
||||||
|
"id_card": idCard.String(),
|
||||||
|
}
|
||||||
|
authFileBase64, err := a.authService.GenerateIVYZ4Y27AuthorizationBase64(userInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("生成IVYZ4Y27授权书失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.tianyuanapi.CallInterface("IVYZ4Y27", map[string]interface{}{
|
||||||
|
"name": name.String(),
|
||||||
|
"id_card": idCard.String(),
|
||||||
|
"auth_authorize_file_base64": authFileBase64,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
1984
app/main/api/internal/service/apirequestService2.md
Normal file
1984
app/main/api/internal/service/apirequestService2.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,11 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"qnc-server/app/main/api/internal/config"
|
"qnc-server/app/main/api/internal/config"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -223,3 +225,149 @@ func getUserInfoString(userInfo map[string]interface{}, key string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generatePDFFromTemplate 从模板文件生成PDF内容
|
||||||
|
func (s *AuthorizationService) generatePDFFromTemplate(templatePath string, data map[string]string) ([]byte, error) {
|
||||||
|
// 查找模板文件
|
||||||
|
tmplPaths := []string{
|
||||||
|
templatePath,
|
||||||
|
"/app/" + templatePath,
|
||||||
|
"app/main/api/" + templatePath,
|
||||||
|
"../../" + templatePath,
|
||||||
|
"../../../" + templatePath,
|
||||||
|
"../../../../" + templatePath,
|
||||||
|
"../../../../../" + templatePath,
|
||||||
|
"../../static/" + filepath.Base(templatePath),
|
||||||
|
"../../../static/" + filepath.Base(templatePath),
|
||||||
|
"../../../../static/" + filepath.Base(templatePath),
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmplFile string
|
||||||
|
for _, p := range tmplPaths {
|
||||||
|
if _, err := os.Stat(p); err == nil {
|
||||||
|
tmplFile = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmplFile == "" {
|
||||||
|
return nil, fmt.Errorf("未找到模板文件: %s", templatePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析模板文件
|
||||||
|
tmpl, err := template.ParseFiles(tmplFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "解析模板文件失败: %s", tmplFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
var contentBuf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&contentBuf, data); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "渲染模板失败")
|
||||||
|
}
|
||||||
|
renderedContent := contentBuf.String()
|
||||||
|
|
||||||
|
// 创建PDF文档
|
||||||
|
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||||
|
|
||||||
|
// 加载中文字体
|
||||||
|
fontPaths := []string{
|
||||||
|
"static/SIMHEI.TTF",
|
||||||
|
"/app/static/SIMHEI.TTF",
|
||||||
|
"app/main/api/static/SIMHEI.TTF",
|
||||||
|
"../../static/SIMHEI.TTF",
|
||||||
|
"../../../static/SIMHEI.TTF",
|
||||||
|
"../../../../static/SIMHEI.TTF",
|
||||||
|
}
|
||||||
|
|
||||||
|
fontAdded := false
|
||||||
|
for _, fontPath := range fontPaths {
|
||||||
|
if _, err := os.Stat(fontPath); err == nil {
|
||||||
|
pdf.AddUTF8Font("ChineseFont", "", fontPath)
|
||||||
|
fontAdded = true
|
||||||
|
logx.Infof("成功加载字体: %s", fontPath)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
logx.Debugf("字体文件不存在: %s, 错误: %v", fontPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fontAdded {
|
||||||
|
pdf.SetFont("Arial", "", 11)
|
||||||
|
logx.Errorf("未找到中文字体文件,使用默认Arial字体,可能无法正确显示中文")
|
||||||
|
} else {
|
||||||
|
pdf.SetFont("ChineseFont", "", 11)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置每页Footer回调,用于在每页添加水印
|
||||||
|
companyName := data["CompanyName"]
|
||||||
|
if companyName == "" {
|
||||||
|
companyName = "海南海宇大数据科技有限公司"
|
||||||
|
}
|
||||||
|
capturedFontAdded := fontAdded
|
||||||
|
capturedCompanyName := companyName
|
||||||
|
pdf.SetFooterFunc(func() {
|
||||||
|
addWatermarkToPage(pdf, capturedFontAdded, capturedCompanyName)
|
||||||
|
})
|
||||||
|
|
||||||
|
pdf.AddPage()
|
||||||
|
|
||||||
|
// 写入渲染后的内容
|
||||||
|
pdf.MultiCell(0, 6, renderedContent, "", "", false)
|
||||||
|
|
||||||
|
// 输出PDF字节
|
||||||
|
var pdfBuf bytes.Buffer
|
||||||
|
if err := pdf.Output(&pdfBuf); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "生成PDF字节数组失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdfBuf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWatermarkToPage 给当前页面添加水印(红色公司名,居中单个水印)
|
||||||
|
func addWatermarkToPage(pdf *gofpdf.Fpdf, fontAdded bool, companyName string) {
|
||||||
|
if fontAdded {
|
||||||
|
pdf.SetFont("ChineseFont", "", 40)
|
||||||
|
} else {
|
||||||
|
pdf.SetFont("Arial", "", 40)
|
||||||
|
}
|
||||||
|
pdf.SetTextColor(255, 200, 200) // 浅红色
|
||||||
|
|
||||||
|
// A4页面尺寸 210x297mm,水印放在页面正中央
|
||||||
|
pageWidth, pageHeight := pdf.GetPageSize()
|
||||||
|
x := pageWidth / 2
|
||||||
|
y := pageHeight / 2
|
||||||
|
|
||||||
|
pdf.TransformBegin()
|
||||||
|
pdf.TransformRotate(45, x, y)
|
||||||
|
pdf.Text(x, y, companyName)
|
||||||
|
pdf.TransformEnd()
|
||||||
|
pdf.SetTextColor(0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateIVYZ4Y27AuthorizationBase64 生成IVYZ4Y27专用授权书PDF并返回Base64编码字符串
|
||||||
|
func (s *AuthorizationService) GenerateIVYZ4Y27AuthorizationBase64(userInfo map[string]interface{}) (string, error) {
|
||||||
|
name := getUserInfoString(userInfo, "name")
|
||||||
|
idCard := getUserInfoString(userInfo, "id_card")
|
||||||
|
if name == "" || idCard == "" {
|
||||||
|
return "", fmt.Errorf("缺少必要的用户信息(name或id_card)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建模板变量
|
||||||
|
data := map[string]string{
|
||||||
|
"CompanyName": "海南海宇大数据科技有限公司",
|
||||||
|
"Name": name,
|
||||||
|
"IdCard": idCard,
|
||||||
|
"Mobile": getUserInfoString(userInfo, "mobile"),
|
||||||
|
"Date": time.Now().Format("2006年1月2日"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从模板生成PDF
|
||||||
|
pdfBytes, err := s.generatePDFFromTemplate("static/authorization_ivyz4y27.tmpl", data)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "生成IVYZ4Y27授权书PDF失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64编码
|
||||||
|
return base64.StdEncoding.EncodeToString(pdfBytes), nil
|
||||||
|
}
|
||||||
|
|||||||
150
app/main/api/internal/service/tianxingjuhe_sdk/client.go
Normal file
150
app/main/api/internal/service/tianxingjuhe_sdk/client.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package tianxingjuhe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client 天行聚合API客户端
|
||||||
|
type Client struct {
|
||||||
|
baseURL string
|
||||||
|
key string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config 客户端配置
|
||||||
|
type Config struct {
|
||||||
|
BaseURL string // API基础URL
|
||||||
|
Key string // API密钥
|
||||||
|
Timeout int // 超时时间(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response 通用API响应结构
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"` // 状态码,200表示成功
|
||||||
|
Msg string `json:"msg"` // 返回说明
|
||||||
|
Result interface{} `json:"result"` // 返回结果集,具体内容根据接口而定
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 创建新的客户端实例
|
||||||
|
func NewClient(config Config) (*Client, error) {
|
||||||
|
if config.BaseURL == "" {
|
||||||
|
return nil, fmt.Errorf("baseURL不能为空")
|
||||||
|
}
|
||||||
|
if config.Key == "" {
|
||||||
|
return nil, fmt.Errorf("key不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
baseURL: config.BaseURL,
|
||||||
|
key: config.Key,
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: time.Duration(config.Timeout) * time.Second,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 发送GET请求
|
||||||
|
func (c *Client) Get(endpoint string, params map[string]interface{}) (*Response, error) {
|
||||||
|
// 构建完整URL
|
||||||
|
fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint)
|
||||||
|
|
||||||
|
// 添加请求参数
|
||||||
|
queryParams := url.Values{}
|
||||||
|
for key, value := range params {
|
||||||
|
queryParams.Set(key, fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加key参数
|
||||||
|
queryParams.Set("key", c.key)
|
||||||
|
|
||||||
|
// 拼接查询参数
|
||||||
|
if len(queryParams) > 0 {
|
||||||
|
fullURL = fmt.Sprintf("%s?%s", fullURL, queryParams.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP请求
|
||||||
|
req, err := http.NewRequest("GET", fullURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var apiResp Response
|
||||||
|
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post 发送POST请求
|
||||||
|
func (c *Client) Post(endpoint string, params map[string]interface{}) (*Response, error) {
|
||||||
|
// 构建完整URL
|
||||||
|
fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint)
|
||||||
|
|
||||||
|
// 添加key参数
|
||||||
|
if params == nil {
|
||||||
|
params = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
params["key"] = c.key
|
||||||
|
|
||||||
|
// 序列化请求体
|
||||||
|
requestBody, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("序列化请求体失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP请求
|
||||||
|
req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(requestBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("发送HTTP请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var apiResp Response
|
||||||
|
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiResp, nil
|
||||||
|
}
|
||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"qnc-server/app/main/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TianyuanapiCallLogService 天元API调用记录服务
|
// TianyuanapiCallLogService 天元API调用记录服务
|
||||||
|
|||||||
8285
app/main/api/internal/service/toolboxService.go
Normal file
8285
app/main/api/internal/service/toolboxService.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"qnc-server/app/main/api/internal/config"
|
"qnc-server/app/main/api/internal/config"
|
||||||
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
||||||
|
|
||||||
@@ -184,8 +185,9 @@ func (r *VerificationService) GetWechatH5OpenID(ctx context.Context, code string
|
|||||||
func (r *VerificationService) GetWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
func (r *VerificationService) GetWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
||||||
appID := r.c.WechatMini.AppID
|
appID := r.c.WechatMini.AppID
|
||||||
appSecret := r.c.WechatMini.AppSecret
|
appSecret := r.c.WechatMini.AppSecret
|
||||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code)
|
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||||
resp, err := http.Get(url)
|
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||||
|
resp, err := http.Get(apiURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"qnc-server/app/main/api/internal/config"
|
"qnc-server/app/main/api/internal/config"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
@@ -446,9 +447,10 @@ func (w *WechatPayService) GenerateOutTradeNo() string {
|
|||||||
func (w *WechatPayService) getWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
func (w *WechatPayService) getWechatMiniOpenID(ctx context.Context, code string) (string, error) {
|
||||||
appID := w.config.WechatMini.AppID
|
appID := w.config.WechatMini.AppID
|
||||||
appSecret := w.config.WechatMini.AppSecret
|
appSecret := w.config.WechatMini.AppSecret
|
||||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code)
|
apiURL := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||||
|
url.QueryEscape(appID), url.QueryEscape(appSecret), url.QueryEscape(code))
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(apiURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("请求微信API失败: %v", err)
|
return "", fmt.Errorf("请求微信API失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
37
app/main/api/internal/service/xpayProductMapping.go
Normal file
37
app/main/api/internal/service/xpayProductMapping.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
// XpayProductIdMap 微信道具 productId(QCXG*)→ 本系统 product_en(toc_*)
|
||||||
|
// mp 后台道具 id 须与左侧 QCXG 编码一致;支付签名 productId 使用 QCXG,业务订单仍用 product_en。
|
||||||
|
var XpayProductIdMap = map[string]string{
|
||||||
|
"QCXG6B4E": "toc_VehicleClaimVerify",
|
||||||
|
"QCXGP00W": "toc_VehicleClaimDetail",
|
||||||
|
"QCXG3Z3L": "toc_VehicleMaintenanceDetail",
|
||||||
|
"QCXG3Y6B": "toc_VehicleMaintenanceSimple",
|
||||||
|
"QCXG4I1Z": "toc_VehicleTransferDetail",
|
||||||
|
"QCXG1H7Y": "toc_VehicleTransferSimple",
|
||||||
|
"QCXGY7F2": "toc_VehicleVinValuation",
|
||||||
|
"QCXG1U4U": "toc_VehicleMileageMixed",
|
||||||
|
"QCXG5U0Z": "toc_VehicleStaticInfo",
|
||||||
|
"QCXG4D2E": "toc_VehiclesUnderNameCount",
|
||||||
|
"QCXG9P1C": "toc_VehiclesUnderNamePlate",
|
||||||
|
"QCXGYTS2": "toc_PersonVehicleVerificationDetail",
|
||||||
|
"QCXGGB2Q": "toc_PersonVehicleVerification",
|
||||||
|
"QCXG7A2B": "toc_VehiclesUnderName",
|
||||||
|
}
|
||||||
|
|
||||||
|
var xpayProductEnToId map[string]string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
xpayProductEnToId = make(map[string]string, len(XpayProductIdMap))
|
||||||
|
for xpayID, productEn := range XpayProductIdMap {
|
||||||
|
xpayProductEnToId[productEn] = xpayID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveXpayProductId 将 product_en 转为 mp 道具 id;未配置时回退 product_en 本身。
|
||||||
|
func ResolveXpayProductId(productEn string) string {
|
||||||
|
if id, ok := xpayProductEnToId[productEn]; ok {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return productEn
|
||||||
|
}
|
||||||
470
app/main/api/internal/service/xpayService.go
Normal file
470
app/main/api/internal/service/xpayService.go
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"qnc-server/app/main/api/internal/config"
|
||||||
|
"qnc-server/app/main/model"
|
||||||
|
"qnc-server/pkg/lzkit/lzUtils"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xpaySessionKeyFmt = "qnc:wx:xpay:session:%s"
|
||||||
|
xpayNotifiedKeyFmt = "qnc:xpay:notified:%s"
|
||||||
|
xpayAccessTokenKey = "qnc:wx:xpay:access_token"
|
||||||
|
xpayMode = "short_series_goods"
|
||||||
|
xpayVirtualPaymentURI = "requestVirtualPayment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XpayPrepayData 前端 wx.requestVirtualPayment 参数
|
||||||
|
type XpayPrepayData struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
SignData string `json:"signData"`
|
||||||
|
PaySig string `json:"paySig"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XpayService struct {
|
||||||
|
config config.Config
|
||||||
|
redis *redis.Redis
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXpayService(c config.Config, r *redis.Redis) *XpayService {
|
||||||
|
return &XpayService{config: c, redis: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) Enabled() bool {
|
||||||
|
return s.config.WechatXpay.Enabled && s.config.WechatXpay.OfferId != "" && s.config.WechatXpay.AppKey != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) Env() int {
|
||||||
|
return s.config.WechatXpay.Env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) SessionKeyRedisKey(userID string) string {
|
||||||
|
return fmt.Sprintf(xpaySessionKeyFmt, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) SaveSessionKey(ctx context.Context, userID, sessionKey string) error {
|
||||||
|
ttl := int(s.config.WechatXpay.SessionKeyTTL)
|
||||||
|
if ttl <= 0 {
|
||||||
|
ttl = int(s.config.JwtAuth.AccessExpire)
|
||||||
|
}
|
||||||
|
return s.redis.SetexCtx(ctx, s.SessionKeyRedisKey(userID), sessionKey, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) GetSessionKey(ctx context.Context, userID string) (string, error) {
|
||||||
|
val, err := s.redis.GetCtx(ctx, s.SessionKeyRedisKey(userID))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if val == "" {
|
||||||
|
return "", redis.Nil
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hmacSHA256Hex(key, data string) string {
|
||||||
|
mac := hmac.New(sha256.New, []byte(key))
|
||||||
|
mac.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(mac.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactJSON(v interface{}) (string, error) {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type signDataPayload struct {
|
||||||
|
OfferId string `json:"offerId"`
|
||||||
|
BuyQuantity int `json:"buyQuantity"`
|
||||||
|
Env int `json:"env"`
|
||||||
|
CurrencyType string `json:"currencyType"`
|
||||||
|
ProductId string `json:"productId"`
|
||||||
|
GoodsPrice int64 `json:"goodsPrice"`
|
||||||
|
OutTradeNo string `json:"outTradeNo"`
|
||||||
|
Attach string `json:"attach"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPayParams 构造虚拟支付双签参数
|
||||||
|
func (s *XpayService) BuildPayParams(ctx context.Context, userID, orderNo, productEn string, amount float64) (*XpayPrepayData, error) {
|
||||||
|
sessionKey, err := s.GetSessionKey(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
if err == redis.Nil {
|
||||||
|
return nil, fmt.Errorf("微信会话已过期,请重新打开小程序")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("获取微信会话失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
goodsPrice := lzUtils.ToWechatAmount(amount)
|
||||||
|
xpayProductID := ResolveXpayProductId(productEn)
|
||||||
|
payload := signDataPayload{
|
||||||
|
OfferId: s.config.WechatXpay.OfferId,
|
||||||
|
BuyQuantity: 1,
|
||||||
|
Env: s.Env(),
|
||||||
|
CurrencyType: "CNY",
|
||||||
|
ProductId: xpayProductID,
|
||||||
|
GoodsPrice: goodsPrice,
|
||||||
|
OutTradeNo: orderNo,
|
||||||
|
Attach: fmt.Sprintf("query:%s", orderNo),
|
||||||
|
}
|
||||||
|
signData, err := compactJSON(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appKey := s.config.WechatXpay.AppKey
|
||||||
|
paySig := hmacSHA256Hex(appKey, xpayVirtualPaymentURI+"&"+signData)
|
||||||
|
signature := hmacSHA256Hex(sessionKey, signData)
|
||||||
|
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] create user=%s order_no=%s env=%d product_en=%s xpay_product_id=%s goodsPrice=%d",
|
||||||
|
userID, orderNo, s.Env(), productEn, xpayProductID, goodsPrice)
|
||||||
|
|
||||||
|
return &XpayPrepayData{
|
||||||
|
Provider: "xpay",
|
||||||
|
Mode: xpayMode,
|
||||||
|
SignData: signData,
|
||||||
|
PaySig: paySig,
|
||||||
|
Signature: signature,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) getAccessToken(ctx context.Context) (string, error) {
|
||||||
|
cached, err := s.redis.GetCtx(ctx, xpayAccessTokenKey)
|
||||||
|
if err == nil && cached != "" {
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
appID := s.config.WechatMini.AppID
|
||||||
|
secret := s.config.WechatMini.AppSecret
|
||||||
|
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, secret)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var tokenResp struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
ErrCode int `json:"errcode"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if tokenResp.ErrCode != 0 || tokenResp.AccessToken == "" {
|
||||||
|
return "", fmt.Errorf("获取 access_token 失败: errcode=%d errmsg=%s", tokenResp.ErrCode, tokenResp.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl := tokenResp.ExpiresIn - 300
|
||||||
|
if ttl < 60 {
|
||||||
|
ttl = 60
|
||||||
|
}
|
||||||
|
_ = s.redis.SetexCtx(ctx, xpayAccessTokenKey, tokenResp.AccessToken, ttl)
|
||||||
|
return tokenResp.AccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type xpayAPIResp struct {
|
||||||
|
ErrCode int `json:"errcode"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
Order json.RawMessage `json:"order"` // query_order 官方返回 order 字段
|
||||||
|
}
|
||||||
|
|
||||||
|
type xpayOrderStatus struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
PaidFee int64 `json:"paid_fee"`
|
||||||
|
LeftFee int64 `json:"left_fee"`
|
||||||
|
OrderID string `json:"order_id"`
|
||||||
|
WxOrderID string `json:"wx_order_id"`
|
||||||
|
ErrMsg string `json:"err_msg"`
|
||||||
|
RawOrder string `json:"-"` // query_order 原始 order JSON,便于日志排查
|
||||||
|
}
|
||||||
|
|
||||||
|
// XpayAPIError 微信 xpay 接口返回 errcode != 0
|
||||||
|
type XpayAPIError struct {
|
||||||
|
URI string
|
||||||
|
ErrCode int
|
||||||
|
ErrMsg string
|
||||||
|
RawBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *XpayAPIError) Error() string {
|
||||||
|
return fmt.Sprintf("xpay %s errcode=%d errmsg=%s body=%s", e.URI, e.ErrCode, e.ErrMsg, e.RawBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) callAPI(ctx context.Context, uri, bodyJSON, sessionKey string, needSignature bool) (*xpayAPIResp, error) {
|
||||||
|
accessToken, err := s.getAccessToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appKey := s.config.WechatXpay.AppKey
|
||||||
|
paySig := hmacSHA256Hex(appKey, uri+"&"+bodyJSON)
|
||||||
|
|
||||||
|
reqURL := fmt.Sprintf("https://api.weixin.qq.com%s?access_token=%s&pay_sig=%s", uri, accessToken, paySig)
|
||||||
|
if needSignature && sessionKey != "" {
|
||||||
|
sig := hmacSHA256Hex(sessionKey, bodyJSON)
|
||||||
|
reqURL += "&signature=" + sig
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewReader([]byte(bodyJSON)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] req uri=%s env=%d body=%s", uri, s.Env(), bodyJSON)
|
||||||
|
|
||||||
|
httpResp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] http err uri=%s err=%v", uri, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer httpResp.Body.Close()
|
||||||
|
respBody, _ := io.ReadAll(httpResp.Body)
|
||||||
|
respStr := string(respBody)
|
||||||
|
|
||||||
|
var apiResp xpayAPIResp
|
||||||
|
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] parse err uri=%s http=%d body=%s err=%v", uri, httpResp.StatusCode, respStr, err)
|
||||||
|
return nil, fmt.Errorf("解析 xpay 响应失败: %w, body=%s", err, respStr)
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] resp uri=%s http=%d errcode=%d errmsg=%s body=%s",
|
||||||
|
uri, httpResp.StatusCode, apiResp.ErrCode, apiResp.ErrMsg, respStr)
|
||||||
|
if apiResp.ErrCode != 0 {
|
||||||
|
return &apiResp, &XpayAPIError{
|
||||||
|
URI: uri,
|
||||||
|
ErrCode: apiResp.ErrCode,
|
||||||
|
ErrMsg: apiResp.ErrMsg,
|
||||||
|
RawBody: respStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &apiResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOrder 调微信 query_order
|
||||||
|
func (s *XpayService) QueryOrder(ctx context.Context, openid, orderNo, sessionKey string) (*xpayOrderStatus, error) {
|
||||||
|
bodyObj := map[string]interface{}{
|
||||||
|
"openid": openid,
|
||||||
|
"env": s.Env(),
|
||||||
|
"order_id": orderNo,
|
||||||
|
}
|
||||||
|
bodyJSON, err := compactJSON(bodyObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 官方文档:query_order 仅需 pay_sig,不需用户态 signature
|
||||||
|
apiResp, err := s.callAPI(ctx, "/xpay/query_order", bodyJSON, "", false)
|
||||||
|
if err != nil {
|
||||||
|
if xpayErr, ok := err.(*XpayAPIError); ok {
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] query_order FAIL order_no=%s openid=%s errcode=%d errmsg=%s raw=%s",
|
||||||
|
orderNo, openid, xpayErr.ErrCode, xpayErr.ErrMsg, xpayErr.RawBody)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orderRaw := apiResp.Order
|
||||||
|
if len(orderRaw) == 0 {
|
||||||
|
orderRaw = apiResp.Data
|
||||||
|
}
|
||||||
|
var status xpayOrderStatus
|
||||||
|
if len(orderRaw) > 0 {
|
||||||
|
status.RawOrder = string(orderRaw)
|
||||||
|
_ = json.Unmarshal(orderRaw, &status)
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] query_order order_no=%s status=%d paid_fee=%d left_fee=%d wx_order_id=%s err_msg=%s",
|
||||||
|
orderNo, status.Status, status.PaidFee, status.LeftFee, status.WxOrderID, status.ErrMsg)
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefundOrder 启动 xpay 退款任务(全额退)
|
||||||
|
func (s *XpayService) RefundOrder(ctx context.Context, openid, orderNo, refundOrderID string, refundFeeFen int64) error {
|
||||||
|
if refundFeeFen <= 0 {
|
||||||
|
return fmt.Errorf("退款金额无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := s.QueryOrder(ctx, openid, orderNo, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("退款前查单失败: %w", err)
|
||||||
|
}
|
||||||
|
if IsXpayAlreadyRefunded(status) {
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] refund_order skip already refunded order_no=%s wx_status=%d left_fee=%d paid_fee=%d",
|
||||||
|
orderNo, status.Status, status.LeftFee, status.PaidFee)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
leftFee := status.LeftFee
|
||||||
|
if leftFee <= 0 {
|
||||||
|
leftFee = status.PaidFee
|
||||||
|
}
|
||||||
|
if leftFee <= 0 {
|
||||||
|
leftFee = refundFeeFen
|
||||||
|
}
|
||||||
|
if refundFeeFen > leftFee {
|
||||||
|
refundFeeFen = leftFee
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyObj := map[string]interface{}{
|
||||||
|
"openid": openid,
|
||||||
|
"env": s.Env(),
|
||||||
|
"order_id": orderNo,
|
||||||
|
"refund_order_id": refundOrderID,
|
||||||
|
"left_fee": leftFee,
|
||||||
|
"refund_fee": refundFeeFen,
|
||||||
|
"biz_meta": fmt.Sprintf("refund:%s", orderNo),
|
||||||
|
"refund_reason": 5,
|
||||||
|
"req_from": 3,
|
||||||
|
}
|
||||||
|
bodyJSON, err := compactJSON(bodyObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiResp, err := s.callAPI(ctx, "/xpay/refund_order", bodyJSON, "", false)
|
||||||
|
if err != nil {
|
||||||
|
if xpayErr, ok := err.(*XpayAPIError); ok {
|
||||||
|
if xpayErr.ErrCode == 268490004 || xpayErr.ErrCode == 268490005 || xpayErr.ErrCode == 268490014 {
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] refund_order idempotent order_no=%s errcode=%d", orderNo, xpayErr.ErrCode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] refund_order FAIL order_no=%s errcode=%d errmsg=%s raw=%s",
|
||||||
|
orderNo, xpayErr.ErrCode, xpayErr.ErrMsg, xpayErr.RawBody)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = apiResp
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] refund_order OK order_no=%s refund_order_id=%s fee=%d", orderNo, refundOrderID, refundFeeFen)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyProvideGoods 主动通知微信已发货
|
||||||
|
func (s *XpayService) NotifyProvideGoods(ctx context.Context, openid, orderNo, wxOrderID, sessionKey string) error {
|
||||||
|
bodyObj := map[string]interface{}{
|
||||||
|
"openid": openid,
|
||||||
|
"env": s.Env(),
|
||||||
|
"order_id": orderNo,
|
||||||
|
}
|
||||||
|
if wxOrderID != "" {
|
||||||
|
bodyObj["wx_order_id"] = wxOrderID
|
||||||
|
}
|
||||||
|
bodyJSON, err := compactJSON(bodyObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.callAPI(ctx, "/xpay/notify_provide_goods", bodyJSON, sessionKey, true)
|
||||||
|
if err != nil {
|
||||||
|
if xpayErr, ok := err.(*XpayAPIError); ok {
|
||||||
|
if xpayErr.ErrCode == 268490004 {
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] notify_provide_goods idempotent order_no=%s", orderNo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Errorf("[xpay] notify_provide_goods FAIL order_no=%s errcode=%d errmsg=%s raw=%s",
|
||||||
|
orderNo, xpayErr.ErrCode, xpayErr.ErrMsg, xpayErr.RawBody)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logx.WithContext(ctx).Infof("[xpay] notify_provide_goods OK order_no=%s", orderNo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsXpayPaidStatus 微信订单状态 2/3/4 视为支付成功
|
||||||
|
func IsXpayPaidStatus(status int) bool {
|
||||||
|
return status == 2 || status == 3 || status == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsXpayRefundedStatus 微信侧已退款:5=订单已经退款,8=用户退款完成
|
||||||
|
func IsXpayRefundedStatus(status int) bool {
|
||||||
|
return status == 5 || status == 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsXpayAlreadyRefunded 查单结果表示微信侧已无可退金额(含在微信后台手动退款、本地未同步的情况)
|
||||||
|
func IsXpayAlreadyRefunded(status *xpayOrderStatus) bool {
|
||||||
|
if status == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if IsXpayRefundedStatus(status.Status) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return status.PaidFee > 0 && status.LeftFee == 0 && status.Status >= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsXpayClosedStatus 6 视为关闭
|
||||||
|
func IsXpayClosedStatus(status int) bool {
|
||||||
|
return status == 6
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPushSignature GET 验签(消息推送配置)
|
||||||
|
func (s *XpayService) VerifyPushSignature(signature, timestamp, nonce string) bool {
|
||||||
|
token := s.config.WechatXpay.MessagePushToken
|
||||||
|
arr := []string{token, timestamp, nonce}
|
||||||
|
sort.Strings(arr)
|
||||||
|
hash := sha1.Sum([]byte(strings.Join(arr, "")))
|
||||||
|
return signature == hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// XpayDeliverNotify 推送消息体(明文 JSON)
|
||||||
|
type XpayDeliverNotify struct {
|
||||||
|
Event string `json:"Event"`
|
||||||
|
OpenId string `json:"OpenId"`
|
||||||
|
OutTradeNo string `json:"OutTradeNo"`
|
||||||
|
Env int `json:"Env"`
|
||||||
|
WeChatPayInfo struct {
|
||||||
|
TransactionId string `json:"TransactionId"`
|
||||||
|
PaidTime int64 `json:"PaidTime"`
|
||||||
|
} `json:"WeChatPayInfo"`
|
||||||
|
GoodsInfo struct {
|
||||||
|
ProductId string `json:"ProductId"`
|
||||||
|
Quantity int `json:"Quantity"`
|
||||||
|
OrigPrice int64 `json:"OrigPrice"`
|
||||||
|
ActualPrice int64 `json:"ActualPrice"`
|
||||||
|
Attach string `json:"Attach"`
|
||||||
|
} `json:"GoodsInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) AlreadyNotified(ctx context.Context, orderNo string) (bool, error) {
|
||||||
|
key := fmt.Sprintf(xpayNotifiedKeyFmt, orderNo)
|
||||||
|
val, err := s.redis.GetCtx(ctx, key)
|
||||||
|
if err == redis.Nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return val != "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XpayService) MarkNotified(ctx context.Context, orderNo string) error {
|
||||||
|
key := fmt.Sprintf(xpayNotifiedKeyFmt, orderNo)
|
||||||
|
return s.redis.SetexCtx(ctx, key, "1", 7*24*3600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWxMiniOpenID 查询用户小程序 openid
|
||||||
|
func (s *XpayService) GetWxMiniOpenID(ctx context.Context, userAuthModel model.UserAuthModel, userID string) (string, error) {
|
||||||
|
auth, err := userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return auth.AuthKey, nil
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"qnc-server/app/main/api/internal/config"
|
"qnc-server/app/main/api/internal/config"
|
||||||
"qnc-server/app/main/api/internal/middleware"
|
"qnc-server/app/main/api/internal/middleware"
|
||||||
"qnc-server/app/main/api/internal/service"
|
"qnc-server/app/main/api/internal/service"
|
||||||
|
tianxingjuhe "qnc-server/app/main/api/internal/service/tianxingjuhe_sdk"
|
||||||
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk"
|
||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"time"
|
"time"
|
||||||
@@ -81,6 +82,7 @@ type ServiceContext struct {
|
|||||||
ExampleModel model.ExampleModel
|
ExampleModel model.ExampleModel
|
||||||
GlobalNotificationsModel model.GlobalNotificationsModel
|
GlobalNotificationsModel model.GlobalNotificationsModel
|
||||||
AuthorizationDocumentModel model.AuthorizationDocumentModel
|
AuthorizationDocumentModel model.AuthorizationDocumentModel
|
||||||
|
InquiryRecordModel model.InquiryRecordModel
|
||||||
|
|
||||||
// 第三方服务
|
// 第三方服务
|
||||||
TianyuanapiCallLogService *service.TianyuanapiCallLogService
|
TianyuanapiCallLogService *service.TianyuanapiCallLogService
|
||||||
@@ -88,6 +90,7 @@ type ServiceContext struct {
|
|||||||
// 服务
|
// 服务
|
||||||
AlipayService *service.AliPayService
|
AlipayService *service.AliPayService
|
||||||
WechatPayService *service.WechatPayService
|
WechatPayService *service.WechatPayService
|
||||||
|
XpayService *service.XpayService
|
||||||
ApplePayService *service.ApplePayService
|
ApplePayService *service.ApplePayService
|
||||||
ApiRequestService *service.ApiRequestService
|
ApiRequestService *service.ApiRequestService
|
||||||
WhitelistService *service.WhitelistService
|
WhitelistService *service.WhitelistService
|
||||||
@@ -99,6 +102,7 @@ type ServiceContext struct {
|
|||||||
DictService *service.DictService
|
DictService *service.DictService
|
||||||
ImageService *service.ImageService
|
ImageService *service.ImageService
|
||||||
AuthorizationService *service.AuthorizationService
|
AuthorizationService *service.AuthorizationService
|
||||||
|
ToolboxService *service.ToolboxService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceContext 创建服务上下文
|
// NewServiceContext 创建服务上下文
|
||||||
@@ -170,6 +174,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
exampleModel := model.NewExampleModel(db, cacheConf)
|
exampleModel := model.NewExampleModel(db, cacheConf)
|
||||||
globalNotificationsModel := model.NewGlobalNotificationsModel(db, cacheConf)
|
globalNotificationsModel := model.NewGlobalNotificationsModel(db, cacheConf)
|
||||||
authorizationDocumentModel := model.NewAuthorizationDocumentModel(db, cacheConf)
|
authorizationDocumentModel := model.NewAuthorizationDocumentModel(db, cacheConf)
|
||||||
|
inquiryRecordModel := model.NewInquiryRecordModel(db, cacheConf)
|
||||||
tianyuanapiCallLogModel := model.NewTianyuanapiCallLogModel(db, cacheConf)
|
tianyuanapiCallLogModel := model.NewTianyuanapiCallLogModel(db, cacheConf)
|
||||||
|
|
||||||
// ============================== 第三方服务初始化 ==============================
|
// ============================== 第三方服务初始化 ==============================
|
||||||
@@ -186,10 +191,12 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
// ============================== 业务服务初始化 ==============================
|
// ============================== 业务服务初始化 ==============================
|
||||||
alipayService := service.NewAliPayService(c)
|
alipayService := service.NewAliPayService(c)
|
||||||
wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey)
|
wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey)
|
||||||
|
xpayService := service.NewXpayService(c, redisClient)
|
||||||
applePayService := service.NewApplePayService(c)
|
applePayService := service.NewApplePayService(c)
|
||||||
tianyuanapiCallLogService := service.NewTianyuanapiCallLogService(tianyuanapiCallLogModel, featureModel)
|
tianyuanapiCallLogService := service.NewTianyuanapiCallLogService(tianyuanapiCallLogModel, featureModel)
|
||||||
whitelistService := service.NewWhitelistService(c, userFeatureWhitelistModel, whitelistOrderModel, whitelistOrderItemModel, queryModel, featureModel)
|
whitelistService := service.NewWhitelistService(c, userFeatureWhitelistModel, whitelistOrderModel, whitelistOrderItemModel, queryModel, featureModel)
|
||||||
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, userFeatureWhitelistModel, tianyuanapi, tianyuanapiCallLogService, whitelistService)
|
authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel)
|
||||||
|
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, userFeatureWhitelistModel, tianyuanapi, tianyuanapiCallLogService, whitelistService, authorizationService)
|
||||||
verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService)
|
verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService)
|
||||||
asynqService := service.NewAsynqService(c)
|
asynqService := service.NewAsynqService(c)
|
||||||
agentService := service.NewAgentService(c, orderModel, agentModel, agentWalletModel,
|
agentService := service.NewAgentService(c, orderModel, agentModel, agentWalletModel,
|
||||||
@@ -199,7 +206,23 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
userService := service.NewUserService(&c, userModel, userAuthModel, agentModel)
|
userService := service.NewUserService(&c, userModel, userAuthModel, agentModel)
|
||||||
dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel)
|
dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel)
|
||||||
imageService := service.NewImageService()
|
imageService := service.NewImageService()
|
||||||
authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel)
|
|
||||||
|
tianxingjuheTimeout := c.Tianxingjuhe.Timeout
|
||||||
|
if tianxingjuheTimeout <= 0 {
|
||||||
|
tianxingjuheTimeout = 30
|
||||||
|
}
|
||||||
|
tianxingjuheClient, tjErr := tianxingjuhe.NewClient(tianxingjuhe.Config{
|
||||||
|
BaseURL: c.Tianxingjuhe.URL,
|
||||||
|
Key: c.Tianxingjuhe.Key,
|
||||||
|
Timeout: tianxingjuheTimeout,
|
||||||
|
})
|
||||||
|
if tjErr != nil {
|
||||||
|
logx.Errorf("初始化天行聚合API失败: %+v", tjErr)
|
||||||
|
}
|
||||||
|
var toolboxService *service.ToolboxService
|
||||||
|
if tianxingjuheClient != nil {
|
||||||
|
toolboxService = service.NewToolboxService(tianxingjuheClient)
|
||||||
|
}
|
||||||
|
|
||||||
// ============================== 异步任务服务 ==============================
|
// ============================== 异步任务服务 ==============================
|
||||||
asynqServer := asynq.NewServer(
|
asynqServer := asynq.NewServer(
|
||||||
@@ -278,6 +301,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
ExampleModel: exampleModel,
|
ExampleModel: exampleModel,
|
||||||
GlobalNotificationsModel: globalNotificationsModel,
|
GlobalNotificationsModel: globalNotificationsModel,
|
||||||
AuthorizationDocumentModel: authorizationDocumentModel,
|
AuthorizationDocumentModel: authorizationDocumentModel,
|
||||||
|
InquiryRecordModel: inquiryRecordModel,
|
||||||
|
|
||||||
// 第三方服务
|
// 第三方服务
|
||||||
TianyuanapiCallLogService: tianyuanapiCallLogService,
|
TianyuanapiCallLogService: tianyuanapiCallLogService,
|
||||||
@@ -285,6 +309,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
// 服务
|
// 服务
|
||||||
AlipayService: alipayService,
|
AlipayService: alipayService,
|
||||||
WechatPayService: wechatPayService,
|
WechatPayService: wechatPayService,
|
||||||
|
XpayService: xpayService,
|
||||||
ApplePayService: applePayService,
|
ApplePayService: applePayService,
|
||||||
ApiRequestService: apiRequestService,
|
ApiRequestService: apiRequestService,
|
||||||
WhitelistService: whitelistService,
|
WhitelistService: whitelistService,
|
||||||
@@ -296,6 +321,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
DictService: dictService,
|
DictService: dictService,
|
||||||
ImageService: imageService,
|
ImageService: imageService,
|
||||||
AuthorizationService: authorizationService,
|
AuthorizationService: authorizationService,
|
||||||
|
ToolboxService: toolboxService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
app/main/api/internal/types/query.go
Normal file
70
app/main/api/internal/types/query.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// ---- 车辆类产品请求结构体 ----
|
||||||
|
|
||||||
|
// TocVehiclesUnderNameCountReq 名下车辆(数量) QCXG4D2E:user_type + id_card
|
||||||
|
type TocVehiclesUnderNameCountReq struct {
|
||||||
|
UserType string `json:"user_type,optional"` // 1=个人(默认) 2=企业
|
||||||
|
IDCard string `json:"id_card" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleVinCodeReq 仅 vin_code(车辆静态信息、过户详版等)
|
||||||
|
type TocVehicleVinCodeReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleMileageMixedReq 车辆里程记录(混合) QCXG1U4U
|
||||||
|
type TocVehicleMileageMixedReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
ImageURL string `json:"image_url,optional"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleVinValuationReq 二手车VIN估值 QCXGY7F2
|
||||||
|
type TocVehicleVinValuationReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
VehicleLocation string `json:"vehicle_location" validate:"required"`
|
||||||
|
FirstRegistrationDate string `json:"first_registrationdate" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleTransferSimpleReq 车辆过户简版 QCXG1H7Y
|
||||||
|
type TocVehicleTransferSimpleReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B
|
||||||
|
type TocVehicleMaintenanceSimpleReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L
|
||||||
|
type TocVehicleMaintenanceDetailReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleClaimDetailReq 车辆出险详版 QCXGP00W
|
||||||
|
type TocVehicleClaimDetailReq struct {
|
||||||
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
|
VlphotoData string `json:"vlphoto_data" validate:"required"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TocVehicleClaimVerifyReq 车辆出险记录核验 QCXG6B4E
|
||||||
|
type TocVehicleClaimVerifyReq struct {
|
||||||
|
VINCode string `json:"vin_code" validate:"required"`
|
||||||
|
Authorized string `json:"authorized,optional"`
|
||||||
|
Mobile string `json:"mobile,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -48,12 +48,12 @@ func main() {
|
|||||||
asynq := queue.NewCronJob(ctx, svcContext)
|
asynq := queue.NewCronJob(ctx, svcContext)
|
||||||
mux := asynq.Register()
|
mux := asynq.Register()
|
||||||
|
|
||||||
// 启动 asynq 消费者
|
// Run 会阻塞;成功日志必须在 Run 之前打印
|
||||||
|
fmt.Println("异步任务启动!!!")
|
||||||
if err := svcContext.AsynqServer.Run(mux); err != nil {
|
if err := svcContext.AsynqServer.Run(mux); err != nil {
|
||||||
logx.WithContext(ctx).Errorf("异步任务启动失败: %v", err)
|
logx.WithContext(ctx).Errorf("异步任务启动失败: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("异步任务启动!!!")
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
server := rest.MustNewServer(c.RestConf)
|
server := rest.MustNewServer(c.RestConf)
|
||||||
|
|||||||
85
app/main/api/static/authorization_ivyz4y27.tmpl
Normal file
85
app/main/api/static/authorization_ivyz4y27.tmpl
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
【重要提示】为了保障您的合法权益,您在签署本授权书前,应当确保您为具有完全民事权利能力和民事行为能力的自然人,并审慎阅读、充分理解本授权书所有条款(特别是加粗字体的条款)。您在操作页面上的确认、勾选等行为或以其他方式接受即表示您已阅读并同意本授权书,本授权书随即在法律上生效并在您和{{.CompanyName}}之间产生法律约束力。
|
||||||
|
|
||||||
|
个人信息处理授权书
|
||||||
|
|
||||||
|
致:{{.CompanyName}}
|
||||||
|
|
||||||
|
本人("授权人")在此确认、同意并授权{{.CompanyName}}("贵司")及贵司授权的第三方,为本文件所列之目的,处理(指收集、整理、存储、使用、加工、分析和比对、核验、传输、提供等)本人的个人信息。
|
||||||
|
|
||||||
|
一、 授权处理的个人信息范围
|
||||||
|
|
||||||
|
1.本人同意并授权贵司处理以下类型的个人信息(无论以电子或非电子形式存在):
|
||||||
|
|
||||||
|
身份信息:包括但不限于姓名、身份证号码。
|
||||||
|
|
||||||
|
教育背景信息:包括但不限于各教育阶段的入学与毕业时间、毕业院校、专业、学位类型、学历学位证书信息、学信网验证信息等。
|
||||||
|
|
||||||
|
婚姻家庭信息:包括但不限于婚姻状况(未婚、已婚、离异等)。
|
||||||
|
|
||||||
|
工作履历信息:包括但不限于过往及当前工作单位、任职时间、职位、职责、证明人及其联系方式等。
|
||||||
|
|
||||||
|
其他与背景调查相关的信息:贵司为完成背景调查认为必要的其他信息。
|
||||||
|
|
||||||
|
2.本人知悉且同意,贵司拟处理的数据信息可能包含身份证件信息、生物识别信息等敏感信息,本人明确知悉对本人权益的影响且同意贵司处理该等信息。
|
||||||
|
|
||||||
|
二、 信息来源与查询方式
|
||||||
|
|
||||||
|
本人知晓并同意,贵司为完成背景调查,将通过以下方式查询并核实本授权书第一条所列的个人信息:
|
||||||
|
|
||||||
|
1. 通过本人或委托贵司开展背景调查的拟入职机构提供的联系方式和证明材料进行直接核实。
|
||||||
|
|
||||||
|
2. 通过贵司链接的权威数据源进行查询或验证、核实,包括但不限于政府部门或事业单位、社会团体、公用事业服务单位、第三方数据库或数据平台(如从中国高等教育学生信息网-学信网)等。本人授权贵司通过地方信用服务平台向前述数据源单位收集、处理、加工和分析本人的婚姻家庭信息、教育背景信息。
|
||||||
|
|
||||||
|
3. 通过向本人前任及现任雇主、同事、证明人等进行访谈核实。
|
||||||
|
|
||||||
|
4. 在法律允许的范围内,通过其他公开、合法的渠道进行核实。
|
||||||
|
|
||||||
|
三、 信息处理的目的与使用
|
||||||
|
|
||||||
|
本人同意贵司将本授权书项下收集的个人信息用于以下目的:
|
||||||
|
|
||||||
|
1. 为{{.CompanyName}}("委托方")对本人进行的员工招聘/录用前背景调查/持续的尽职调查提供信息核实与报告服务。
|
||||||
|
|
||||||
|
2. 基于核实的信息,生成关于本人的《背景调查报告》或《尽职调查报告》("报告"),并提供给委托方,以供其评估本人的任职资格与工作能力。
|
||||||
|
|
||||||
|
3. 贵司为内部质量控制和合规目的而对相关信息进行留存、处理与管理。
|
||||||
|
|
||||||
|
四、 信息存储与保护
|
||||||
|
|
||||||
|
贵司承诺将采取必要的技术和管理措施,保护本人个人信息的安全性与机密性,防止信息丢失、泄露、篡改或毁损。
|
||||||
|
|
||||||
|
除非法律法规另有规定或为履行本授权书目的之必要,贵司处理及存储本人个人信息的时间将在完成本次背景调查目的后12个月内,或直至委托方与本人的劳动关系确立或明确终止之时。具体留存期限届满后,贵司将依法对相关信息进行删除或匿名化处理。删除个人信息从技术上难以实现的,贵司将停止除存储和采取必要的安全保护措施之外的处理。
|
||||||
|
|
||||||
|
五、 授权转移与共享
|
||||||
|
|
||||||
|
(一)本人知晓并同意,为实现本授权书之目的,贵司可能将本人的个人信息提供给以下接收方:
|
||||||
|
|
||||||
|
1. 委托方(即本次背景调查的发起企业/拟入职机构)。
|
||||||
|
|
||||||
|
2. 为完成特定核实工作而必需的第三方合作伙伴,如数据源提供方(包括但不限于地方融资信用服务平台、政府部门或事业单位)等,但贵司应确保该等第三方受到与本授权书同等严格的保密义务约束。
|
||||||
|
|
||||||
|
六、 权利告知与行使
|
||||||
|
|
||||||
|
1.本人知悉并理解,根据相关法律法规,本人有权:
|
||||||
|
|
||||||
|
(1)查阅、复制及要求更正本人的个人信息;
|
||||||
|
|
||||||
|
(2)在满足法定条件时,要求删除个人信息或撤回本授权同意。
|
||||||
|
|
||||||
|
本人确认,撤回本授权同意将不影响撤回前基于本授权已进行的个人信息处理活动的效力。但若撤回授权,可能导致委托方无法完整评估本人的任职资格,从而可能影响本次招聘/录用结果,本人将自行承担相应后果。
|
||||||
|
|
||||||
|
2. 本人知悉:如本人对贵司或数据源单位个人信息处理活动有任何疑问、意见建议或需要依法行使权利,可通过以下联系方式进行咨询、反映或行使法定权利:
|
||||||
|
|
||||||
|
贵司:【联系方式:{{.Mobile}}】
|
||||||
|
|
||||||
|
七、 全部协议与授权效力
|
||||||
|
|
||||||
|
本授权书自本人同意之日起生效,并在上述业务办理及存续期间持续有效,至本授权书所述的所有业务终结之日止。
|
||||||
|
|
||||||
|
本人已仔细阅读并完全理解本授权书的全部内容,特别是加粗字体部分。本人的签署是基于本人的真实意愿,本人知悉且理解由此产生的法律效力及相应信息披露产生的不利后果(包括不限于第三方通过非法手段或方式获取、使用该信息,可能会给本人造成人身财产的损害,或是造成本人预期利益减少、损失扩大,或是其他不良或不利影响等),自愿作出上述授权。
|
||||||
|
|
||||||
|
|
||||||
|
授权人签署:{{.Name}}
|
||||||
|
身份证号码:{{.IdCard}}
|
||||||
|
|
||||||
|
签署日期:{{.Date}}
|
||||||
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
@@ -18,6 +21,8 @@ type (
|
|||||||
agentModel
|
agentModel
|
||||||
// UpdateInTransaction 在事务中更新刚插入的记录,避免 UpdateWithVersion 中的 FindOne 缓存问题
|
// UpdateInTransaction 在事务中更新刚插入的记录,避免 UpdateWithVersion 中的 FindOne 缓存问题
|
||||||
UpdateInTransaction(ctx context.Context, session sqlx.Session, data *Agent) error
|
UpdateInTransaction(ctx context.Context, session sqlx.Session, data *Agent) error
|
||||||
|
// CountSubordinatesByTeamLeaderIds 按团队首领 id 统计未删除的直属代理数量(team_leader_id = 传入 id)
|
||||||
|
CountSubordinatesByTeamLeaderIds(ctx context.Context, leaderIds []string) (map[string]int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
customAgentModel struct {
|
customAgentModel struct {
|
||||||
@@ -63,3 +68,36 @@ func (m *customAgentModel) UpdateInTransaction(ctx context.Context, session sqlx
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type teamLeaderSubCountRow struct {
|
||||||
|
TeamLeaderId string `db:"team_leader_id"`
|
||||||
|
Cnt int64 `db:"cnt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *customAgentModel) CountSubordinatesByTeamLeaderIds(ctx context.Context, leaderIds []string) (map[string]int64, error) {
|
||||||
|
out := make(map[string]int64)
|
||||||
|
if len(leaderIds) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
// 不含「自己挂在自己名下」的记录,否则首领会在自己的下级列表里且下级数量虚高
|
||||||
|
qb := squirrel.Select("team_leader_id", "COUNT(*) AS cnt").
|
||||||
|
From(m.table).
|
||||||
|
Where(squirrel.Eq{"del_state": globalkey.DelStateNo}).
|
||||||
|
Where(squirrel.Eq{"team_leader_id": leaderIds}).
|
||||||
|
Where("`id` <> `team_leader_id`").
|
||||||
|
GroupBy("team_leader_id")
|
||||||
|
query, args, err := qb.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var rows []teamLeaderSubCountRow
|
||||||
|
if err := m.QueryRowsNoCacheCtx(ctx, &rows, query, args...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, r := range rows {
|
||||||
|
if r.TeamLeaderId != "" {
|
||||||
|
out[r.TeamLeaderId] = r.Cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|||||||
27
app/main/model/inquiryRecordModel.go
Normal file
27
app/main/model/inquiryRecordModel.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ InquiryRecordModel = (*customInquiryRecordModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// InquiryRecordModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customInquiryRecordModel.
|
||||||
|
InquiryRecordModel interface {
|
||||||
|
inquiryRecordModel
|
||||||
|
}
|
||||||
|
|
||||||
|
customInquiryRecordModel struct {
|
||||||
|
*defaultInquiryRecordModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInquiryRecordModel returns a model for the database table.
|
||||||
|
func NewInquiryRecordModel(conn sqlx.SqlConn, c cache.CacheConf) InquiryRecordModel {
|
||||||
|
return &customInquiryRecordModel{
|
||||||
|
defaultInquiryRecordModel: newInquiryRecordModel(conn, c),
|
||||||
|
}
|
||||||
|
}
|
||||||
384
app/main/model/inquiryRecordModel_gen.go
Normal file
384
app/main/model/inquiryRecordModel_gen.go
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT!
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"qnc-server/common/globalkey"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inquiryRecordFieldNames = builder.RawFieldNames(&InquiryRecord{})
|
||||||
|
inquiryRecordRows = strings.Join(inquiryRecordFieldNames, ",")
|
||||||
|
inquiryRecordRowsExpectAutoSet = strings.Join(stringx.Remove(inquiryRecordFieldNames, "`create_time`", "`update_time`"), ",")
|
||||||
|
inquiryRecordRowsWithPlaceHolder = strings.Join(stringx.Remove(inquiryRecordFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
|
||||||
|
|
||||||
|
cacheQncInquiryRecordIdPrefix = "cache:qnc:inquiryRecord:id:"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
inquiryRecordModel interface {
|
||||||
|
Insert(ctx context.Context, session sqlx.Session, data *InquiryRecord) (sql.Result, error)
|
||||||
|
FindOne(ctx context.Context, id int64) (*InquiryRecord, error)
|
||||||
|
Update(ctx context.Context, session sqlx.Session, data *InquiryRecord) (sql.Result, error)
|
||||||
|
UpdateWithVersion(ctx context.Context, session sqlx.Session, data *InquiryRecord) error
|
||||||
|
Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error
|
||||||
|
SelectBuilder() squirrel.SelectBuilder
|
||||||
|
DeleteSoft(ctx context.Context, session sqlx.Session, data *InquiryRecord) error
|
||||||
|
FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error)
|
||||||
|
FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error)
|
||||||
|
FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*InquiryRecord, error)
|
||||||
|
FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*InquiryRecord, error)
|
||||||
|
FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*InquiryRecord, int64, error)
|
||||||
|
FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*InquiryRecord, error)
|
||||||
|
FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*InquiryRecord, error)
|
||||||
|
Delete(ctx context.Context, session sqlx.Session, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultInquiryRecordModel struct {
|
||||||
|
sqlc.CachedConn
|
||||||
|
table string
|
||||||
|
}
|
||||||
|
|
||||||
|
InquiryRecord struct {
|
||||||
|
Id int64 `db:"id"`
|
||||||
|
UserPhoneTail string `db:"user_phone_tail"` // 用户手机尾号
|
||||||
|
DisplayName string `db:"display_name"` // 展示名称
|
||||||
|
VinMasked string `db:"vin_masked"` // 脱敏VIN
|
||||||
|
CarModel string `db:"car_model"` // 车型
|
||||||
|
InquiryTag string `db:"inquiry_tag"` // 查询标签
|
||||||
|
Status int64 `db:"status"` // 状态 1:显示 0:隐藏
|
||||||
|
DelState int64 `db:"del_state"` // 删除状态 0:未删除 1:已删除
|
||||||
|
Version int64 `db:"version"` // 版本号
|
||||||
|
CreateTime time.Time `db:"create_time"` // 创建时间
|
||||||
|
UpdateTime time.Time `db:"update_time"` // 更新时间
|
||||||
|
DeleteTime sql.NullTime `db:"delete_time"` // 删除时间
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newInquiryRecordModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultInquiryRecordModel {
|
||||||
|
return &defaultInquiryRecordModel{
|
||||||
|
CachedConn: sqlc.NewConn(conn, c),
|
||||||
|
table: "`inquiry_record`",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) Insert(ctx context.Context, session sqlx.Session, data *InquiryRecord) (sql.Result, error) {
|
||||||
|
data.DelState = globalkey.DelStateNo
|
||||||
|
// 移除 insertUUID,使用数据库自增 ID
|
||||||
|
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
|
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, strings.Join(stringx.Remove(inquiryRecordFieldNames, "`id`", "`create_time`", "`update_time`"), ","))
|
||||||
|
if session != nil {
|
||||||
|
return session.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime)
|
||||||
|
}
|
||||||
|
return conn.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindOne(ctx context.Context, id int64) (*InquiryRecord, error) {
|
||||||
|
qncInquiryRecordIdKey := fmt.Sprintf("%s%v", cacheQncInquiryRecordIdPrefix, id)
|
||||||
|
var resp InquiryRecord
|
||||||
|
err := m.QueryRowCtx(ctx, &resp, qncInquiryRecordIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
|
||||||
|
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", inquiryRecordRows, m.table)
|
||||||
|
return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo)
|
||||||
|
})
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &resp, nil
|
||||||
|
case sqlc.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) Update(ctx context.Context, session sqlx.Session, data *InquiryRecord) (sql.Result, error) {
|
||||||
|
qncInquiryRecordIdKey := fmt.Sprintf("%s%v", cacheQncInquiryRecordIdPrefix, data.Id)
|
||||||
|
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
|
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, inquiryRecordRowsWithPlaceHolder)
|
||||||
|
if session != nil {
|
||||||
|
return session.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id)
|
||||||
|
}
|
||||||
|
return conn.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id)
|
||||||
|
}, qncInquiryRecordIdKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *InquiryRecord) error {
|
||||||
|
|
||||||
|
oldVersion := data.Version
|
||||||
|
data.Version += 1
|
||||||
|
|
||||||
|
var sqlResult sql.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
qncInquiryRecordIdKey := fmt.Sprintf("%s%v", cacheQncInquiryRecordIdPrefix, data.Id)
|
||||||
|
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
|
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, inquiryRecordRowsWithPlaceHolder)
|
||||||
|
if session != nil {
|
||||||
|
return session.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id, oldVersion)
|
||||||
|
}
|
||||||
|
return conn.ExecCtx(ctx, query, data.UserPhoneTail, data.DisplayName, data.VinMasked, data.CarModel, data.InquiryTag, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id, oldVersion)
|
||||||
|
}, qncInquiryRecordIdKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateCount, err := sqlResult.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if updateCount == 0 {
|
||||||
|
return ErrNoRowsUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *InquiryRecord) error {
|
||||||
|
data.DelState = globalkey.DelStateYes
|
||||||
|
data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true}
|
||||||
|
if err := m.UpdateWithVersion(ctx, session, data); err != nil {
|
||||||
|
return errors.Wrapf(errors.New("delete soft failed "), "InquiryRecordModel delete err : %+v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) {
|
||||||
|
|
||||||
|
if len(field) == 0 {
|
||||||
|
return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = builder.Columns("IFNULL(SUM(" + field + "),0)")
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp float64
|
||||||
|
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) {
|
||||||
|
|
||||||
|
if len(field) == 0 {
|
||||||
|
return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = builder.Columns("COUNT(" + field + ")")
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp int64
|
||||||
|
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*InquiryRecord, error) {
|
||||||
|
|
||||||
|
builder = builder.Columns(inquiryRecordRows)
|
||||||
|
|
||||||
|
if orderBy == "" {
|
||||||
|
builder = builder.OrderBy("id DESC")
|
||||||
|
} else {
|
||||||
|
builder = builder.OrderBy(orderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*InquiryRecord
|
||||||
|
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*InquiryRecord, error) {
|
||||||
|
|
||||||
|
builder = builder.Columns(inquiryRecordRows)
|
||||||
|
|
||||||
|
if orderBy == "" {
|
||||||
|
builder = builder.OrderBy("id DESC")
|
||||||
|
} else {
|
||||||
|
builder = builder.OrderBy(orderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*InquiryRecord
|
||||||
|
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*InquiryRecord, int64, error) {
|
||||||
|
|
||||||
|
totalBuilder := builder.Columns("COUNT(*)")
|
||||||
|
query, values, err := totalBuilder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
err = m.QueryRowNoCacheCtx(ctx, &total, query, values...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
return []*InquiryRecord{}, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = builder.Columns(inquiryRecordRows)
|
||||||
|
|
||||||
|
if orderBy == "" {
|
||||||
|
builder = builder.OrderBy("id DESC")
|
||||||
|
} else {
|
||||||
|
builder = builder.OrderBy(orderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
query, values, err = builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*InquiryRecord
|
||||||
|
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, total, nil
|
||||||
|
default:
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*InquiryRecord, error) {
|
||||||
|
|
||||||
|
builder = builder.Columns(inquiryRecordRows)
|
||||||
|
|
||||||
|
if preMinId != 0 {
|
||||||
|
builder = builder.Where("id < ?", preMinId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*InquiryRecord
|
||||||
|
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*InquiryRecord, error) {
|
||||||
|
|
||||||
|
builder = builder.Columns(inquiryRecordRows)
|
||||||
|
|
||||||
|
if preMaxId != 0 {
|
||||||
|
builder = builder.Where("id > ?", preMaxId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []*InquiryRecord
|
||||||
|
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error {
|
||||||
|
|
||||||
|
return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
|
return fn(ctx, session)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) SelectBuilder() squirrel.SelectBuilder {
|
||||||
|
return squirrel.Select().From(m.table)
|
||||||
|
}
|
||||||
|
func (m *defaultInquiryRecordModel) Delete(ctx context.Context, session sqlx.Session, id int64) error {
|
||||||
|
qncInquiryRecordIdKey := fmt.Sprintf("%s%v", cacheQncInquiryRecordIdPrefix, id)
|
||||||
|
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
|
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
|
||||||
|
if session != nil {
|
||||||
|
return session.ExecCtx(ctx, query, id)
|
||||||
|
}
|
||||||
|
return conn.ExecCtx(ctx, query, id)
|
||||||
|
}, qncInquiryRecordIdKey)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (m *defaultInquiryRecordModel) formatPrimary(primary interface{}) string {
|
||||||
|
return fmt.Sprintf("%s%v", cacheQncInquiryRecordIdPrefix, primary)
|
||||||
|
}
|
||||||
|
func (m *defaultInquiryRecordModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
|
||||||
|
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", inquiryRecordRows, m.table)
|
||||||
|
return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultInquiryRecordModel) tableName() string {
|
||||||
|
return m.table
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user