From 3c6e2683f5f128736f5abcb4cb457caeb735c410 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Mon, 12 Jan 2026 16:43:08 +0800 Subject: [PATCH] fix --- app/main/api/desc/admin/admin_agent.api | 31 +- app/main/api/desc/admin/admin_complaint.api | 179 ++++++ app/main/api/desc/admin/admin_feature.api | 34 +- app/main/api/desc/admin/dashboard.api | 77 +++ app/main/api/desc/front/agent.api | 162 ++++- app/main/api/desc/front/pay.api | 6 +- app/main/api/desc/main.api | 2 + .../admingetcomplaintdetailhandler.go | 29 + .../admingetcomplaintlisthandler.go | 29 + .../adminupdatecomplaintremarkhandler.go | 29 + .../adminupdatecomplaintstatushandler.go | 29 + .../admingetdashboardstatisticshandler.go | 17 + .../checkfeaturewhiteliststatushandler.go | 31 + .../agent/createwhitelistorderhandler.go | 30 + .../agent/getlastwithdrawalinfohandler.go | 29 + .../agent/getwhitelistfeatureshandler.go | 31 + .../handler/agent/getwhitelistlisthandler.go | 31 + .../handler/agent/offlinefeaturehandler.go | 31 + .../internal/handler/pay/alipayfromhandler.go | 18 + app/main/api/internal/handler/routes.go | 84 +++ .../admin_agent/adminauditwithdrawallogic.go | 170 ++++-- .../admingetagentorderlistlogic.go | 47 +- .../admingetagentproductconfiglistlogic.go | 3 +- .../admingetagentrealnamelistlogic.go | 4 +- .../admingetagentrebatelistlogic.go | 8 +- .../admingetagentupgradelistlogic.go | 4 +- .../admingetagentwithdrawallistlogic.go | 11 + .../admingetinvitecodelistlogic.go | 4 +- .../admingetcomplaintdetaillogic.go | 188 ++++++ .../admingetcomplaintlistlogic.go | 174 ++++++ .../adminupdatecomplaintremarklogic.go | 47 ++ .../adminupdatecomplaintstatuslogic.go | 55 ++ .../admingetdashboardstatisticslogic.go | 381 ++++++++++++ .../admin_feature/admincreatefeaturelogic.go | 4 + .../admingetfeaturedetaillogic.go | 11 +- .../admin_feature/admingetfeaturelistlogic.go | 11 +- .../admin_feature/adminupdatefeaturelogic.go | 3 + .../admin_order/admingetorderdetaillogic.go | 2 - .../admin_order/admingetorderlistlogic.go | 2 - .../admin_order/adminrefundorderlogic.go | 166 ++++-- .../admingetquerycleanupconfiglistlogic.go | 4 +- .../admingetquerycleanupdetaillistlogic.go | 4 +- .../admingetquerycleanuploglistlogic.go | 4 +- .../logic/agent/applywithdrawallogic.go | 123 +++- .../agent/checkfeaturewhiteliststatuslogic.go | 91 +++ .../logic/agent/createwhitelistorderlogic.go | 169 ++++++ .../logic/agent/getconversionratelogic.go | 16 +- .../logic/agent/getlastwithdrawalinfologic.go | 95 +++ .../logic/agent/getrebatelistlogic.go | 1 + .../logic/agent/getrevenueinfologic.go | 75 ++- .../getsubordinatecontributiondetaillogic.go | 10 +- .../internal/logic/agent/getteamlistlogic.go | 18 +- .../logic/agent/getteamstatisticslogic.go | 49 +- .../logic/agent/getwhitelistfeatureslogic.go | 57 ++ .../logic/agent/getwhitelistlistlogic.go | 117 ++++ .../logic/agent/getwithdrawallistlogic.go | 37 +- .../logic/agent/offlinefeaturelogic.go | 174 ++++++ .../internal/logic/agent/realnameauthlogic.go | 60 +- .../internal/logic/pay/alipaycallbacklogic.go | 93 +++ .../api/internal/logic/pay/alipayfromlogic.go | 240 ++++++++ .../internal/logic/pay/paymentchecklogic.go | 14 +- .../api/internal/logic/pay/paymentlogic.go | 211 +++++++ .../logic/pay/wechatpaycallbacklogic.go | 88 +++ .../logic/pay/wechatpayrefundcallbacklogic.go | 15 + app/main/api/internal/queue/cleanQueryData.go | 3 - .../api/internal/queue/paySuccessNotify.go | 9 + app/main/api/internal/queue/routes.go | 8 + .../api/internal/queue/syncAlipayComplaint.go | 130 ++++ app/main/api/internal/service/agentService.go | 234 +++++++- .../service/alipayComplaintService.go | 541 +++++++++++++++++ .../api/internal/service/alipayService.go | 1 - .../api/internal/service/apirequestService.go | 40 +- .../internal/service/authorizationService.go | 2 +- .../api/internal/service/whitelistService.go | 560 ++++++++++++++++++ app/main/api/internal/svc/servicecontext.go | 89 ++- app/main/api/internal/types/adminagent.go | 2 + app/main/api/internal/types/admincomplaint.go | 63 ++ app/main/api/internal/types/admindashboard.go | 11 + app/main/api/internal/types/adminfeature.go | 23 +- app/main/api/internal/types/agent.go | 97 ++- app/main/api/internal/types/pay.go | 2 +- app/main/api/internal/types/taskname.go | 1 + app/main/api/internal/types/types.go | 190 +++++- app/main/model/agentWithdrawalModel_gen.go | 47 +- app/main/model/alipayFromCallbackModel.go | 27 + app/main/model/alipayFromCallbackModel_gen.go | 391 ++++++++++++ app/main/model/complaintAlipayModel.go | 27 + app/main/model/complaintAlipayModel_gen.go | 470 +++++++++++++++ app/main/model/complaintAlipayTradeModel.go | 27 + .../model/complaintAlipayTradeModel_gen.go | 395 ++++++++++++ app/main/model/complaintMainModel.go | 27 + app/main/model/complaintMainModel_gen.go | 395 ++++++++++++ app/main/model/complaintManualModel.go | 27 + app/main/model/complaintManualModel_gen.go | 391 ++++++++++++ app/main/model/featureModel_gen.go | 31 +- app/main/model/userFeatureWhitelistModel.go | 27 + .../model/userFeatureWhitelistModel_gen.go | 484 +++++++++++++++ app/main/model/whitelistOrderItemModel.go | 27 + app/main/model/whitelistOrderItemModel_gen.go | 390 ++++++++++++ app/main/model/whitelistOrderModel.go | 27 + app/main/model/whitelistOrderModel_gen.go | 433 ++++++++++++++ deploy/script/gen_models.ps1 | 14 +- deploy/sql/alipay_from_callback_migration.sql | 40 ++ deploy/sql/complaint_system_migration.sql | 173 ++++++ .../sql/feature_whitelist_price_migration.sql | 17 + .../refund_commission_cancel_migration.sql | 75 +++ .../sql/user_feature_whitelist_migration.sql | 44 ++ deploy/sql/whitelist_order_migration.sql | 70 +++ .../sql/withdrawal_alipay_quota_migration.sql | 26 + deploy/sql/withdrawal_bank_card_migration.sql | 34 ++ 110 files changed, 9630 insertions(+), 481 deletions(-) create mode 100644 app/main/api/desc/admin/admin_complaint.api create mode 100644 app/main/api/desc/admin/dashboard.api create mode 100644 app/main/api/internal/handler/admin_complaint/admingetcomplaintdetailhandler.go create mode 100644 app/main/api/internal/handler/admin_complaint/admingetcomplaintlisthandler.go create mode 100644 app/main/api/internal/handler/admin_complaint/adminupdatecomplaintremarkhandler.go create mode 100644 app/main/api/internal/handler/admin_complaint/adminupdatecomplaintstatushandler.go create mode 100644 app/main/api/internal/handler/admin_dashboard/admingetdashboardstatisticshandler.go create mode 100644 app/main/api/internal/handler/agent/checkfeaturewhiteliststatushandler.go create mode 100644 app/main/api/internal/handler/agent/createwhitelistorderhandler.go create mode 100644 app/main/api/internal/handler/agent/getlastwithdrawalinfohandler.go create mode 100644 app/main/api/internal/handler/agent/getwhitelistfeatureshandler.go create mode 100644 app/main/api/internal/handler/agent/getwhitelistlisthandler.go create mode 100644 app/main/api/internal/handler/agent/offlinefeaturehandler.go create mode 100644 app/main/api/internal/handler/pay/alipayfromhandler.go create mode 100644 app/main/api/internal/logic/admin_complaint/admingetcomplaintdetaillogic.go create mode 100644 app/main/api/internal/logic/admin_complaint/admingetcomplaintlistlogic.go create mode 100644 app/main/api/internal/logic/admin_complaint/adminupdatecomplaintremarklogic.go create mode 100644 app/main/api/internal/logic/admin_complaint/adminupdatecomplaintstatuslogic.go create mode 100644 app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go create mode 100644 app/main/api/internal/logic/agent/checkfeaturewhiteliststatuslogic.go create mode 100644 app/main/api/internal/logic/agent/createwhitelistorderlogic.go create mode 100644 app/main/api/internal/logic/agent/getlastwithdrawalinfologic.go create mode 100644 app/main/api/internal/logic/agent/getwhitelistfeatureslogic.go create mode 100644 app/main/api/internal/logic/agent/getwhitelistlistlogic.go create mode 100644 app/main/api/internal/logic/agent/offlinefeaturelogic.go create mode 100644 app/main/api/internal/logic/pay/alipayfromlogic.go create mode 100644 app/main/api/internal/queue/syncAlipayComplaint.go create mode 100644 app/main/api/internal/service/alipayComplaintService.go create mode 100644 app/main/api/internal/service/whitelistService.go create mode 100644 app/main/api/internal/types/admincomplaint.go create mode 100644 app/main/api/internal/types/admindashboard.go create mode 100644 app/main/model/alipayFromCallbackModel.go create mode 100644 app/main/model/alipayFromCallbackModel_gen.go create mode 100644 app/main/model/complaintAlipayModel.go create mode 100644 app/main/model/complaintAlipayModel_gen.go create mode 100644 app/main/model/complaintAlipayTradeModel.go create mode 100644 app/main/model/complaintAlipayTradeModel_gen.go create mode 100644 app/main/model/complaintMainModel.go create mode 100644 app/main/model/complaintMainModel_gen.go create mode 100644 app/main/model/complaintManualModel.go create mode 100644 app/main/model/complaintManualModel_gen.go create mode 100644 app/main/model/userFeatureWhitelistModel.go create mode 100644 app/main/model/userFeatureWhitelistModel_gen.go create mode 100644 app/main/model/whitelistOrderItemModel.go create mode 100644 app/main/model/whitelistOrderItemModel_gen.go create mode 100644 app/main/model/whitelistOrderModel.go create mode 100644 app/main/model/whitelistOrderModel_gen.go create mode 100644 deploy/sql/alipay_from_callback_migration.sql create mode 100644 deploy/sql/complaint_system_migration.sql create mode 100644 deploy/sql/feature_whitelist_price_migration.sql create mode 100644 deploy/sql/refund_commission_cancel_migration.sql create mode 100644 deploy/sql/user_feature_whitelist_migration.sql create mode 100644 deploy/sql/whitelist_order_migration.sql create mode 100644 deploy/sql/withdrawal_alipay_quota_migration.sql create mode 100644 deploy/sql/withdrawal_bank_card_migration.sql diff --git a/app/main/api/desc/admin/admin_agent.api b/app/main/api/desc/admin/admin_agent.api index 90bb543..6bb808d 100644 --- a/app/main/api/desc/admin/admin_agent.api +++ b/app/main/api/desc/admin/admin_agent.api @@ -153,6 +153,7 @@ type ( AgentId *string `form:"agent_id,optional"` // 代理ID(可选) OrderId *string `form:"order_id,optional"` // 订单ID(可选) ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选) + OrderStatus *string `form:"order_status,optional"` // 订单状态(可选):pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 } AgentOrderListItem { Id string `json:"id"` // 主键 @@ -165,7 +166,8 @@ type ( ActualBasePrice float64 `json:"actual_base_price"` // 实际底价 PriceCost float64 `json:"price_cost"` // 提价成本 AgentProfit float64 `json:"agent_profit"` // 代理收益 - ProcessStatus int64 `json:"process_status"` // 处理状态 + ProcessStatus int64 `json:"process_status"` // 处理状态(保留用于筛选,前端不显示) + OrderStatus string `json:"order_status"` // 订单状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 CreateTime string `json:"create_time"` // 创建时间 } AdminGetAgentOrderListResp { @@ -200,6 +202,7 @@ type ( AgentId *string `form:"agent_id,optional"` // 代理ID(可选) SourceAgentId *string `form:"source_agent_id,optional"` // 来源代理ID(可选) RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选) + Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款) } AgentRebateListItem { Id string `json:"id"` // 主键 @@ -208,6 +211,7 @@ type ( OrderId string `json:"order_id"` // 订单ID RebateType int64 `json:"rebate_type"` // 返佣类型 Amount float64 `json:"amount"` // 金额 + Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款) CreateTime string `json:"create_time"` // 创建时间 } AdminGetAgentRebateListResp { @@ -246,17 +250,20 @@ type ( WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选) } AgentWithdrawalListItem { - Id string `json:"id"` // 主键 - AgentId string `json:"agent_id"` // 代理ID - WithdrawNo string `json:"withdraw_no"` // 提现单号 - Amount float64 `json:"amount"` // 金额 - TaxAmount float64 `json:"tax_amount"` // 税费金额 - ActualAmount float64 `json:"actual_amount"` // 实际到账金额 - Status int64 `json:"status"` // 状态 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 - Remark string `json:"remark"` // 备注 - CreateTime string `json:"create_time"` // 创建时间 + Id string `json:"id"` // 主键 + AgentId string `json:"agent_id"` // 代理ID + WithdrawNo string `json:"withdraw_no"` // 提现单号 + Amount float64 `json:"amount"` // 金额 + TaxAmount float64 `json:"tax_amount"` // 税费金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额 + Status int64 `json:"status"` // 状态 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时使用) + BankName string `json:"bank_name"` // 开户行名称(银行卡提现时使用) + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 } AdminGetAgentWithdrawalListResp { Total int64 `json:"total"` // 总数 diff --git a/app/main/api/desc/admin/admin_complaint.api b/app/main/api/desc/admin/admin_complaint.api new file mode 100644 index 0000000..a8ee082 --- /dev/null +++ b/app/main/api/desc/admin/admin_complaint.api @@ -0,0 +1,179 @@ +syntax = "v1" + +info ( + title: "投诉管理服务" + desc: "投诉管理相关接口" + version: "v1" +) + +@server ( + prefix: api/v1/admin/complaint + group: admin_complaint + middleware: AdminAuthInterceptor +) +service main { + @doc "获取投诉列表" + @handler AdminGetComplaintList + get /list (AdminGetComplaintListReq) returns (AdminGetComplaintListResp) + + @doc "获取投诉详情" + @handler AdminGetComplaintDetail + get /detail/:id (AdminGetComplaintDetailReq) returns (AdminGetComplaintDetailResp) + + @doc "更新投诉状态" + @handler AdminUpdateComplaintStatus + put /update-status/:id (AdminUpdateComplaintStatusReq) returns (AdminUpdateComplaintStatusResp) + + @doc "更新投诉备注" + @handler AdminUpdateComplaintRemark + put /update-remark/:id (AdminUpdateComplaintRemarkReq) returns (AdminUpdateComplaintRemarkResp) +} + +type ( + // 列表请求 + AdminGetComplaintListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Type string `form:"type,optional"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + Status string `form:"status,optional"` // 投诉状态:pending-待处理,processing-处理中,resolved-已解决,closed-已关闭 + Name string `form:"name,optional"` // 投诉人姓名 + Contact string `form:"contact,optional"` // 联系方式 + OrderId string `form:"order_id,optional"` // 关联订单ID + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + HandleTimeStart string `form:"handle_time_start,optional"` // 处理时间开始 + HandleTimeEnd string `form:"handle_time_end,optional"` // 处理时间结束 + } + + // 列表响应 + AdminGetComplaintListResp { + Total int64 `json:"total"` // 总数 + Items []ComplaintListItem `json:"items"` // 列表 + } + + // 列表项 + ComplaintListItem { + Id string `json:"id"` // 投诉ID + Type string `json:"type"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + OrderId string `json:"order_id"` // 关联订单ID + Name string `json:"name"` // 投诉人姓名 + Contact string `json:"contact"` // 联系方式 + Content string `json:"content"` // 投诉内容 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 状态描述 + Remark string `json:"remark"` // 处理备注 + HandlerId string `json:"handler_id"` // 处理人ID + HandleTime string `json:"handle_time"` // 处理时间 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + // 支付宝投诉特有字段 + TaskId string `json:"task_id"` // 支付宝投诉单号 + TradeNo string `json:"trade_no"` // 支付宝交易单号 + ComplainAmount string `json:"complain_amount"` // 投诉金额 + GmtComplain string `json:"gmt_complain"` // 投诉时间 + // 主动投诉特有字段 + Subject string `json:"subject"` // 投诉主题 + Priority string `json:"priority"` // 优先级 + Source string `json:"source"` // 投诉来源 + } + + // 详情请求 + AdminGetComplaintDetailReq { + Id string `path:"id"` // 投诉ID + } + + // 详情响应 + AdminGetComplaintDetailResp { + Id string `json:"id"` // 投诉ID + Type string `json:"type"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + OrderId string `json:"order_id"` // 关联订单ID + Name string `json:"name"` // 投诉人姓名 + Contact string `json:"contact"` // 联系方式 + Content string `json:"content"` // 投诉内容 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 状态描述 + Remark string `json:"remark"` // 处理备注 + HandlerId string `json:"handler_id"` // 处理人ID + HandleTime string `json:"handle_time"` // 处理时间 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + // 支付宝投诉详情 + AlipayComplaint *AlipayComplaintDetail `json:"alipay_complaint,optional"` // 支付宝投诉详情 + // 主动投诉详情 + ManualComplaint *ManualComplaintDetail `json:"manual_complaint,optional"` // 主动投诉详情 + } + + // 支付宝投诉详情 + AlipayComplaintDetail { + Id string `json:"id"` // 支付宝投诉表ID + AlipayId int64 `json:"alipay_id"` // 支付宝投诉主表的主键id + TaskId string `json:"task_id"` // 投诉单号id + OppositePid string `json:"opposite_pid"` // 被投诉人pid + OppositeName string `json:"opposite_name"` // 被投诉方名称 + ComplainAmount string `json:"complain_amount"` // 投诉单涉及交易总金额 + GmtComplain string `json:"gmt_complain"` // 投诉时间 + GmtProcess string `json:"gmt_process"` // 处理时间 + GmtOverdue string `json:"gmt_overdue"` // 过期时间 + ComplainContent string `json:"complain_content"` // 用户投诉内容 + TradeNo string `json:"trade_no"` // 投诉交易单号 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 投诉单状态枚举值描述 + ProcessCode string `json:"process_code"` // 商家处理结果码 + ProcessMessage string `json:"process_message"` // 商家处理结果码对应描述 + ProcessRemark string `json:"process_remark"` // 商家处理备注 + ProcessImgUrlList []string `json:"process_img_url_list"` // 商家处理备注图片url列表 + GmtRiskFinishTime string `json:"gmt_risk_finish_time"` // 推送时间 + ComplainUrl string `json:"complain_url"` // 投诉网址 + CertifyInfo []string `json:"certify_info"` // 投诉凭证图片信息 + TradeInfoList []AlipayComplaintTradeInfo `json:"trade_info_list"` // 交易信息列表 + } + + // 支付宝投诉交易信息 + AlipayComplaintTradeInfo { + Id string `json:"id"` // 交易信息表ID + AlipayTradeId string `json:"alipay_trade_id"` // 交易信息表主键id + AlipayComplaintRecordId string `json:"alipay_complaint_record_id"` // 投诉主表id + TradeNo string `json:"trade_no"` // 支付宝交易单号 + OutNo string `json:"out_no"` // 商家订单号 + GmtTrade string `json:"gmt_trade"` // 交易时间 + GmtRefund string `json:"gmt_refund"` // 退款时间 + Status string `json:"status"` // 交易投诉状态 + StatusDescription string `json:"status_description"` // 交易投诉状态描述 + Amount string `json:"amount"` // 交易单金额 + } + + // 主动投诉详情 + ManualComplaintDetail { + Id string `json:"id"` // 主动投诉表ID + UserId string `json:"user_id"` // 关联用户ID + Subject string `json:"subject"` // 投诉主题 + Priority string `json:"priority"` // 优先级:low-低,medium-中,high-高,urgent-紧急 + Source string `json:"source"` // 投诉来源:web-网站,phone-电话,email-邮件,other-其他 + AttachmentUrls []string `json:"attachment_urls"` // 附件URL列表 + } + + // 更新投诉状态请求 + AdminUpdateComplaintStatusReq { + Id string `path:"id"` // 投诉ID + Status string `json:"status"` // 投诉状态:pending-待处理,processing-处理中,resolved-已解决,closed-已关闭 + StatusDescription string `json:"status_description,optional"` // 状态描述 + HandlerId string `json:"handler_id,optional"` // 处理人ID + } + + // 更新投诉状态响应 + AdminUpdateComplaintStatusResp { + Success bool `json:"success"` // 是否成功 + } + + // 更新投诉备注请求 + AdminUpdateComplaintRemarkReq { + Id string `path:"id"` // 投诉ID + Remark string `json:"remark"` // 处理备注 + } + + // 更新投诉备注响应 + AdminUpdateComplaintRemarkResp { + Success bool `json:"success"` // 是否成功 + } +) + diff --git a/app/main/api/desc/admin/admin_feature.api b/app/main/api/desc/admin/admin_feature.api index 03a7212..29b15f7 100644 --- a/app/main/api/desc/admin/admin_feature.api +++ b/app/main/api/desc/admin/admin_feature.api @@ -45,8 +45,9 @@ service main { type ( // 创建功能请求 AdminCreateFeatureReq { - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元) } // 创建功能响应 AdminCreateFeatureResp { @@ -54,9 +55,10 @@ type ( } // 更新功能请求 AdminUpdateFeatureReq { - Id string `path:"id"` // 功能ID - ApiId *string `json:"api_id,optional"` // API标识 - Name *string `json:"name,optional"` // 描述 + Id string `path:"id"` // 功能ID + ApiId *string `json:"api_id,optional"` // API标识 + Name *string `json:"name,optional"` // 描述 + WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元) } // 更新功能响应 AdminUpdateFeatureResp { @@ -79,11 +81,12 @@ type ( } // 功能列表项 FeatureListItem { - Id string `json:"id"` // 功能ID - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 + Id string `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元) + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 } // 获取功能列表响应 AdminGetFeatureListResp { @@ -96,11 +99,12 @@ type ( } // 获取功能详情响应 AdminGetFeatureDetailResp { - Id string `json:"id"` // 功能ID - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 + Id string `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元) + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 } // 配置功能示例数据请求 AdminConfigFeatureExampleReq { diff --git a/app/main/api/desc/admin/dashboard.api b/app/main/api/desc/admin/dashboard.api new file mode 100644 index 0000000..306c93f --- /dev/null +++ b/app/main/api/desc/admin/dashboard.api @@ -0,0 +1,77 @@ +syntax = "v1" + +info ( + title: "后台统计面板服务" + desc: "后台统计面板相关接口" + author: "team" + version: "v1" +) + +// ============================================ +// 统计面板接口 +// ============================================ +@server ( + prefix: /api/v1/admin/dashboard + group: admin_dashboard + middleware: AdminAuthInterceptor +) +service main { + // 获取统计面板数据 + @handler AdminGetDashboardStatistics + get /statistics returns (AdminGetDashboardStatisticsResp) +} + +type ( + // 统计面板响应 + AdminGetDashboardStatisticsResp { + // 订单统计 + OrderStats AdminOrderStatistics `json:"order_stats"` + // 营收统计 + RevenueStats AdminRevenueStatistics `json:"revenue_stats"` + // 代理统计 + AgentStats AdminAgentStatistics `json:"agent_stats"` + // 利润统计 + ProfitStats AdminProfitStatistics `json:"profit_stats"` + // 订单趋势(最近7天) + OrderTrend []AdminTrendData `json:"order_trend"` + // 营收趋势(最近7天) + RevenueTrend []AdminTrendData `json:"revenue_trend"` + } + // 订单统计 + AdminOrderStatistics { + TodayCount int64 `json:"today_count"` // 今日订单数 + MonthCount int64 `json:"month_count"` // 当月订单数 + TotalCount int64 `json:"total_count"` // 总订单数 + YesterdayCount int64 `json:"yesterday_count"` // 昨日订单数 + ChangeRate float64 `json:"change_rate"` // 变化率(百分比) + } + // 营收统计 + AdminRevenueStatistics { + TodayAmount float64 `json:"today_amount"` // 今日营收 + MonthAmount float64 `json:"month_amount"` // 当月营收 + TotalAmount float64 `json:"total_amount"` // 总营收 + YesterdayAmount float64 `json:"yesterday_amount"` // 昨日营收 + ChangeRate float64 `json:"change_rate"` // 变化率(百分比) + } + // 代理统计 + AdminAgentStatistics { + TotalCount int64 `json:"total_count"` // 代理总数 + TodayNew int64 `json:"today_new"` // 今日新增 + MonthNew int64 `json:"month_new"` // 当月新增 + } + // 利润统计 + AdminProfitStatistics { + TodayProfit float64 `json:"today_profit"` // 今日利润 + MonthProfit float64 `json:"month_profit"` // 当月利润 + TotalProfit float64 `json:"total_profit"` // 总利润 + TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率 + MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率 + TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率 + } + // 趋势数据 + AdminTrendData { + Date string `json:"date"` // 日期(格式:MM-DD) + Value float64 `json:"value"` // 数值 + } +) + diff --git a/app/main/api/desc/front/agent.api b/app/main/api/desc/front/agent.api index b6f0e20..a03eee1 100644 --- a/app/main/api/desc/front/agent.api +++ b/app/main/api/desc/front/agent.api @@ -1,5 +1,7 @@ syntax = "v1" +import "product.api" + info ( title: "代理服务" desc: "新代理系统接口" @@ -224,6 +226,10 @@ service main { @handler ApplyWithdrawal post /withdrawal/apply (ApplyWithdrawalReq) returns (ApplyWithdrawalResp) + // 获取上次提现信息(用于前端预填) + @handler GetLastWithdrawalInfo + get /withdrawal/last_info (GetLastWithdrawalInfoReq) returns (GetLastWithdrawalInfoResp) + // 实名认证 @handler RealNameAuth post /real_name (RealNameAuthReq) returns (RealNameAuthResp) @@ -251,6 +257,28 @@ service main { // 获取推广查询报告列表 @handler GetPromotionQueryList get /promotion/query/list (GetPromotionQueryListReq) returns (GetPromotionQueryListResp) + + // ============================================ + // 用户模块白名单相关接口 + // ============================================ + @handler GetWhitelistFeatures + get /whitelist/features (GetWhitelistFeaturesReq) returns (GetWhitelistFeaturesResp) + + // 创建白名单订单 + @handler CreateWhitelistOrder + post /whitelist/order/create (CreateWhitelistOrderReq) returns (CreateWhitelistOrderResp) + + // 获取用户白名单列表 + @handler GetWhitelistList + get /whitelist/list (GetWhitelistListReq) returns (GetWhitelistListResp) + + // 检查模块是否已下架(用于显示下架按钮状态) + @handler CheckFeatureWhitelistStatus + get /whitelist/check (CheckFeatureWhitelistStatusReq) returns (CheckFeatureWhitelistStatusResp) + + // 下架单个模块(创建订单并支付) + @handler OfflineFeature + post /whitelist/offline (OfflineFeatureReq) returns (OfflineFeatureResp) } type ( @@ -425,16 +453,18 @@ type ( } // 收益信息 GetRevenueInfoResp { - Balance float64 `json:"balance"` // 可用余额 - FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 - TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益) - WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现 - CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金) - CommissionToday float64 `json:"commission_today"` // 佣金今日收益 - CommissionMonth float64 `json:"commission_month"` // 佣金本月收益 - RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣) - RebateToday float64 `json:"rebate_today"` // 返佣今日收益 - RebateMonth float64 `json:"rebate_month"` // 返佣本月收益 + Balance float64 `json:"balance"` // 可用余额 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益) + WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现 + CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金) + CommissionToday float64 `json:"commission_today"` // 佣金今日收益 + CommissionMonth float64 `json:"commission_month"` // 佣金本月收益 + RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣) + RebateToday float64 `json:"rebate_today"` // 返佣今日收益 + RebateMonth float64 `json:"rebate_month"` // 返佣本月收益 + AlipayMonthQuota float64 `json:"alipay_month_quota"` // 支付宝每月提现总额度 + AlipayMonthUsed float64 `json:"alipay_month_used"` // 本月已使用的支付宝提现额度 } // 佣金记录 GetCommissionListReq { @@ -473,6 +503,7 @@ type ( OrderNo string `json:"order_no"` // 订单号 RebateType int64 `json:"rebate_type"` // 返佣类型:1=直接上级,2=钻石上级,3=黄金上级 Amount float64 `json:"amount"` // 返佣金额 + Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款) CreateTime string `json:"create_time"` // 创建时间 } // 升级返佣记录 @@ -539,28 +570,46 @@ type ( Total int64 `json:"total"` // 总数 List []WithdrawalItem `json:"list"` // 列表 } + // 提现记录 WithdrawalItem { - Id string `json:"id"` // 记录ID - WithdrawalNo string `json:"withdrawal_no"` // 提现单号 - Amount float64 `json:"amount"` // 提现金额 - TaxAmount float64 `json:"tax_amount"` // 税费金额 - ActualAmount float64 `json:"actual_amount"` // 实际到账金额 - Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 - Remark string `json:"remark"` // 备注 - CreateTime string `json:"create_time"` // 创建时间 + Id string `json:"id"` // 记录ID + WithdrawalNo string `json:"withdrawal_no"` // 提现单号 + Amount float64 `json:"amount"` // 提现金额 + TaxAmount float64 `json:"tax_amount"` // 税费金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额 + Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时填写) + BankName string `json:"bank_name"` // 开户行名称(银行卡提现时填写) + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 } // 申请提现 ApplyWithdrawalReq { - Amount float64 `json:"amount"` // 提现金额 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 + Amount float64 `json:"amount"` // 提现金额 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no,optional"` // 银行卡号(银行卡提现必填) + BankName string `json:"bank_name,optional"` // 开户行名称(银行卡提现必填) } ApplyWithdrawalResp { WithdrawalId string `json:"withdrawal_id"` // 提现记录ID WithdrawalNo string `json:"withdrawal_no"` // 提现单号 } + // 获取上次提现信息(用于前端预填) + GetLastWithdrawalInfoReq { + WithdrawalType int64 `form:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + } + GetLastWithdrawalInfoResp { + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户行名称 + } // 实名认证 RealNameAuthReq { Name string `json:"name"` // 姓名 @@ -589,6 +638,73 @@ type ( Params map[string]interface{} `json:"params"` // 查询参数(已脱敏) Price float64 `json:"price"` // 查询价格 } + // ============================================ + // 用户模块白名单相关类型 + // ============================================ + GetWhitelistFeaturesReq {} + GetWhitelistFeaturesResp { + List []WhitelistFeatureItem `json:"list"` // 可屏蔽的feature列表 + } + WhitelistFeatureItem { + FeatureId string `json:"feature_id"` // Feature的UUID + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + FeatureName string `json:"feature_name"` // Feature的名称 + WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元) + } + CreateWhitelistOrderReq { + IdCard string `json:"id_card"` // 身份证号(查询对象标识) + FeatureIds []string `json:"feature_ids"` // 要屏蔽的feature ID列表 + OrderId string `json:"order_id,optional"` // 关联的查询订单ID(可选) + } + CreateWhitelistOrderResp { + OrderId string `json:"order_id"` // 订单ID + OrderNo string `json:"order_no"` // 订单号 + TotalAmount float64 `json:"total_amount"` // 总金额 + } + GetWhitelistListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数量 + IdCard string `form:"id_card,optional"` // 身份证号(可选,用于筛选) + } + GetWhitelistListResp { + Total int64 `json:"total"` // 总数 + List []WhitelistItem `json:"list"` // 列表 + } + WhitelistItem { + Id string `json:"id"` // 白名单记录ID + IdCard string `json:"id_card"` // 身份证号 + FeatureId string `json:"feature_id"` // Feature的UUID + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + FeatureName string `json:"feature_name"` // Feature的名称 + Amount float64 `json:"amount"` // 费用 + Status int64 `json:"status"` // 状态:1=生效,2=已失效 + StatusText string `json:"status_text"` // 状态文本 + CreateTime string `json:"create_time"` // 创建时间 + } + // 检查模块白名单状态请求 + CheckFeatureWhitelistStatusReq { + IdCard string `form:"id_card"` // 身份证号 + FeatureApiId string `form:"feature_api_id"` // Feature的API标识 + QueryId string `form:"query_id,optional"` // 查询记录ID(可选,用于检查报告数据是否已删除) + } + // 检查模块白名单状态响应 + CheckFeatureWhitelistStatusResp { + IsWhitelisted bool `json:"is_whitelisted"` // 是否在白名单中 + WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元),如果为0表示不支持下架 + FeatureId string `json:"feature_id"` // Feature的UUID + DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除(仅当提供了query_id时有效) + } + // 下架单个模块请求 + OfflineFeatureReq { + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + QueryId string `json:"query_id"` // 查询记录ID(Query表的ID,必选) + } + // 下架单个模块响应 + OfflineFeatureResp { + Success bool `json:"success"` // 是否已完成下架 + NeedPay bool `json:"need_pay"` // 是否需要发起支付 + Amount float64 `json:"amount"` // 需要支付的金额(单位:元),0表示无需支付 + } ) // ============================================ diff --git a/app/main/api/desc/front/pay.api b/app/main/api/desc/front/pay.api index afc4f93..a00ec38 100644 --- a/app/main/api/desc/front/pay.api +++ b/app/main/api/desc/front/pay.api @@ -19,6 +19,10 @@ service main { @handler AlipayCallback post /pay/alipay/callback + // 支付宝from消息回调 + @handler AlipayFrom + post /pay/alipay/from + // 微信退款回调 @handler WechatPayRefundCallback post /pay/wechat/refund_callback @@ -46,7 +50,7 @@ type ( PaymentReq { Id string `json:"id"` PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式) - PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"` + PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade whitelist"` } PaymentResp { PrepayData interface{} `json:"prepay_data"` diff --git a/app/main/api/desc/main.api b/app/main/api/desc/main.api index 855660b..d63545b 100644 --- a/app/main/api/desc/main.api +++ b/app/main/api/desc/main.api @@ -28,3 +28,5 @@ import "./admin/admin_query.api" import "./admin/admin_agent.api" import "./admin/admin_api.api" import "./admin/admin_role_api.api" +import "./admin/admin_complaint.api" +import "./admin/dashboard.api" diff --git a/app/main/api/internal/handler/admin_complaint/admingetcomplaintdetailhandler.go b/app/main/api/internal/handler/admin_complaint/admingetcomplaintdetailhandler.go new file mode 100644 index 0000000..d64e4bf --- /dev/null +++ b/app/main/api/internal/handler/admin_complaint/admingetcomplaintdetailhandler.go @@ -0,0 +1,29 @@ +package admin_complaint + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/admin_complaint" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func AdminGetComplaintDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetComplaintDetailReq + 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_complaint.NewAdminGetComplaintDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetComplaintDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_complaint/admingetcomplaintlisthandler.go b/app/main/api/internal/handler/admin_complaint/admingetcomplaintlisthandler.go new file mode 100644 index 0000000..5c4b7d6 --- /dev/null +++ b/app/main/api/internal/handler/admin_complaint/admingetcomplaintlisthandler.go @@ -0,0 +1,29 @@ +package admin_complaint + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/admin_complaint" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func AdminGetComplaintListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetComplaintListReq + 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_complaint.NewAdminGetComplaintListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetComplaintList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintremarkhandler.go b/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintremarkhandler.go new file mode 100644 index 0000000..aab769b --- /dev/null +++ b/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintremarkhandler.go @@ -0,0 +1,29 @@ +package admin_complaint + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/admin_complaint" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func AdminUpdateComplaintRemarkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateComplaintRemarkReq + 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_complaint.NewAdminUpdateComplaintRemarkLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateComplaintRemark(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintstatushandler.go b/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintstatushandler.go new file mode 100644 index 0000000..51840ca --- /dev/null +++ b/app/main/api/internal/handler/admin_complaint/adminupdatecomplaintstatushandler.go @@ -0,0 +1,29 @@ +package admin_complaint + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/admin_complaint" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func AdminUpdateComplaintStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateComplaintStatusReq + 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_complaint.NewAdminUpdateComplaintStatusLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateComplaintStatus(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_dashboard/admingetdashboardstatisticshandler.go b/app/main/api/internal/handler/admin_dashboard/admingetdashboardstatisticshandler.go new file mode 100644 index 0000000..5d7cd2a --- /dev/null +++ b/app/main/api/internal/handler/admin_dashboard/admingetdashboardstatisticshandler.go @@ -0,0 +1,17 @@ +package admin_dashboard + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/admin_dashboard" + "ycc-server/app/main/api/internal/svc" + "ycc-server/common/result" +) + +func AdminGetDashboardStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := admin_dashboard.NewAdminGetDashboardStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetDashboardStatistics() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/checkfeaturewhiteliststatushandler.go b/app/main/api/internal/handler/agent/checkfeaturewhiteliststatushandler.go new file mode 100644 index 0000000..7ca65b7 --- /dev/null +++ b/app/main/api/internal/handler/agent/checkfeaturewhiteliststatushandler.go @@ -0,0 +1,31 @@ +package agent + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CheckFeatureWhitelistStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CheckFeatureWhitelistStatusReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewCheckFeatureWhitelistStatusLogic(r.Context(), svcCtx) + resp, err := l.CheckFeatureWhitelistStatus(&req) + result.HttpResult(r, w, resp, err) + } +} + diff --git a/app/main/api/internal/handler/agent/createwhitelistorderhandler.go b/app/main/api/internal/handler/agent/createwhitelistorderhandler.go new file mode 100644 index 0000000..37ad3b4 --- /dev/null +++ b/app/main/api/internal/handler/agent/createwhitelistorderhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CreateWhitelistOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreateWhitelistOrderReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewCreateWhitelistOrderLogic(r.Context(), svcCtx) + resp, err := l.CreateWhitelistOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getlastwithdrawalinfohandler.go b/app/main/api/internal/handler/agent/getlastwithdrawalinfohandler.go new file mode 100644 index 0000000..3673b9b --- /dev/null +++ b/app/main/api/internal/handler/agent/getlastwithdrawalinfohandler.go @@ -0,0 +1,29 @@ +package agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" +) + +func GetLastWithdrawalInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetLastWithdrawalInfoReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewGetLastWithdrawalInfoLogic(r.Context(), svcCtx) + resp, err := l.GetLastWithdrawalInfo(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getwhitelistfeatureshandler.go b/app/main/api/internal/handler/agent/getwhitelistfeatureshandler.go new file mode 100644 index 0000000..785a2d1 --- /dev/null +++ b/app/main/api/internal/handler/agent/getwhitelistfeatureshandler.go @@ -0,0 +1,31 @@ +package agent + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetWhitelistFeaturesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetWhitelistFeaturesReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewGetWhitelistFeaturesLogic(r.Context(), svcCtx) + resp, err := l.GetWhitelistFeatures(&req) + result.HttpResult(r, w, resp, err) + } +} + diff --git a/app/main/api/internal/handler/agent/getwhitelistlisthandler.go b/app/main/api/internal/handler/agent/getwhitelistlisthandler.go new file mode 100644 index 0000000..1a59ab3 --- /dev/null +++ b/app/main/api/internal/handler/agent/getwhitelistlisthandler.go @@ -0,0 +1,31 @@ +package agent + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetWhitelistListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetWhitelistListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewGetWhitelistListLogic(r.Context(), svcCtx) + resp, err := l.GetWhitelistList(&req) + result.HttpResult(r, w, resp, err) + } +} + diff --git a/app/main/api/internal/handler/agent/offlinefeaturehandler.go b/app/main/api/internal/handler/agent/offlinefeaturehandler.go new file mode 100644 index 0000000..0ab61ab --- /dev/null +++ b/app/main/api/internal/handler/agent/offlinefeaturehandler.go @@ -0,0 +1,31 @@ +package agent + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/agent" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/result" + "ycc-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func OfflineFeatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.OfflineFeatureReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := agent.NewOfflineFeatureLogic(r.Context(), svcCtx) + resp, err := l.OfflineFeature(&req) + result.HttpResult(r, w, resp, err) + } +} + diff --git a/app/main/api/internal/handler/pay/alipayfromhandler.go b/app/main/api/internal/handler/pay/alipayfromhandler.go new file mode 100644 index 0000000..d2e36e3 --- /dev/null +++ b/app/main/api/internal/handler/pay/alipayfromhandler.go @@ -0,0 +1,18 @@ +package pay + +import ( + "net/http" + + "ycc-server/app/main/api/internal/logic/pay" + "ycc-server/app/main/api/internal/svc" + "ycc-server/common/result" +) + +func AlipayFromHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := pay.NewAlipayFromLogic(r.Context(), svcCtx) + err := l.AlipayFrom(w, r) + result.HttpResult(r, w, nil, err) + } +} + diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index bca6d94..6d147d1 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -7,6 +7,8 @@ import ( admin_agent "ycc-server/app/main/api/internal/handler/admin_agent" admin_api "ycc-server/app/main/api/internal/handler/admin_api" admin_auth "ycc-server/app/main/api/internal/handler/admin_auth" + admin_complaint "ycc-server/app/main/api/internal/handler/admin_complaint" + admin_dashboard "ycc-server/app/main/api/internal/handler/admin_dashboard" admin_feature "ycc-server/app/main/api/internal/handler/admin_feature" admin_menu "ycc-server/app/main/api/internal/handler/admin_menu" admin_notification "ycc-server/app/main/api/internal/handler/admin_notification" @@ -172,6 +174,53 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { rest.WithPrefix("/api/v1/admin/auth"), ) + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 获取投诉详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_complaint.AdminGetComplaintDetailHandler(serverCtx), + }, + { + // 获取投诉列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_complaint.AdminGetComplaintListHandler(serverCtx), + }, + { + // 更新投诉备注 + Method: http.MethodPut, + Path: "/update-remark/:id", + Handler: admin_complaint.AdminUpdateComplaintRemarkHandler(serverCtx), + }, + { + // 更新投诉状态 + Method: http.MethodPut, + Path: "/update-status/:id", + Handler: admin_complaint.AdminUpdateComplaintStatusHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/complaint"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/statistics", + Handler: admin_dashboard.AdminGetDashboardStatisticsHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/dashboard"), + ) + server.AddRoutes( rest.WithMiddlewares( []rest.Middleware{serverCtx.AdminAuthInterceptor}, @@ -731,11 +780,41 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/upgrade/subordinate", Handler: agent.UpgradeSubordinateHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/whitelist/check", + Handler: agent.CheckFeatureWhitelistStatusHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/whitelist/features", + Handler: agent.GetWhitelistFeaturesHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/whitelist/list", + Handler: agent.GetWhitelistListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/whitelist/offline", + Handler: agent.OfflineFeatureHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/whitelist/order/create", + Handler: agent.CreateWhitelistOrderHandler(serverCtx), + }, { Method: http.MethodPost, Path: "/withdrawal/apply", Handler: agent.ApplyWithdrawalHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/withdrawal/last_info", + Handler: agent.GetLastWithdrawalInfoHandler(serverCtx), + }, { Method: http.MethodGet, Path: "/withdrawal/list", @@ -831,6 +910,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/pay/alipay/callback", Handler: pay.AlipayCallbackHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/pay/alipay/from", + Handler: pay.AlipayFromHandler(serverCtx), + }, { Method: http.MethodPost, Path: "/pay/wechat/callback", diff --git a/app/main/api/internal/logic/admin_agent/adminauditwithdrawallogic.go b/app/main/api/internal/logic/admin_agent/adminauditwithdrawallogic.go index 5da04c0..91fbadf 100644 --- a/app/main/api/internal/logic/admin_agent/adminauditwithdrawallogic.go +++ b/app/main/api/internal/logic/admin_agent/adminauditwithdrawallogic.go @@ -4,8 +4,8 @@ import ( "context" "database/sql" "fmt" + "os" "time" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/lzUtils" @@ -32,6 +32,13 @@ func NewAdminAuditWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContex } } +// parseFloat 解析配置中的浮点数 +func (l *AdminAuditWithdrawalLogic) parseFloat(s string) (float64, error) { + var result float64 + _, err := fmt.Sscanf(s, "%f", &result) + return result, err +} + func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWithdrawalReq) (resp *types.AdminAuditWithdrawalResp, err error) { // 1. 查询提现记录 withdrawal, err := l.svcCtx.AgentWithdrawalModel.FindOne(l.ctx, req.WithdrawalId) @@ -47,38 +54,122 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi // 4. 使用事务处理审核 err = l.svcCtx.AgentWithdrawalModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { if req.Status == 2 { // 审核通过 - // 4.1 更新提现记录状态为提现中 - withdrawal.Status = 4 // 提现中 - withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true} - if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { - return errors.Wrapf(err, "更新提现记录失败") - } + // 4.1 根据提现方式处理 + if withdrawal.WithdrawalType == 1 { + // 支付宝提现:审核通过前再次校验月度额度,避免一次性通过多笔超限 + now := time.Now() + monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + nextMonthStart := monthStart.AddDate(0, 1, 0) - // 4.2 调用支付宝转账接口 - outBizNo := withdrawal.WithdrawNo - transferResp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo) - if err != nil { - // 转账失败,更新状态为失败 - withdrawal.Status = 6 // 提现失败 - withdrawal.Remark = sql.NullString{String: fmt.Sprintf("转账失败: %v", err), Valid: true} - l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal) - - // 解冻余额 - wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) - if err == nil { - wallet.FrozenBalance -= withdrawal.Amount - wallet.Balance += withdrawal.Amount - l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet) + // 获取支付宝月度额度配置(默认 800 元) + alipayQuota := 800.0 + if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(transCtx, "alipay_month_quota"); cfgErr == nil { + if parsed, parseErr := l.parseFloat(cfg.ConfigValue); parseErr == nil && parsed > 0 { + alipayQuota = parsed + } } - return errors.Wrapf(err, "支付宝转账失败") - } + // 统计本月已成功的支付宝提现金额(status=5) + withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder(). + Where("agent_id = ? AND withdrawal_type = ? AND status = ? AND create_time >= ? AND create_time < ?", + withdrawal.AgentId, 1, 5, monthStart, nextMonthStart) + usedAmount, sumErr := l.svcCtx.AgentWithdrawalModel.FindSum(transCtx, withdrawBuilder, "amount") + if sumErr != nil { + return errors.Wrapf(sumErr, "查询本月支付宝提现额度使用情况失败") + } - // 4.3 根据转账结果更新状态 - switch transferResp.Status { - case "SUCCESS": - // 转账成功 + if usedAmount+withdrawal.Amount > alipayQuota { + // 超出额度,不允许通过,保持待审核状态并提示原因 + withdrawal.Status = 1 + withdrawal.Remark = sql.NullString{ + String: fmt.Sprintf("超过本月支付宝提现额度(限额:%.2f 元,已用:%.2f 元),请使用银行卡提现或调整金额", alipayQuota, usedAmount), + Valid: true, + } + if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { + return errors.Wrapf(err, "更新提现记录失败") + } + return errors.Wrapf(xerr.NewErrMsg("超过本月支付宝提现额度,无法通过该笔提现"), "") + } + + // 支付宝提现:开发环境下做模拟,不调用真实支付宝转账 + mockTransferStatus := "SUCCESS" + var transferResp struct { + Status string + SubCode string + } + + if os.Getenv("ENV") == "development" { + transferResp.Status = mockTransferStatus + logx.Infof("【DEV】模拟支付宝转账成功,withdrawNo=%s, amount=%.2f, payee=%s", + withdrawal.WithdrawNo, withdrawal.ActualAmount, withdrawal.PayeeAccount) + } else { + // 生产环境:同步调用支付宝转账接口 + outBizNo := withdrawal.WithdrawNo + resp, err := l.svcCtx.AlipayService.AliTransfer(transCtx, withdrawal.PayeeAccount, withdrawal.PayeeName, withdrawal.ActualAmount, "代理提现", outBizNo) + if err != nil { + // 调用失败:保持状态为待审核(1),只记录备注,方便管理员重试 + withdrawal.Status = 1 // 待审核 + withdrawal.Remark = sql.NullString{String: fmt.Sprintf("支付宝转账调用失败: %v", err), Valid: true} + _ = l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal) + + return errors.Wrapf(err, "支付宝转账失败") + } + transferResp.Status = resp.Status + transferResp.SubCode = resp.SubCode + } + + // 4.2 根据转账结果更新状态 + switch transferResp.Status { + case "SUCCESS": + // 转账成功 + withdrawal.Status = 5 // 提现成功 + if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { + return errors.Wrapf(err, "更新提现记录失败") + } + + // 更新钱包(解冻并扣除) + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) + if err != nil { + return errors.Wrapf(err, "查询钱包失败") + } + wallet.FrozenBalance -= withdrawal.Amount + wallet.WithdrawnAmount += withdrawal.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil { + return errors.Wrapf(err, "更新钱包失败") + } + + // 更新扣税记录状态 + taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). + Where("withdrawal_id = ?", withdrawal.Id) + taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "") + if err == nil && len(taxRecords) > 0 { + taxRecord := taxRecords[0] + taxRecord.TaxStatus = 2 // 已扣税 + taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now()) + l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord) + } + + case "FAIL": + // 转账失败:保持待审核状态,方便人工处理或重试 + withdrawal.Status = 1 // 待审核 + errorMsg := l.mapAlipayError(transferResp.SubCode) + withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true} + if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { + return errors.Wrapf(err, "更新提现记录失败") + } + + case "DEALING": + // 处理中:同样保持待审核状态(1),由管理员后续确认 + withdrawal.Status = 1 + withdrawal.Remark = sql.NullString{String: "支付宝处理中,请稍后重试或联系平台", Valid: true} + if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { + return errors.Wrapf(err, "更新提现记录失败") + } + } + } else if withdrawal.WithdrawalType == 2 { + // 银行卡提现:审核通过即视为提现成功(线下已/将立即打款) withdrawal.Status = 5 // 提现成功 + withdrawal.Remark = sql.NullString{String: req.Remark, Valid: true} if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { return errors.Wrapf(err, "更新提现记录失败") } @@ -96,7 +187,7 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi // 更新扣税记录状态 taxBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). - Where("withdrawal_id = ? AND del_state = ?", withdrawal.Id, globalkey.DelStateNo) + Where("withdrawal_id = ?", withdrawal.Id) taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(transCtx, taxBuilder, "") if err == nil && len(taxRecords) > 0 { taxRecord := taxRecords[0] @@ -104,27 +195,6 @@ func (l *AdminAuditWithdrawalLogic) AdminAuditWithdrawal(req *types.AdminAuditWi taxRecord.TaxTime = lzUtils.TimeToNullTime(time.Now()) l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(transCtx, session, taxRecord) } - - case "FAIL": - // 转账失败 - withdrawal.Status = 6 // 提现失败 - errorMsg := l.mapAlipayError(transferResp.SubCode) - withdrawal.Remark = sql.NullString{String: errorMsg, Valid: true} - if err := l.svcCtx.AgentWithdrawalModel.UpdateWithVersion(transCtx, session, withdrawal); err != nil { - return errors.Wrapf(err, "更新提现记录失败") - } - - // 解冻余额 - wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(transCtx, withdrawal.AgentId) - if err == nil { - wallet.FrozenBalance -= withdrawal.Amount - wallet.Balance += withdrawal.Amount - l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet) - } - - case "DEALING": - // 处理中,保持提现中状态,后续通过轮询更新 - // 状态已经是4(提现中),无需更新 } } else if req.Status == 3 { // 审核拒绝 diff --git a/app/main/api/internal/logic/admin_agent/admingetagentorderlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentorderlistlogic.go index 797e53d..167d512 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentorderlistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentorderlistlogic.go @@ -2,7 +2,6 @@ package admin_agent import ( "context" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/Masterminds/squirrel" @@ -29,8 +28,7 @@ func NewAdminGetAgentOrderListLogic(ctx context.Context, svcCtx *svc.ServiceCont } func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGetAgentOrderListReq) (resp *types.AdminGetAgentOrderListResp, err error) { - builder := l.svcCtx.AgentOrderModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentOrderModel.SelectBuilder() if req.AgentId != nil { builder = builder.Where("agent_id = ?", *req.AgentId) @@ -42,6 +40,27 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet builder = builder.Where("process_status = ?", *req.ProcessStatus) } + // 如果提供了订单状态筛选,先查询符合条件的订单ID列表 + var filteredOrderIds []string + if req.OrderStatus != nil && *req.OrderStatus != "" { + orderBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ?", *req.OrderStatus). + Columns("id") + orders, _ := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "") + filteredOrderIds = make([]string, 0, len(orders)) + for _, o := range orders { + filteredOrderIds = append(filteredOrderIds, o.Id) + } + // 如果没有符合条件的订单,直接返回空结果 + if len(filteredOrderIds) == 0 { + return &types.AdminGetAgentOrderListResp{ + Total: 0, + Items: []types.AgentOrderListItem{}, + }, nil + } + builder = builder.Where(squirrel.Eq{"order_id": filteredOrderIds}) + } + // 分页查询 page := req.Page if page <= 0 { @@ -74,9 +93,30 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet } } + // 批量查询订单状态 + orderIdSet := make(map[string]struct{}) + for _, order := range orders { + orderIdSet[order.OrderId] = struct{}{} + } + orderIdList := make([]string, 0, len(orderIdSet)) + for id := range orderIdSet { + orderIdList = append(orderIdList, id) + } + orderStatusMap := make(map[string]string) + if len(orderIdList) > 0 { + orderList, _ := l.svcCtx.OrderModel.FindAll(l.ctx, l.svcCtx.OrderModel.SelectBuilder().Where(squirrel.Eq{"id": orderIdList}), "") + for _, o := range orderList { + orderStatusMap[o.Id] = o.Status + } + } + // 组装响应 items := make([]types.AgentOrderListItem, 0, len(orders)) for _, order := range orders { + orderStatus := orderStatusMap[order.OrderId] + if orderStatus == "" { + orderStatus = "unknown" // 如果查询不到订单,默认为 unknown + } items = append(items, types.AgentOrderListItem{ Id: order.Id, AgentId: order.AgentId, @@ -89,6 +129,7 @@ func (l *AdminGetAgentOrderListLogic) AdminGetAgentOrderList(req *types.AdminGet PriceCost: order.PriceCost, AgentProfit: order.AgentProfit, ProcessStatus: order.ProcessStatus, + OrderStatus: orderStatus, CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"), }) } diff --git a/app/main/api/internal/logic/admin_agent/admingetagentproductconfiglistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentproductconfiglistlogic.go index dc07c27..fd15488 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentproductconfiglistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentproductconfiglistlogic.go @@ -28,8 +28,7 @@ func NewAdminGetAgentProductConfigListLogic(ctx context.Context, svcCtx *svc.Ser } func (l *AdminGetAgentProductConfigListLogic) AdminGetAgentProductConfigList(req *types.AdminGetAgentProductConfigListReq) (resp *types.AdminGetAgentProductConfigListResp, err error) { - builder := l.svcCtx.AgentProductConfigModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentProductConfigModel.SelectBuilder() // 如果提供了产品ID,直接过滤 if req.ProductId != nil { diff --git a/app/main/api/internal/logic/admin_agent/admingetagentrealnamelistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentrealnamelistlogic.go index 54ac6ac..29dd5e9 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentrealnamelistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentrealnamelistlogic.go @@ -2,7 +2,6 @@ package admin_agent import ( "context" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/crypto" @@ -29,8 +28,7 @@ func NewAdminGetAgentRealNameListLogic(ctx context.Context, svcCtx *svc.ServiceC } func (l *AdminGetAgentRealNameListLogic) AdminGetAgentRealNameList(req *types.AdminGetAgentRealNameListReq) (resp *types.AdminGetAgentRealNameListResp, err error) { - builder := l.svcCtx.AgentRealNameModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentRealNameModel.SelectBuilder() if req.AgentId != nil { builder = builder.Where("agent_id = ?", *req.AgentId) diff --git a/app/main/api/internal/logic/admin_agent/admingetagentrebatelistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentrebatelistlogic.go index 2503dae..f0ff639 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentrebatelistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentrebatelistlogic.go @@ -2,7 +2,6 @@ package admin_agent import ( "context" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/Masterminds/squirrel" @@ -29,8 +28,7 @@ func NewAdminGetAgentRebateListLogic(ctx context.Context, svcCtx *svc.ServiceCon } func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminGetAgentRebateListReq) (resp *types.AdminGetAgentRebateListResp, err error) { - builder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentRebateModel.SelectBuilder() if req.AgentId != nil { builder = builder.Where("agent_id = ?", *req.AgentId) @@ -41,6 +39,9 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG if req.RebateType != nil { builder = builder.Where("rebate_type = ?", *req.RebateType) } + if req.Status != nil { + builder = builder.Where("status = ?", *req.Status) + } // 分页查询 page := req.Page @@ -84,6 +85,7 @@ func (l *AdminGetAgentRebateListLogic) AdminGetAgentRebateList(req *types.AdminG OrderId: rebate.OrderId, RebateType: rebate.RebateType, Amount: rebate.RebateAmount, + Status: rebate.Status, CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"), }) } diff --git a/app/main/api/internal/logic/admin_agent/admingetagentupgradelistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentupgradelistlogic.go index 6d49334..ac96b02 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentupgradelistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentupgradelistlogic.go @@ -2,7 +2,6 @@ package admin_agent import ( "context" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/pkg/errors" @@ -28,8 +27,7 @@ func NewAdminGetAgentUpgradeListLogic(ctx context.Context, svcCtx *svc.ServiceCo } func (l *AdminGetAgentUpgradeListLogic) AdminGetAgentUpgradeList(req *types.AdminGetAgentUpgradeListReq) (resp *types.AdminGetAgentUpgradeListResp, err error) { - builder := l.svcCtx.AgentUpgradeModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentUpgradeModel.SelectBuilder() if req.AgentId != nil { builder = builder.Where("agent_id = ?", *req.AgentId) diff --git a/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go index 2a92685..ec384cb 100644 --- a/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go @@ -49,6 +49,17 @@ func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *type item.Remark = v.Remark.String } item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + + // 如果是银行卡提现,填充银行卡信息 + if v.WithdrawalType == 2 { + if v.BankCardNo.Valid { + item.BankCardNo = v.BankCardNo.String + } + if v.BankName.Valid { + item.BankName = v.BankName.String + } + } + items = append(items, item) } resp = &types.AdminGetAgentWithdrawalListResp{ diff --git a/app/main/api/internal/logic/admin_agent/admingetinvitecodelistlogic.go b/app/main/api/internal/logic/admin_agent/admingetinvitecodelistlogic.go index f4d17ad..9d64ba1 100644 --- a/app/main/api/internal/logic/admin_agent/admingetinvitecodelistlogic.go +++ b/app/main/api/internal/logic/admin_agent/admingetinvitecodelistlogic.go @@ -2,7 +2,6 @@ package admin_agent import ( "context" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/crypto" @@ -31,8 +30,7 @@ func NewAdminGetInviteCodeListLogic(ctx context.Context, svcCtx *svc.ServiceCont func (l *AdminGetInviteCodeListLogic) AdminGetInviteCodeList(req *types.AdminGetInviteCodeListReq) (resp *types.AdminGetInviteCodeListResp, err error) { // 1. 构建查询条件 - builder := l.svcCtx.AgentInviteCodeModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.AgentInviteCodeModel.SelectBuilder() if req.Code != nil && *req.Code != "" { builder = builder.Where("code = ?", *req.Code) diff --git a/app/main/api/internal/logic/admin_complaint/admingetcomplaintdetaillogic.go b/app/main/api/internal/logic/admin_complaint/admingetcomplaintdetaillogic.go new file mode 100644 index 0000000..726bdd6 --- /dev/null +++ b/app/main/api/internal/logic/admin_complaint/admingetcomplaintdetaillogic.go @@ -0,0 +1,188 @@ +package admin_complaint + +import ( + "context" + "encoding/json" + "strconv" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/app/main/model" + "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetComplaintDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetComplaintDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetComplaintDetailLogic { + return &AdminGetComplaintDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetComplaintDetailLogic) AdminGetComplaintDetail(req *types.AdminGetComplaintDetailReq) (resp *types.AdminGetComplaintDetailResp, err error) { + // 获取投诉主表信息 + complaint, err := l.svcCtx.ComplaintMainModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintDetail, 查询投诉失败 err: %v", err) + } + + // 构建响应 + resp = &types.AdminGetComplaintDetailResp{ + Id: complaint.Id, + Type: complaint.Type, + OrderId: lzUtils.NullStringToString(complaint.OrderId), + Name: lzUtils.NullStringToString(complaint.Name), + Contact: lzUtils.NullStringToString(complaint.Contact), + Content: lzUtils.NullStringToString(complaint.Content), + Status: lzUtils.NullStringToString(complaint.Status), + StatusDescription: lzUtils.NullStringToString(complaint.StatusDescription), + Remark: lzUtils.NullStringToString(complaint.Remark), + HandlerId: lzUtils.NullStringToString(complaint.HandlerId), + CreateTime: complaint.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: complaint.UpdateTime.Format("2006-01-02 15:04:05"), + } + + if complaint.HandleTime.Valid { + resp.HandleTime = complaint.HandleTime.Time.Format("2006-01-02 15:04:05") + } + + // 获取支付宝投诉详情 + if complaint.Type == "alipay" { + alipayBuilder := l.svcCtx.ComplaintAlipayModel.SelectBuilder(). + Where("complaint_id = ?", complaint.Id) + alipayComplaints, err := l.svcCtx.ComplaintAlipayModel.FindAll(l.ctx, alipayBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintDetail, 查询支付宝投诉失败 err: %v", err) + } + + if len(alipayComplaints) > 0 { + alipayComplaint := alipayComplaints[0] + alipayDetail := &types.AlipayComplaintDetail{ + Id: alipayComplaint.Id, + AlipayId: alipayComplaint.AlipayId, + TaskId: alipayComplaint.TaskId, + OppositePid: lzUtils.NullStringToString(alipayComplaint.OppositePid), + OppositeName: lzUtils.NullStringToString(alipayComplaint.OppositeName), + ComplainContent: lzUtils.NullStringToString(alipayComplaint.ComplainContent), + TradeNo: lzUtils.NullStringToString(alipayComplaint.TradeNo), + Status: lzUtils.NullStringToString(alipayComplaint.Status), + StatusDescription: lzUtils.NullStringToString(alipayComplaint.StatusDescription), + ProcessCode: lzUtils.NullStringToString(alipayComplaint.ProcessCode), + ProcessMessage: lzUtils.NullStringToString(alipayComplaint.ProcessMessage), + ProcessRemark: lzUtils.NullStringToString(alipayComplaint.ProcessRemark), + ComplainUrl: lzUtils.NullStringToString(alipayComplaint.ComplainUrl), + } + + if alipayComplaint.ComplainAmount.Valid { + alipayDetail.ComplainAmount = strconv.FormatFloat(alipayComplaint.ComplainAmount.Float64, 'f', -1, 64) + } + if alipayComplaint.GmtComplain.Valid { + alipayDetail.GmtComplain = alipayComplaint.GmtComplain.Time.Format("2006-01-02 15:04:05") + } + if alipayComplaint.GmtProcess.Valid { + alipayDetail.GmtProcess = alipayComplaint.GmtProcess.Time.Format("2006-01-02 15:04:05") + } + if alipayComplaint.GmtOverdue.Valid { + alipayDetail.GmtOverdue = alipayComplaint.GmtOverdue.Time.Format("2006-01-02 15:04:05") + } + if alipayComplaint.GmtRiskFinishTime.Valid { + alipayDetail.GmtRiskFinishTime = alipayComplaint.GmtRiskFinishTime.Time.Format("2006-01-02 15:04:05") + } + + // 解析图片列表 + if alipayComplaint.ProcessImgUrlList.Valid { + var imgList []string + if err := json.Unmarshal([]byte(alipayComplaint.ProcessImgUrlList.String), &imgList); err == nil { + alipayDetail.ProcessImgUrlList = imgList + } + } + if alipayComplaint.CertifyInfo.Valid { + var certifyList []string + if err := json.Unmarshal([]byte(alipayComplaint.CertifyInfo.String), &certifyList); err == nil { + alipayDetail.CertifyInfo = certifyList + } + } + + // 获取交易信息列表 + tradeBuilder := l.svcCtx.ComplaintAlipayTradeModel.SelectBuilder(). + Where("complaint_alipay_id = ?", alipayComplaint.Id) + trades, err := l.svcCtx.ComplaintAlipayTradeModel.FindAll(l.ctx, tradeBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintDetail, 查询交易信息失败 err: %v", err) + } + + alipayDetail.TradeInfoList = make([]types.AlipayComplaintTradeInfo, 0, len(trades)) + for _, trade := range trades { + tradeInfo := types.AlipayComplaintTradeInfo{ + Id: trade.Id, + TradeNo: lzUtils.NullStringToString(trade.TradeNo), + OutNo: lzUtils.NullStringToString(trade.OutNo), + Status: lzUtils.NullStringToString(trade.Status), + StatusDescription: lzUtils.NullStringToString(trade.StatusDescription), + } + + if trade.AlipayTradeId.Valid { + tradeInfo.AlipayTradeId = strconv.FormatInt(trade.AlipayTradeId.Int64, 10) + } + if trade.AlipayComplaintRecordId.Valid { + tradeInfo.AlipayComplaintRecordId = strconv.FormatInt(trade.AlipayComplaintRecordId.Int64, 10) + } + if trade.GmtTrade.Valid { + tradeInfo.GmtTrade = trade.GmtTrade.Time.Format("2006-01-02 15:04:05") + } + if trade.GmtRefund.Valid { + tradeInfo.GmtRefund = trade.GmtRefund.Time.Format("2006-01-02 15:04:05") + } + if trade.Amount.Valid { + tradeInfo.Amount = strconv.FormatFloat(trade.Amount.Float64, 'f', -1, 64) + } + + alipayDetail.TradeInfoList = append(alipayDetail.TradeInfoList, tradeInfo) + } + + resp.AlipayComplaint = alipayDetail + } + } + + // 获取主动投诉详情 + if complaint.Type == "manual" { + manualBuilder := l.svcCtx.ComplaintManualModel.SelectBuilder(). + Where("complaint_id = ?", complaint.Id) + manualComplaints, err := l.svcCtx.ComplaintManualModel.FindAll(l.ctx, manualBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintDetail, 查询主动投诉失败 err: %v", err) + } + + if len(manualComplaints) > 0 { + manualComplaint := manualComplaints[0] + manualDetail := &types.ManualComplaintDetail{ + Id: manualComplaint.Id, + UserId: lzUtils.NullStringToString(manualComplaint.UserId), + Subject: lzUtils.NullStringToString(manualComplaint.Subject), + Priority: lzUtils.NullStringToString(manualComplaint.Priority), + Source: lzUtils.NullStringToString(manualComplaint.Source), + } + + // 解析附件URL列表 + if manualComplaint.AttachmentUrls.Valid { + var urlList []string + if err := json.Unmarshal([]byte(manualComplaint.AttachmentUrls.String), &urlList); err == nil { + manualDetail.AttachmentUrls = urlList + } + } + + resp.ManualComplaint = manualDetail + } + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_complaint/admingetcomplaintlistlogic.go b/app/main/api/internal/logic/admin_complaint/admingetcomplaintlistlogic.go new file mode 100644 index 0000000..b6aeadd --- /dev/null +++ b/app/main/api/internal/logic/admin_complaint/admingetcomplaintlistlogic.go @@ -0,0 +1,174 @@ +package admin_complaint + +import ( + "context" + "strconv" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/app/main/model" + "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetComplaintListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetComplaintListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetComplaintListLogic { + return &AdminGetComplaintListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetComplaintListLogic) AdminGetComplaintList(req *types.AdminGetComplaintListReq) (resp *types.AdminGetComplaintListResp, err error) { + // 构建查询条件 + builder := l.svcCtx.ComplaintMainModel.SelectBuilder() + if req.Type != "" { + builder = builder.Where("type = ?", req.Type) + } + if req.Status != "" { + builder = builder.Where("status = ?", req.Status) + } + if req.Name != "" { + builder = builder.Where("name LIKE ?", "%"+req.Name+"%") + } + if req.Contact != "" { + builder = builder.Where("contact LIKE ?", "%"+req.Contact+"%") + } + if req.OrderId != "" { + builder = builder.Where("order_id = ?", req.OrderId) + } + // 时间范围查询 + if req.CreateTimeStart != "" { + builder = builder.Where("create_time >= ?", req.CreateTimeStart) + } + if req.CreateTimeEnd != "" { + builder = builder.Where("create_time <= ?", req.CreateTimeEnd) + } + if req.HandleTimeStart != "" { + builder = builder.Where("handle_time >= ?", req.HandleTimeStart) + } + if req.HandleTimeEnd != "" { + builder = builder.Where("handle_time <= ?", req.HandleTimeEnd) + } + + // 并发获取总数和列表 + var total int64 + var complaints []*model.ComplaintMain + err = mr.Finish(func() error { + var err error + total, err = l.svcCtx.ComplaintMainModel.FindCount(l.ctx, builder, "id") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintList, 查询投诉总数失败 err: %v", err) + } + return nil + }, func() error { + var err error + complaints, err = l.svcCtx.ComplaintMainModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintList, 查询投诉列表失败 err: %v", err) + } + return nil + }) + if err != nil { + return nil, err + } + + // 构建响应 + resp = &types.AdminGetComplaintListResp{ + Total: total, + Items: make([]types.ComplaintListItem, 0, len(complaints)), + } + + // 批量获取支付宝投诉和主动投诉的详细信息 + complaintIds := make([]string, 0, len(complaints)) + for _, complaint := range complaints { + complaintIds = append(complaintIds, complaint.Id) + } + + // 获取支付宝投诉信息 + alipayComplaintMap := make(map[string]*model.ComplaintAlipay) + if len(complaintIds) > 0 { + alipayBuilder := l.svcCtx.ComplaintAlipayModel.SelectBuilder(). + Where(squirrel.Eq{"complaint_id": complaintIds}) + alipayComplaints, err := l.svcCtx.ComplaintAlipayModel.FindAll(l.ctx, alipayBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintList, 批量查询支付宝投诉失败 err: %v", err) + } + for _, alipayComplaint := range alipayComplaints { + alipayComplaintMap[alipayComplaint.ComplaintId] = alipayComplaint + } + } + + // 获取主动投诉信息 + manualComplaintMap := make(map[string]*model.ComplaintManual) + if len(complaintIds) > 0 { + manualBuilder := l.svcCtx.ComplaintManualModel.SelectBuilder(). + Where(squirrel.Eq{"complaint_id": complaintIds}) + manualComplaints, err := l.svcCtx.ComplaintManualModel.FindAll(l.ctx, manualBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetComplaintList, 批量查询主动投诉失败 err: %v", err) + } + for _, manualComplaint := range manualComplaints { + manualComplaintMap[manualComplaint.ComplaintId] = manualComplaint + } + } + + // 构建列表项 + for _, complaint := range complaints { + item := types.ComplaintListItem{ + Id: complaint.Id, + Type: complaint.Type, + OrderId: lzUtils.NullStringToString(complaint.OrderId), + Name: lzUtils.NullStringToString(complaint.Name), + Contact: lzUtils.NullStringToString(complaint.Contact), + Content: lzUtils.NullStringToString(complaint.Content), + Status: lzUtils.NullStringToString(complaint.Status), + StatusDescription: lzUtils.NullStringToString(complaint.StatusDescription), + Remark: lzUtils.NullStringToString(complaint.Remark), + HandlerId: lzUtils.NullStringToString(complaint.HandlerId), + CreateTime: complaint.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: complaint.UpdateTime.Format("2006-01-02 15:04:05"), + } + + if complaint.HandleTime.Valid { + item.HandleTime = complaint.HandleTime.Time.Format("2006-01-02 15:04:05") + } + + // 填充支付宝投诉特有字段 + if complaint.Type == "alipay" { + if alipayComplaint, ok := alipayComplaintMap[complaint.Id]; ok { + item.TaskId = alipayComplaint.TaskId + item.TradeNo = lzUtils.NullStringToString(alipayComplaint.TradeNo) + if alipayComplaint.ComplainAmount.Valid { + item.ComplainAmount = strconv.FormatFloat(alipayComplaint.ComplainAmount.Float64, 'f', -1, 64) + } + if alipayComplaint.GmtComplain.Valid { + item.GmtComplain = alipayComplaint.GmtComplain.Time.Format("2006-01-02 15:04:05") + } + } + } + + // 填充主动投诉特有字段 + if complaint.Type == "manual" { + if manualComplaint, ok := manualComplaintMap[complaint.Id]; ok { + item.Subject = lzUtils.NullStringToString(manualComplaint.Subject) + item.Priority = lzUtils.NullStringToString(manualComplaint.Priority) + item.Source = lzUtils.NullStringToString(manualComplaint.Source) + } + } + + resp.Items = append(resp.Items, item) + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintremarklogic.go b/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintremarklogic.go new file mode 100644 index 0000000..747c8b4 --- /dev/null +++ b/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintremarklogic.go @@ -0,0 +1,47 @@ +package admin_complaint + +import ( + "context" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateComplaintRemarkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateComplaintRemarkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateComplaintRemarkLogic { + return &AdminUpdateComplaintRemarkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateComplaintRemarkLogic) AdminUpdateComplaintRemark(req *types.AdminUpdateComplaintRemarkReq) (resp *types.AdminUpdateComplaintRemarkResp, err error) { + // 获取投诉主表信息 + complaint, err := l.svcCtx.ComplaintMainModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateComplaintRemark, 查询投诉失败 err: %v", err) + } + + // 更新备注 + complaint.Remark = lzUtils.StringToNullString(req.Remark) + + // 更新数据库 + err = l.svcCtx.ComplaintMainModel.UpdateWithVersion(l.ctx, nil, complaint) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateComplaintRemark, 更新投诉备注失败 err: %v", err) + } + + return &types.AdminUpdateComplaintRemarkResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintstatuslogic.go b/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintstatuslogic.go new file mode 100644 index 0000000..132f400 --- /dev/null +++ b/app/main/api/internal/logic/admin_complaint/adminupdatecomplaintstatuslogic.go @@ -0,0 +1,55 @@ +package admin_complaint + +import ( + "context" + "time" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateComplaintStatusLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateComplaintStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateComplaintStatusLogic { + return &AdminUpdateComplaintStatusLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateComplaintStatusLogic) AdminUpdateComplaintStatus(req *types.AdminUpdateComplaintStatusReq) (resp *types.AdminUpdateComplaintStatusResp, err error) { + // 获取投诉主表信息 + complaint, err := l.svcCtx.ComplaintMainModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateComplaintStatus, 查询投诉失败 err: %v", err) + } + + // 更新状态 + complaint.Status = lzUtils.StringToNullString(req.Status) + if req.StatusDescription != "" { + complaint.StatusDescription = lzUtils.StringToNullString(req.StatusDescription) + } + if req.HandlerId != "" { + complaint.HandlerId = lzUtils.StringToNullString(req.HandlerId) + complaint.HandleTime = lzUtils.TimeToNullTime(time.Now()) + } + + // 更新数据库 + err = l.svcCtx.ComplaintMainModel.UpdateWithVersion(l.ctx, nil, complaint) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateComplaintStatus, 更新投诉状态失败 err: %v", err) + } + + return &types.AdminUpdateComplaintStatusResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go b/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go new file mode 100644 index 0000000..5be9d91 --- /dev/null +++ b/app/main/api/internal/logic/admin_dashboard/admingetdashboardstatisticslogic.go @@ -0,0 +1,381 @@ +package admin_dashboard + +import ( + "context" + "time" + "ycc-server/common/globalkey" + "ycc-server/common/xerr" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetDashboardStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetDashboardStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetDashboardStatisticsLogic { + return &AdminGetDashboardStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetDashboardStatisticsLogic) AdminGetDashboardStatistics() (resp *types.AdminGetDashboardStatisticsResp, err error) { + // 使用Asia/Shanghai时区 + loc, _ := time.LoadLocation("Asia/Shanghai") + now := time.Now().In(loc) + + // 计算时间范围 + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) + todayEnd := todayStart.AddDate(0, 0, 1) + yesterdayStart := todayStart.AddDate(0, 0, -1) + yesterdayEnd := todayStart + monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc) + monthEnd := monthStart.AddDate(0, 1, 0) + + // 1. 订单统计 + orderStats, err := l.calculateOrderStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算订单统计失败, %v", err) + } + + // 2. 营收统计 + revenueStats, err := l.calculateRevenueStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算营收统计失败, %v", err) + } + + // 3. 代理统计 + agentStats, err := l.calculateAgentStatistics(todayStart, monthStart) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算代理统计失败, %v", err) + } + + // 4. 利润统计 + profitStats, err := l.calculateProfitStatistics(todayStart, todayEnd, monthStart, monthEnd, revenueStats) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算利润统计失败, %v", err) + } + + // 5. 订单趋势(最近7天) + orderTrend, err := l.calculateOrderTrend(now, loc) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算订单趋势失败, %v", err) + } + + // 6. 营收趋势(最近7天) + revenueTrend, err := l.calculateRevenueTrend(now, loc) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算营收趋势失败, %v", err) + } + + return &types.AdminGetDashboardStatisticsResp{ + OrderStats: orderStats, + RevenueStats: revenueStats, + AgentStats: agentStats, + ProfitStats: profitStats, + OrderTrend: orderTrend, + RevenueTrend: revenueTrend, + }, nil +} + +// calculateOrderStatistics 计算订单统计 +func (l *AdminGetDashboardStatisticsLogic) calculateOrderStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd time.Time) (types.AdminOrderStatistics, error) { + var stats types.AdminOrderStatistics + + // 今日订单数 + todayBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", todayStart, todayEnd) + todayCount, err := l.svcCtx.OrderModel.FindCount(l.ctx, todayBuilder, "id") + if err != nil { + return stats, err + } + stats.TodayCount = todayCount + + // 昨日订单数 + yesterdayBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", yesterdayStart, yesterdayEnd) + yesterdayCount, err := l.svcCtx.OrderModel.FindCount(l.ctx, yesterdayBuilder, "id") + if err != nil { + return stats, err + } + stats.YesterdayCount = yesterdayCount + + // 当月订单数 + monthBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", monthStart, monthEnd) + monthCount, err := l.svcCtx.OrderModel.FindCount(l.ctx, monthBuilder, "id") + if err != nil { + return stats, err + } + stats.MonthCount = monthCount + + // 总订单数 + totalBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ?", "paid") + totalCount, err := l.svcCtx.OrderModel.FindCount(l.ctx, totalBuilder, "id") + if err != nil { + return stats, err + } + stats.TotalCount = totalCount + + // 计算变化率 + if stats.YesterdayCount > 0 { + stats.ChangeRate = float64(stats.TodayCount-stats.YesterdayCount) / float64(stats.YesterdayCount) * 100 + } else if stats.TodayCount > 0 { + stats.ChangeRate = 100 // 从0增长到有值,算100%增长 + } + + return stats, nil +} + +// calculateRevenueStatistics 计算营收统计 +func (l *AdminGetDashboardStatisticsLogic) calculateRevenueStatistics(todayStart, todayEnd, yesterdayStart, yesterdayEnd, monthStart, monthEnd time.Time) (types.AdminRevenueStatistics, error) { + var stats types.AdminRevenueStatistics + + // 今日营收 + todayBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", todayStart, todayEnd) + todayAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, todayBuilder, "amount") + if err != nil { + return stats, err + } + stats.TodayAmount = todayAmount + + // 昨日营收 + yesterdayBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", yesterdayStart, yesterdayEnd) + yesterdayAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, yesterdayBuilder, "amount") + if err != nil { + return stats, err + } + stats.YesterdayAmount = yesterdayAmount + + // 当月营收 + monthBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", monthStart, monthEnd) + monthAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, monthBuilder, "amount") + if err != nil { + return stats, err + } + stats.MonthAmount = monthAmount + + // 总营收 + totalBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ?", "paid") + totalAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, totalBuilder, "amount") + if err != nil { + return stats, err + } + stats.TotalAmount = totalAmount + + // 计算变化率 + if stats.YesterdayAmount > 0 { + stats.ChangeRate = (stats.TodayAmount - stats.YesterdayAmount) / stats.YesterdayAmount * 100 + } else if stats.TodayAmount > 0 { + stats.ChangeRate = 100 // 从0增长到有值,算100%增长 + } + + return stats, nil +} + +// calculateAgentStatistics 计算代理统计 +func (l *AdminGetDashboardStatisticsLogic) calculateAgentStatistics(todayStart, monthStart time.Time) (types.AdminAgentStatistics, error) { + var stats types.AdminAgentStatistics + + // 代理总数 + totalBuilder := l.svcCtx.AgentModel.SelectBuilder() + totalCount, err := l.svcCtx.AgentModel.FindCount(l.ctx, totalBuilder, "id") + if err != nil { + return stats, err + } + stats.TotalCount = totalCount + + // 今日新增 + todayBuilder := l.svcCtx.AgentModel.SelectBuilder(). + Where("create_time >= ?", todayStart) + todayNew, err := l.svcCtx.AgentModel.FindCount(l.ctx, todayBuilder, "id") + if err != nil { + return stats, err + } + stats.TodayNew = todayNew + + // 当月新增 + monthBuilder := l.svcCtx.AgentModel.SelectBuilder(). + Where("create_time >= ?", monthStart) + monthNew, err := l.svcCtx.AgentModel.FindCount(l.ctx, monthBuilder, "id") + if err != nil { + return stats, err + } + stats.MonthNew = monthNew + + return stats, nil +} + +// calculateProfitStatistics 计算利润统计 +func (l *AdminGetDashboardStatisticsLogic) calculateProfitStatistics(todayStart, todayEnd, monthStart, monthEnd time.Time, revenueStats types.AdminRevenueStatistics) (types.AdminProfitStatistics, error) { + var stats types.AdminProfitStatistics + + // 公司交税比例(6%) + const companyTaxRate = 0.06 + + // 今日利润计算 + // 今日营收 + todayRevenue := revenueStats.TodayAmount + // 今日佣金 + todayCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). + Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, todayStart, todayEnd) + todayCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, todayCommissionBuilder, "amount") + if err != nil { + return stats, err + } + // 今日返利 + todayRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). + Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, todayStart, todayEnd) + todayRebate, err := l.svcCtx.AgentRebateModel.FindSum(l.ctx, todayRebateBuilder, "rebate_amount") + if err != nil { + return stats, err + } + // 今日公司交税(订单金额的6%) + todayCompanyTax := todayRevenue * companyTaxRate + // 今日平台收入税(agent_withdrawal_tax表中tax_status=2的tax_amount总和) + todayTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). + Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, todayStart, todayEnd) + todayTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, todayTaxIncomeBuilder, "tax_amount") + if err != nil { + return stats, err + } + // 今日利润 = 营收 - 佣金 - 返利 - 公司交税 + 平台收入税 + stats.TodayProfit = todayRevenue - todayCommission - todayRebate - todayCompanyTax + todayTaxIncome + if todayRevenue > 0 { + stats.TodayProfitRate = stats.TodayProfit / todayRevenue * 100 + } + + // 当月利润计算 + // 当月营收 + monthRevenue := revenueStats.MonthAmount + // 当月佣金 + monthCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). + Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, monthStart, monthEnd) + monthCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, monthCommissionBuilder, "amount") + if err != nil { + return stats, err + } + // 当月返利 + monthRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). + Where("del_state = ? AND status != ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 3, monthStart, monthEnd) + monthRebate, err := l.svcCtx.AgentRebateModel.FindSum(l.ctx, monthRebateBuilder, "rebate_amount") + if err != nil { + return stats, err + } + // 当月公司交税 + monthCompanyTax := monthRevenue * companyTaxRate + // 当月平台收入税 + monthTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). + Where("del_state = ? AND tax_status = ? AND create_time >= ? AND create_time < ?", globalkey.DelStateNo, 2, monthStart, monthEnd) + monthTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, monthTaxIncomeBuilder, "tax_amount") + if err != nil { + return stats, err + } + // 当月利润 + stats.MonthProfit = monthRevenue - monthCommission - monthRebate - monthCompanyTax + monthTaxIncome + if monthRevenue > 0 { + stats.MonthProfitRate = stats.MonthProfit / monthRevenue * 100 + } + + // 总利润计算 + // 总营收 + totalRevenue := revenueStats.TotalAmount + // 总佣金 + totalCommissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). + Where("del_state = ? AND status != ?", globalkey.DelStateNo, 3) + totalCommission, err := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, totalCommissionBuilder, "amount") + if err != nil { + return stats, err + } + // 总返利 + totalRebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). + Where("del_state = ? AND status != ?", globalkey.DelStateNo, 3) + totalRebate, err := l.svcCtx.AgentRebateModel.FindSum(l.ctx, totalRebateBuilder, "rebate_amount") + if err != nil { + return stats, err + } + // 总公司交税 + totalCompanyTax := totalRevenue * companyTaxRate + // 总平台收入税 + totalTaxIncomeBuilder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). + Where("del_state = ? AND tax_status = ?", globalkey.DelStateNo, 2) + totalTaxIncome, err := l.svcCtx.AgentWithdrawalTaxModel.FindSum(l.ctx, totalTaxIncomeBuilder, "tax_amount") + if err != nil { + return stats, err + } + // 总利润 + stats.TotalProfit = totalRevenue - totalCommission - totalRebate - totalCompanyTax + totalTaxIncome + if totalRevenue > 0 { + stats.TotalProfitRate = stats.TotalProfit / totalRevenue * 100 + } + + return stats, nil +} + +// calculateOrderTrend 计算订单趋势(最近7天) +func (l *AdminGetDashboardStatisticsLogic) calculateOrderTrend(now time.Time, loc *time.Location) ([]types.AdminTrendData, error) { + var trend []types.AdminTrendData + + // 计算最近7天的日期 + for i := 6; i >= 0; i-- { + date := now.AddDate(0, 0, -i) + dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc) + dateEnd := dateStart.AddDate(0, 0, 1) + + // 查询当天的订单数 + builder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", dateStart, dateEnd) + count, err := l.svcCtx.OrderModel.FindCount(l.ctx, builder, "id") + if err != nil { + return nil, err + } + + trend = append(trend, types.AdminTrendData{ + Date: date.Format("01-02"), + Value: float64(count), + }) + } + + return trend, nil +} + +// calculateRevenueTrend 计算营收趋势(最近7天) +func (l *AdminGetDashboardStatisticsLogic) calculateRevenueTrend(now time.Time, loc *time.Location) ([]types.AdminTrendData, error) { + var trend []types.AdminTrendData + + // 计算最近7天的日期 + for i := 6; i >= 0; i-- { + date := now.AddDate(0, 0, -i) + dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc) + dateEnd := dateStart.AddDate(0, 0, 1) + + // 查询当天的营收 + builder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND create_time >= ? AND create_time < ?", "paid", dateStart, dateEnd) + amount, err := l.svcCtx.OrderModel.FindSum(l.ctx, builder, "amount") + if err != nil { + return nil, err + } + + trend = append(trend, types.AdminTrendData{ + Date: date.Format("01-02"), + Value: amount, + }) + } + + return trend, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go b/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go index b422c8d..e5e4cfc 100644 --- a/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go +++ b/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go @@ -34,6 +34,10 @@ func (l *AdminCreateFeatureLogic) AdminCreateFeature(req *types.AdminCreateFeatu ApiId: req.ApiId, Name: req.Name, } + // 设置白名单屏蔽价格 + if req.WhitelistPrice != nil { + data.WhitelistPrice = *req.WhitelistPrice + } // 2. 数据库操作 result, err := l.svcCtx.FeatureModel.Insert(l.ctx, nil, data) diff --git a/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go b/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go index 97ac608..6625821 100644 --- a/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go +++ b/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go @@ -35,11 +35,12 @@ func (l *AdminGetFeatureDetailLogic) AdminGetFeatureDetail(req *types.AdminGetFe // 2. 构建响应 resp = &types.AdminGetFeatureDetailResp{ - Id: record.Id, - ApiId: record.ApiId, - Name: record.Name, - CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"), - UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"), + Id: record.Id, + ApiId: record.ApiId, + Name: record.Name, + WhitelistPrice: record.WhitelistPrice, + CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"), } return resp, nil diff --git a/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go b/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go index 19120d2..c4a91d4 100644 --- a/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go +++ b/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go @@ -49,11 +49,12 @@ func (l *AdminGetFeatureListLogic) AdminGetFeatureList(req *types.AdminGetFeatur items := make([]types.FeatureListItem, 0, len(list)) for _, item := range list { listItem := types.FeatureListItem{ - Id: item.Id, - ApiId: item.ApiId, - Name: item.Name, - CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"), - UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"), + Id: item.Id, + ApiId: item.ApiId, + Name: item.Name, + WhitelistPrice: item.WhitelistPrice, + CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"), } items = append(items, listItem) } diff --git a/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go b/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go index 526be22..73f7a9f 100644 --- a/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go +++ b/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go @@ -46,6 +46,9 @@ func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatu if req.Name != nil && *req.Name != "" { record.Name = *req.Name } + if req.WhitelistPrice != nil { + record.WhitelistPrice = *req.WhitelistPrice + } // 4. 执行更新操作 err = l.svcCtx.FeatureModel.UpdateWithVersion(l.ctx, nil, record) diff --git a/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go b/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go index 7a787a8..2aec6bb 100644 --- a/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go +++ b/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go @@ -6,7 +6,6 @@ import ( "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/pkg/errors" @@ -84,7 +83,6 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD // 查询清理日志 cleanupBuilder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder(). Where("order_id = ?", order.Id). - Where("del_state = ?", globalkey.DelStateNo). OrderBy("create_time DESC"). Limit(1) cleanupDetails, err := l.svcCtx.QueryCleanupDetailModel.FindAll(l.ctx, cleanupBuilder, "") diff --git a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go index 8b0a502..1712be2 100644 --- a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go +++ b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go @@ -7,7 +7,6 @@ import ( "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/Masterminds/squirrel" @@ -139,7 +138,6 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR // 查询清理日志 cleanupBuilder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder(). Where(squirrel.Eq{"order_id": notFoundOrderIds}). - Where("del_state = ?", globalkey.DelStateNo). OrderBy("create_time DESC") cleanupDetails, err := l.svcCtx.QueryCleanupDetailModel.FindAll(l.ctx, cleanupBuilder, "") if err != nil && !errors.Is(err, model.ErrNotFound) { diff --git a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go index d915445..f91d124 100644 --- a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go +++ b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go @@ -1,27 +1,30 @@ package admin_order import ( - "context" - "database/sql" - "fmt" - "time" + "context" + "database/sql" + "fmt" + "time" - "ycc-server/app/main/api/internal/svc" - "ycc-server/app/main/api/internal/types" - "ycc-server/app/main/model" - "ycc-server/common/xerr" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + "ycc-server/app/main/model" + "ycc-server/common/xerr" - "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/logx" - "github.com/zeromicro/go-zero/core/stores/sqlx" - "github.com/google/uuid" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/smartwalle/alipay/v3" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" ) const ( - PaymentPlatformAlipay = "alipay" - PaymentPlatformWechat = "wechat" - OrderStatusPaid = "paid" - RefundNoPrefix = "refund-" + PaymentPlatformAlipay = "alipay" + PaymentPlatformWechat = "wechat" + PaymentPlatformTest = "test" + PaymentPlatformTestEmpty = "test_empty" + OrderStatusPaid = "paid" + RefundNoPrefix = "refund-" ) type AdminRefundOrderLogic struct { @@ -44,6 +47,11 @@ func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) return nil, err } + // 检查是否为测试支付平台(test 或 test_empty),如果是则模拟退款成功 + if order.PaymentPlatform == PaymentPlatformTest || order.PaymentPlatform == PaymentPlatformTestEmpty { + return l.handleTestRefund(order, req) + } + // 根据支付平台处理退款 switch order.PaymentPlatform { case PaymentPlatformAlipay: @@ -75,29 +83,39 @@ func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId string, refundAmount return order, nil } +// handleTestRefund 处理测试支付平台退款(模拟退款成功) +func (l *AdminRefundOrderLogic) handleTestRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { + refundNo := l.generateRefundNo(order.OrderNo) + // 创建模拟的平台退款ID + platformRefundId := fmt.Sprintf("MOCK_REFUND_%s_%d", order.OrderNo, time.Now().Unix()) + + logx.Infof("测试支付平台模拟退款:订单 %s,退款金额 %.2f,跳过实际退款接口调用", order.OrderNo, req.RefundAmount) + + // 直接标记为退款成功 + err := l.createRefundRecordAndUpdateOrder(order, req, refundNo, platformRefundId, model.OrderStatusRefunded, model.OrderRefundStatusSuccess) + if err != nil { + return nil, err + } + + return &types.AdminRefundOrderResp{ + Status: model.OrderStatusRefunded, + RefundNo: refundNo, + Amount: req.RefundAmount, + }, nil +} + // handleAlipayRefund 处理支付宝退款 func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { + refundNo := l.generateRefundNo(order.OrderNo) + // 调用支付宝退款接口 + var refundResp *alipay.TradeRefundRsp refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err) } - refundNo := l.generateRefundNo(order.OrderNo) - - if refundResp.IsSuccess() { - // 支付宝退款成功,创建成功记录 - err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess) - if err != nil { - return nil, err - } - - return &types.AdminRefundOrderResp{ - Status: model.OrderStatusRefunded, - RefundNo: refundNo, - Amount: req.RefundAmount, - }, nil - } else { + if !refundResp.IsSuccess() { // 支付宝退款失败,创建失败记录但不更新订单状态 err = l.createRefundRecordOnly(order, req, refundNo, refundResp.TradeNo, model.OrderRefundStatusFailed) if err != nil { @@ -105,10 +123,24 @@ func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *type } return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败: %v", refundResp.Msg)), "AdminRefundOrder, 支付宝退款失败") } + + // 支付宝退款成功,创建成功记录 + err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess) + if err != nil { + return nil, err + } + + return &types.AdminRefundOrderResp{ + Status: model.OrderStatusRefunded, + RefundNo: refundNo, + Amount: req.RefundAmount, + }, nil } // handleWechatRefund 处理微信退款 func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { + refundNo := l.generateRefundNo(order.OrderNo) + // 调用微信退款接口 err := l.svcCtx.WechatPayService.WeChatRefund(l.ctx, order.OrderNo, req.RefundAmount, order.Amount) if err != nil { @@ -116,7 +148,6 @@ func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *type } // 微信退款是异步的,创建pending状态的退款记录 - refundNo := l.generateRefundNo(order.OrderNo) err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunding, model.OrderRefundStatusPending) if err != nil { return nil, err @@ -131,20 +162,20 @@ func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *type // createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态 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 { + err := l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { // 创建退款记录 - refund := &model.OrderRefund{ - Id: uuid.NewString(), - RefundNo: refundNo, - PlatformRefundId: l.createNullString(platformRefundId), - OrderId: order.Id, - UserId: order.UserId, - ProductId: order.ProductId, - RefundAmount: req.RefundAmount, - RefundReason: l.createNullString(req.RefundReason), - Status: refundStatus, // 使用传入的状态,不再硬编码 - RefundTime: sql.NullTime{Time: time.Now(), Valid: true}, - } + refund := &model.OrderRefund{ + Id: uuid.NewString(), + RefundNo: refundNo, + PlatformRefundId: l.createNullString(platformRefundId), + OrderId: order.Id, + UserId: order.UserId, + ProductId: order.ProductId, + RefundAmount: req.RefundAmount, + RefundReason: l.createNullString(req.RefundReason), + Status: refundStatus, // 使用传入的状态,不再硬编码 + RefundTime: sql.NullTime{Time: time.Now(), Valid: true}, + } if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil { return fmt.Errorf("创建退款记录失败: %v", err) @@ -158,22 +189,43 @@ func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Or return nil }) + + if err != nil { + return err + } + + // 退款成功后,检查并处理代理订单(在事务外执行,避免影响退款流程) + if refundStatus == model.OrderRefundStatusSuccess { + // 检查代理订单是否已处理 + agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, order.Id) + if err == nil && agentOrder != nil && agentOrder.ProcessStatus == 1 { + // 代理订单已处理,需要撤销收益 + if cancelErr := l.svcCtx.AgentService.CancelAgentCommission(l.ctx, order.Id); cancelErr != nil { + logx.Errorf("撤销代理收益失败,订单ID: %s, 错误: %v", order.Id, cancelErr) + // 不阻断退款流程,只记录日志(退款已成功,不能回滚) + } else { + logx.Infof("成功撤销代理收益,订单ID: %s", order.Id) + } + } + } + + return nil } // createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况) func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error { - refund := &model.OrderRefund{ - Id: uuid.NewString(), - RefundNo: refundNo, - PlatformRefundId: l.createNullString(platformRefundId), - OrderId: order.Id, - UserId: order.UserId, - ProductId: order.ProductId, - RefundAmount: req.RefundAmount, - RefundReason: l.createNullString(req.RefundReason), - Status: refundStatus, - RefundTime: sql.NullTime{Time: time.Now(), Valid: true}, - } + refund := &model.OrderRefund{ + Id: uuid.NewString(), + RefundNo: refundNo, + PlatformRefundId: l.createNullString(platformRefundId), + OrderId: order.Id, + UserId: order.UserId, + ProductId: order.ProductId, + RefundAmount: req.RefundAmount, + RefundReason: l.createNullString(req.RefundReason), + Status: refundStatus, + RefundTime: sql.NullTime{Time: time.Now(), Valid: true}, + } _, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund) if err != nil { diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go index de2a153..c86ca7f 100644 --- a/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go @@ -4,7 +4,6 @@ import ( "context" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/pkg/errors" @@ -27,8 +26,7 @@ func NewAdminGetQueryCleanupConfigListLogic(ctx context.Context, svcCtx *svc.Ser func (l *AdminGetQueryCleanupConfigListLogic) AdminGetQueryCleanupConfigList(req *types.AdminGetQueryCleanupConfigListReq) (resp *types.AdminGetQueryCleanupConfigListResp, err error) { // 构建查询条件 - builder := l.svcCtx.QueryCleanupConfigModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.QueryCleanupConfigModel.SelectBuilder() if req.Status > 0 { builder = builder.Where("status = ?", req.Status) diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go index 3cf91ec..d780a3f 100644 --- a/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go @@ -6,7 +6,6 @@ import ( "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/pkg/errors" @@ -40,8 +39,7 @@ func (l *AdminGetQueryCleanupDetailListLogic) AdminGetQueryCleanupDetailList(req // 2. 构建查询条件 builder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder(). - Where("cleanup_log_id = ?", req.LogId). - Where("del_state = ?", globalkey.DelStateNo) + Where("cleanup_log_id = ?", req.LogId) // 3. 并发获取总数和列表 var total int64 diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go index e15be09..8ba94db 100644 --- a/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go @@ -5,7 +5,6 @@ import ( "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "github.com/pkg/errors" @@ -29,8 +28,7 @@ func NewAdminGetQueryCleanupLogListLogic(ctx context.Context, svcCtx *svc.Servic func (l *AdminGetQueryCleanupLogListLogic) AdminGetQueryCleanupLogList(req *types.AdminGetQueryCleanupLogListReq) (resp *types.AdminGetQueryCleanupLogListResp, err error) { // 构建查询条件 - builder := l.svcCtx.QueryCleanupLogModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo) + builder := l.svcCtx.QueryCleanupLogModel.SelectBuilder() if req.Status > 0 { builder = builder.Where("status = ?", req.Status) diff --git a/app/main/api/internal/logic/agent/applywithdrawallogic.go b/app/main/api/internal/logic/agent/applywithdrawallogic.go index 4afe366..5d6aff7 100644 --- a/app/main/api/internal/logic/agent/applywithdrawallogic.go +++ b/app/main/api/internal/logic/agent/applywithdrawallogic.go @@ -6,7 +6,6 @@ import ( "time" "ycc-server/app/main/model" "ycc-server/common/ctxdata" - "ycc-server/common/globalkey" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/lzUtils" @@ -62,53 +61,133 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r return nil, errors.Wrapf(xerr.NewErrMsg("请先完成实名认证"), "") } - // 3. 验证提现金额 + // 3. 验证提现方式 + if req.WithdrawalType != 1 && req.WithdrawalType != 2 { + return nil, errors.Wrapf(xerr.NewErrMsg("提现方式无效"), "") + } + + // 4. 验证提现信息 + if req.WithdrawalType == 1 { + // 支付宝提现:验证支付宝账号和姓名 + if req.PayeeAccount == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("请输入支付宝账号"), "") + } + if req.PayeeName == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("请输入收款人姓名"), "") + } + } else if req.WithdrawalType == 2 { + // 银行卡提现:验证银行卡号、开户行和姓名 + if req.BankCardNo == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("请输入银行卡号"), "") + } + if req.BankName == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("请输入开户行名称"), "") + } + if req.PayeeName == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("请输入收款人姓名"), "") + } + } + + // 5. 验证提现金额 if req.Amount <= 0 { return nil, errors.Wrapf(xerr.NewErrMsg("提现金额必须大于0"), "") } - // 4. 获取钱包信息 + // 6. 获取钱包信息 wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败, %v", err) } - // 5. 验证余额 + // 7. 验证余额(包括检查是否为负数) + if wallet.Balance < 0 { + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("账户存在欠款,请先补足欠款后再申请提现,当前余额:%.2f", wallet.Balance)), "") + } if wallet.Balance < req.Amount { return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("余额不足,当前余额:%.2f", wallet.Balance)), "") } - // 6. 计算税费 + // 8. 支付宝月度提现额度校验(仅针对支付宝提现) + if req.WithdrawalType == 1 { + now := time.Now() + monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + nextMonthStart := monthStart.AddDate(0, 1, 0) + + // 8.1 获取支付宝月度额度配置(默认 800 元) + alipayQuota := 800.0 + if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, "alipay_month_quota"); cfgErr == nil { + if parsed, parseErr := l.parseFloat(cfg.ConfigValue); parseErr == nil && parsed > 0 { + alipayQuota = parsed + } + } + + // 8.2 统计本月已申请/成功的支付宝提现金额(status IN (1,5)),避免多次申请占用超额 + withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder(). + Where("agent_id = ? AND withdrawal_type = ? AND status IN (1,5) AND create_time >= ? AND create_time < ?", + agent.Id, 1, monthStart, nextMonthStart) + usedAmount, sumErr := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, withdrawBuilder, "amount") + if sumErr != nil { + return nil, errors.Wrapf(sumErr, "查询本月支付宝提现额度使用情况失败") + } + + remainQuota := alipayQuota - usedAmount + if remainQuota <= 0 { + return nil, errors.Wrapf( + xerr.NewErrMsg(fmt.Sprintf("本月支付宝提现额度已用完(额度:%.2f 元),请使用银行卡提现", alipayQuota)), + "", + ) + } + + if req.Amount > remainQuota { + return nil, errors.Wrapf( + xerr.NewErrMsg(fmt.Sprintf("本月支付宝最高可提现 %.2f 元,请调整提现金额或使用银行卡提现", remainQuota)), + "", + ) + } + } + + // 9. 计算税费 yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month())) taxInfo, err := l.calculateTax(l.ctx, agent.Id, req.Amount, yearMonth) if err != nil { return nil, errors.Wrapf(err, "计算税费失败") } - // 7. 生成提现单号 - withdrawNo := fmt.Sprintf("WD%d%d", time.Now().Unix(), agent.Id) + // 10. 生成提现单号 + withdrawNo := fmt.Sprintf("WD%d%s", time.Now().Unix(), agent.Id) - // 8. 使用事务处理提现申请 + // 11. 使用事务处理提现申请 var withdrawalId string err = l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { - // 8.1 冻结余额 + // 11.1 冻结余额 wallet.FrozenBalance += req.Amount wallet.Balance -= req.Amount if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet); err != nil { return errors.Wrapf(err, "冻结余额失败") } - // 8.2 创建提现记录 + // 11.2 创建提现记录 withdrawal := &model.AgentWithdrawal{ - Id: uuid.New().String(), - AgentId: agent.Id, - WithdrawNo: withdrawNo, - PayeeAccount: req.PayeeAccount, - PayeeName: req.PayeeName, - Amount: req.Amount, - ActualAmount: taxInfo.ActualAmount, - TaxAmount: taxInfo.TaxAmount, - Status: 1, // 处理中(待审核) + Id: uuid.New().String(), + AgentId: agent.Id, + WithdrawNo: withdrawNo, + WithdrawalType: req.WithdrawalType, + PayeeAccount: req.PayeeAccount, + PayeeName: req.PayeeName, + Amount: req.Amount, + ActualAmount: taxInfo.ActualAmount, + TaxAmount: taxInfo.TaxAmount, + Status: 1, // 待审核 + } + + // 如果是银行卡提现,设置银行卡相关字段 + if req.WithdrawalType == 2 { + withdrawal.BankCardNo = lzUtils.StringToNullString(req.BankCardNo) + withdrawal.BankName = lzUtils.StringToNullString(req.BankName) + // 银行卡提现时,payee_account 可以存储银行卡号(便于查询),也可以留空 + if req.PayeeAccount == "" { + withdrawal.PayeeAccount = req.BankCardNo + } } _, err := l.svcCtx.AgentWithdrawalModel.Insert(transCtx, session, withdrawal) @@ -117,7 +196,7 @@ func (l *ApplyWithdrawalLogic) ApplyWithdrawal(req *types.ApplyWithdrawalReq) (r } withdrawalId = withdrawal.Id - // 8.3 创建扣税记录 + // 11.3 创建扣税记录 taxRecord := &model.AgentWithdrawalTax{ AgentId: agent.Id, WithdrawalId: withdrawalId, @@ -167,8 +246,10 @@ func (l *ApplyWithdrawalLogic) calculateTax(ctx context.Context, agentId string, } // 查询本月已提现金额 + // 注意:FindAll 方法会自动添加 del_state = ? 条件,所以这里不需要手动添加 + // 这里对 year_month 使用反引号包裹,避免与某些数据库版本/SQL 模式下的关键字冲突 builder := l.svcCtx.AgentWithdrawalTaxModel.SelectBuilder(). - Where("agent_id = ? AND year_month = ? AND del_state = ?", agentId, yearMonth, globalkey.DelStateNo) + Where("agent_id = ? AND `year_month` = ?", agentId, yearMonth) taxRecords, err := l.svcCtx.AgentWithdrawalTaxModel.FindAll(ctx, builder, "") if err != nil { return nil, errors.Wrapf(err, "查询月度提现记录失败") diff --git a/app/main/api/internal/logic/agent/checkfeaturewhiteliststatuslogic.go b/app/main/api/internal/logic/agent/checkfeaturewhiteliststatuslogic.go new file mode 100644 index 0000000..74bea3d --- /dev/null +++ b/app/main/api/internal/logic/agent/checkfeaturewhiteliststatuslogic.go @@ -0,0 +1,91 @@ +package agent + +import ( + "context" + "strings" + "ycc-server/app/main/model" + "ycc-server/common/xerr" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CheckFeatureWhitelistStatusLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCheckFeatureWhitelistStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckFeatureWhitelistStatusLogic { + return &CheckFeatureWhitelistStatusLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CheckFeatureWhitelistStatusLogic) CheckFeatureWhitelistStatus(req *types.CheckFeatureWhitelistStatusReq) (resp *types.CheckFeatureWhitelistStatusResp, err error) { + // 1. 验证参数 + if req.IdCard == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("身份证号不能为空"), "") + } + if req.FeatureApiId == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("模块API标识不能为空"), "") + } + + // 2. 提取主模块ID(去掉下划线后的部分) + // 例如:JRZQ7F1A_BigDataReport -> JRZQ7F1A + mainApiId := req.FeatureApiId + if idx := strings.Index(req.FeatureApiId, "_"); idx > 0 { + mainApiId = req.FeatureApiId[:idx] + } + + // 3. 查询feature信息(使用主模块ID) + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, mainApiId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("模块不存在"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询模块信息失败, %v", err) + } + + // 4. 检查是否在白名单中(使用主模块ID) + whitelistBuilder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder(). + Where("id_card = ? AND feature_api_id = ? AND status = ?", req.IdCard, mainApiId, 1) + whitelists, err := l.svcCtx.UserFeatureWhitelistModel.FindAll(l.ctx, whitelistBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单记录失败, %v", err) + } + + isWhitelisted := len(whitelists) > 0 + + // 5. 如果提供了 queryId,检查当前报告数据是否已删除 + dataDeleted := false + if req.QueryId != "" { + containsFeature, err := l.svcCtx.WhitelistService.CheckQueryDataContainsFeature(l.ctx, req.QueryId, req.FeatureApiId) + if err != nil { + // 检查失败不影响主流程,记录日志即可 + logx.Errorf("检查报告数据是否包含模块失败:查询记录 %s,模块 %s,错误:%v", req.QueryId, req.FeatureApiId, err) + // 默认认为数据已删除(保守处理) + dataDeleted = true + } else { + // 如果数据中不包含该模块,说明数据已删除 + dataDeleted = !containsFeature + } + } else { + // 未提供 queryId,无法判断数据是否已删除,默认认为已删除(保守处理) + dataDeleted = true + } + + return &types.CheckFeatureWhitelistStatusResp{ + IsWhitelisted: isWhitelisted, + WhitelistPrice: feature.WhitelistPrice, + FeatureId: feature.Id, + DataDeleted: dataDeleted, + }, nil +} + diff --git a/app/main/api/internal/logic/agent/createwhitelistorderlogic.go b/app/main/api/internal/logic/agent/createwhitelistorderlogic.go new file mode 100644 index 0000000..8846795 --- /dev/null +++ b/app/main/api/internal/logic/agent/createwhitelistorderlogic.go @@ -0,0 +1,169 @@ +package agent + +import ( + "context" + "fmt" + "ycc-server/app/main/model" + "ycc-server/common/ctxdata" + "ycc-server/common/xerr" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateWhitelistOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCreateWhitelistOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateWhitelistOrderLogic { + return &CreateWhitelistOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateWhitelistOrderLogic) CreateWhitelistOrder(req *types.CreateWhitelistOrderReq) (resp *types.CreateWhitelistOrderResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) + } + + // 1. 获取代理信息并验证是否为钻石代理 + agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) + } + + // 只有钻石代理可以操作 + if agent.Level != 3 { + return nil, errors.Wrapf(xerr.NewErrMsg("只有钻石代理可以操作白名单"), "") + } + + // 2. 验证参数 + if req.IdCard == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("身份证号不能为空"), "") + } + if len(req.FeatureIds) == 0 { + return nil, errors.Wrapf(xerr.NewErrMsg("请至少选择一个模块"), "") + } + + // 3. 查询feature信息并计算总金额 + var totalAmount float64 + var orderItems []struct { + FeatureId string + FeatureApiId string + FeatureName string + Price float64 + } + + for _, featureId := range req.FeatureIds { + feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, featureId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("模块不存在: %s", featureId)), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询模块信息失败, %v", err) + } + + // 直接使用feature的WhitelistPrice字段 + whitelistPrice := feature.WhitelistPrice + + if whitelistPrice <= 0 { + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("模块 %s 不支持白名单屏蔽", feature.Name)), "") + } + + // 检查该身份证号+feature是否已经存在白名单记录 + // 注意:系统会自动处理del_state,不需要手动添加 + whitelistBuilder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder(). + Where("id_card = ? AND feature_id = ?", req.IdCard, featureId) + existing, err := l.svcCtx.UserFeatureWhitelistModel.FindAll(l.ctx, whitelistBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单记录失败, %v", err) + } + // 检查是否有生效的记录(status=1) + for _, item := range existing { + if item.Status == 1 { + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("身份证号 %s 的模块 %s 已经加入白名单", req.IdCard, feature.Name)), "") + } + } + + totalAmount += whitelistPrice + orderItems = append(orderItems, struct { + FeatureId string + FeatureApiId string + FeatureName string + Price float64 + }{ + FeatureId: feature.Id, + FeatureApiId: feature.ApiId, + FeatureName: feature.Name, + Price: whitelistPrice, + }) + } + + // 4. 生成订单号(白名单订单前缀 W_,限制长度不超过32) + base := l.svcCtx.AlipayService.GenerateOutTradeNo() + orderNo := "W_" + base + if len(orderNo) > 32 { + orderNo = orderNo[:32] + } + + // 5. 使用事务创建订单和订单明细 + var orderId string + err = l.svcCtx.WhitelistOrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建订单 + order := &model.WhitelistOrder{ + Id: uuid.NewString(), + OrderNo: orderNo, + UserId: userID, + IdCard: req.IdCard, + TotalAmount: totalAmount, + Status: 1, // 待支付 + } + _, err := l.svcCtx.WhitelistOrderModel.Insert(ctx, session, order) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建订单失败, %v", err) + } + orderId = order.Id + + // 创建订单明细 + for _, item := range orderItems { + orderItem := &model.WhitelistOrderItem{ + Id: uuid.NewString(), + OrderId: orderId, + FeatureId: item.FeatureId, + FeatureApiId: item.FeatureApiId, + FeatureName: item.FeatureName, + Price: item.Price, + } + _, err := l.svcCtx.WhitelistOrderItemModel.Insert(ctx, session, orderItem) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建订单明细失败, %v", err) + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &types.CreateWhitelistOrderResp{ + OrderId: orderId, + OrderNo: orderNo, + TotalAmount: totalAmount, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getconversionratelogic.go b/app/main/api/internal/logic/agent/getconversionratelogic.go index 92d01f4..33badab 100644 --- a/app/main/api/internal/logic/agent/getconversionratelogic.go +++ b/app/main/api/internal/logic/agent/getconversionratelogic.go @@ -185,7 +185,6 @@ func (l *GetConversionRateLogic) calculateConversionRate(agentId string, subordi func (l *GetConversionRateLogic) calculatePeriodConversion(agentId string, subordinateIds []string, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData { // 构建 agent_order 查询条件 agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo). Where("create_time >= ? AND create_time < ?", startTime, endTime) if agentId != "" { @@ -252,8 +251,7 @@ func (l *GetConversionRateLogic) calculatePeriodConversion(agentId string, subor // 查询订单信息 orderBuilder := l.svcCtx.OrderModel.SelectBuilder(). - Where(squirrel.Eq{"id": orderIds}). - Where("del_state = ?", globalkey.DelStateNo) + Where(squirrel.Eq{"id": orderIds}) orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "") if err != nil && !errors.Is(err, model.ErrNotFound) { @@ -305,9 +303,9 @@ func (l *GetConversionRateLogic) calculatePeriodConversion(agentId string, subor // 2. 付费量和金额:通过agent_rebate表统计(只有付费的订单才会产生返佣) func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgentId string, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData { // 1. 查询agent_rebate表:获取所有曾经给当前用户产生返佣的source_agent_id(这些代理在某个时间点是下级) - // 不限制时间,获取所有历史返佣记录,用于确定哪些代理曾经是下级 + // 不限制时间,获取所有历史返佣记录,用于确定哪些代理曾经是下级(排除已取消的记录 status=3) rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo). + Where("agent_id = ? AND del_state = ? AND status != ?", parentAgentId, globalkey.DelStateNo, 3). Where("source_agent_id != ?", parentAgentId) allRebates, err := l.svcCtx.AgentRebateModel.FindAll(l.ctx, rebateBuilder, "") @@ -360,7 +358,6 @@ func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgen // 2. 查询agent_order表:统计这些代理在时间段内的所有订单(包括未付费的) agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder(). - Where("del_state = ?", globalkey.DelStateNo). Where("create_time >= ? AND create_time < ?", startTime, endTime). Where(squirrel.Eq{"agent_id": sourceAgentIds}) @@ -413,8 +410,7 @@ func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgen // 4. 查询订单信息 orderBuilder := l.svcCtx.OrderModel.SelectBuilder(). - Where(squirrel.Eq{"id": orderIds}). - Where("del_state = ?", globalkey.DelStateNo) + Where(squirrel.Eq{"id": orderIds}) orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "") if err != nil && !errors.Is(err, model.ErrNotFound) { @@ -429,9 +425,9 @@ func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgen } } - // 5. 查询时间段内的返佣记录,获取已付费订单的金额 + // 5. 查询时间段内的返佣记录,获取已付费订单的金额(排除已取消的记录 status=3) rebateBuilder = l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo). + Where("agent_id = ? AND del_state = ? AND status != ?", parentAgentId, globalkey.DelStateNo, 3). Where("source_agent_id != ?", parentAgentId). Where("create_time >= ? AND create_time < ?", startTime, endTime). Where(squirrel.Eq{"order_id": orderIds}) diff --git a/app/main/api/internal/logic/agent/getlastwithdrawalinfologic.go b/app/main/api/internal/logic/agent/getlastwithdrawalinfologic.go new file mode 100644 index 0000000..cd4fab1 --- /dev/null +++ b/app/main/api/internal/logic/agent/getlastwithdrawalinfologic.go @@ -0,0 +1,95 @@ +package agent + +import ( + "context" + "ycc-server/app/main/model" + "ycc-server/common/ctxdata" + "ycc-server/common/xerr" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetLastWithdrawalInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetLastWithdrawalInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLastWithdrawalInfoLogic { + return &GetLastWithdrawalInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetLastWithdrawalInfoLogic) GetLastWithdrawalInfo(req *types.GetLastWithdrawalInfoReq) (resp *types.GetLastWithdrawalInfoResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) + } + + // 1. 获取代理信息 + agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) + } + + // 2. 验证提现方式 + if req.WithdrawalType != 1 && req.WithdrawalType != 2 { + return nil, errors.Wrapf(xerr.NewErrMsg("提现方式无效"), "") + } + + // 3. 查询该代理最近一次该类型的提现记录 + // 注意:FindAll 方法会自动添加 del_state = ? 条件,所以这里不需要手动添加 + builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder(). + Where("agent_id = ? AND withdrawal_type = ?", agent.Id, req.WithdrawalType). + OrderBy("create_time DESC"). + Limit(1) + + withdrawals, err := l.svcCtx.AgentWithdrawalModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上次提现记录失败, %v", err) + } + + // 4. 如果没有找到记录,返回空信息 + if len(withdrawals) == 0 { + return &types.GetLastWithdrawalInfoResp{ + WithdrawalType: req.WithdrawalType, + PayeeAccount: "", + PayeeName: "", + BankCardNo: "", + BankName: "", + }, nil + } + + // 5. 组装响应 + lastWithdrawal := withdrawals[0] + resp = &types.GetLastWithdrawalInfoResp{ + WithdrawalType: lastWithdrawal.WithdrawalType, + PayeeAccount: lastWithdrawal.PayeeAccount, + PayeeName: lastWithdrawal.PayeeName, + BankCardNo: "", + BankName: "", + } + + // 如果是银行卡提现,填充银行卡信息 + if lastWithdrawal.WithdrawalType == 2 { + if lastWithdrawal.BankCardNo.Valid { + resp.BankCardNo = lastWithdrawal.BankCardNo.String + } + if lastWithdrawal.BankName.Valid { + resp.BankName = lastWithdrawal.BankName.String + } + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/agent/getrebatelistlogic.go b/app/main/api/internal/logic/agent/getrebatelistlogic.go index 4d404eb..bf3f95b 100644 --- a/app/main/api/internal/logic/agent/getrebatelistlogic.go +++ b/app/main/api/internal/logic/agent/getrebatelistlogic.go @@ -117,6 +117,7 @@ func (l *GetRebateListLogic) GetRebateList(req *types.GetRebateListReq) (resp *t OrderNo: orderNo, RebateType: rebate.RebateType, Amount: rebate.RebateAmount, + Status: rebate.Status, CreateTime: rebate.CreateTime.Format("2006-01-02 15:04:05"), }) } diff --git a/app/main/api/internal/logic/agent/getrevenueinfologic.go b/app/main/api/internal/logic/agent/getrevenueinfologic.go index 050051d..55fc02c 100644 --- a/app/main/api/internal/logic/agent/getrevenueinfologic.go +++ b/app/main/api/internal/logic/agent/getrevenueinfologic.go @@ -2,6 +2,7 @@ package agent import ( "context" + "fmt" "time" "ycc-server/app/main/model" "ycc-server/common/ctxdata" @@ -57,26 +58,27 @@ func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp, todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) // 本月开始时间(1号 00:00:00) monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + nextMonthStart := monthStart.AddDate(0, 1, 0) - // 3. 统计佣金总额(从 agent_commission 表统计) + // 3. 统计佣金总额(从 agent_commission 表统计,排除已取消的记录 status=3) commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo) + Where("agent_id = ? AND del_state = ? AND status != ?", agent.Id, globalkey.DelStateNo, 3) commissionTotal, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionBuilder, "amount") - // 3.1 统计佣金今日收益 + // 3.1 统计佣金今日收益(排除已取消的记录 status=3) commissionTodayBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, todayStart) + Where("agent_id = ? AND del_state = ? AND status != ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, 3, todayStart) commissionToday, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionTodayBuilder, "amount") - // 3.2 统计佣金本月收益 + // 3.2 统计佣金本月收益(排除已取消的记录 status=3) commissionMonthBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, monthStart) + Where("agent_id = ? AND del_state = ? AND status != ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, 3, monthStart) commissionMonth, _ := l.svcCtx.AgentCommissionModel.FindSum(l.ctx, commissionMonthBuilder, "amount") - // 4. 统计返佣总额(包括推广返佣和升级返佣) - // 4.1 统计推广返佣(从 agent_rebate 表) + // 4. 统计返佣总额(包括推广返佣和升级返佣,排除已取消的记录 status=3) + // 4.1 统计推广返佣(从 agent_rebate 表,排除已取消的记录 status=3) rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", agent.Id, globalkey.DelStateNo) + Where("agent_id = ? AND del_state = ? AND status != ?", agent.Id, globalkey.DelStateNo, 3) promoteRebateTotal, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount") // 4.2 统计升级返佣(从 agent_upgrade 表,查询 rebate_agent_id = 当前代理ID 且 status = 2(已完成)且 upgrade_type = 1(自主付费)的记录) @@ -88,10 +90,10 @@ func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp, rebateTotal := promoteRebateTotal + upgradeRebateTotal - // 4.3 统计返佣今日收益 + // 4.3 统计返佣今日收益(排除已取消的记录 status=3) // 推广返佣今日 promoteRebateTodayBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, todayStart) + Where("agent_id = ? AND del_state = ? AND status != ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, 3, todayStart) promoteRebateToday, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, promoteRebateTodayBuilder, "rebate_amount") // 升级返佣今日 upgradeRebateTodayBuilder := l.svcCtx.AgentUpgradeModel.SelectBuilder(). @@ -100,10 +102,10 @@ func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp, upgradeRebateToday, _ := l.svcCtx.AgentUpgradeModel.FindSum(l.ctx, upgradeRebateTodayBuilder, "rebate_amount") rebateToday := promoteRebateToday + upgradeRebateToday - // 4.4 统计返佣本月收益 + // 4.4 统计返佣本月收益(排除已取消的记录 status=3) // 推广返佣本月 promoteRebateMonthBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, monthStart) + Where("agent_id = ? AND del_state = ? AND status != ? AND create_time >= ?", agent.Id, globalkey.DelStateNo, 3, monthStart) promoteRebateMonth, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, promoteRebateMonthBuilder, "rebate_amount") // 升级返佣本月 upgradeRebateMonthBuilder := l.svcCtx.AgentUpgradeModel.SelectBuilder(). @@ -112,16 +114,43 @@ func (l *GetRevenueInfoLogic) GetRevenueInfo() (resp *types.GetRevenueInfoResp, upgradeRebateMonth, _ := l.svcCtx.AgentUpgradeModel.FindSum(l.ctx, upgradeRebateMonthBuilder, "rebate_amount") rebateMonth := promoteRebateMonth + upgradeRebateMonth + // 5. 统计本月支付宝提现额度使用情况 + // 5.1 获取配置的支付宝月度额度(默认 800 元) + alipayQuota := 800.0 + if cfg, cfgErr := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, "alipay_month_quota"); cfgErr == nil { + if parsed, parseErr := parseFloatConfig(cfg.ConfigValue); parseErr == nil && parsed > 0 { + alipayQuota = parsed + } + } + + // 5.2 统计本月已申请/成功的支付宝提现金额(status IN (1,5)),用于前端展示额度占用 + withdrawBuilder := l.svcCtx.AgentWithdrawalModel.SelectBuilder(). + Where("agent_id = ? AND withdrawal_type = ? AND status IN (1,5) AND create_time >= ? AND create_time < ?", + agent.Id, 1, monthStart, nextMonthStart) + alipayUsed, _ := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, withdrawBuilder, "amount") + if alipayUsed < 0 { + alipayUsed = 0 + } + return &types.GetRevenueInfoResp{ - Balance: wallet.Balance, - FrozenBalance: wallet.FrozenBalance, - TotalEarnings: wallet.TotalEarnings, - WithdrawnAmount: wallet.WithdrawnAmount, - CommissionTotal: commissionTotal, // 佣金累计总收益(推广订单获得的佣金) - CommissionToday: commissionToday, // 佣金今日收益 - CommissionMonth: commissionMonth, // 佣金本月收益 - RebateTotal: rebateTotal, // 返佣累计总收益(包括推广返佣和升级返佣) - RebateToday: rebateToday, // 返佣今日收益 - RebateMonth: rebateMonth, // 返佣本月收益 + Balance: wallet.Balance, + FrozenBalance: wallet.FrozenBalance, + TotalEarnings: wallet.TotalEarnings, + WithdrawnAmount: wallet.WithdrawnAmount, + CommissionTotal: commissionTotal, // 佣金累计总收益(推广订单获得的佣金) + CommissionToday: commissionToday, // 佣金今日收益 + CommissionMonth: commissionMonth, // 佣金本月收益 + RebateTotal: rebateTotal, // 返佣累计总收益(包括推广返佣和升级返佣) + RebateToday: rebateToday, // 返佣今日收益 + RebateMonth: rebateMonth, // 返佣本月收益 + AlipayMonthQuota: alipayQuota, // 支付宝每月提现总额度 + AlipayMonthUsed: alipayUsed, // 本月已使用的支付宝提现额度 }, nil } + +// parseFloatConfig 解析配置中的浮点数 +func parseFloatConfig(s string) (float64, error) { + var result float64 + _, err := fmt.Sscanf(s, "%f", &result) + return result, err +} diff --git a/app/main/api/internal/logic/agent/getsubordinatecontributiondetaillogic.go b/app/main/api/internal/logic/agent/getsubordinatecontributiondetaillogic.go index d0a9f3e..b781f94 100644 --- a/app/main/api/internal/logic/agent/getsubordinatecontributiondetaillogic.go +++ b/app/main/api/internal/logic/agent/getsubordinatecontributiondetaillogic.go @@ -88,10 +88,10 @@ func (l *GetSubordinateContributionDetailLogic) GetSubordinateContributionDetail todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) - // 7. 统计订单数据(仅统计有返佣给当前用户的订单) + // 7. 统计订单数据(仅统计有返佣给当前用户的订单,排除已取消的记录 status=3) // 通过 agent_rebate 表统计 DISTINCT order_id rebateBaseBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agent.Id, req.SubordinateId, globalkey.DelStateNo) + Where("agent_id = ? AND source_agent_id = ? AND del_state = ? AND status != ?", agent.Id, req.SubordinateId, globalkey.DelStateNo, 3) // 总订单量(去重订单ID) totalOrders := l.countDistinctOrders(l.ctx, rebateBaseBuilder) @@ -104,7 +104,7 @@ func (l *GetSubordinateContributionDetailLogic) GetSubordinateContributionDetail monthRebateBuilder := rebateBaseBuilder.Where("create_time >= ?", monthStart) monthOrders := l.countDistinctOrders(l.ctx, monthRebateBuilder) - // 8. 统计返佣金额 + // 8. 统计返佣金额(排除已取消的记录 status=3) totalRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBaseBuilder, "rebate_amount") todayRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, todayRebateBuilder, "rebate_amount") monthRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, monthRebateBuilder, "rebate_amount") @@ -227,12 +227,12 @@ func (l *GetSubordinateContributionDetailLogic) countDistinctOrders(ctx context. func (l *GetSubordinateContributionDetailLogic) getOrderList(ctx context.Context, agentId, subordinateId string, page, pageSize int64) ([]types.OrderItem, int64, error) { // 1. 查询所有返佣记录 rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, subordinateId, globalkey.DelStateNo). + Where("agent_id = ? AND source_agent_id = ? AND del_state = ? AND status != ?", agentId, subordinateId, globalkey.DelStateNo, 3). OrderBy("create_time DESC") // 查询总数(去重订单ID) totalBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, subordinateId, globalkey.DelStateNo) + Where("agent_id = ? AND source_agent_id = ? AND del_state = ? AND status != ?", agentId, subordinateId, globalkey.DelStateNo, 3) total := l.countDistinctOrders(ctx, totalBuilder) // 查询所有返佣记录 diff --git a/app/main/api/internal/logic/agent/getteamlistlogic.go b/app/main/api/internal/logic/agent/getteamlistlogic.go index 1d61250..5ef311e 100644 --- a/app/main/api/internal/logic/agent/getteamlistlogic.go +++ b/app/main/api/internal/logic/agent/getteamlistlogic.go @@ -109,8 +109,7 @@ func (l *GetTeamListLogic) GetTeamList(req *types.GetTeamListReq) (resp *types.G // 7. 查询所有下级代理信息(不进行手机号过滤,因为需要模糊搜索) builder := l.svcCtx.AgentModel.SelectBuilder(). - Where(squirrel.Eq{"id": subordinateIds}). - Where("del_state = ?", globalkey.DelStateNo) + Where(squirrel.Eq{"id": subordinateIds}) allTeamMembers, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "") if err != nil { @@ -207,10 +206,10 @@ func (l *GetTeamListLogic) calculateTeamStatistics(agentId string, subordinateId stats.TodayNewMembers = todayNewCount stats.MonthNewMembers = monthNewCount - // 统计团队总查询量(仅统计有返佣给当前用户的订单,通过agent_rebate表去重统计order_id) + // 统计团队总查询量(仅统计有返佣给当前用户的订单,通过agent_rebate表去重统计order_id,排除已取消的记录 status=3) if len(subordinateIds) > 0 { rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", agentId, globalkey.DelStateNo). + Where("agent_id = ? AND del_state = ? AND status != ?", agentId, globalkey.DelStateNo, 3). Where("source_agent_id != ?", agentId). // 明确排除自己 Where(squirrel.Eq{"source_agent_id": subordinateIds}) @@ -229,14 +228,15 @@ func (l *GetTeamListLogic) calculateTeamStatistics(agentId string, subordinateId stats.MonthPromotions = monthPromotions } - // 统计收益:只统计从下级获得的返佣(依靠团队得到的收益) + // 统计收益:只统计从下级获得的返佣(依靠团队得到的收益,排除已取消的记录 status=3) // 返佣:从agent_rebate表统计(从下级获得的返佣) // agent_id = 当前代理ID(获得返佣的代理) // source_agent_id IN 下级ID列表(来源代理,即产生订单的下级代理) // 明确排除自己:source_agent_id != agentId(确保不统计自己的数据) + // 排除已取消:status != 3(确保不统计已撤销的返佣) if len(subordinateIds) > 0 { rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND del_state = ?", agentId, globalkey.DelStateNo). + Where("agent_id = ? AND del_state = ? AND status != ?", agentId, globalkey.DelStateNo, 3). Where("source_agent_id != ?", agentId). // 明确排除自己 Where(squirrel.Eq{"source_agent_id": subordinateIds}) totalRebate, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount") @@ -304,16 +304,16 @@ func (l *GetTeamListLogic) buildTeamMemberItem(agentId string, member *model.Age } } - // 统计查询量(仅统计有返佣给当前用户的订单,通过agent_rebate表去重统计order_id) + // 统计查询量(仅统计有返佣给当前用户的订单,通过agent_rebate表去重统计order_id,排除已取消的记录 status=3) rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder(). - Where("agent_id = ? AND source_agent_id = ? AND del_state = ?", agentId, member.Id, globalkey.DelStateNo) + Where("agent_id = ? AND source_agent_id = ? AND del_state = ? AND status != ?", agentId, member.Id, globalkey.DelStateNo, 3) totalQueries := l.countDistinctOrdersForMember(l.ctx, rebateBuilder) todayRebateBuilder := rebateBuilder.Where("create_time >= ?", todayStart) todayQueries := l.countDistinctOrdersForMember(l.ctx, todayRebateBuilder) monthRebateBuilder := rebateBuilder.Where("create_time >= ?", monthStart) monthQueries := l.countDistinctOrdersForMember(l.ctx, monthRebateBuilder) - // 统计返佣给我的金额(从agent_rebate表,source_agent_id = member.Id, agent_id = agentId) + // 统计返佣给我的金额(从agent_rebate表,source_agent_id = member.Id, agent_id = agentId,排除已取消的记录 status=3) totalRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder, "rebate_amount") todayRebateAmount, _ := l.svcCtx.AgentRebateModel.FindSum(l.ctx, rebateBuilder.Where("create_time >= ?", todayStart), "rebate_amount") diff --git a/app/main/api/internal/logic/agent/getteamstatisticslogic.go b/app/main/api/internal/logic/agent/getteamstatisticslogic.go index 698be2d..64d0392 100644 --- a/app/main/api/internal/logic/agent/getteamstatisticslogic.go +++ b/app/main/api/internal/logic/agent/getteamstatisticslogic.go @@ -47,12 +47,12 @@ func (l *GetTeamStatisticsLogic) GetTeamStatistics() (resp *types.TeamStatistics } // 2. 递归查询所有下级(直接+间接) - allSubordinateIds := make(map[string]bool) - directSubordinateIds := make(map[string]bool) + allSubordinateIds := make(map[string]bool) + directSubordinateIds := make(map[string]bool) // 递归函数:收集所有下级ID - var collectSubordinates func(string) error - collectSubordinates = func(parentId string) error { + var collectSubordinates func(string) error + collectSubordinates = func(parentId string) error { // 查询直接下级 builder := l.svcCtx.AgentRelationModel.SelectBuilder(). Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo) @@ -63,23 +63,23 @@ func (l *GetTeamStatisticsLogic) GetTeamStatistics() (resp *types.TeamStatistics for _, relation := range relations { // 如果是第一层,标记为直接下级 - if parentId == agent.Id { - directSubordinateIds[relation.ChildId] = true - } - // 添加到所有下级集合 - allSubordinateIds[relation.ChildId] = true - // 递归查询下级的下级 - if err := collectSubordinates(relation.ChildId); err != nil { - return err - } - } - return nil - } + if parentId == agent.Id { + directSubordinateIds[relation.ChildId] = true + } + // 添加到所有下级集合 + allSubordinateIds[relation.ChildId] = true + // 递归查询下级的下级 + if err := collectSubordinates(relation.ChildId); err != nil { + return err + } + } + return nil + } // 开始递归收集所有下级 - if err := collectSubordinates(agent.Id); err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级关系失败, %v", err) - } + if err := collectSubordinates(agent.Id); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询下级关系失败, %v", err) + } // 3. 获取当前时间用于统计今日和本月新增 now := time.Now() @@ -100,15 +100,14 @@ func (l *GetTeamStatisticsLogic) GetTeamStatistics() (resp *types.TeamStatistics } // 5. 将下级ID转换为切片用于查询 - subordinateIds := make([]string, 0, len(allSubordinateIds)) - for id := range allSubordinateIds { - subordinateIds = append(subordinateIds, id) - } + subordinateIds := make([]string, 0, len(allSubordinateIds)) + for id := range allSubordinateIds { + subordinateIds = append(subordinateIds, id) + } // 6. 查询所有下级代理信息 builder := l.svcCtx.AgentModel.SelectBuilder(). - Where(squirrel.Eq{"id": subordinateIds}). - Where("del_state = ?", globalkey.DelStateNo) + Where(squirrel.Eq{"id": subordinateIds}) teamMembers, err := l.svcCtx.AgentModel.FindAll(l.ctx, builder, "") if err != nil { diff --git a/app/main/api/internal/logic/agent/getwhitelistfeatureslogic.go b/app/main/api/internal/logic/agent/getwhitelistfeatureslogic.go new file mode 100644 index 0000000..c38eecc --- /dev/null +++ b/app/main/api/internal/logic/agent/getwhitelistfeatureslogic.go @@ -0,0 +1,57 @@ +package agent + +import ( + "context" + "ycc-server/common/xerr" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetWhitelistFeaturesLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetWhitelistFeaturesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhitelistFeaturesLogic { + return &GetWhitelistFeaturesLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetWhitelistFeaturesLogic) GetWhitelistFeatures(req *types.GetWhitelistFeaturesReq) (resp *types.GetWhitelistFeaturesResp, err error) { + // 使用SelectBuilder查询所有feature,然后筛选whitelist_price > 0的 + // 注意:系统会自动处理del_state,不需要手动添加 + builder := l.svcCtx.FeatureModel.SelectBuilder(). + Where("whitelist_price > 0"). + OrderBy("name ASC") + + features, err := l.svcCtx.FeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询feature列表失败, %v", err) + } + + // 组装响应,直接使用feature的WhitelistPrice字段 + list := make([]types.WhitelistFeatureItem, 0, len(features)) + for _, f := range features { + if f.WhitelistPrice > 0 { + list = append(list, types.WhitelistFeatureItem{ + FeatureId: f.Id, + FeatureApiId: f.ApiId, + FeatureName: f.Name, + WhitelistPrice: f.WhitelistPrice, + }) + } + } + + return &types.GetWhitelistFeaturesResp{ + List: list, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getwhitelistlistlogic.go b/app/main/api/internal/logic/agent/getwhitelistlistlogic.go new file mode 100644 index 0000000..0a779c6 --- /dev/null +++ b/app/main/api/internal/logic/agent/getwhitelistlistlogic.go @@ -0,0 +1,117 @@ +package agent + +import ( + "context" + "ycc-server/app/main/model" + "ycc-server/common/ctxdata" + "ycc-server/common/xerr" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetWhitelistListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetWhitelistListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetWhitelistListLogic { + return &GetWhitelistListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetWhitelistListLogic) GetWhitelistList(req *types.GetWhitelistListReq) (resp *types.GetWhitelistListResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) + } + + // 1. 验证是否为代理(查询白名单列表需要是代理) + _, err = l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) + } + + // 2. 构建查询条件 + // 注意:系统会自动处理del_state,不需要手动添加 + builder := l.svcCtx.UserFeatureWhitelistModel.SelectBuilder(). + Where("user_id = ?", userID) + + // 如果指定了身份证号,添加筛选条件 + if req.IdCard != "" { + builder = builder.Where("id_card = ?", req.IdCard) + } + + // 3. 分页查询 + total, err := l.svcCtx.UserFeatureWhitelistModel.FindCount(l.ctx, builder, "id") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询总数失败, %v", err) + } + + whitelists, err := l.svcCtx.UserFeatureWhitelistModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单列表失败, %v", err) + } + + // 4. 组装响应 + list := make([]types.WhitelistItem, 0, len(whitelists)) + for _, w := range whitelists { + statusText := "生效" + if w.Status == 2 { + statusText = "已失效" + } + + list = append(list, types.WhitelistItem{ + Id: w.Id, + IdCard: w.IdCard, + FeatureId: w.FeatureId, + FeatureApiId: w.FeatureApiId, + FeatureName: "", // 需要从feature表查询,或者冗余存储 + Amount: w.Amount, + Status: w.Status, + StatusText: statusText, + CreateTime: w.CreateTime.Format("2006-01-02 15:04:05"), + }) + } + + // 5. 查询feature名称(批量查询优化) + if len(list) > 0 { + featureIds := make([]string, 0, len(list)) + featureMap := make(map[string]string) // feature_id -> feature_name + for _, item := range list { + featureIds = append(featureIds, item.FeatureId) + } + + // 批量查询feature + for _, featureId := range featureIds { + feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, featureId) + if err == nil { + featureMap[featureId] = feature.Name + } + } + + // 填充feature名称 + for i := range list { + if name, ok := featureMap[list[i].FeatureId]; ok { + list[i].FeatureName = name + } + } + } + + return &types.GetWhitelistListResp{ + Total: total, + List: list, + }, nil +} + diff --git a/app/main/api/internal/logic/agent/getwithdrawallistlogic.go b/app/main/api/internal/logic/agent/getwithdrawallistlogic.go index adf8456..32910a5 100644 --- a/app/main/api/internal/logic/agent/getwithdrawallistlogic.go +++ b/app/main/api/internal/logic/agent/getwithdrawallistlogic.go @@ -81,18 +81,31 @@ func (l *GetWithdrawalListLogic) GetWithdrawalList(req *types.GetWithdrawalListR remark = withdrawal.Remark.String } - list = append(list, types.WithdrawalItem{ - Id: withdrawal.Id, - WithdrawalNo: withdrawal.WithdrawNo, - Amount: withdrawal.Amount, - TaxAmount: withdrawal.TaxAmount, - ActualAmount: withdrawal.ActualAmount, - Status: withdrawal.Status, - PayeeAccount: withdrawal.PayeeAccount, - PayeeName: withdrawal.PayeeName, - Remark: remark, - CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"), - }) + item := types.WithdrawalItem{ + Id: withdrawal.Id, + WithdrawalNo: withdrawal.WithdrawNo, + WithdrawalType: withdrawal.WithdrawalType, + Amount: withdrawal.Amount, + TaxAmount: withdrawal.TaxAmount, + ActualAmount: withdrawal.ActualAmount, + Status: withdrawal.Status, + PayeeAccount: withdrawal.PayeeAccount, + PayeeName: withdrawal.PayeeName, + Remark: remark, + CreateTime: withdrawal.CreateTime.Format("2006-01-02 15:04:05"), + } + + // 如果是银行卡提现,填充银行卡信息 + if withdrawal.WithdrawalType == 2 { + if withdrawal.BankCardNo.Valid { + item.BankCardNo = withdrawal.BankCardNo.String + } + if withdrawal.BankName.Valid { + item.BankName = withdrawal.BankName.String + } + } + + list = append(list, item) } return &types.GetWithdrawalListResp{ diff --git a/app/main/api/internal/logic/agent/offlinefeaturelogic.go b/app/main/api/internal/logic/agent/offlinefeaturelogic.go new file mode 100644 index 0000000..3ec3422 --- /dev/null +++ b/app/main/api/internal/logic/agent/offlinefeaturelogic.go @@ -0,0 +1,174 @@ +package agent + +import ( + "context" + "encoding/hex" + "encoding/json" + "ycc-server/app/main/model" + "ycc-server/common/ctxdata" + "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type OfflineFeatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewOfflineFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OfflineFeatureLogic { + return &OfflineFeatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *OfflineFeatureLogic) OfflineFeature(req *types.OfflineFeatureReq) (resp *types.OfflineFeatureResp, err error) { + // 1. 验证参数 + if err := l.validateRequest(req); err != nil { + return nil, err + } + + // 2. 获取用户ID并验证代理权限 + userID, err := l.verifyDiamondAgent() + if err != nil { + return nil, err + } + + // 3. 获取查询记录和身份证号 + queryModel, idCard, err := l.getQueryInfo(req.QueryId) + if err != nil { + return nil, err + } + _ = queryModel // 避免未使用变量警告 + + // 4. 调用 WhitelistService 统一下架处理 + needPay, amount, whitelistCreated, err := l.svcCtx.WhitelistService.ProcessOfflineFeature( + l.ctx, + nil, // 不使用事务,因为可能只是检查 + idCard, + req.FeatureApiId, + userID, + req.QueryId, + ) + if err != nil { + return nil, err + } + + // 5. 如果已创建白名单,需要删除报告数据 + if whitelistCreated { + if err := l.deleteQueryData(req.QueryId, req.FeatureApiId); err != nil { + // 删除报告数据失败不影响主流程,只记录日志 + logx.Errorf("下架模块后删除报告数据失败:查询记录 %s,模块 %s,错误:%v", req.QueryId, req.FeatureApiId, err) + } + } + + return &types.OfflineFeatureResp{ + Success: whitelistCreated, + NeedPay: needPay, + Amount: amount, + }, nil +} + +// validateRequest 验证请求参数 +func (l *OfflineFeatureLogic) validateRequest(req *types.OfflineFeatureReq) error { + if req.QueryId == "" { + return errors.Wrapf(xerr.NewErrMsg("查询记录ID不能为空"), "") + } + if req.FeatureApiId == "" { + return errors.Wrapf(xerr.NewErrMsg("模块API标识不能为空"), "") + } + return nil +} + +// verifyDiamondAgent 验证是否为钻石代理并返回用户ID +func (l *OfflineFeatureLogic) verifyDiamondAgent() (string, error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) + } + + agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return "", errors.Wrapf(xerr.NewErrMsg("您不是代理"), "") + } + return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) + } + + if agent.Level != 3 { + return "", errors.Wrapf(xerr.NewErrMsg("只有钻石代理可以操作白名单下架"), "") + } + + return userID, nil +} + +// getQueryInfo 获取查询记录和身份证号 +func (l *OfflineFeatureLogic) getQueryInfo(queryId string) (*model.Query, string, error) { + queryModel, err := l.svcCtx.QueryModel.FindOne(l.ctx, queryId) + 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) + } + + // 解密 QueryParams 获取 idCard + idCard, err := l.extractIdCardFromQueryParams(queryModel) + if err != nil { + return nil, "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取身份证号失败, %v", err) + } + + if idCard == "" { + return nil, "", errors.Wrapf(xerr.NewErrMsg("查询参数中缺少身份证号"), "") + } + + return queryModel, idCard, nil +} + +// extractIdCardFromQueryParams 从 QueryParams 中提取身份证号 +func (l *OfflineFeatureLogic) extractIdCardFromQueryParams(queryModel *model.Query) (string, error) { + if queryModel.QueryParams == "" { + return "", errors.New("查询参数为空") + } + + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return "", errors.Wrap(decodeErr, "获取AES密钥失败") + } + + decryptedParams, decryptErr := crypto.AesDecrypt(queryModel.QueryParams, key) + if decryptErr != nil { + return "", errors.Wrap(decryptErr, "解密查询参数失败") + } + + var params map[string]interface{} + unmarshalErr := json.Unmarshal(decryptedParams, ¶ms) + if unmarshalErr != nil { + return "", errors.Wrap(unmarshalErr, "解析查询参数失败") + } + + idCard, ok := params["id_card"].(string) + if !ok || idCard == "" { + return "", errors.New("查询参数中缺少 id_card 或 id_card 为空") + } + + return idCard, nil +} + +// deleteQueryData 删除报告数据 +func (l *OfflineFeatureLogic) deleteQueryData(queryId, featureApiId string) error { + return l.svcCtx.QueryModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + return l.svcCtx.WhitelistService.DeleteFeatureFromQueryData(ctx, session, queryId, featureApiId) + }) +} diff --git a/app/main/api/internal/logic/agent/realnameauthlogic.go b/app/main/api/internal/logic/agent/realnameauthlogic.go index fa60321..302959a 100644 --- a/app/main/api/internal/logic/agent/realnameauthlogic.go +++ b/app/main/api/internal/logic/agent/realnameauthlogic.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/hex" "fmt" + "os" "ycc-server/app/main/model" "ycc-server/common/ctxdata" "ycc-server/common/xerr" @@ -61,39 +62,42 @@ func (l *RealNameAuthLogic) RealNameAuth(req *types.RealNameAuthReq) (resp *type return nil, errors.Wrapf(xerr.NewErrMsg("手机号与代理注册手机号不匹配"), "") } - // 3. 验证验证码 - 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) - } - redisKey := fmt.Sprintf("realName:%s", encryptedMobile) - cacheCode, err := l.svcCtx.Redis.Get(redisKey) - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "") + // 开发环境下跳过验证码校验 + if os.Getenv("ENV") != "development" { + 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) + } + redisKey := fmt.Sprintf("realName:%s", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "") } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err) - } - if cacheCode != req.Code { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "") } - // 4. 三要素核验(姓名、身份证号、手机号) - verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(service.ThreeFactorVerificationRequest{ - Name: req.Name, - IDCard: req.IdCard, - Mobile: req.Mobile, - }) - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素核验失败: %v", err) - } - if !verification.Passed { - if verification.Err != nil { - return nil, errors.Wrapf(xerr.NewErrMsg(verification.Err.Error()), "三要素核验不通过") + if os.Getenv("ENV") != "development" { + // 4. 三要素核验(姓名、身份证号、手机号) + verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(service.ThreeFactorVerificationRequest{ + Name: req.Name, + IDCard: req.IdCard, + Mobile: req.Mobile, + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素核验失败: %v", err) + } + if !verification.Passed { + if verification.Err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg(verification.Err.Error()), "三要素核验不通过") + } + return nil, errors.Wrapf(xerr.NewErrMsg("三要素核验不通过"), "") } - return nil, errors.Wrapf(xerr.NewErrMsg("三要素核验不通过"), "") } - // 5. 检查是否已有实名认证记录 existingRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id) if err != nil && !errors.Is(err, model.ErrNotFound) { diff --git a/app/main/api/internal/logic/pay/alipaycallbacklogic.go b/app/main/api/internal/logic/pay/alipaycallbacklogic.go index cdfcc6f..d331be9 100644 --- a/app/main/api/internal/logic/pay/alipaycallbacklogic.go +++ b/app/main/api/internal/logic/pay/alipaycallbacklogic.go @@ -45,6 +45,9 @@ func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Requ } else if strings.HasPrefix(orderNo, "U_") { // 代理升级订单处理 return l.handleAgentUpgradeOrderPayment(w, notification) + } else if strings.HasPrefix(orderNo, "W_") { + // 白名单下架订单处理 + return l.handleWhitelistOrderPayment(w, notification) } else if strings.HasPrefix(orderNo, "A_") { // 旧系统会员充值订单(已废弃,新系统使用升级功能) // return l.handleAgentVipOrderPayment(w, notification) @@ -110,6 +113,96 @@ func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, not return nil } +// 处理白名单下架订单支付 +func (l *AlipayCallbackLogic) handleWhitelistOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error { + orderNo := notification.OutTradeNo + + // 1. 查找订单 + order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo) + if findOrderErr != nil { + logx.Errorf("支付宝白名单支付回调,查找订单失败: %+v", findOrderErr) + alipay.ACKNotification(w) + return nil + } + + // 2. 验证金额 + amount := lzUtils.ToAlipayAmount(order.Amount) + user, err := l.svcCtx.UserModel.FindOne(l.ctx, order.UserId) + if err == nil && user.Inside != 1 { + if amount != notification.TotalAmount { + logx.Errorf("支付宝白名单支付回调,金额不一致,订单号: %s", orderNo) + alipay.ACKNotification(w) + return nil + } + } + + // 3. 检查订单状态 + if order.Status != "pending" { + alipay.ACKNotification(w) + return nil + } + + // 4. 查找白名单订单 + whitelistOrder, findWhitelistErr := l.svcCtx.WhitelistOrderModel.FindOneByOrderNo(l.ctx, orderNo) + if findWhitelistErr != nil { + logx.Errorf("支付宝白名单支付回调,查找白名单订单失败,订单号: %s, 错误: %+v", orderNo, findWhitelistErr) + alipay.ACKNotification(w) + return nil + } + if whitelistOrder.Status != 1 { + // 白名单订单状态不是待支付,直接返回成功 + alipay.ACKNotification(w) + return nil + } + + // 5. 处理支付状态 + switch notification.TradeStatus { + case alipay.TradeStatusSuccess: + order.Status = "paid" + order.PayTime = lzUtils.TimeToNullTime(time.Now()) + order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo) + whitelistOrder.Status = 2 // 已支付 + whitelistOrder.PayTime = lzUtils.TimeToNullTime(time.Now()) + case alipay.TradeStatusClosed: + order.Status = "closed" + order.CloseTime = lzUtils.TimeToNullTime(time.Now()) + whitelistOrder.Status = 3 // 已取消 + default: + alipay.ACKNotification(w) + return nil + } + + // 6. 更新订单和白名单订单 + 创建白名单记录并删除报告数据 + err = l.svcCtx.WhitelistOrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 6.1 更新订单状态 + if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, session, order); updateErr != nil { + return errors.Wrapf(updateErr, "支付宝白名单支付回调,更新订单状态失败") + } + + // 6.2 更新白名单订单状态 + if updateErr := l.svcCtx.WhitelistOrderModel.UpdateWithVersion(ctx, session, whitelistOrder); updateErr != nil { + return errors.Wrapf(updateErr, "支付宝白名单支付回调,更新白名单订单状态失败") + } + + // 6.3 如果支付成功,调用 WhitelistService 处理白名单和报告数据 + if whitelistOrder.Status == 2 { + if processErr := l.svcCtx.WhitelistService.ProcessPaidWhitelistOrder(ctx, session, order, whitelistOrder); processErr != nil { + return errors.Wrapf(processErr, "支付宝白名单支付回调,处理白名单订单失败") + } + } + + return nil + }) + if err != nil { + logx.Errorf("支付宝白名单支付回调,事务处理失败: %+v", err) + alipay.ACKNotification(w) + return nil + } + + alipay.ACKNotification(w) + return nil +} + // 处理代理升级订单支付 func (l *AlipayCallbackLogic) handleAgentUpgradeOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error { orderNo := notification.OutTradeNo diff --git a/app/main/api/internal/logic/pay/alipayfromlogic.go b/app/main/api/internal/logic/pay/alipayfromlogic.go new file mode 100644 index 0000000..a9e21d5 --- /dev/null +++ b/app/main/api/internal/logic/pay/alipayfromlogic.go @@ -0,0 +1,240 @@ +package pay + +import ( + "context" + "encoding/json" + "net/http" + "ycc-server/app/main/api/internal/svc" + "ycc-server/app/main/model" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/google/uuid" + "github.com/smartwalle/alipay/v3" + "github.com/zeromicro/go-zero/core/logx" +) + +type AlipayFromLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAlipayFromLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayFromLogic { + return &AlipayFromLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// TradeComplainContent 交易投诉通知的 biz_content 内容 +type TradeComplainContent struct { + ComplainEventID string `json:"complain_event_id"` // 支付宝侧投诉单号 + ComplainNotifyAppID string `json:"complain_notify_app_id"` // 投诉消息通知的应用ID + + Status string `json:"status"` // 投诉单状态,可能的枚举值如下(用Go语法注释): + /* + MERCHANT_PROCESSING // 待处理 + MERCHANT_FEEDBACKED // 已处理 + FINISHED // 投诉完结 + CANCELLED // 投诉关闭 + PLATFORM_PROCESSING // 客服处理中 + PLATFORM_FINISH // 客服处理完结 + CLOSED // 投诉关闭 + */ + BizType string `json:"biz_type"` // 业务类型 + OrderID string `json:"order_id"` // 订单ID +} + +// SecurityRiskComplaintsNotifyContent 安全风险投诉商户通知的 biz_content 内容 +type SecurityRiskComplaintsNotifyContent struct { + ComplaintID string `json:"complaint_id"` // 投诉ID + MessageType string `json:"message_type"` // 消息类型,可能的枚举值如下(用Go语法注释): + /* + USER_REPLY // 用户回复 + MERCHANT_REPLY // 商户回复 + SYSTEM_NOTIFY // 系统通知 + */ + ReplyContent string `json:"reply_content"` // 回复内容 + ReplyTime string `json:"reply_time"` // 回复时间,格式:YYYY-MM-DD HH:mm:ss +} + +func (l *AlipayFromLogic) AlipayFrom(w http.ResponseWriter, r *http.Request) error { + // 1. 解析表单 + err := r.ParseForm() + if err != nil { + logx.Errorf("支付宝from消息回调,解析表单失败: %v", err) + // 保存失败记录 + l.saveCallbackRecord(r, "", "", "failed", "解析表单失败: "+err.Error()) + return nil + } + + // 获取基本信息 + appID := r.Form.Get("app_id") + msgMethod := r.Form.Get("msg_method") + + // 2. 验签 + client := l.svcCtx.AlipayService.AlipayClient + if err := client.VerifySign(r.Form); err != nil { + logx.Errorf("支付宝from消息回调,验签失败: %v", err) + // 保存失败记录 + l.saveCallbackRecord(r, appID, msgMethod, "failed", "验签失败: "+err.Error()) + return nil + } + + // 3. 验证 app_id + if appID == "" { + logx.Errorf("支付宝from消息回调,app_id为空") + // 保存失败记录 + l.saveCallbackRecord(r, appID, msgMethod, "failed", "app_id为空") + return nil + } + if appID != l.svcCtx.Config.Alipay.AppID { + logx.Errorf("支付宝from消息回调,app_id不匹配,期望: %s, 实际: %s", l.svcCtx.Config.Alipay.AppID, appID) + // 保存失败记录 + l.saveCallbackRecord(r, appID, msgMethod, "failed", "app_id不匹配") + return nil + } + + // 4. 获取 msg_method 判断业务类型 + if msgMethod == "" { + logx.Errorf("支付宝from消息回调,msg_method为空") + // 保存失败记录 + l.saveCallbackRecord(r, appID, msgMethod, "failed", "msg_method为空") + return nil + } + + // 5. 先保存回调记录(pending状态) + callbackID := l.saveCallbackRecord(r, appID, msgMethod, "pending", "") + if callbackID == "" { + logx.Errorf("支付宝from消息回调,保存回调记录失败") + // 即使保存失败也继续处理,避免影响业务 + } + + // 6. 根据 msg_method 路由到对应的处理函数 + var handleErr error + switch msgMethod { + case "alipay.merchant.tradecomplain.changed": + // 交易投诉通知回调 + handleErr = l.handleTradeComplainChanged(w, r, callbackID) + case "alipay.security.risk.complaints.merchants.notify": + // 安全风险投诉商户通知回调 + handleErr = l.handleSecurityRiskComplaintsNotify(w, r, callbackID) + default: + logx.Infof("支付宝from消息回调,未处理的msg_method: %s", msgMethod) + // 更新为已处理(未处理的也标记为已处理) + if callbackID != "" { + l.updateCallbackStatus(callbackID, "processed", "") + } + alipay.ACKNotification(w) + return nil + } + + // 7. 根据处理结果更新状态 + if callbackID != "" { + if handleErr != nil { + l.updateCallbackStatus(callbackID, "failed", handleErr.Error()) + } else { + l.updateCallbackStatus(callbackID, "processed", "") + } + } + + return handleErr +} + +// saveCallbackRecord 保存回调记录到数据库 +func (l *AlipayFromLogic) saveCallbackRecord(r *http.Request, appID, msgMethod, status, errorMsg string) string { + callback := &model.AlipayFromCallback{ + Id: uuid.NewString(), + MsgMethod: msgMethod, + AppId: appID, + NotifyId: lzUtils.StringToNullString(r.Form.Get("notify_id")), + BizContent: r.Form.Get("biz_content"), + Status: status, + ErrorMessage: lzUtils.StringToNullString(errorMsg), + } + + _, err := l.svcCtx.AlipayFromCallbackModel.Insert(l.ctx, nil, callback) + if err != nil { + logx.Errorf("保存支付宝from回调记录失败: %v", err) + return "" + } + + return callback.Id +} + +// updateCallbackStatus 更新回调记录状态 +func (l *AlipayFromLogic) updateCallbackStatus(callbackID, status, errorMsg string) { + callback, err := l.svcCtx.AlipayFromCallbackModel.FindOne(l.ctx, callbackID) + if err != nil { + logx.Errorf("查找支付宝from回调记录失败: %v", err) + return + } + + callback.Status = status + if errorMsg != "" { + callback.ErrorMessage = lzUtils.StringToNullString(errorMsg) + } + + err = l.svcCtx.AlipayFromCallbackModel.UpdateWithVersion(l.ctx, nil, callback) + if err != nil { + logx.Errorf("更新支付宝from回调记录状态失败: %v", err) + } +} + +// handleTradeComplainChanged 处理交易投诉通知回调 +func (l *AlipayFromLogic) handleTradeComplainChanged(w http.ResponseWriter, r *http.Request, callbackID string) error { + // 获取 biz_content + bizContent := r.Form.Get("biz_content") + if bizContent == "" { + logx.Errorf("支付宝交易投诉通知回调,biz_content为空") + return nil + } + + // 解析 biz_content JSON + var content TradeComplainContent + if err := json.Unmarshal([]byte(bizContent), &content); err != nil { + logx.Errorf("支付宝交易投诉通知回调,解析biz_content失败: %v, content: %s", err, bizContent) + return nil + } + + // 记录日志 + logx.Infof("支付宝交易投诉通知回调,投诉事件ID: %s, 订单ID: %s, 状态: %s, 业务类型: %s", + content.ComplainEventID, content.OrderID, content.Status, content.BizType) + + // TODO: 后续在这里添加具体的业务处理逻辑 + // 例如:根据订单ID查找订单,更新订单状态,发送通知等 + + alipay.ACKNotification(w) + return nil +} + +// handleSecurityRiskComplaintsNotify 处理安全风险投诉商户通知回调 +func (l *AlipayFromLogic) handleSecurityRiskComplaintsNotify(w http.ResponseWriter, r *http.Request, callbackID string) error { + // 获取 biz_content + bizContent := r.Form.Get("biz_content") + if bizContent == "" { + logx.Errorf("支付宝安全风险投诉商户通知回调,biz_content为空") + return nil + } + + // 解析 biz_content JSON + var content SecurityRiskComplaintsNotifyContent + if err := json.Unmarshal([]byte(bizContent), &content); err != nil { + logx.Errorf("支付宝安全风险投诉商户通知回调,解析biz_content失败: %v, content: %s", err, bizContent) + return nil + } + + // 记录日志 + logx.Infof("支付宝安全风险投诉商户通知回调,投诉ID: %s, 消息类型: %s, 回复内容: %s, 回复时间: %s", + content.ComplaintID, content.MessageType, content.ReplyContent, content.ReplyTime) + + // 根据投诉ID查询详情并更新投诉记录 + if err := l.svcCtx.AlipayComplaintService.QueryComplaintByTaskId(l.ctx, content.ComplaintID); err != nil { + logx.Errorf("查询并更新投诉记录失败, complaint_id: %s, error: %v", content.ComplaintID, err) + // 即使失败也返回成功,避免支付宝重复通知 + } + + alipay.ACKNotification(w) + return nil +} diff --git a/app/main/api/internal/logic/pay/paymentchecklogic.go b/app/main/api/internal/logic/pay/paymentchecklogic.go index f6d43cb..f199ca4 100644 --- a/app/main/api/internal/logic/pay/paymentchecklogic.go +++ b/app/main/api/internal/logic/pay/paymentchecklogic.go @@ -38,7 +38,19 @@ func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *type Status: order.Status, }, nil } - + + if strings.HasPrefix(req.OrderNo, "W_") { + // 白名单订单 + order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单订单失败: %v", err) + } + return &types.PaymentCheckResp{ + Type: "whitelist", + Status: order.Status, + }, nil + } + // 查询订单(包括代理订单) order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) if err != nil { diff --git a/app/main/api/internal/logic/pay/paymentlogic.go b/app/main/api/internal/logic/pay/paymentlogic.go index a443261..051beef 100644 --- a/app/main/api/internal/logic/pay/paymentlogic.go +++ b/app/main/api/internal/logic/pay/paymentlogic.go @@ -72,6 +72,11 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, if err != nil { return err } + case "whitelist": + paymentTypeResp, err = l.WhitelistOrderPayment(req, session) + if err != nil { + return err + } } // 开发环境测试支付模式:跳过实际支付流程 @@ -182,6 +187,37 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, } else { logx.Infof("开发测试模式,代理升级成功,订单号: %s, 代理ID: %s", paymentTypeResp.outTradeNo, upgradeRecord.AgentId) } + } else if strings.HasPrefix(paymentTypeResp.outTradeNo, "W_") { + // 白名单订单:更新白名单订单状态并处理白名单记录 + whitelistOrder, findWhitelistErr := l.svcCtx.WhitelistOrderModel.FindOneByOrderNo(context.Background(), paymentTypeResp.outTradeNo) + if findWhitelistErr != nil { + logx.Errorf("开发测试模式,查找白名单订单失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, findWhitelistErr) + return + } + + // 更新白名单订单状态为已支付并处理白名单记录 + err := l.svcCtx.WhitelistOrderModel.Trans(context.Background(), func(transCtx context.Context, session sqlx.Session) error { + // 更新白名单订单状态为已支付 + whitelistOrder.Status = 2 // 已支付 + now := time.Now() + whitelistOrder.PayTime = sql.NullTime{Time: now, Valid: true} + if updateErr := l.svcCtx.WhitelistOrderModel.UpdateWithVersion(transCtx, session, whitelistOrder); updateErr != nil { + return errors.Wrapf(updateErr, "更新白名单订单状态失败") + } + + // 调用 WhitelistService 处理白名单记录 + if processErr := l.svcCtx.WhitelistService.ProcessPaidWhitelistOrder(transCtx, session, order, whitelistOrder); processErr != nil { + return errors.Wrapf(processErr, "处理白名单订单失败") + } + + return nil + }) + + if err != nil { + logx.Errorf("开发测试模式,处理白名单订单失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, err) + } else { + logx.Infof("开发测试模式,白名单订单支付成功,订单号: %s", paymentTypeResp.outTradeNo) + } } else { // 查询订单:发送支付成功通知任务,触发后续流程(生成报告和代理处理) if sendErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); sendErr != nil { @@ -202,6 +238,181 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, } } +// WhitelistOrderPayment 白名单下架支付订单 +// PaymentReq.Id 支持两种格式: +// 1. "{idCard}|{featureApiId}" - 单个模块下架(创建新订单) +// 2. 订单号(以 W_ 开头) - 批量订单支付(使用已存在的订单) +func (l *PaymentLogic) WhitelistOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成白名单订单, 获取用户信息失败, %v", err) + } + + // 判断是订单号还是 "{idCard}|{featureApiId}" 格式 + var whitelistOrder *model.WhitelistOrder + var orderNo string + var amount float64 + var description string + + if strings.HasPrefix(req.Id, "W_") { + // 格式2:订单号,使用已存在的批量订单 + orderNo = req.Id + whitelistOrder, err = l.svcCtx.WhitelistOrderModel.FindOneByOrderNo(l.ctx, orderNo) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("白名单订单不存在"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询白名单订单失败: %v", err) + } + + // 验证订单状态和用户 + if whitelistOrder.Status != 1 { + return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法支付"), "") + } + if whitelistOrder.UserId != userID { + return nil, errors.Wrapf(xerr.NewErrMsg("无权支付此订单"), "") + } + + amount = whitelistOrder.TotalAmount + description = "模块屏蔽(批量)" + + // 获取用户信息(用于内部用户测试金额) + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户信息失败: %v", err) + } + if user.Inside == 1 { + amount = 0.01 + } + + // 检查是否已存在支付订单(Order表) + existingOrder, findErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo) + if findErr == nil && existingOrder != nil { + // 已存在支付订单,直接使用 + return &PaymentTypeResp{ + amount: amount, + outTradeNo: orderNo, + description: description, + orderID: existingOrder.Id, + }, nil + } + + // 创建主订单记录(用于支付系统) + order := model.Order{ + Id: uuid.NewString(), + OrderNo: orderNo, + UserId: userID, + ProductId: "", // 白名单订单没有具体产品ID + PaymentPlatform: req.PayMethod, + PaymentScene: "app", // 白名单订单,使用简短标识 + Amount: amount, + Status: "pending", + } + if _, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order); insertOrderErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建支付订单失败: %+v", insertOrderErr) + } + + return &PaymentTypeResp{ + amount: amount, + outTradeNo: orderNo, + description: description, + orderID: order.Id, + }, nil + } + + // 格式1:"{idCard}|{featureApiId}" - 单个模块下架 + parts := strings.SplitN(req.Id, "|", 2) + if len(parts) != 2 { + return nil, errors.Wrapf(xerr.NewErrMsg("参数错误:id 格式不正确,应为订单号(W_开头)或 {idCard}|{featureApiId}"), "") + } + idCard := parts[0] + featureApiId := parts[1] + + if idCard == "" || featureApiId == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("参数错误:身份证号或模块标识为空"), "") + } + + // 查询 feature 信息 + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, featureApiId) + 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) + } + + amount = feature.WhitelistPrice + if amount <= 0 { + return nil, errors.Wrapf(xerr.NewErrMsg("该模块无需付费下架"), "") + } + + // 获取用户信息(用于内部用户测试金额) + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成白名单订单, 获取用户信息失败: %v", err) + } + if user.Inside == 1 { + amount = 0.01 + } + + // 生成订单号(白名单订单前缀 W_,限制长度不超过32) + base := l.svcCtx.AlipayService.GenerateOutTradeNo() + outTradeNo := "W_" + base + if len(outTradeNo) > 32 { + outTradeNo = outTradeNo[:32] + } + + // 创建主订单记录(用于支付系统) + order := model.Order{ + Id: uuid.NewString(), + OrderNo: outTradeNo, + UserId: userID, + ProductId: "", // 白名单订单没有具体产品ID + PaymentPlatform: req.PayMethod, + PaymentScene: "app", // 白名单订单,使用简短标识 + Amount: amount, + Status: "pending", + } + if _, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order); insertOrderErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成白名单订单, 保存订单失败: %+v", insertOrderErr) + } + + // 创建白名单订单(业务订单,状态=待支付) + whitelistOrder = &model.WhitelistOrder{ + Id: uuid.NewString(), + OrderNo: outTradeNo, + UserId: userID, + IdCard: idCard, + TotalAmount: amount, + Status: 1, // 待支付 + } + if _, err := l.svcCtx.WhitelistOrderModel.Insert(l.ctx, session, whitelistOrder); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成白名单订单, 保存白名单订单失败: %+v", err) + } + + // 创建白名单订单明细 + orderItem := &model.WhitelistOrderItem{ + Id: uuid.NewString(), + OrderId: whitelistOrder.Id, + FeatureId: feature.Id, + FeatureApiId: feature.ApiId, + FeatureName: feature.Name, + Price: amount, + } + if _, err := l.svcCtx.WhitelistOrderItemModel.Insert(l.ctx, session, orderItem); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成白名单订单, 保存白名单订单明细失败: %+v", err) + } + + description = fmt.Sprintf("模块下架:%s", feature.Name) + + return &PaymentTypeResp{ + amount: amount, + outTradeNo: outTradeNo, + description: description, + orderID: order.Id, + }, nil +} + func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) if getUidErr != nil { diff --git a/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go index e6d1d51..f29dd74 100644 --- a/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go +++ b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go @@ -45,6 +45,9 @@ func (l *WechatPayCallbackLogic) WechatPayCallback(w http.ResponseWriter, r *htt } else if strings.HasPrefix(orderNo, "U_") { // 代理升级订单处理 return l.handleAgentUpgradeOrderPayment(w, notification) + } else if strings.HasPrefix(orderNo, "W_") { + // 白名单下架订单处理 + return l.handleWhitelistOrderPayment(w, notification) } else if strings.HasPrefix(orderNo, "A_") { // 旧系统会员充值订单(已废弃,新系统使用升级功能) // return l.handleAgentVipOrderPayment(w, notification) @@ -109,6 +112,91 @@ func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, return nil } +// 处理白名单下架订单支付 +func (l *WechatPayCallbackLogic) handleWhitelistOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error { + orderNo := *notification.OutTradeNo + + // 1. 查找订单 + order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo) + if findOrderErr != nil { + logx.Errorf("微信白名单支付回调,查找订单信息失败: %+v", findOrderErr) + return nil + } + + // 2. 验证金额 + amount := lzUtils.ToWechatAmount(order.Amount) + if amount != *notification.Amount.Total { + logx.Errorf("微信白名单支付回调,金额不一致") + return nil + } + + // 3. 检查订单状态 + if order.Status != "pending" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil + } + + // 4. 查找白名单订单 + whitelistOrder, findWhitelistErr := l.svcCtx.WhitelistOrderModel.FindOneByOrderNo(l.ctx, orderNo) + if findWhitelistErr != nil { + logx.Errorf("微信白名单支付回调,查找白名单订单失败,订单号: %s, 错误: %+v", orderNo, findWhitelistErr) + return nil + } + if whitelistOrder.Status != 1 { + // 白名单订单状态不是待支付,直接返回成功 + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil + } + + // 5. 处理支付状态 + switch *notification.TradeState { + case service.TradeStateSuccess: + order.Status = "paid" + order.PayTime = lzUtils.TimeToNullTime(time.Now()) + order.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId) + whitelistOrder.Status = 2 // 已支付 + whitelistOrder.PayTime = lzUtils.TimeToNullTime(time.Now()) + case service.TradeStateClosed: + order.Status = "closed" + order.CloseTime = lzUtils.TimeToNullTime(time.Now()) + whitelistOrder.Status = 3 // 已取消 + default: + return nil + } + + // 6. 更新订单和白名单订单 + 创建白名单记录并删除报告数据 + err := l.svcCtx.WhitelistOrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 6.1 更新订单状态 + if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, session, order); updateErr != nil { + return errors.Wrapf(updateErr, "微信白名单支付回调,更新订单状态失败") + } + + // 6.2 更新白名单订单状态 + if updateErr := l.svcCtx.WhitelistOrderModel.UpdateWithVersion(ctx, session, whitelistOrder); updateErr != nil { + return errors.Wrapf(updateErr, "微信白名单支付回调,更新白名单订单状态失败") + } + + // 6.3 如果支付成功,调用 WhitelistService 处理白名单和报告数据 + if whitelistOrder.Status == 2 { + if processErr := l.svcCtx.WhitelistService.ProcessPaidWhitelistOrder(ctx, session, order, whitelistOrder); processErr != nil { + return errors.Wrapf(processErr, "微信白名单支付回调,处理白名单订单失败") + } + } + + return nil + }) + if err != nil { + logx.Errorf("微信白名单支付回调,事务处理失败: %+v", err) + return nil + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil +} + // 处理代理升级订单支付 func (l *WechatPayCallbackLogic) handleAgentUpgradeOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error { orderNo := *notification.OutTradeNo diff --git a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go index 48985af..f3ba1d8 100644 --- a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go +++ b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go @@ -114,6 +114,21 @@ func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, st return errors.Wrapf(err, "更新订单和退款记录失败: %s", orderNo) } + // 退款成功后,检查并处理代理订单(在事务外执行,避免影响退款流程) + if status == refunddomestic.STATUS_SUCCESS { + // 检查代理订单是否已处理 + agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, order.Id) + if err == nil && agentOrder != nil && agentOrder.ProcessStatus == 1 { + // 代理订单已处理,需要撤销收益 + if cancelErr := l.svcCtx.AgentService.CancelAgentCommission(l.ctx, order.Id); cancelErr != nil { + logx.Errorf("撤销代理收益失败,订单ID: %s, 错误: %v", order.Id, cancelErr) + // 不阻断退款流程,只记录日志(退款已成功,不能回滚) + } else { + logx.Infof("成功撤销代理收益,订单ID: %s", order.Id) + } + } + } + return nil } diff --git a/app/main/api/internal/queue/cleanQueryData.go b/app/main/api/internal/queue/cleanQueryData.go index 13b5a0e..6ef2994 100644 --- a/app/main/api/internal/queue/cleanQueryData.go +++ b/app/main/api/internal/queue/cleanQueryData.go @@ -8,7 +8,6 @@ import ( "time" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/model" - "ycc-server/common/globalkey" "github.com/google/uuid" "github.com/hibiken/asynq" @@ -99,7 +98,6 @@ func (l *CleanQueryDataHandler) ProcessTask(ctx context.Context, t *asynq.Task) // 先快速检查是否有数据需要清理(避免创建无用的日志记录) checkBuilder := l.svcCtx.QueryModel.SelectBuilder(). Where("create_time < ?", cleanupBefore). - Where("del_state = ?", globalkey.DelStateNo). Limit(1) // 只查询1条,用于判断是否有数据 checkQueries, checkErr := l.svcCtx.QueryModel.FindAll(taskCtx, checkBuilder, "") @@ -160,7 +158,6 @@ func (l *CleanQueryDataHandler) ProcessTask(ctx context.Context, t *asynq.Task) // 1. 查询一批要删除的记录(添加排序确保一致性) builder := l.svcCtx.QueryModel.SelectBuilder(). Where("create_time < ?", cleanupBefore). - Where("del_state = ?", globalkey.DelStateNo). OrderBy("id ASC"). // 添加排序,确保处理顺序一致 Limit(uint64(batchSize)) diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index 0fcc958..27d60de 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -240,6 +240,15 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error logx.Errorf("更新订单状态失败,订单ID: %s, 错误: %v", order.Id, updateOrderErr) return fmt.Errorf("更新订单状态失败: %v", updateOrderErr) } + // 检查并处理代理订单(虽然查询失败时代理订单通常未处理,但为了完整性) + agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(ctx, order.Id) + if err == nil && agentOrder != nil && agentOrder.ProcessStatus == 1 { + if cancelErr := l.svcCtx.AgentService.CancelAgentCommission(ctx, order.Id); cancelErr != nil { + logx.Errorf("撤销代理收益失败,订单ID: %s, 错误: %v", order.Id, cancelErr) + } else { + logx.Infof("成功撤销代理收益,订单ID: %s", order.Id) + } + } return asynq.SkipRetry } else { logx.Errorf("支付宝退款失败:%v", refundErr) diff --git a/app/main/api/internal/queue/routes.go b/app/main/api/internal/queue/routes.go index a31611f..29ff082 100644 --- a/app/main/api/internal/queue/routes.go +++ b/app/main/api/internal/queue/routes.go @@ -39,6 +39,13 @@ func (l *CronJob) Register() *asynq.ServeMux { panic(fmt.Sprintf("解冻佣金扫描定时任务注册失败:%v", err)) } + // 注册支付宝投诉同步定时任务(每5分钟执行一次) + syncComplaintTask := asynq.NewTask(types.MsgSyncAlipayComplaint, nil, nil) + _, err = scheduler.Register("*/5 * * * *", syncComplaintTask) // 每5分钟执行一次 + if err != nil { + panic(fmt.Sprintf("支付宝投诉同步定时任务注册失败:%v", err)) + } + scheduler.Start() fmt.Println("定时任务启动!!!") @@ -48,6 +55,7 @@ func (l *CronJob) Register() *asynq.ServeMux { mux.Handle(types.MsgAgentProcess, NewAgentProcessHandler(l.svcCtx)) mux.Handle(types.MsgUnfreezeCommission, NewUnfreezeCommissionHandler(l.svcCtx)) mux.Handle(types.MsgUnfreezeCommissionScan, NewUnfreezeCommissionScanHandler(l.svcCtx)) + mux.Handle(types.MsgSyncAlipayComplaint, NewSyncAlipayComplaintHandler(l.svcCtx)) return mux } diff --git a/app/main/api/internal/queue/syncAlipayComplaint.go b/app/main/api/internal/queue/syncAlipayComplaint.go new file mode 100644 index 0000000..40728af --- /dev/null +++ b/app/main/api/internal/queue/syncAlipayComplaint.go @@ -0,0 +1,130 @@ +package queue + +import ( + "context" + "os" + "time" + "ycc-server/app/main/api/internal/svc" + + "github.com/hibiken/asynq" + "github.com/pkg/errors" + "github.com/smartwalle/alipay/v3" + "github.com/zeromicro/go-zero/core/logx" +) + +// SyncAlipayComplaintHandler 支付宝投诉同步任务处理器 +type SyncAlipayComplaintHandler struct { + svcCtx *svc.ServiceContext +} + +func NewSyncAlipayComplaintHandler(svcCtx *svc.ServiceContext) *SyncAlipayComplaintHandler { + return &SyncAlipayComplaintHandler{ + svcCtx: svcCtx, + } +} + +// ProcessTask 处理投诉同步任务 +func (l *SyncAlipayComplaintHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + if os.Getenv("ENV") == "development" { + return nil + } + startTime := time.Now() + logx.Infof("开始同步支付宝投诉数据,当前时间: %v", startTime) + + // 1. 查询数据库中最新投诉的投诉时间 + latestComplainTime, err := l.svcCtx.AlipayComplaintService.GetLatestComplainTime(ctx) + if err != nil { + logx.Errorf("查询最新投诉时间失败: %v", err) + return errors.Wrapf(err, "查询最新投诉时间失败") + } + + // 2. 如果数据库为空,查询最近7天的投诉;否则查询最新投诉时间之后的投诉 + var gmtComplainStart string + if latestComplainTime.IsZero() { + // 数据库为空,查询最近7天 + sevenDaysAgo := time.Now().AddDate(0, 0, -7) + gmtComplainStart = sevenDaysAgo.Format("2006-01-02 15:04:05") + logx.Infof("数据库为空,查询最近7天的投诉,起始时间: %s", gmtComplainStart) + } else { + // 查询最新投诉时间之后的投诉(往前推1分钟,避免边界问题) + gmtComplainStart = latestComplainTime.Add(-1 * time.Minute).Format("2006-01-02 15:04:05") + logx.Infof("查询最新投诉时间之后的投诉,起始时间: %s (最新投诉时间: %v)", gmtComplainStart, latestComplainTime) + } + + // 3. 分页查询投诉列表 + pageNum := int64(1) + pageSize := int64(10) + totalSynced := 0 + + for { + // 调用支付宝接口查询投诉列表 + req := alipay.SecurityRiskComplaintInfoBatchQueryReq{ + CurrentPageNum: pageNum, + PageSize: pageSize, + GmtComplaintStart: gmtComplainStart, + GmtComplaintEnd: time.Now().Format("2006-01-02 15:04:05"), + } + + resp, err := l.svcCtx.AlipayService.AlipayClient.SecurityRiskComplaintInfoBatchQuery(ctx, req) + if err != nil { + logx.Errorf("查询支付宝投诉列表失败: %v", err) + return errors.Wrapf(err, "查询支付宝投诉列表失败") + } + + // 检查响应是否成功 + if !resp.IsSuccess() { + logx.Errorf("支付宝返回错误: code=%s, msg=%s", resp.Code, resp.Msg) + return errors.Errorf("支付宝返回错误: %s-%s", resp.Code, resp.Msg) + } + + // 如果没有数据,退出循环 + if len(resp.ComplaintList) == 0 { + logx.Infof("第 %d 页没有数据,同步完成", pageNum) + break + } + + // 4. 保存投诉数据到数据库 + synced, err := l.saveComplaints(ctx, resp.ComplaintList) + if err != nil { + logx.Errorf("保存投诉数据失败: %v", err) + return errors.Wrapf(err, "保存投诉数据失败") + } + + totalSynced += synced + logx.Infof("第 %d 页同步完成,本页同步 %d 条,累计同步 %d 条", pageNum, synced, totalSynced) + + // 5. 检查是否还有下一页 + if pageNum >= resp.CurrentPage { + // 当前页已经是最后一页 + if len(resp.ComplaintList) < int(pageSize) { + // 当前页数据不足一页,说明没有更多数据了 + break + } + } + + // 继续查询下一页 + pageNum++ + } + + duration := time.Since(startTime) + logx.Infof("支付宝投诉数据同步完成,共同步 %d 条投诉,耗时: %v", totalSynced, duration) + + return nil +} + +// saveComplaints 保存投诉列表到数据库 +func (l *SyncAlipayComplaintHandler) saveComplaints(ctx context.Context, complaintList []alipay.SecurityRiskComplaintInfo) (int, error) { + synced := 0 + + for _, complaint := range complaintList { + // 使用 service 保存投诉数据 + if err := l.svcCtx.AlipayComplaintService.SaveComplaint(ctx, &complaint); err != nil { + logx.Errorf("保存投诉失败, task_id: %s, error: %v", complaint.TaskId, err) + continue // 继续处理下一条 + } + + synced++ + } + + return synced, nil +} diff --git a/app/main/api/internal/service/agentService.go b/app/main/api/internal/service/agentService.go index cda806e..9fa5b77 100644 --- a/app/main/api/internal/service/agentService.go +++ b/app/main/api/internal/service/agentService.go @@ -630,18 +630,18 @@ func (s *AgentService) getRebateConfigFloat(ctx context.Context, configKey strin // giveRebate 发放返佣 func (s *AgentService) giveRebate(ctx context.Context, session sqlx.Session, agentId, sourceAgentId, orderId, productId string, amount float64, levelBonus int64, rebateType int64) error { - // 1. 创建返佣记录 - rebate := &model.AgentRebate{ - Id: uuid.NewString(), - AgentId: agentId, - SourceAgentId: sourceAgentId, - OrderId: orderId, - ProductId: productId, - RebateType: rebateType, - LevelBonus: float64(levelBonus), // 等级加成金额 - RebateAmount: amount, - Status: 1, // 已发放 - } + // 1. 创建返佣记录 + rebate := &model.AgentRebate{ + Id: uuid.NewString(), + AgentId: agentId, + SourceAgentId: sourceAgentId, + OrderId: orderId, + ProductId: productId, + RebateType: rebateType, + LevelBonus: float64(levelBonus), // 等级加成金额 + RebateAmount: amount, + Status: 1, // 已发放 + } if _, err := s.AgentRebateModel.Insert(ctx, session, rebate); err != nil { return errors.Wrapf(err, "创建返佣记录失败") } @@ -971,12 +971,12 @@ func (s *AgentService) reconnectToGrandparent(ctx context.Context, session sqlx. } // 创建新的关系连接到原上级的上级 - relation := &model.AgentRelation{ - Id: uuid.NewString(), - ParentId: grandparent.Id, - ChildId: agentId, - RelationType: 1, // 直接关系 - } + relation := &model.AgentRelation{ + Id: uuid.NewString(), + ParentId: grandparent.Id, + ChildId: agentId, + RelationType: 1, // 直接关系 + } if _, err := s.AgentRelationModel.Insert(ctx, session, relation); err != nil { return errors.Wrapf(err, "创建新关系失败") } @@ -1073,3 +1073,201 @@ func (s *AgentService) GetUpgradeRebate(ctx context.Context, fromLevel, toLevel } return 0, nil } + +// CancelAgentCommission 撤销代理佣金和返佣(订单退款时调用) +// 功能:收回已发放的代理佣金和返佣,处理冻结任务 +// 余额不足时允许余额为负数(欠款),通过提现限制控制 +func (s *AgentService) CancelAgentCommission(ctx context.Context, orderId string) error { + // 1. 查找代理订单 + agentOrder, err := s.AgentOrderModel.FindOneByOrderId(ctx, orderId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 不是代理订单,直接返回 + return nil + } + return errors.Wrapf(err, "查询代理订单失败, orderId: %s", orderId) + } + + // 2. 检查是否已处理(只有已处理的订单才需要收回) + if agentOrder.ProcessStatus != 1 { + logx.Infof("代理订单未处理,无需撤销佣金, orderId: %s, processStatus: %d", orderId, agentOrder.ProcessStatus) + return nil + } + + // 3. 查找所有佣金记录 + builder := s.AgentCommissionModel.SelectBuilder(). + Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo) + commissions, err := s.AgentCommissionModel.FindAll(ctx, builder, "") + if err != nil { + return errors.Wrapf(err, "查询佣金记录失败, orderId: %s", orderId) + } + + // 4. 查找所有返佣记录 + rebateBuilder := s.AgentRebateModel.SelectBuilder(). + Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo) + rebates, err := s.AgentRebateModel.FindAll(ctx, rebateBuilder, "") + if err != nil { + return errors.Wrapf(err, "查询返佣记录失败, orderId: %s", orderId) + } + + // 5. 查找相关冻结任务 + freezeBuilder := s.AgentFreezeTaskModel.SelectBuilder(). + Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo) + freezeTasks, err := s.AgentFreezeTaskModel.FindAll(ctx, freezeBuilder, "") + if err != nil { + return errors.Wrapf(err, "查询冻结任务失败, orderId: %s", orderId) + } + + // 6. 使用事务处理收回逻辑 + return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error { + // 6.1 收回代理佣金 + for _, commission := range commissions { + // 只处理已发放的佣金(Status=1) + if commission.Status != 1 { + logx.Infof("佣金记录状态不是已发放,跳过, commissionId: %s, status: %d", commission.Id, commission.Status) + continue + } + + // 查找冻结任务(如果有) + var freezeTask *model.AgentFreezeTask + for _, task := range freezeTasks { + if task.CommissionId == commission.Id && task.Status == 1 { + freezeTask = task + break + } + } + + // 扣除代理钱包余额 + if err := s.deductCommissionFromWallet(transCtx, session, commission.AgentId, commission.Amount, freezeTask); err != nil { + return errors.Wrapf(err, "扣除代理佣金失败, agentId: %s, amount: %.2f", commission.AgentId, commission.Amount) + } + + // 更新佣金记录状态为已撤销 + commission.Status = 3 // 3=已取消 + if err := s.AgentCommissionModel.UpdateWithVersion(transCtx, session, commission); err != nil { + return errors.Wrapf(err, "更新佣金记录状态失败, commissionId: %s", commission.Id) + } + + // 如果有冻结任务,更新为已撤销 + if freezeTask != nil { + freezeTask.Status = 3 // 3=已取消 + if err := s.AgentFreezeTaskModel.UpdateWithVersion(transCtx, session, freezeTask); err != nil { + return errors.Wrapf(err, "更新冻结任务状态失败, freezeTaskId: %s", freezeTask.Id) + } + } + } + + // 6.2 收回返佣 + for _, rebate := range rebates { + // 只处理已发放的返佣(Status=1) + if rebate.Status != 1 { + logx.Infof("返佣记录状态不是已发放,跳过, rebateId: %s, status: %d", rebate.Id, rebate.Status) + continue + } + + // 扣除上级代理钱包余额 + if err := s.deductRebateFromWallet(transCtx, session, rebate.AgentId, rebate.RebateAmount); err != nil { + return errors.Wrapf(err, "扣除返佣失败, agentId: %s, amount: %.2f", rebate.AgentId, rebate.RebateAmount) + } + + // 更新返佣记录状态为已撤销 + rebate.Status = 3 // 3=已取消 + if err := s.AgentRebateModel.UpdateWithVersion(transCtx, session, rebate); err != nil { + return errors.Wrapf(err, "更新返佣记录状态失败, rebateId: %s", rebate.Id) + } + } + + // 6.3 更新代理订单备注(可选,记录撤销信息) + agentOrder.ProcessRemark = sql.NullString{ + String: "订单退款,佣金和返佣已撤销", + Valid: true, + } + if err := s.AgentOrderModel.UpdateWithVersion(transCtx, session, agentOrder); err != nil { + logx.Errorf("更新代理订单备注失败, orderId: %s, err: %v", orderId, err) + // 不阻断流程,只记录日志 + } + + return nil + }) +} + +// deductCommissionFromWallet 从钱包扣除佣金(支持余额不足,允许负数) +func (s *AgentService) deductCommissionFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64, freezeTask *model.AgentFreezeTask) error { + wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId) + if err != nil { + return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId) + } + + // 计算可用余额 + availableBalance := wallet.Balance + wallet.FrozenBalance + + // 如果有冻结任务,需要先解冻 + if freezeTask != nil && freezeTask.Status == 1 { + freezeAmount := freezeTask.FreezeAmount + // 解冻:从冻结余额转回可用余额 + if wallet.FrozenBalance >= freezeAmount { + wallet.FrozenBalance -= freezeAmount + wallet.Balance += freezeAmount + } else { + // 冻结余额不足(异常情况),记录日志 + logx.Errorf("冻结余额不足,异常情况, agentId: %s, frozenBalance: %.2f, freezeAmount: %.2f", agentId, wallet.FrozenBalance, freezeAmount) + wallet.Balance += wallet.FrozenBalance + wallet.FrozenBalance = 0 + } + } + + // 扣除佣金金额 + if availableBalance >= amount { + // 余额充足:优先扣除冻结余额,再扣除可用余额 + if wallet.FrozenBalance >= amount { + wallet.FrozenBalance -= amount + } else { + remaining := amount - wallet.FrozenBalance + wallet.FrozenBalance = 0 + wallet.Balance -= remaining + } + } else { + // 余额不足:扣除所有可用余额,允许负数 + wallet.Balance = wallet.Balance - (amount - wallet.FrozenBalance) + wallet.FrozenBalance = 0 + // 记录日志:欠款金额 + logx.Errorf("代理余额不足,产生欠款: agentId=%s, orderId=%s, debt=%.2f, balance=%.2f", + agentId, "", -wallet.Balance, wallet.Balance) + } + + // TotalEarnings 始终减少(即使余额为负数) + wallet.TotalEarnings -= amount + if wallet.TotalEarnings < 0 { + wallet.TotalEarnings = 0 // 累计收益不能为负数 + } + + return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) +} + +// deductRebateFromWallet 从钱包扣除返佣(支持余额不足,允许负数) +func (s *AgentService) deductRebateFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64) error { + wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId) + if err != nil { + return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId) + } + + // 扣除返佣金额 + if wallet.Balance >= amount { + // 余额充足:正常扣除 + wallet.Balance -= amount + } else { + // 余额不足:扣除所有余额,允许负数 + wallet.Balance -= amount + // 记录日志:欠款金额 + logx.Errorf("代理返佣余额不足,产生欠款: agentId=%s, debt=%.2f, balance=%.2f", + agentId, -wallet.Balance, wallet.Balance) + } + + // TotalEarnings 始终减少(即使余额为负数) + wallet.TotalEarnings -= amount + if wallet.TotalEarnings < 0 { + wallet.TotalEarnings = 0 // 累计收益不能为负数 + } + + return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) +} diff --git a/app/main/api/internal/service/alipayComplaintService.go b/app/main/api/internal/service/alipayComplaintService.go new file mode 100644 index 0000000..2136119 --- /dev/null +++ b/app/main/api/internal/service/alipayComplaintService.go @@ -0,0 +1,541 @@ +package service + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "time" + "ycc-server/app/main/api/internal/config" + "ycc-server/app/main/model" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/smartwalle/alipay/v3" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// AlipayComplaintService 支付宝投诉服务,集中处理投诉相关的业务逻辑 +type AlipayComplaintService struct { + config config.Config + AlipayClient *alipay.Client + ComplaintMainModel model.ComplaintMainModel + ComplaintAlipayModel model.ComplaintAlipayModel + ComplaintAlipayTradeModel model.ComplaintAlipayTradeModel + OrderModel model.OrderModel // 用于订单关联 +} + +// NewAlipayComplaintService 创建支付宝投诉服务 +func NewAlipayComplaintService( + c config.Config, + alipayClient *alipay.Client, + complaintMainModel model.ComplaintMainModel, + complaintAlipayModel model.ComplaintAlipayModel, + complaintAlipayTradeModel model.ComplaintAlipayTradeModel, + orderModel model.OrderModel, +) *AlipayComplaintService { + return &AlipayComplaintService{ + config: c, + AlipayClient: alipayClient, + ComplaintMainModel: complaintMainModel, + ComplaintAlipayModel: complaintAlipayModel, + ComplaintAlipayTradeModel: complaintAlipayTradeModel, + OrderModel: orderModel, + } +} + +// QueryComplaintDetail 查询投诉详情 +func (s *AlipayComplaintService) QueryComplaintDetail(ctx context.Context, complainId int64) (*alipay.SecurityRiskComplaintInfo, error) { + req := alipay.SecurityRiskComplaintInfoQueryReq{ + ComplainId: complainId, + } + + resp, err := s.AlipayClient.SecurityRiskComplaintInfoQuery(ctx, req) + if err != nil { + return nil, errors.Wrapf(err, "查询投诉详情失败, complain_id: %d", complainId) + } + + if !resp.IsSuccess() { + return nil, errors.Errorf("支付宝返回错误: %s-%s", resp.Code, resp.Msg) + } + + return &resp.SecurityRiskComplaintInfo, nil +} + +// QueryComplaintByTaskId 根据 task_id 查询投诉详情并更新记录 +func (s *AlipayComplaintService) QueryComplaintByTaskId(ctx context.Context, taskId string) error { + // 1. 通过 task_id 查找数据库中的投诉记录 + complaint, err := s.ComplaintAlipayModel.FindOneByTaskId(ctx, taskId) + if err != nil { + if err == model.ErrNotFound { + logx.Infof("投诉记录不存在, task_id: %s,可能是新投诉,将在定时任务中同步", taskId) + return nil // 新投诉会在定时任务中同步,这里不处理 + } + return errors.Wrapf(err, "查找投诉记录失败, task_id: %s", taskId) + } + + // 2. 使用 alipay_id 查询投诉详情 + detail, err := s.QueryComplaintDetail(ctx, complaint.AlipayId) + if err != nil { + return errors.Wrapf(err, "查询投诉详情失败, task_id: %s, alipay_id: %d", taskId, complaint.AlipayId) + } + + logx.Infof("查询投诉详情成功, task_id: %s, alipay_id: %d, status: %s", taskId, complaint.AlipayId, detail.Status) + + // 3. 更新投诉记录 + return s.UpdateComplaintFromDetail(ctx, complaint, detail) +} + +// UpdateComplaintFromDetail 根据查询详情更新投诉记录 +func (s *AlipayComplaintService) UpdateComplaintFromDetail(ctx context.Context, complaint *model.ComplaintAlipay, detail *alipay.SecurityRiskComplaintInfo) error { + return s.ComplaintAlipayModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error { + // 1. 更新或创建投诉主表记录 + var complaintMain *model.ComplaintMain + var err error + if complaint.ComplaintId != "" { + // 查找主表记录 + complaintMain, err = s.ComplaintMainModel.FindOne(transCtx, complaint.ComplaintId) + if err != nil { + return errors.Wrapf(err, "查找投诉主表失败, complaint_id: %s", complaint.ComplaintId) + } + // 更新主表信息 + complaintMain.Name = lzUtils.StringToNullString(detail.OppositeName) // 使用被投诉方名称作为投诉人姓名 + complaintMain.Contact = lzUtils.StringToNullString(detail.Contact) + complaintMain.Content = lzUtils.StringToNullString(detail.ComplainContent) + complaintMain.Status = lzUtils.StringToNullString(detail.Status) + complaintMain.StatusDescription = lzUtils.StringToNullString(detail.StatusDescription) + // 如果主表没有订单ID,尝试关联订单 + if !complaintMain.OrderId.Valid { + if detail.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, detail.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + } + } + // 尝试从交易信息中查找 + if !complaintMain.OrderId.Valid && len(detail.ComplaintTradeInfoList) > 0 { + for _, tradeInfo := range detail.ComplaintTradeInfoList { + if tradeInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + break + } + } + if tradeInfo.OutNo != "" { + order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo) + if err == nil && order != nil { + complaintMain.OrderId = lzUtils.StringToNullString(order.Id) + break + } + } + } + } + } + if err := s.ComplaintMainModel.UpdateWithVersion(transCtx, session, complaintMain); err != nil { + return errors.Wrapf(err, "更新投诉主表失败, complaint_id: %s", complaint.ComplaintId) + } + } else { + // 创建新的主表记录 + complaintMain = &model.ComplaintMain{ + Id: uuid.NewString(), + Type: "alipay", + Name: lzUtils.StringToNullString(detail.OppositeName), + Contact: lzUtils.StringToNullString(detail.Contact), + Content: lzUtils.StringToNullString(detail.ComplainContent), + Status: lzUtils.StringToNullString(detail.Status), + StatusDescription: lzUtils.StringToNullString(detail.StatusDescription), + } + // 如果有交易单号,尝试关联订单 + if detail.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, detail.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + } + } + // 如果主表没有订单ID,尝试从交易信息中查找 + if !complaintMain.OrderId.Valid && len(detail.ComplaintTradeInfoList) > 0 { + for _, tradeInfo := range detail.ComplaintTradeInfoList { + if tradeInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + break + } + } + // 也可以尝试通过 out_no(商家订单号)查找 + if tradeInfo.OutNo != "" { + order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo) + if err == nil && order != nil { + complaintMain.OrderId = lzUtils.StringToNullString(order.Id) + break + } + } + } + } + if _, err := s.ComplaintMainModel.Insert(transCtx, session, complaintMain); err != nil { + return errors.Wrapf(err, "创建投诉主表失败, task_id: %s", complaint.TaskId) + } + complaint.ComplaintId = complaintMain.Id + } + + // 2. 更新支付宝投诉表信息 + complaint.OppositePid = lzUtils.StringToNullString(detail.OppositePid) + complaint.OppositeName = lzUtils.StringToNullString(detail.OppositeName) + if detail.ComplainAmount != "" { + amount, err := s.parseDecimal(detail.ComplainAmount) + if err == nil { + complaint.ComplainAmount = lzUtils.Float64ToNullFloat64(amount) + } + } + complaint.GmtComplain = s.parseTime(detail.GmtComplain) + complaint.GmtProcess = s.parseTime(detail.GmtProcess) + complaint.GmtOverdue = s.parseTime(detail.GmtOverdue) + complaint.ComplainContent = lzUtils.StringToNullString(detail.ComplainContent) + complaint.TradeNo = lzUtils.StringToNullString(detail.TradeNo) + complaint.Status = lzUtils.StringToNullString(detail.Status) + complaint.StatusDescription = lzUtils.StringToNullString(detail.StatusDescription) + complaint.ProcessCode = lzUtils.StringToNullString(detail.ProcessCode) + complaint.ProcessMessage = lzUtils.StringToNullString(detail.ProcessMessage) + complaint.ProcessRemark = lzUtils.StringToNullString(detail.ProcessRemark) + complaint.GmtRiskFinishTime = s.parseTime(detail.GmtRiskFinishTime) + complaint.ComplainUrl = lzUtils.StringToNullString(detail.ComplainUrl) + + // 处理图片列表 + if len(detail.ProcessImgUrlList) > 0 { + imgJson, _ := json.Marshal(detail.ProcessImgUrlList) + complaint.ProcessImgUrlList = lzUtils.StringToNullString(string(imgJson)) + } + if len(detail.CertifyInfo) > 0 { + certifyJson, _ := json.Marshal(detail.CertifyInfo) + complaint.CertifyInfo = lzUtils.StringToNullString(string(certifyJson)) + } + + // 更新支付宝投诉表 + if err := s.ComplaintAlipayModel.UpdateWithVersion(transCtx, session, complaint); err != nil { + return errors.Wrapf(err, "更新支付宝投诉表失败, task_id: %s", complaint.TaskId) + } + + // 3. 更新交易信息:先删除旧的,再插入新的 + oldTrades, _ := s.ComplaintAlipayTradeModel.FindAll(transCtx, + s.ComplaintAlipayTradeModel.SelectBuilder(). + Where("complaint_id = ? AND del_state = ?", complaint.Id, 0), "") + for _, oldTrade := range oldTrades { + oldTrade.DelState = 1 + oldTrade.DeleteTime = lzUtils.TimeToNullTime(time.Now()) + s.ComplaintAlipayTradeModel.UpdateWithVersion(transCtx, session, oldTrade) + } + + // 插入新的交易信息 + for _, tradeInfo := range detail.ComplaintTradeInfoList { + trade := &model.ComplaintAlipayTrade{ + Id: uuid.NewString(), + ComplaintId: complaint.Id, + AlipayTradeId: lzUtils.Int64ToNullInt64(tradeInfo.Id), + AlipayComplaintRecordId: lzUtils.Int64ToNullInt64(tradeInfo.ComplaintRecordId), + TradeNo: lzUtils.StringToNullString(tradeInfo.TradeNo), + OutNo: lzUtils.StringToNullString(tradeInfo.OutNo), + GmtTrade: s.parseTime(tradeInfo.GmtTrade), + GmtRefund: s.parseTime(tradeInfo.GmtRefund), + Status: lzUtils.StringToNullString(tradeInfo.Status), + StatusDescription: lzUtils.StringToNullString(tradeInfo.StatusDescription), + } + if tradeInfo.Amount != "" { + amount, err := s.parseDecimal(tradeInfo.Amount) + if err == nil { + trade.Amount = lzUtils.Float64ToNullFloat64(amount) + } + } + + if _, err := s.ComplaintAlipayTradeModel.Insert(transCtx, session, trade); err != nil { + return errors.Wrapf(err, "插入投诉交易信息失败, task_id: %s", complaint.TaskId) + } + } + + return nil + }) +} + +// SaveComplaint 保存投诉数据到数据库(用于定时任务同步) +func (s *AlipayComplaintService) SaveComplaint(ctx context.Context, complaintInfo *alipay.SecurityRiskComplaintInfo) error { + // 检查投诉是否已存在(通过 task_id) + existing, err := s.ComplaintAlipayModel.FindOneByTaskId(ctx, complaintInfo.TaskId) + if err != nil && err != model.ErrNotFound { + return errors.Wrapf(err, "查询投诉失败, task_id: %s", complaintInfo.TaskId) + } + + return s.ComplaintAlipayModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error { + var complaintRecord *model.ComplaintAlipay + var complaintMain *model.ComplaintMain + var isUpdate bool + + if existing != nil { + // 更新现有投诉 + complaintRecord = existing + isUpdate = true + // 查找主表记录 + if complaintRecord.ComplaintId != "" { + complaintMain, err = s.ComplaintMainModel.FindOne(transCtx, complaintRecord.ComplaintId) + if err != nil { + return errors.Wrapf(err, "查找投诉主表失败, complaint_id: %s", complaintRecord.ComplaintId) + } + } + } else { + // 创建新投诉 + complaintRecord = &model.ComplaintAlipay{ + Id: uuid.NewString(), + } + isUpdate = false + } + + // 1. 创建或更新投诉主表记录 + if complaintMain == nil { + // 创建新的主表记录 + complaintMain = &model.ComplaintMain{ + Id: uuid.NewString(), + Type: "alipay", + Name: lzUtils.StringToNullString(complaintInfo.OppositeName), + Contact: lzUtils.StringToNullString(complaintInfo.Contact), + Content: lzUtils.StringToNullString(complaintInfo.ComplainContent), + Status: lzUtils.StringToNullString(complaintInfo.Status), + StatusDescription: lzUtils.StringToNullString(complaintInfo.StatusDescription), + } + // 如果有交易单号,尝试关联订单 + if complaintInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, complaintInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + } + } + // 如果主表没有订单ID,尝试从交易信息中查找 + if !complaintMain.OrderId.Valid && len(complaintInfo.ComplaintTradeInfoList) > 0 { + for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList { + if tradeInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + break + } + } + // 也可以尝试通过 out_no(商家订单号)查找 + if tradeInfo.OutNo != "" { + order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo) + if err == nil && order != nil { + complaintMain.OrderId = lzUtils.StringToNullString(order.Id) + break + } + } + } + } + if _, err := s.ComplaintMainModel.Insert(transCtx, session, complaintMain); err != nil { + return errors.Wrapf(err, "创建投诉主表失败, task_id: %s", complaintInfo.TaskId) + } + complaintRecord.ComplaintId = complaintMain.Id + } else { + // 更新主表信息 + complaintMain.Name = lzUtils.StringToNullString(complaintInfo.OppositeName) + complaintMain.Contact = lzUtils.StringToNullString(complaintInfo.Contact) + complaintMain.Content = lzUtils.StringToNullString(complaintInfo.ComplainContent) + complaintMain.Status = lzUtils.StringToNullString(complaintInfo.Status) + complaintMain.StatusDescription = lzUtils.StringToNullString(complaintInfo.StatusDescription) + // 如果主表没有订单ID,尝试关联订单 + if !complaintMain.OrderId.Valid { + if complaintInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, complaintInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + } + } + // 尝试从交易信息中查找 + if !complaintMain.OrderId.Valid && len(complaintInfo.ComplaintTradeInfoList) > 0 { + for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList { + if tradeInfo.TradeNo != "" { + orderId := s.findOrderByPlatformOrderId(transCtx, tradeInfo.TradeNo) + if orderId != "" { + complaintMain.OrderId = lzUtils.StringToNullString(orderId) + break + } + } + if tradeInfo.OutNo != "" { + order, err := s.OrderModel.FindOneByOrderNo(transCtx, tradeInfo.OutNo) + if err == nil && order != nil { + complaintMain.OrderId = lzUtils.StringToNullString(order.Id) + break + } + } + } + } + } + if err := s.ComplaintMainModel.UpdateWithVersion(transCtx, session, complaintMain); err != nil { + return errors.Wrapf(err, "更新投诉主表失败, task_id: %s", complaintInfo.TaskId) + } + } + + // 2. 填充支付宝投诉表数据 + complaintRecord.AlipayId = complaintInfo.Id + complaintRecord.TaskId = complaintInfo.TaskId + complaintRecord.OppositePid = lzUtils.StringToNullString(complaintInfo.OppositePid) + complaintRecord.OppositeName = lzUtils.StringToNullString(complaintInfo.OppositeName) + if complaintInfo.ComplainAmount != "" { + amount, err := s.parseDecimal(complaintInfo.ComplainAmount) + if err == nil { + complaintRecord.ComplainAmount = lzUtils.Float64ToNullFloat64(amount) + } + } + complaintRecord.GmtComplain = s.parseTime(complaintInfo.GmtComplain) + complaintRecord.GmtProcess = s.parseTime(complaintInfo.GmtProcess) + complaintRecord.GmtOverdue = s.parseTime(complaintInfo.GmtOverdue) + complaintRecord.ComplainContent = lzUtils.StringToNullString(complaintInfo.ComplainContent) + complaintRecord.TradeNo = lzUtils.StringToNullString(complaintInfo.TradeNo) + complaintRecord.Status = lzUtils.StringToNullString(complaintInfo.Status) + complaintRecord.StatusDescription = lzUtils.StringToNullString(complaintInfo.StatusDescription) + complaintRecord.ProcessCode = lzUtils.StringToNullString(complaintInfo.ProcessCode) + complaintRecord.ProcessMessage = lzUtils.StringToNullString(complaintInfo.ProcessMessage) + complaintRecord.ProcessRemark = lzUtils.StringToNullString(complaintInfo.ProcessRemark) + complaintRecord.GmtRiskFinishTime = s.parseTime(complaintInfo.GmtRiskFinishTime) + complaintRecord.ComplainUrl = lzUtils.StringToNullString(complaintInfo.ComplainUrl) + + // 处理图片列表(转换为JSON字符串) + if len(complaintInfo.ProcessImgUrlList) > 0 { + imgJson, _ := json.Marshal(complaintInfo.ProcessImgUrlList) + complaintRecord.ProcessImgUrlList = lzUtils.StringToNullString(string(imgJson)) + } + if len(complaintInfo.CertifyInfo) > 0 { + certifyJson, _ := json.Marshal(complaintInfo.CertifyInfo) + complaintRecord.CertifyInfo = lzUtils.StringToNullString(string(certifyJson)) + } + + // 保存或更新支付宝投诉表 + if isUpdate { + if err := s.ComplaintAlipayModel.UpdateWithVersion(transCtx, session, complaintRecord); err != nil { + return errors.Wrapf(err, "更新支付宝投诉表失败, task_id: %s", complaintInfo.TaskId) + } + } else { + if _, err := s.ComplaintAlipayModel.Insert(transCtx, session, complaintRecord); err != nil { + return errors.Wrapf(err, "插入支付宝投诉表失败, task_id: %s", complaintInfo.TaskId) + } + } + + // 3. 保存交易信息 + if len(complaintInfo.ComplaintTradeInfoList) > 0 { + // 先删除旧的交易信息(如果存在) + if isUpdate { + oldTrades, _ := s.ComplaintAlipayTradeModel.FindAll(transCtx, + s.ComplaintAlipayTradeModel.SelectBuilder(). + Where("complaint_id = ? AND del_state = ?", complaintRecord.Id, 0), "") + for _, oldTrade := range oldTrades { + oldTrade.DelState = 1 + oldTrade.DeleteTime = lzUtils.TimeToNullTime(time.Now()) + s.ComplaintAlipayTradeModel.UpdateWithVersion(transCtx, session, oldTrade) + } + } + + // 插入新的交易信息 + for _, tradeInfo := range complaintInfo.ComplaintTradeInfoList { + trade := &model.ComplaintAlipayTrade{ + Id: uuid.NewString(), + ComplaintId: complaintRecord.Id, + AlipayTradeId: lzUtils.Int64ToNullInt64(tradeInfo.Id), + AlipayComplaintRecordId: lzUtils.Int64ToNullInt64(tradeInfo.ComplaintRecordId), + TradeNo: lzUtils.StringToNullString(tradeInfo.TradeNo), + OutNo: lzUtils.StringToNullString(tradeInfo.OutNo), + GmtTrade: s.parseTime(tradeInfo.GmtTrade), + GmtRefund: s.parseTime(tradeInfo.GmtRefund), + Status: lzUtils.StringToNullString(tradeInfo.Status), + StatusDescription: lzUtils.StringToNullString(tradeInfo.StatusDescription), + } + if tradeInfo.Amount != "" { + amount, err := s.parseDecimal(tradeInfo.Amount) + if err == nil { + trade.Amount = lzUtils.Float64ToNullFloat64(amount) + } + } + + if _, err := s.ComplaintAlipayTradeModel.Insert(transCtx, session, trade); err != nil { + return errors.Wrapf(err, "插入投诉交易信息失败, task_id: %s", complaintInfo.TaskId) + } + } + } + + return nil + }) +} + +// GetLatestComplainTime 查询数据库中最新投诉的投诉时间 +func (s *AlipayComplaintService) GetLatestComplainTime(ctx context.Context) (time.Time, error) { + // 从支付宝投诉表查询最新投诉时间(因为主表没有 gmt_complain 字段) + builder := s.ComplaintAlipayModel.SelectBuilder(). + Where("del_state = ?", 0). + OrderBy("gmt_complain DESC"). + Limit(1) + + complaints, err := s.ComplaintAlipayModel.FindAll(ctx, builder, "") + if err != nil { + return time.Time{}, errors.Wrapf(err, "查询最新投诉时间失败") + } + + if len(complaints) == 0 { + return time.Time{}, nil // 数据库为空 + } + + // 返回最新投诉的投诉时间 + if complaints[0].GmtComplain.Valid { + return complaints[0].GmtComplain.Time, nil + } + + return time.Time{}, nil +} + +// parseTime 解析时间字符串 +func (s *AlipayComplaintService) parseTime(timeStr string) sql.NullTime { + if timeStr == "" { + return sql.NullTime{Valid: false} + } + + // 尝试多种时间格式 + formats := []string{ + "2006-01-02 15:04:05", + "2006-01-02T15:04:05", + "2006-01-02", + } + + for _, format := range formats { + if t, err := time.Parse(format, timeStr); err == nil { + return sql.NullTime{Time: t, Valid: true} + } + } + + return sql.NullTime{Valid: false} +} + +// parseDecimal 解析金额字符串 +func (s *AlipayComplaintService) parseDecimal(amountStr string) (float64, error) { + if amountStr == "" { + return 0, nil + } + // 使用 fmt.Sscanf 解析 + var amount float64 + _, err := fmt.Sscanf(amountStr, "%f", &amount) + return amount, err +} + +// findOrderByPlatformOrderId 根据支付宝交易单号查找订单ID +func (s *AlipayComplaintService) findOrderByPlatformOrderId(ctx context.Context, platformOrderId string) string { + if platformOrderId == "" { + return "" + } + + // 通过 PlatformOrderId 查找订单 + builder := s.OrderModel.SelectBuilder(). + Where("platform_order_id = ? AND del_state = ?", platformOrderId, 0). + Limit(1) + + orders, err := s.OrderModel.FindAll(ctx, builder, "") + if err != nil || len(orders) == 0 { + return "" + } + + return orders[0].Id +} diff --git a/app/main/api/internal/service/alipayService.go b/app/main/api/internal/service/alipayService.go index 722796e..81ebf41 100644 --- a/app/main/api/internal/service/alipayService.go +++ b/app/main/api/internal/service/alipayService.go @@ -63,7 +63,6 @@ func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, out NotifyURL: a.config.NotifyUrl, // 异步回调通知地址 }, } - // 获取APP支付字符串,这里会签名 payStr, err := client.TradeAppPay(p) if err != nil { diff --git a/app/main/api/internal/service/apirequestService.go b/app/main/api/internal/service/apirequestService.go index b6d9eb6..5afeb61 100644 --- a/app/main/api/internal/service/apirequestService.go +++ b/app/main/api/internal/service/apirequestService.go @@ -33,19 +33,22 @@ func generateAuthDateRange() string { } type ApiRequestService struct { - config config.Config - featureModel model.FeatureModel - productFeatureModel model.ProductFeatureModel - tianyuanapi *tianyuanapi.Client + config config.Config + featureModel model.FeatureModel + productFeatureModel model.ProductFeatureModel + userFeatureWhitelistModel model.UserFeatureWhitelistModel + whitelistService *WhitelistService + tianyuanapi *tianyuanapi.Client } // NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService -func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService { +func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, userFeatureWhitelistModel model.UserFeatureWhitelistModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService { return &ApiRequestService{ - config: c, - featureModel: featureModel, - productFeatureModel: productFeatureModel, - tianyuanapi: tianyuanapi, + config: c, + featureModel: featureModel, + productFeatureModel: productFeatureModel, + userFeatureWhitelistModel: userFeatureWhitelistModel, + tianyuanapi: tianyuanapi, } } @@ -61,6 +64,13 @@ type APIResponseData struct { func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) { var ctx, cancel = context.WithCancel(context.Background()) defer cancel() + + // 从params中提取id_card,用于白名单检查 + idCard := gjson.GetBytes(params, "id_card").String() + + // 查询白名单(如果提供了id_card),集中由 WhitelistService 处理 + whitelistedFeatureApiIds, _ := a.whitelistService.GetWhitelistedFeatureApisByIdCard(ctx, idCard) + build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{ "product_id": productID, }) @@ -109,6 +119,18 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] Success: false, } timestamp := time.Now().Format("2006-01-02 15:04:05") + + // 检查是否在白名单中 + if whitelistedFeatureApiIds[feature.ApiId] { + // 在白名单中,返回空结构(data 为 null),保持与正常返回结构一致 + // 直接设置 data 为 null 的 JSON 字节 + result.Data = []byte("null") + result.Success = true + result.Timestamp = timestamp + resultsCh <- result + return + } + var ( resp json.RawMessage preprocessErr error diff --git a/app/main/api/internal/service/authorizationService.go b/app/main/api/internal/service/authorizationService.go index 11b5104..f89a1da 100644 --- a/app/main/api/internal/service/authorizationService.go +++ b/app/main/api/internal/service/authorizationService.go @@ -173,7 +173,7 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{ 用户声明与承诺: 本人在授权签署前,已通过实名认证及动态验证码验证(或其他身份验证手段),确认本授权行为为本人真实意思表示,平台已履行身份验证义务。 -本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人信用评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。 +本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人风险评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。 若用户冒名签署或提供虚假信息,由用户自行承担全部法律责任,平台不承担任何后果。 特别提示: diff --git a/app/main/api/internal/service/whitelistService.go b/app/main/api/internal/service/whitelistService.go new file mode 100644 index 0000000..110b551 --- /dev/null +++ b/app/main/api/internal/service/whitelistService.go @@ -0,0 +1,560 @@ +package service + +import ( + "context" + "database/sql" + "encoding/hex" + "encoding/json" + "strings" + + "ycc-server/app/main/api/internal/config" + "ycc-server/app/main/model" + "ycc-server/pkg/lzkit/crypto" + "ycc-server/pkg/lzkit/lzUtils" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// WhitelistService 白名单领域服务,集中处理白名单相关的业务逻辑 +type WhitelistService struct { + config config.Config + UserFeatureWhitelistModel model.UserFeatureWhitelistModel + WhitelistOrderModel model.WhitelistOrderModel + WhitelistOrderItemModel model.WhitelistOrderItemModel + QueryModel model.QueryModel + FeatureModel model.FeatureModel +} + +// NewWhitelistService 创建白名单服务 +func NewWhitelistService( + c config.Config, + userFeatureWhitelistModel model.UserFeatureWhitelistModel, + whitelistOrderModel model.WhitelistOrderModel, + whitelistOrderItemModel model.WhitelistOrderItemModel, + queryModel model.QueryModel, + featureModel model.FeatureModel, +) *WhitelistService { + return &WhitelistService{ + config: c, + UserFeatureWhitelistModel: userFeatureWhitelistModel, + WhitelistOrderModel: whitelistOrderModel, + WhitelistOrderItemModel: whitelistOrderItemModel, + QueryModel: queryModel, + FeatureModel: featureModel, + } +} + +// EnsureFreeWhitelist 免费下架:如果还没有生效白名单,则创建一条免费白名单记录 +func (s *WhitelistService) EnsureFreeWhitelist( + ctx context.Context, + session sqlx.Session, + idCard string, + feature *model.Feature, + userId string, + orderId string, +) error { + // 检查是否已存在生效白名单 + builder := s.UserFeatureWhitelistModel.SelectBuilder(). + Where("id_card = ? AND feature_id = ?", idCard, feature.Id) + records, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") + if err != nil { + return errors.Wrap(err, "查询白名单记录失败") + } + for _, r := range records { + if r.Status == 1 { + // 已经下架,直接返回 + return nil + } + } + + wl := &model.UserFeatureWhitelist{ + Id: uuid.NewString(), + IdCard: idCard, + FeatureId: feature.Id, + FeatureApiId: feature.ApiId, + UserId: userId, + OrderId: lzUtils.StringToNullString(orderId), + WhitelistOrderId: lzUtils.StringToNullString(""), + Amount: 0, + Status: 1, + } + + _, err = s.UserFeatureWhitelistModel.Insert(ctx, session, wl) + if err != nil { + return errors.Wrap(err, "创建免费白名单记录失败") + } + return nil +} + +// CreateWhitelistByPaidOrder 根据已支付的白名单订单,创建对应的白名单记录 +func (s *WhitelistService) CreateWhitelistByPaidOrder( + ctx context.Context, + session sqlx.Session, + order *model.Order, + whitelistOrder *model.WhitelistOrder, +) error { + if whitelistOrder.Status != 2 { + // 只处理已支付状态 + return nil + } + + itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). + Where("order_id = ?", whitelistOrder.Id) + items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") + if err != nil { + return errors.Wrap(err, "查询白名单订单明细失败") + } + + for _, item := range items { + wl := &model.UserFeatureWhitelist{ + Id: uuid.NewString(), + IdCard: whitelistOrder.IdCard, + FeatureId: item.FeatureId, + FeatureApiId: item.FeatureApiId, + UserId: whitelistOrder.UserId, + OrderId: lzUtils.StringToNullString(order.Id), + WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id), + Amount: item.Price, + Status: 1, + } + if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { + return errors.Wrap(err, "创建白名单记录失败") + } + } + + return nil +} + +// GetWhitelistedFeatureApisByIdCard 获取某个身份证号已下架的 feature_api_id 集合 +func (s *WhitelistService) GetWhitelistedFeatureApisByIdCard( + ctx context.Context, + idCard string, +) (map[string]bool, error) { + result := make(map[string]bool) + if idCard == "" { + return result, nil + } + + builder := s.UserFeatureWhitelistModel.SelectBuilder(). + Where("id_card = ? AND status = ?", idCard, 1) + list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") + if err != nil { + return nil, errors.Wrap(err, "查询白名单失败") + } + + for _, wl := range list { + result[wl.FeatureApiId] = true + } + return result, nil +} + +// CheckWhitelistExists 检查指定身份证号和模块是否已有生效的白名单记录 +func (s *WhitelistService) CheckWhitelistExists( + ctx context.Context, + idCard string, + featureId string, +) (bool, error) { + if idCard == "" || featureId == "" { + return false, nil + } + + builder := s.UserFeatureWhitelistModel.SelectBuilder(). + Where("id_card = ? AND feature_id = ? AND status = ?", idCard, featureId, 1) + list, err := s.UserFeatureWhitelistModel.FindAll(ctx, builder, "") + if err != nil { + return false, errors.Wrap(err, "查询白名单记录失败") + } + + return len(list) > 0, nil +} + +// ProcessOfflineFeature 统一下架处理:处理免费下架或检查付费下架 +// 返回:needPay(是否需要支付), amount(金额), whitelistCreated(是否已创建白名单) +func (s *WhitelistService) ProcessOfflineFeature( + ctx context.Context, + session sqlx.Session, + idCard string, + featureApiId string, + userId string, + orderId string, +) (needPay bool, amount float64, whitelistCreated bool, err error) { + // 1. 提取主模块ID并查询feature信息 + mainApiId := s.extractMainApiId(featureApiId) + feature, err := s.getFeatureByApiId(ctx, mainApiId) + if err != nil { + return false, 0, false, err + } + + // 2. 检查是否已有白名单 + exists, err := s.CheckWhitelistExists(ctx, idCard, feature.Id) + if err != nil { + return false, 0, false, err + } + if exists { + // 已有白名单,直接返回成功 + return false, 0, true, nil + } + + price := feature.WhitelistPrice + + // 3. 免费下架:直接创建白名单记录 + if price <= 0 { + if err := s.EnsureFreeWhitelist(ctx, session, idCard, feature, userId, orderId); err != nil { + return false, 0, false, err + } + return false, 0, true, nil + } + + // 4. 付费下架:检查是否已有支付成功的订单 + paidOrderId, err := s.findPaidWhitelistOrder(ctx, userId, idCard, feature.Id) + if err != nil { + return false, 0, false, err + } + + // 5. 如果已有支付成功订单,补创建白名单记录 + if paidOrderId != "" { + if err := s.createWhitelistFromPaidOrder(ctx, session, idCard, feature, userId, orderId, paidOrderId, price); err != nil { + return false, 0, false, err + } + return false, price, true, nil + } + + // 6. 需要支付 + return true, price, false, nil +} + +// extractMainApiId 提取主模块ID(去掉下划线后的部分) +func (s *WhitelistService) extractMainApiId(featureApiId string) string { + if idx := strings.Index(featureApiId, "_"); idx > 0 { + return featureApiId[:idx] + } + return featureApiId +} + +// getFeatureByApiId 根据API ID查询feature信息 +func (s *WhitelistService) getFeatureByApiId(ctx context.Context, apiId string) (*model.Feature, error) { + feature, err := s.FeatureModel.FindOneByApiId(ctx, apiId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrap(err, "模块不存在") + } + return nil, errors.Wrap(err, "查询模块信息失败") + } + return feature, nil +} + +// findPaidWhitelistOrder 查找已支付的白名单订单中是否包含指定feature +// 返回:paidOrderId(如果找到已支付订单),error +func (s *WhitelistService) findPaidWhitelistOrder( + ctx context.Context, + userId string, + idCard string, + featureId string, +) (string, error) { + orderBuilder := s.WhitelistOrderModel.SelectBuilder(). + Where("user_id = ? AND id_card = ? AND status = ?", userId, idCard, 2) // 2表示已支付 + orders, err := s.WhitelistOrderModel.FindAll(ctx, orderBuilder, "") + if err != nil { + return "", errors.Wrap(err, "查询白名单订单失败") + } + + // 查找已支付订单中是否包含该feature + for _, order := range orders { + itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). + Where("order_id = ? AND feature_id = ?", order.Id, featureId) + items, itemErr := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") + if itemErr != nil { + return "", errors.Wrap(itemErr, "查询白名单订单明细失败") + } + if len(items) > 0 { + return order.Id, nil + } + } + + return "", nil +} + +// createWhitelistFromPaidOrder 根据已支付订单创建白名单记录 +func (s *WhitelistService) createWhitelistFromPaidOrder( + ctx context.Context, + session sqlx.Session, + idCard string, + feature *model.Feature, + userId string, + orderId string, + paidOrderId string, + price float64, +) error { + wl := &model.UserFeatureWhitelist{ + Id: uuid.NewString(), + IdCard: idCard, + FeatureId: feature.Id, + FeatureApiId: feature.ApiId, + UserId: userId, + OrderId: lzUtils.StringToNullString(orderId), + WhitelistOrderId: lzUtils.StringToNullString(paidOrderId), + Amount: price, + Status: 1, // 生效 + } + if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { + return errors.Wrap(err, "根据已支付订单创建白名单记录失败") + } + return nil +} + +// DeleteFeatureFromQueryData 从报告数据中删除指定模块的数据 +// queryId: 查询记录ID(Query表的ID) +// featureApiId: 要删除的模块API标识 +func (s *WhitelistService) DeleteFeatureFromQueryData( + ctx context.Context, + session sqlx.Session, + queryId string, + featureApiId string, +) error { + // 1. 获取查询记录 + queryModel, err := s.getQueryModel(ctx, queryId) + if err != nil { + return err + } + if queryModel == nil { + // 报告不存在或数据为空,直接返回成功 + return nil + } + + // 2. 解密并解析报告数据 + mainApiId := s.extractMainApiId(featureApiId) + dataArray, key, err := s.decryptQueryData(queryModel) + if err != nil { + return err + } + + // 3. 清空对应模块的数据(将 data 字段设置为 null) + modifiedArray, hasModified := s.clearFeatureData(dataArray, mainApiId) + if !hasModified { + logx.Infof("删除报告数据:查询记录 %s 中未找到模块 %s 的数据,跳过删除", queryId, featureApiId) + return nil + } + + // 4. 重新加密并更新数据库 + if err := s.updateQueryData(ctx, session, queryModel, modifiedArray, key); err != nil { + return err + } + + logx.Infof("删除报告数据成功:查询记录 %s,模块 %s,已将对应模块的 data 字段设置为 null", queryId, featureApiId) + return nil +} + +// getQueryModel 获取查询记录,如果不存在或数据为空则返回nil +func (s *WhitelistService) getQueryModel(ctx context.Context, queryId string) (*model.Query, error) { + queryModel, err := s.QueryModel.FindOne(ctx, queryId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + logx.Infof("删除报告数据:查询记录 %s 不存在,跳过删除", queryId) + return nil, nil + } + return nil, errors.Wrap(err, "查询报告记录失败") + } + + // 如果报告数据为空,直接返回 + if !queryModel.QueryData.Valid || queryModel.QueryData.String == "" { + logx.Infof("删除报告数据:查询记录 %s 对应的报告数据为空,跳过删除", queryId) + return nil, nil + } + + return queryModel, nil +} + +// decryptQueryData 解密并解析报告数据 +func (s *WhitelistService) decryptQueryData(queryModel *model.Query) ([]map[string]interface{}, []byte, error) { + // 获取加密密钥 + secretKey := s.config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, nil, errors.Wrap(decodeErr, "获取AES密钥失败") + } + + // 解密报告数据 + decryptedData, decryptErr := crypto.AesDecrypt(queryModel.QueryData.String, key) + if decryptErr != nil { + return nil, nil, errors.Wrap(decryptErr, "解密报告数据失败") + } + + // 解析JSON数组 + var dataArray []map[string]interface{} + unmarshalErr := json.Unmarshal(decryptedData, &dataArray) + if unmarshalErr != nil { + return nil, nil, errors.Wrap(unmarshalErr, "解析报告数据失败") + } + + return dataArray, key, nil +} + +// clearFeatureData 清空指定模块的数据(将 data 字段设置为 null) +// 返回修改后的数组和是否进行了修改 +func (s *WhitelistService) clearFeatureData(dataArray []map[string]interface{}, mainApiId string) ([]map[string]interface{}, bool) { + modifiedArray := make([]map[string]interface{}, 0, len(dataArray)) + hasModified := false + + for _, item := range dataArray { + // 深拷贝 item,避免修改原数据 + newItem := make(map[string]interface{}) + for k, v := range item { + newItem[k] = v + } + + apiID, ok := item["apiID"].(string) + if !ok { + // 如果apiID不存在或类型不对,保留原样 + modifiedArray = append(modifiedArray, newItem) + continue + } + + // 提取主模块ID进行比较 + itemMainApiId := s.extractMainApiId(apiID) + + // 如果主模块ID匹配,将 data 字段设置为 null + if itemMainApiId == mainApiId { + newItem["data"] = nil + hasModified = true + } + + modifiedArray = append(modifiedArray, newItem) + } + + return modifiedArray, hasModified +} + +// updateQueryData 重新加密并更新数据库 +func (s *WhitelistService) updateQueryData( + ctx context.Context, + session sqlx.Session, + queryModel *model.Query, + filteredArray []map[string]interface{}, + key []byte, +) error { + // 重新序列化 + filteredBytes, marshalErr := json.Marshal(filteredArray) + if marshalErr != nil { + return errors.Wrap(marshalErr, "序列化过滤后的报告数据失败") + } + + // 重新加密 + encryptedData, encryptErr := crypto.AesEncrypt(filteredBytes, key) + if encryptErr != nil { + return errors.Wrap(encryptErr, "加密过滤后的报告数据失败") + } + + // 更新数据库 + queryModel.QueryData = sql.NullString{ + String: encryptedData, + Valid: true, + } + updateErr := s.QueryModel.UpdateWithVersion(ctx, session, queryModel) + if updateErr != nil { + return errors.Wrap(updateErr, "更新报告数据失败") + } + + return nil +} + +// CheckQueryDataContainsFeature 检查报告数据中是否包含指定的模块 +// 返回 true 表示包含该模块,false 表示不包含(已删除) +func (s *WhitelistService) CheckQueryDataContainsFeature( + ctx context.Context, + queryId string, + featureApiId string, +) (bool, error) { + // 1. 获取查询记录 + queryModel, err := s.getQueryModel(ctx, queryId) + if err != nil { + return false, err + } + if queryModel == nil { + // 报告不存在,认为数据已删除 + return false, nil + } + + // 2. 解密并解析报告数据 + mainApiId := s.extractMainApiId(featureApiId) + dataArray, _, err := s.decryptQueryData(queryModel) + if err != nil { + return false, err + } + + // 3. 检查数据中是否包含该模块(且 data 不为 null) + for _, item := range dataArray { + apiID, ok := item["apiID"].(string) + if !ok { + continue + } + // 提取主模块ID进行比较 + itemMainApiId := s.extractMainApiId(apiID) + if itemMainApiId == mainApiId { + // 找到了该模块,检查 data 字段 + dataValue, exists := item["data"] + if !exists || dataValue == nil { + // data 字段不存在或为 null,认为数据已删除 + return false, nil + } + // data 字段存在且不为 null,认为数据存在 + return true, nil + } + } + + // 未找到该模块的数据,说明已删除 + return false, nil +} + +// ProcessPaidWhitelistOrder 处理已支付的白名单订单:创建白名单记录并删除报告数据 +// order: 支付订单(Order表) +// whitelistOrder: 白名单订单(WhitelistOrder表) +func (s *WhitelistService) ProcessPaidWhitelistOrder( + ctx context.Context, + session sqlx.Session, + order *model.Order, + whitelistOrder *model.WhitelistOrder, +) error { + if whitelistOrder.Status != 2 { + // 只处理已支付状态 + return nil + } + + // 查询订单明细 + itemBuilder := s.WhitelistOrderItemModel.SelectBuilder(). + Where("order_id = ?", whitelistOrder.Id) + items, err := s.WhitelistOrderItemModel.FindAll(ctx, itemBuilder, "") + if err != nil { + return errors.Wrap(err, "查询白名单订单明细失败") + } + + // 为每个明细创建白名单记录并删除报告数据 + for _, item := range items { + // 创建白名单记录 + wl := &model.UserFeatureWhitelist{ + Id: uuid.NewString(), + IdCard: whitelistOrder.IdCard, + FeatureId: item.FeatureId, + FeatureApiId: item.FeatureApiId, + UserId: whitelistOrder.UserId, + OrderId: lzUtils.StringToNullString(""), // 查询订单ID,如果有的话会在后续步骤中设置 + WhitelistOrderId: lzUtils.StringToNullString(whitelistOrder.Id), + Amount: item.Price, + Status: 1, // 生效 + } + if _, err := s.UserFeatureWhitelistModel.Insert(ctx, session, wl); err != nil { + return errors.Wrap(err, "创建白名单记录失败") + } + + // 尝试删除报告数据 + // 注意:由于支付回调时可能不知道具体的查询订单ID,这里先尝试根据 id_card 查找 + // 如果找不到对应的报告,就跳过删除步骤(不影响主流程) + // 实际的报告数据删除应该在 OfflineFeature 接口中完成(如果提供了 orderId) + // 这里暂时不删除,因为无法确定是哪个具体的查询订单 + logx.Infof("白名单订单支付成功:订单 %s,模块 %s,已创建白名单记录。如需删除报告数据,请在 OfflineFeature 接口中提供查询订单ID", whitelistOrder.OrderNo, item.FeatureApiId) + } + + return nil +} diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index 3d9150b..bd77626 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -34,6 +34,11 @@ type ServiceContext struct { FeatureModel model.FeatureModel ProductFeatureModel model.ProductFeatureModel + // 白名单相关模型 + UserFeatureWhitelistModel model.UserFeatureWhitelistModel + WhitelistOrderModel model.WhitelistOrderModel + WhitelistOrderItemModel model.WhitelistOrderItemModel + // 订单相关模型 OrderModel model.OrderModel OrderRefundModel model.OrderRefundModel @@ -76,20 +81,28 @@ type ServiceContext struct { ExampleModel model.ExampleModel GlobalNotificationsModel model.GlobalNotificationsModel AuthorizationDocumentModel model.AuthorizationDocumentModel + AlipayFromCallbackModel model.AlipayFromCallbackModel + // 投诉系统模型 + ComplaintMainModel model.ComplaintMainModel + ComplaintAlipayModel model.ComplaintAlipayModel + ComplaintAlipayTradeModel model.ComplaintAlipayTradeModel + ComplaintManualModel model.ComplaintManualModel // 服务 - AlipayService *service.AliPayService - WechatPayService *service.WechatPayService - ApplePayService *service.ApplePayService - ApiRequestService *service.ApiRequestService - AsynqServer *asynq.Server - AsynqService *service.AsynqService - VerificationService *service.VerificationService - AgentService *service.AgentService - UserService *service.UserService - DictService *service.DictService - ImageService *service.ImageService - AuthorizationService *service.AuthorizationService + AlipayService *service.AliPayService + WechatPayService *service.WechatPayService + ApplePayService *service.ApplePayService + ApiRequestService *service.ApiRequestService + WhitelistService *service.WhitelistService + AsynqServer *asynq.Server + AsynqService *service.AsynqService + VerificationService *service.VerificationService + AgentService *service.AgentService + UserService *service.UserService + DictService *service.DictService + ImageService *service.ImageService + AuthorizationService *service.AuthorizationService + AlipayComplaintService *service.AlipayComplaintService } // NewServiceContext 创建服务上下文 @@ -115,6 +128,11 @@ func NewServiceContext(c config.Config) *ServiceContext { featureModel := model.NewFeatureModel(db, cacheConf) productFeatureModel := model.NewProductFeatureModel(db, cacheConf) + // ============================== 白名单相关模型 ============================== + userFeatureWhitelistModel := model.NewUserFeatureWhitelistModel(db, cacheConf) + whitelistOrderModel := model.NewWhitelistOrderModel(db, cacheConf) + whitelistOrderItemModel := model.NewWhitelistOrderItemModel(db, cacheConf) + // ============================== 订单相关模型 ============================== orderModel := model.NewOrderModel(db, cacheConf) queryModel := model.NewQueryModel(db, cacheConf) @@ -156,6 +174,12 @@ func NewServiceContext(c config.Config) *ServiceContext { exampleModel := model.NewExampleModel(db, cacheConf) globalNotificationsModel := model.NewGlobalNotificationsModel(db, cacheConf) authorizationDocumentModel := model.NewAuthorizationDocumentModel(db, cacheConf) + alipayFromCallbackModel := model.NewAlipayFromCallbackModel(db, cacheConf) + // 投诉系统模型 + complaintMainModel := model.NewComplaintMainModel(db, cacheConf) + complaintAlipayModel := model.NewComplaintAlipayModel(db, cacheConf) + complaintAlipayTradeModel := model.NewComplaintAlipayTradeModel(db, cacheConf) + complaintManualModel := model.NewComplaintManualModel(db, cacheConf) // ============================== 第三方服务初始化 ============================== tianyuanapi, err := tianyuanapi.NewClient(tianyuanapi.Config{ @@ -172,7 +196,8 @@ func NewServiceContext(c config.Config) *ServiceContext { alipayService := service.NewAliPayService(c) wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey) applePayService := service.NewApplePayService(c) - apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi) + apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, userFeatureWhitelistModel, tianyuanapi) + whitelistService := service.NewWhitelistService(c, userFeatureWhitelistModel, whitelistOrderModel, whitelistOrderItemModel, queryModel, featureModel) verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService) asynqService := service.NewAsynqService(c) agentService := service.NewAgentService(c, orderModel, agentModel, agentWalletModel, @@ -183,6 +208,7 @@ func NewServiceContext(c config.Config) *ServiceContext { dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel) imageService := service.NewImageService() authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel) + alipayComplaintService := service.NewAlipayComplaintService(c, alipayService.AlipayClient, complaintMainModel, complaintAlipayModel, complaintAlipayTradeModel, orderModel) // ============================== 异步任务服务 ============================== asynqServer := asynq.NewServer( @@ -214,6 +240,11 @@ func NewServiceContext(c config.Config) *ServiceContext { FeatureModel: featureModel, ProductFeatureModel: productFeatureModel, + // 白名单相关模型 + UserFeatureWhitelistModel: userFeatureWhitelistModel, + WhitelistOrderModel: whitelistOrderModel, + WhitelistOrderItemModel: whitelistOrderItemModel, + // 订单相关模型 OrderModel: orderModel, QueryModel: queryModel, @@ -256,20 +287,28 @@ func NewServiceContext(c config.Config) *ServiceContext { ExampleModel: exampleModel, GlobalNotificationsModel: globalNotificationsModel, AuthorizationDocumentModel: authorizationDocumentModel, + AlipayFromCallbackModel: alipayFromCallbackModel, + // 投诉系统模型 + ComplaintMainModel: complaintMainModel, + ComplaintAlipayModel: complaintAlipayModel, + ComplaintAlipayTradeModel: complaintAlipayTradeModel, + ComplaintManualModel: complaintManualModel, // 服务 - AlipayService: alipayService, - WechatPayService: wechatPayService, - ApplePayService: applePayService, - ApiRequestService: apiRequestService, - AsynqServer: asynqServer, - AsynqService: asynqService, - VerificationService: verificationService, - AgentService: agentService, - UserService: userService, - DictService: dictService, - ImageService: imageService, - AuthorizationService: authorizationService, + AlipayService: alipayService, + WechatPayService: wechatPayService, + ApplePayService: applePayService, + ApiRequestService: apiRequestService, + WhitelistService: whitelistService, + AsynqServer: asynqServer, + AsynqService: asynqService, + VerificationService: verificationService, + AgentService: agentService, + UserService: userService, + DictService: dictService, + ImageService: imageService, + AuthorizationService: authorizationService, + AlipayComplaintService: alipayComplaintService, } } diff --git a/app/main/api/internal/types/adminagent.go b/app/main/api/internal/types/adminagent.go index 35e185e..acfd98c 100644 --- a/app/main/api/internal/types/adminagent.go +++ b/app/main/api/internal/types/adminagent.go @@ -90,6 +90,7 @@ type AdminGetAgentOrderListReq struct { AgentId *string `form:"agent_id,optional"` // 代理ID(可选) OrderId *string `form:"order_id,optional"` // 订单ID(可选) ProcessStatus *int64 `form:"process_status,optional"` // 处理状态(可选) + OrderStatus *string `form:"order_status,optional"` // 订单状态(可选):pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 } type AdminGetAgentOrderListResp struct { @@ -127,6 +128,7 @@ type AdminGetAgentRebateListReq struct { AgentId *string `form:"agent_id,optional"` // 代理ID(可选) SourceAgentId *string `form:"source_agent_id,optional"` // 来源代理ID(可选) RebateType *int64 `form:"rebate_type,optional"` // 返佣类型(可选) + Status *int64 `form:"status,optional"` // 状态(可选):1=已发放,2=已冻结,3=已取消(已退款) } type AdminGetAgentRebateListResp struct { diff --git a/app/main/api/internal/types/admincomplaint.go b/app/main/api/internal/types/admincomplaint.go new file mode 100644 index 0000000..b919118 --- /dev/null +++ b/app/main/api/internal/types/admincomplaint.go @@ -0,0 +1,63 @@ +// Code generated by goctl. DO NOT EDIT. +package types + +type AdminGetComplaintDetailReq struct { + Id string `path:"id"` // 投诉ID +} + +type AdminGetComplaintDetailResp struct { + Id string `json:"id"` // 投诉ID + Type string `json:"type"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + OrderId string `json:"order_id"` // 关联订单ID + Name string `json:"name"` // 投诉人姓名 + Contact string `json:"contact"` // 联系方式 + Content string `json:"content"` // 投诉内容 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 状态描述 + Remark string `json:"remark"` // 处理备注 + HandlerId string `json:"handler_id"` // 处理人ID + HandleTime string `json:"handle_time"` // 处理时间 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + AlipayComplaint *AlipayComplaintDetail `json:"alipay_complaint,optional"` // 支付宝投诉详情 + ManualComplaint *ManualComplaintDetail `json:"manual_complaint,optional"` // 主动投诉详情 +} + +type AdminGetComplaintListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Type string `form:"type,optional"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + Status string `form:"status,optional"` // 投诉状态:pending-待处理,processing-处理中,resolved-已解决,closed-已关闭 + Name string `form:"name,optional"` // 投诉人姓名 + Contact string `form:"contact,optional"` // 联系方式 + OrderId string `form:"order_id,optional"` // 关联订单ID + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + HandleTimeStart string `form:"handle_time_start,optional"` // 处理时间开始 + HandleTimeEnd string `form:"handle_time_end,optional"` // 处理时间结束 +} + +type AdminGetComplaintListResp struct { + Total int64 `json:"total"` // 总数 + Items []ComplaintListItem `json:"items"` // 列表 +} + +type AdminUpdateComplaintRemarkReq struct { + Id string `path:"id"` // 投诉ID + Remark string `json:"remark"` // 处理备注 +} + +type AdminUpdateComplaintRemarkResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateComplaintStatusReq struct { + Id string `path:"id"` // 投诉ID + Status string `json:"status"` // 投诉状态:pending-待处理,processing-处理中,resolved-已解决,closed-已关闭 + StatusDescription string `json:"status_description,optional"` // 状态描述 + HandlerId string `json:"handler_id,optional"` // 处理人ID +} + +type AdminUpdateComplaintStatusResp struct { + Success bool `json:"success"` // 是否成功 +} diff --git a/app/main/api/internal/types/admindashboard.go b/app/main/api/internal/types/admindashboard.go new file mode 100644 index 0000000..81cbe09 --- /dev/null +++ b/app/main/api/internal/types/admindashboard.go @@ -0,0 +1,11 @@ +// Code generated by goctl. DO NOT EDIT. +package types + +type AdminGetDashboardStatisticsResp struct { + OrderStats AdminOrderStatistics `json:"order_stats"` + RevenueStats AdminRevenueStatistics `json:"revenue_stats"` + AgentStats AdminAgentStatistics `json:"agent_stats"` + ProfitStats AdminProfitStatistics `json:"profit_stats"` + OrderTrend []AdminTrendData `json:"order_trend"` + RevenueTrend []AdminTrendData `json:"revenue_trend"` +} diff --git a/app/main/api/internal/types/adminfeature.go b/app/main/api/internal/types/adminfeature.go index 7b479e8..b3d2659 100644 --- a/app/main/api/internal/types/adminfeature.go +++ b/app/main/api/internal/types/adminfeature.go @@ -11,8 +11,9 @@ type AdminConfigFeatureExampleResp struct { } type AdminCreateFeatureReq struct { - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元) } type AdminCreateFeatureResp struct { @@ -32,11 +33,12 @@ type AdminGetFeatureDetailReq struct { } type AdminGetFeatureDetailResp struct { - Id string `json:"id"` // 功能ID - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 + Id string `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元) + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 } type AdminGetFeatureExampleReq struct { @@ -65,9 +67,10 @@ type AdminGetFeatureListResp struct { } type AdminUpdateFeatureReq struct { - Id string `path:"id"` // 功能ID - ApiId *string `json:"api_id,optional"` // API标识 - Name *string `json:"name,optional"` // 描述 + Id string `path:"id"` // 功能ID + ApiId *string `json:"api_id,optional"` // API标识 + Name *string `json:"name,optional"` // 描述 + WhitelistPrice *float64 `json:"whitelist_price,optional"` // 白名单屏蔽价格(单位:元) } type AdminUpdateFeatureResp struct { diff --git a/app/main/api/internal/types/agent.go b/app/main/api/internal/types/agent.go index 3e72729..11fa4dc 100644 --- a/app/main/api/internal/types/agent.go +++ b/app/main/api/internal/types/agent.go @@ -54,9 +54,12 @@ type ApplyUpgradeResp struct { } type ApplyWithdrawalReq struct { - Amount float64 `json:"amount"` // 提现金额 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 + Amount float64 `json:"amount"` // 提现金额 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no,optional"` // 银行卡号(银行卡提现必填) + BankName string `json:"bank_name,optional"` // 开户行名称(银行卡提现必填) } type ApplyWithdrawalResp struct { @@ -64,11 +67,36 @@ type ApplyWithdrawalResp struct { WithdrawalNo string `json:"withdrawal_no"` // 提现单号 } +type CheckFeatureWhitelistStatusReq struct { + IdCard string `form:"id_card"` // 身份证号 + FeatureApiId string `form:"feature_api_id"` // Feature的API标识 + QueryId string `form:"query_id,optional"` // 查询记录ID(可选,用于检查报告数据是否已删除) +} + +type CheckFeatureWhitelistStatusResp struct { + IsWhitelisted bool `json:"is_whitelisted"` // 是否在白名单中 + WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元),如果为0表示不支持下架 + FeatureId string `json:"feature_id"` // Feature的UUID + DataDeleted bool `json:"data_deleted"` // 报告数据是否已删除(仅当提供了query_id时有效) +} + type ConversionRateResp struct { MyConversionRate ConversionRateData `json:"my_conversion_rate"` // 我的转化率 SubordinateConversionRate ConversionRateData `json:"subordinate_conversion_rate"` // 我的下级转化率 } +type CreateWhitelistOrderReq struct { + IdCard string `json:"id_card"` // 身份证号(查询对象标识) + FeatureIds []string `json:"feature_ids"` // 要屏蔽的feature ID列表 + OrderId string `json:"order_id,optional"` // 关联的查询订单ID(可选) +} + +type CreateWhitelistOrderResp struct { + OrderId string `json:"order_id"` // 订单ID + OrderNo string `json:"order_no"` // 订单号 + TotalAmount float64 `json:"total_amount"` // 总金额 +} + type DeleteInviteCodeReq struct { Id string `json:"id"` // 邀请码ID } @@ -116,6 +144,18 @@ type GetInviteLinkResp struct { InviteLink string `json:"invite_link"` // 邀请链接 } +type GetLastWithdrawalInfoReq struct { + WithdrawalType int64 `form:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 +} + +type GetLastWithdrawalInfoResp struct { + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户行名称 +} + type GetLevelPrivilegeResp struct { Levels []LevelPrivilegeItem `json:"levels"` UpgradeToGoldFee float64 `json:"upgrade_to_gold_fee"` @@ -162,16 +202,18 @@ type GetRebateListResp struct { } type GetRevenueInfoResp struct { - Balance float64 `json:"balance"` // 可用余额 - FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 - TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益) - WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现 - CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金) - CommissionToday float64 `json:"commission_today"` // 佣金今日收益 - CommissionMonth float64 `json:"commission_month"` // 佣金本月收益 - RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣) - RebateToday float64 `json:"rebate_today"` // 返佣今日收益 - RebateMonth float64 `json:"rebate_month"` // 返佣本月收益 + Balance float64 `json:"balance"` // 可用余额 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + TotalEarnings float64 `json:"total_earnings"` // 累计收益(钱包总收益) + WithdrawnAmount float64 `json:"withdrawn_amount"` // 累计提现 + CommissionTotal float64 `json:"commission_total"` // 佣金累计总收益(推广订单获得的佣金) + CommissionToday float64 `json:"commission_today"` // 佣金今日收益 + CommissionMonth float64 `json:"commission_month"` // 佣金本月收益 + RebateTotal float64 `json:"rebate_total"` // 返佣累计总收益(包括推广返佣和升级返佣) + RebateToday float64 `json:"rebate_today"` // 返佣今日收益 + RebateMonth float64 `json:"rebate_month"` // 返佣本月收益 + AlipayMonthQuota float64 `json:"alipay_month_quota"` // 支付宝每月提现总额度 + AlipayMonthUsed float64 `json:"alipay_month_used"` // 本月已使用的支付宝提现额度 } type GetSubordinateContributionDetailReq struct { @@ -236,6 +278,24 @@ type GetUpgradeRebateListResp struct { List []UpgradeRebateItem `json:"list"` // 列表 } +type GetWhitelistFeaturesReq struct { +} + +type GetWhitelistFeaturesResp struct { + List []WhitelistFeatureItem `json:"list"` // 可屏蔽的feature列表 +} + +type GetWhitelistListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数量 + IdCard string `form:"id_card,optional"` // 身份证号(可选,用于筛选) +} + +type GetWhitelistListResp struct { + Total int64 `json:"total"` // 总数 + List []WhitelistItem `json:"list"` // 列表 +} + type GetWithdrawalListReq struct { Page int64 `form:"page"` // 页码 PageSize int64 `form:"page_size"` // 每页数量 @@ -246,6 +306,17 @@ type GetWithdrawalListResp struct { List []WithdrawalItem `json:"list"` // 列表 } +type OfflineFeatureReq struct { + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + QueryId string `json:"query_id"` // 查询记录ID(Query表的ID,必选) +} + +type OfflineFeatureResp struct { + Success bool `json:"success"` // 是否已完成下架 + NeedPay bool `json:"need_pay"` // 是否需要发起支付 + Amount float64 `json:"amount"` // 需要支付的金额(单位:元),0表示无需支付 +} + type RealNameAuthReq struct { Name string `json:"name"` // 姓名 IdCard string `json:"id_card"` // 身份证号 diff --git a/app/main/api/internal/types/pay.go b/app/main/api/internal/types/pay.go index 5e672a1..f9dcd60 100644 --- a/app/main/api/internal/types/pay.go +++ b/app/main/api/internal/types/pay.go @@ -18,7 +18,7 @@ type PaymentCheckResp struct { type PaymentReq struct { Id string `json:"id"` PayMethod string `json:"pay_method"` // 支付方式: wechat, alipay, appleiap, test(仅开发环境), test_empty(仅开发环境-空报告模式) - PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"` + PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade whitelist"` } type PaymentResp struct { diff --git a/app/main/api/internal/types/taskname.go b/app/main/api/internal/types/taskname.go index 4a7b9e6..f9ded9f 100644 --- a/app/main/api/internal/types/taskname.go +++ b/app/main/api/internal/types/taskname.go @@ -5,3 +5,4 @@ const MsgCleanQueryData = "msg:clean_query_data" const MsgAgentProcess = "msg:agent:process" const MsgUnfreezeCommission = "msg:unfreeze:commission" const MsgUnfreezeCommissionScan = "msg:unfreeze:commission:scan" +const MsgSyncAlipayComplaint = "msg:sync:alipay:complaint" diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index fd4a055..ea15b0c 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -1,6 +1,12 @@ // Code generated by goctl. DO NOT EDIT. package types +type AdminAgentStatistics struct { + TotalCount int64 `json:"total_count"` // 代理总数 + TodayNew int64 `json:"today_new"` // 今日新增 + MonthNew int64 `json:"month_new"` // 当月新增 +} + type AdminApiInfo struct { Id string `json:"id"` ApiName string `json:"api_name"` @@ -23,11 +29,36 @@ type AdminAuditRealNameResp struct { Success bool `json:"success"` } +type AdminOrderStatistics struct { + TodayCount int64 `json:"today_count"` // 今日订单数 + MonthCount int64 `json:"month_count"` // 当月订单数 + TotalCount int64 `json:"total_count"` // 总订单数 + YesterdayCount int64 `json:"yesterday_count"` // 昨日订单数 + ChangeRate float64 `json:"change_rate"` // 变化率(百分比) +} + +type AdminProfitStatistics struct { + TodayProfit float64 `json:"today_profit"` // 今日利润 + MonthProfit float64 `json:"month_profit"` // 当月利润 + TotalProfit float64 `json:"total_profit"` // 总利润 + TodayProfitRate float64 `json:"today_profit_rate"` // 今日利润率 + MonthProfitRate float64 `json:"month_profit_rate"` // 当月利润率 + TotalProfitRate float64 `json:"total_profit_rate"` // 总利润率 +} + type AdminQueryItem struct { Feature interface{} `json:"feature"` Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct } +type AdminRevenueStatistics struct { + TodayAmount float64 `json:"today_amount"` // 今日营收 + MonthAmount float64 `json:"month_amount"` // 当月营收 + TotalAmount float64 `json:"total_amount"` // 总营收 + YesterdayAmount float64 `json:"yesterday_amount"` // 昨日营收 + ChangeRate float64 `json:"change_rate"` // 变化率(百分比) +} + type AdminRoleApiInfo struct { Id string `json:"id"` RoleId string `json:"role_id"` @@ -40,6 +71,11 @@ type AdminRoleApiInfo struct { Description string `json:"description"` } +type AdminTrendData struct { + Date string `json:"date"` // 日期(格式:MM-DD) + Value float64 `json:"value"` // 数值 +} + type AdminUserListItem struct { Id string `json:"id"` // 用户ID Username string `json:"username"` // 用户名 @@ -99,7 +135,8 @@ type AgentOrderListItem struct { ActualBasePrice float64 `json:"actual_base_price"` // 实际底价 PriceCost float64 `json:"price_cost"` // 提价成本 AgentProfit float64 `json:"agent_profit"` // 代理收益 - ProcessStatus int64 `json:"process_status"` // 处理状态 + ProcessStatus int64 `json:"process_status"` // 处理状态(保留用于筛选,前端不显示) + OrderStatus string `json:"order_status"` // 订单状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 CreateTime string `json:"create_time"` // 创建时间 } @@ -133,6 +170,7 @@ type AgentRebateListItem struct { OrderId string `json:"order_id"` // 订单ID RebateType int64 `json:"rebate_type"` // 返佣类型 Amount float64 `json:"amount"` // 金额 + Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款) CreateTime string `json:"create_time"` // 创建时间 } @@ -149,17 +187,57 @@ type AgentUpgradeListItem struct { } type AgentWithdrawalListItem struct { - Id string `json:"id"` // 主键 - AgentId string `json:"agent_id"` // 代理ID - WithdrawNo string `json:"withdraw_no"` // 提现单号 - Amount float64 `json:"amount"` // 金额 - TaxAmount float64 `json:"tax_amount"` // 税费金额 - ActualAmount float64 `json:"actual_amount"` // 实际到账金额 - Status int64 `json:"status"` // 状态 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 - Remark string `json:"remark"` // 备注 - CreateTime string `json:"create_time"` // 创建时间 + Id string `json:"id"` // 主键 + AgentId string `json:"agent_id"` // 代理ID + WithdrawNo string `json:"withdraw_no"` // 提现单号 + Amount float64 `json:"amount"` // 金额 + TaxAmount float64 `json:"tax_amount"` // 税费金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额 + Status int64 `json:"status"` // 状态 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时使用) + BankName string `json:"bank_name"` // 开户行名称(银行卡提现时使用) + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AlipayComplaintDetail struct { + Id string `json:"id"` // 支付宝投诉表ID + AlipayId int64 `json:"alipay_id"` // 支付宝投诉主表的主键id + TaskId string `json:"task_id"` // 投诉单号id + OppositePid string `json:"opposite_pid"` // 被投诉人pid + OppositeName string `json:"opposite_name"` // 被投诉方名称 + ComplainAmount string `json:"complain_amount"` // 投诉单涉及交易总金额 + GmtComplain string `json:"gmt_complain"` // 投诉时间 + GmtProcess string `json:"gmt_process"` // 处理时间 + GmtOverdue string `json:"gmt_overdue"` // 过期时间 + ComplainContent string `json:"complain_content"` // 用户投诉内容 + TradeNo string `json:"trade_no"` // 投诉交易单号 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 投诉单状态枚举值描述 + ProcessCode string `json:"process_code"` // 商家处理结果码 + ProcessMessage string `json:"process_message"` // 商家处理结果码对应描述 + ProcessRemark string `json:"process_remark"` // 商家处理备注 + ProcessImgUrlList []string `json:"process_img_url_list"` // 商家处理备注图片url列表 + GmtRiskFinishTime string `json:"gmt_risk_finish_time"` // 推送时间 + ComplainUrl string `json:"complain_url"` // 投诉网址 + CertifyInfo []string `json:"certify_info"` // 投诉凭证图片信息 + TradeInfoList []AlipayComplaintTradeInfo `json:"trade_info_list"` // 交易信息列表 +} + +type AlipayComplaintTradeInfo struct { + Id string `json:"id"` // 交易信息表ID + AlipayTradeId string `json:"alipay_trade_id"` // 交易信息表主键id + AlipayComplaintRecordId string `json:"alipay_complaint_record_id"` // 投诉主表id + TradeNo string `json:"trade_no"` // 支付宝交易单号 + OutNo string `json:"out_no"` // 商家订单号 + GmtTrade string `json:"gmt_trade"` // 交易时间 + GmtRefund string `json:"gmt_refund"` // 退款时间 + Status string `json:"status"` // 交易投诉状态 + StatusDescription string `json:"status_description"` // 交易投诉状态描述 + Amount string `json:"amount"` // 交易单金额 } type AuthorizationDocumentInfo struct { @@ -191,6 +269,29 @@ type CommissionItem struct { CreateTime string `json:"create_time"` // 创建时间 } +type ComplaintListItem struct { + Id string `json:"id"` // 投诉ID + Type string `json:"type"` // 投诉类型:alipay-支付宝投诉,manual-主动投诉 + OrderId string `json:"order_id"` // 关联订单ID + Name string `json:"name"` // 投诉人姓名 + Contact string `json:"contact"` // 联系方式 + Content string `json:"content"` // 投诉内容 + Status string `json:"status"` // 投诉状态 + StatusDescription string `json:"status_description"` // 状态描述 + Remark string `json:"remark"` // 处理备注 + HandlerId string `json:"handler_id"` // 处理人ID + HandleTime string `json:"handle_time"` // 处理时间 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + TaskId string `json:"task_id"` // 支付宝投诉单号 + TradeNo string `json:"trade_no"` // 支付宝交易单号 + ComplainAmount string `json:"complain_amount"` // 投诉金额 + GmtComplain string `json:"gmt_complain"` // 投诉时间 + Subject string `json:"subject"` // 投诉主题 + Priority string `json:"priority"` // 优先级 + Source string `json:"source"` // 投诉来源 +} + type ConversionRateData struct { Daily []PeriodConversionData `json:"daily"` // 日统计(今日、昨日、前日) Weekly []PeriodConversionData `json:"weekly"` // 周统计(这周、前一周、前两周) @@ -210,11 +311,12 @@ type Feature struct { } type FeatureListItem struct { - Id string `json:"id"` // 功能ID - ApiId string `json:"api_id"` // API标识 - Name string `json:"name"` // 描述 - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 + Id string `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + WhitelistPrice float64 `json:"whitelist_price"` // 白名单屏蔽价格(单位:元) + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 } type InviteCodeItem struct { @@ -279,6 +381,15 @@ type LevelPrivilegeItem struct { InviteDiamondReward float64 `json:"invite_diamond_reward"` // 邀请钻石代理奖励金额(元) } +type ManualComplaintDetail struct { + Id string `json:"id"` // 主动投诉表ID + UserId string `json:"user_id"` // 关联用户ID + Subject string `json:"subject"` // 投诉主题 + Priority string `json:"priority"` // 优先级:low-低,medium-中,high-高,urgent-紧急 + Source string `json:"source"` // 投诉来源:web-网站,phone-电话,email-邮件,other-其他 + AttachmentUrls []string `json:"attachment_urls"` // 附件URL列表 +} + type Notification struct { Title string `json:"title"` // 通知标题 Content string `json:"content"` // 通知内容 (富文本) @@ -473,6 +584,7 @@ type RebateItem struct { OrderNo string `json:"order_no"` // 订单号 RebateType int64 `json:"rebate_type"` // 返佣类型:1=直接上级,2=钻石上级,3=黄金上级 Amount float64 `json:"amount"` // 返佣金额 + Status int64 `json:"status"` // 状态:1=已发放,2=已冻结,3=已取消(已退款) CreateTime string `json:"create_time"` // 创建时间 } @@ -572,15 +684,37 @@ type User struct { UserType int64 `json:"userType"` } -type WithdrawalItem struct { - Id string `json:"id"` // 记录ID - WithdrawalNo string `json:"withdrawal_no"` // 提现单号 - Amount float64 `json:"amount"` // 提现金额 - TaxAmount float64 `json:"tax_amount"` // 税费金额 - ActualAmount float64 `json:"actual_amount"` // 实际到账金额 - Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败 - PayeeAccount string `json:"payee_account"` // 收款账户 - PayeeName string `json:"payee_name"` // 收款人姓名 - Remark string `json:"remark"` // 备注 - CreateTime string `json:"create_time"` // 创建时间 +type WhitelistFeatureItem struct { + FeatureId string `json:"feature_id"` // Feature的UUID + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + FeatureName string `json:"feature_name"` // Feature的名称 + WhitelistPrice float64 `json:"whitelist_price"` // 屏蔽价格(单位:元) +} + +type WhitelistItem struct { + Id string `json:"id"` // 白名单记录ID + IdCard string `json:"id_card"` // 身份证号 + FeatureId string `json:"feature_id"` // Feature的UUID + FeatureApiId string `json:"feature_api_id"` // Feature的API标识 + FeatureName string `json:"feature_name"` // Feature的名称 + Amount float64 `json:"amount"` // 费用 + Status int64 `json:"status"` // 状态:1=生效,2=已失效 + StatusText string `json:"status_text"` // 状态文本 + CreateTime string `json:"create_time"` // 创建时间 +} + +type WithdrawalItem struct { + Id string `json:"id"` // 记录ID + WithdrawalNo string `json:"withdrawal_no"` // 提现单号 + Amount float64 `json:"amount"` // 提现金额 + TaxAmount float64 `json:"tax_amount"` // 税费金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额 + Status int64 `json:"status"` // 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败 + WithdrawalType int64 `json:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + PayeeAccount string `json:"payee_account"` // 收款账户(支付宝账号或银行卡号) + PayeeName string `json:"payee_name"` // 收款人姓名 + BankCardNo string `json:"bank_card_no"` // 银行卡号(银行卡提现时填写) + BankName string `json:"bank_name"` // 开户行名称(银行卡提现时填写) + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 } diff --git a/app/main/model/agentWithdrawalModel_gen.go b/app/main/model/agentWithdrawalModel_gen.go index 81ee0b7..660ebc2 100644 --- a/app/main/model/agentWithdrawalModel_gen.go +++ b/app/main/model/agentWithdrawalModel_gen.go @@ -58,21 +58,24 @@ type ( } AgentWithdrawal struct { - Id string `db:"id"` - AgentId string `db:"agent_id"` - WithdrawNo string `db:"withdraw_no"` // 提现单号 - PayeeAccount string `db:"payee_account"` // 收款账户 - PayeeName string `db:"payee_name"` // 收款人姓名 - Amount float64 `db:"amount"` // 提现金额 - ActualAmount float64 `db:"actual_amount"` // 实际到账金额(扣除税费后) - TaxAmount float64 `db:"tax_amount"` // 税费金额 - Status int64 `db:"status"` // 状态:1=处理中,2=成功,3=失败 - Remark sql.NullString `db:"remark"` // 备注 - CreateTime time.Time `db:"create_time"` // 创建时间 - UpdateTime time.Time `db:"update_time"` // 更新时间 - DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 - DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 - Version int64 `db:"version"` // 版本号(乐观锁) + Id string `db:"id"` + AgentId string `db:"agent_id"` + WithdrawalType int64 `db:"withdrawal_type"` // 提现方式:1=支付宝,2=银行卡 + WithdrawNo string `db:"withdraw_no"` // 提现单号 + PayeeAccount string `db:"payee_account"` // 收款账户 + BankCardNo sql.NullString `db:"bank_card_no"` // 银行卡号(银行卡提现时使用) + BankName sql.NullString `db:"bank_name"` // 开户行名称(银行卡提现时使用) + PayeeName string `db:"payee_name"` // 收款人姓名 + Amount float64 `db:"amount"` // 提现金额 + ActualAmount float64 `db:"actual_amount"` // 实际到账金额(扣除税费后) + TaxAmount float64 `db:"tax_amount"` // 税费金额 + Status int64 `db:"status"` // 状态:1=处理中,2=成功,3=失败 + Remark sql.NullString `db:"remark"` // 备注 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) } ) @@ -89,11 +92,11 @@ func (m *defaultAgentWithdrawalModel) Insert(ctx context.Context, session sqlx.S yccAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheYccAgentWithdrawalIdPrefix, data.Id) yccAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheYccAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo) 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, agentWithdrawalRowsExpectAutoSet) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentWithdrawalRowsExpectAutoSet) if session != nil { - return session.ExecCtx(ctx, query, data.Id, data.AgentId, data.WithdrawNo, data.PayeeAccount, data.PayeeName, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) + return session.ExecCtx(ctx, query, data.Id, data.AgentId, data.WithdrawalType, data.WithdrawNo, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) } - return conn.ExecCtx(ctx, query, data.Id, data.AgentId, data.WithdrawNo, data.PayeeAccount, data.PayeeName, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) + return conn.ExecCtx(ctx, query, data.Id, data.AgentId, data.WithdrawalType, data.WithdrawNo, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.Remark, data.DeleteTime, data.DelState, data.Version) }, yccAgentWithdrawalIdKey, yccAgentWithdrawalWithdrawNoKey) } func (m *defaultAgentWithdrawalModel) insertUUID(data *AgentWithdrawal) { @@ -160,9 +163,9 @@ func (m *defaultAgentWithdrawalModel) Update(ctx context.Context, session sqlx.S 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, agentWithdrawalRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.PayeeAccount, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawalType, newData.WithdrawNo, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) } - return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.PayeeAccount, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawalType, newData.WithdrawNo, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) }, yccAgentWithdrawalIdKey, yccAgentWithdrawalWithdrawNoKey) } @@ -183,9 +186,9 @@ func (m *defaultAgentWithdrawalModel) UpdateWithVersion(ctx context.Context, ses 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, agentWithdrawalRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.PayeeAccount, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawalType, newData.WithdrawNo, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) } - return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.PayeeAccount, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawalType, newData.WithdrawNo, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) }, yccAgentWithdrawalIdKey, yccAgentWithdrawalWithdrawNoKey) if err != nil { return err diff --git a/app/main/model/alipayFromCallbackModel.go b/app/main/model/alipayFromCallbackModel.go new file mode 100644 index 0000000..69a1f94 --- /dev/null +++ b/app/main/model/alipayFromCallbackModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AlipayFromCallbackModel = (*customAlipayFromCallbackModel)(nil) + +type ( + // AlipayFromCallbackModel is an interface to be customized, add more methods here, + // and implement the added methods in customAlipayFromCallbackModel. + AlipayFromCallbackModel interface { + alipayFromCallbackModel + } + + customAlipayFromCallbackModel struct { + *defaultAlipayFromCallbackModel + } +) + +// NewAlipayFromCallbackModel returns a model for the database table. +func NewAlipayFromCallbackModel(conn sqlx.SqlConn, c cache.CacheConf) AlipayFromCallbackModel { + return &customAlipayFromCallbackModel{ + defaultAlipayFromCallbackModel: newAlipayFromCallbackModel(conn, c), + } +} diff --git a/app/main/model/alipayFromCallbackModel_gen.go b/app/main/model/alipayFromCallbackModel_gen.go new file mode 100644 index 0000000..746fbdc --- /dev/null +++ b/app/main/model/alipayFromCallbackModel_gen.go @@ -0,0 +1,391 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + alipayFromCallbackFieldNames = builder.RawFieldNames(&AlipayFromCallback{}) + alipayFromCallbackRows = strings.Join(alipayFromCallbackFieldNames, ",") + alipayFromCallbackRowsExpectAutoSet = strings.Join(stringx.Remove(alipayFromCallbackFieldNames, "`create_time`", "`update_time`"), ",") + alipayFromCallbackRowsWithPlaceHolder = strings.Join(stringx.Remove(alipayFromCallbackFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccAlipayFromCallbackIdPrefix = "cache:ycc:alipayFromCallback:id:" +) + +type ( + alipayFromCallbackModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) (sql.Result, error) + FindOne(ctx context.Context, id string) (*AlipayFromCallback, error) + Update(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) 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 *AlipayFromCallback) 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) ([]*AlipayFromCallback, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AlipayFromCallback, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AlipayFromCallback, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AlipayFromCallback, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AlipayFromCallback, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultAlipayFromCallbackModel struct { + sqlc.CachedConn + table string + } + + AlipayFromCallback struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + MsgMethod string `db:"msg_method"` // 消息类型,如:alipay.merchant.tradecomplain.changed, alipay.security.risk.complaints.merchants.notify + AppId string `db:"app_id"` // 应用ID + NotifyId sql.NullString `db:"notify_id"` // 通知ID(支付宝返回的) + BizContent string `db:"biz_content"` // 业务内容(JSON字符串,存储完整的biz_content) + Status string `db:"status"` // 处理状态:pending=待处理,processed=已处理,failed=处理失败 + ErrorMessage sql.NullString `db:"error_message"` // 错误信息(如果处理失败) + } +) + +func newAlipayFromCallbackModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAlipayFromCallbackModel { + return &defaultAlipayFromCallbackModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`alipay_from_callback`", + } +} + +func (m *defaultAlipayFromCallbackModel) Insert(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccAlipayFromCallbackIdKey := fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, data.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, alipayFromCallbackRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage) + }, yccAlipayFromCallbackIdKey) +} +func (m *defaultAlipayFromCallbackModel) insertUUID(data *AlipayFromCallback) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultAlipayFromCallbackModel) FindOne(ctx context.Context, id string) (*AlipayFromCallback, error) { + yccAlipayFromCallbackIdKey := fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, id) + var resp AlipayFromCallback + err := m.QueryRowCtx(ctx, &resp, yccAlipayFromCallbackIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", alipayFromCallbackRows, 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 *defaultAlipayFromCallbackModel) Update(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) (sql.Result, error) { + yccAlipayFromCallbackIdKey := fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, 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, alipayFromCallbackRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage, data.Id) + }, yccAlipayFromCallbackIdKey) +} + +func (m *defaultAlipayFromCallbackModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccAlipayFromCallbackIdKey := fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, 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, alipayFromCallbackRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.MsgMethod, data.AppId, data.NotifyId, data.BizContent, data.Status, data.ErrorMessage, data.Id, oldVersion) + }, yccAlipayFromCallbackIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAlipayFromCallbackModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AlipayFromCallback) 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 "), "AlipayFromCallbackModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAlipayFromCallbackModel) 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 *defaultAlipayFromCallbackModel) 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 *defaultAlipayFromCallbackModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AlipayFromCallback, error) { + + builder = builder.Columns(alipayFromCallbackRows) + + 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 []*AlipayFromCallback + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAlipayFromCallbackModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AlipayFromCallback, error) { + + builder = builder.Columns(alipayFromCallbackRows) + + 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 []*AlipayFromCallback + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAlipayFromCallbackModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AlipayFromCallback, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(alipayFromCallbackRows) + + 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, total, err + } + + var resp []*AlipayFromCallback + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAlipayFromCallbackModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AlipayFromCallback, error) { + + builder = builder.Columns(alipayFromCallbackRows) + + 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 []*AlipayFromCallback + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAlipayFromCallbackModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AlipayFromCallback, error) { + + builder = builder.Columns(alipayFromCallbackRows) + + 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 []*AlipayFromCallback + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAlipayFromCallbackModel) 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 *defaultAlipayFromCallbackModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAlipayFromCallbackModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + yccAlipayFromCallbackIdKey := fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, 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) + }, yccAlipayFromCallbackIdKey) + return err +} +func (m *defaultAlipayFromCallbackModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccAlipayFromCallbackIdPrefix, primary) +} +func (m *defaultAlipayFromCallbackModel) 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", alipayFromCallbackRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAlipayFromCallbackModel) tableName() string { + return m.table +} diff --git a/app/main/model/complaintAlipayModel.go b/app/main/model/complaintAlipayModel.go new file mode 100644 index 0000000..4547637 --- /dev/null +++ b/app/main/model/complaintAlipayModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ ComplaintAlipayModel = (*customComplaintAlipayModel)(nil) + +type ( + // ComplaintAlipayModel is an interface to be customized, add more methods here, + // and implement the added methods in customComplaintAlipayModel. + ComplaintAlipayModel interface { + complaintAlipayModel + } + + customComplaintAlipayModel struct { + *defaultComplaintAlipayModel + } +) + +// NewComplaintAlipayModel returns a model for the database table. +func NewComplaintAlipayModel(conn sqlx.SqlConn, c cache.CacheConf) ComplaintAlipayModel { + return &customComplaintAlipayModel{ + defaultComplaintAlipayModel: newComplaintAlipayModel(conn, c), + } +} diff --git a/app/main/model/complaintAlipayModel_gen.go b/app/main/model/complaintAlipayModel_gen.go new file mode 100644 index 0000000..5eb05c9 --- /dev/null +++ b/app/main/model/complaintAlipayModel_gen.go @@ -0,0 +1,470 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + complaintAlipayFieldNames = builder.RawFieldNames(&ComplaintAlipay{}) + complaintAlipayRows = strings.Join(complaintAlipayFieldNames, ",") + complaintAlipayRowsExpectAutoSet = strings.Join(stringx.Remove(complaintAlipayFieldNames, "`create_time`", "`update_time`"), ",") + complaintAlipayRowsWithPlaceHolder = strings.Join(stringx.Remove(complaintAlipayFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccComplaintAlipayIdPrefix = "cache:ycc:complaintAlipay:id:" + cacheYccComplaintAlipayAlipayIdPrefix = "cache:ycc:complaintAlipay:alipayId:" + cacheYccComplaintAlipayTaskIdPrefix = "cache:ycc:complaintAlipay:taskId:" +) + +type ( + complaintAlipayModel interface { + Insert(ctx context.Context, session sqlx.Session, data *ComplaintAlipay) (sql.Result, error) + FindOne(ctx context.Context, id string) (*ComplaintAlipay, error) + FindOneByAlipayId(ctx context.Context, alipayId int64) (*ComplaintAlipay, error) + FindOneByTaskId(ctx context.Context, taskId string) (*ComplaintAlipay, error) + Update(ctx context.Context, session sqlx.Session, data *ComplaintAlipay) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintAlipay) 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 *ComplaintAlipay) 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) ([]*ComplaintAlipay, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipay, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipay, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintAlipay, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintAlipay, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultComplaintAlipayModel struct { + sqlc.CachedConn + table string + } + + ComplaintAlipay struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + ComplaintId string `db:"complaint_id"` // 关联投诉主表ID(关联complaint_main.id) + AlipayId int64 `db:"alipay_id"` // 支付宝投诉主表的主键id(支付宝返回的id) + TaskId string `db:"task_id"` // 投诉单号id(支付宝返回的task_id,用于唯一标识) + OppositePid sql.NullString `db:"opposite_pid"` // 被投诉人pid + OppositeName sql.NullString `db:"opposite_name"` // 投诉单中被投诉方的名称 + ComplainAmount sql.NullFloat64 `db:"complain_amount"` // 投诉单涉及交易总金额(单位:人民币元) + GmtComplain sql.NullTime `db:"gmt_complain"` // 投诉时间 + GmtProcess sql.NullTime `db:"gmt_process"` // 处理时间 + GmtOverdue sql.NullTime `db:"gmt_overdue"` // 过期时间 + ComplainContent sql.NullString `db:"complain_content"` // 用户投诉内容 + TradeNo sql.NullString `db:"trade_no"` // 投诉交易单号 + Status sql.NullString `db:"status"` // 投诉状态(支付宝返回的状态) + StatusDescription sql.NullString `db:"status_description"` // 投诉单状态枚举值描述 + ProcessCode sql.NullString `db:"process_code"` // 商家处理结果码 + ProcessMessage sql.NullString `db:"process_message"` // 商家处理结果码对应描述 + ProcessRemark sql.NullString `db:"process_remark"` // 商家处理备注 + ProcessImgUrlList sql.NullString `db:"process_img_url_list"` // 商家处理备注图片url列表(JSON数组字符串) + GmtRiskFinishTime sql.NullTime `db:"gmt_risk_finish_time"` // 推送时间 + ComplainUrl sql.NullString `db:"complain_url"` // 投诉网址 + CertifyInfo sql.NullString `db:"certify_info"` // 投诉凭证图片信息(JSON数组字符串) + } +) + +func newComplaintAlipayModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultComplaintAlipayModel { + return &defaultComplaintAlipayModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`complaint_alipay`", + } +} + +func (m *defaultComplaintAlipayModel) Insert(ctx context.Context, session sqlx.Session, data *ComplaintAlipay) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccComplaintAlipayAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayAlipayIdPrefix, data.AlipayId) + yccComplaintAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, data.Id) + yccComplaintAlipayTaskIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTaskIdPrefix, data.TaskId) + 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, complaintAlipayRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayId, data.TaskId, data.OppositePid, data.OppositeName, data.ComplainAmount, data.GmtComplain, data.GmtProcess, data.GmtOverdue, data.ComplainContent, data.TradeNo, data.Status, data.StatusDescription, data.ProcessCode, data.ProcessMessage, data.ProcessRemark, data.ProcessImgUrlList, data.GmtRiskFinishTime, data.ComplainUrl, data.CertifyInfo) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayId, data.TaskId, data.OppositePid, data.OppositeName, data.ComplainAmount, data.GmtComplain, data.GmtProcess, data.GmtOverdue, data.ComplainContent, data.TradeNo, data.Status, data.StatusDescription, data.ProcessCode, data.ProcessMessage, data.ProcessRemark, data.ProcessImgUrlList, data.GmtRiskFinishTime, data.ComplainUrl, data.CertifyInfo) + }, yccComplaintAlipayAlipayIdKey, yccComplaintAlipayIdKey, yccComplaintAlipayTaskIdKey) +} +func (m *defaultComplaintAlipayModel) insertUUID(data *ComplaintAlipay) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultComplaintAlipayModel) FindOne(ctx context.Context, id string) (*ComplaintAlipay, error) { + yccComplaintAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, id) + var resp ComplaintAlipay + err := m.QueryRowCtx(ctx, &resp, yccComplaintAlipayIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", complaintAlipayRows, 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 *defaultComplaintAlipayModel) FindOneByAlipayId(ctx context.Context, alipayId int64) (*ComplaintAlipay, error) { + yccComplaintAlipayAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayAlipayIdPrefix, alipayId) + var resp ComplaintAlipay + err := m.QueryRowIndexCtx(ctx, &resp, yccComplaintAlipayAlipayIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `alipay_id` = ? and del_state = ? limit 1", complaintAlipayRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, alipayId, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) FindOneByTaskId(ctx context.Context, taskId string) (*ComplaintAlipay, error) { + yccComplaintAlipayTaskIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTaskIdPrefix, taskId) + var resp ComplaintAlipay + err := m.QueryRowIndexCtx(ctx, &resp, yccComplaintAlipayTaskIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `task_id` = ? and del_state = ? limit 1", complaintAlipayRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, taskId, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) Update(ctx context.Context, session sqlx.Session, newData *ComplaintAlipay) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + yccComplaintAlipayAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayAlipayIdPrefix, data.AlipayId) + yccComplaintAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, data.Id) + yccComplaintAlipayTaskIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTaskIdPrefix, data.TaskId) + 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, complaintAlipayRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ComplaintId, newData.AlipayId, newData.TaskId, newData.OppositePid, newData.OppositeName, newData.ComplainAmount, newData.GmtComplain, newData.GmtProcess, newData.GmtOverdue, newData.ComplainContent, newData.TradeNo, newData.Status, newData.StatusDescription, newData.ProcessCode, newData.ProcessMessage, newData.ProcessRemark, newData.ProcessImgUrlList, newData.GmtRiskFinishTime, newData.ComplainUrl, newData.CertifyInfo, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ComplaintId, newData.AlipayId, newData.TaskId, newData.OppositePid, newData.OppositeName, newData.ComplainAmount, newData.GmtComplain, newData.GmtProcess, newData.GmtOverdue, newData.ComplainContent, newData.TradeNo, newData.Status, newData.StatusDescription, newData.ProcessCode, newData.ProcessMessage, newData.ProcessRemark, newData.ProcessImgUrlList, newData.GmtRiskFinishTime, newData.ComplainUrl, newData.CertifyInfo, newData.Id) + }, yccComplaintAlipayAlipayIdKey, yccComplaintAlipayIdKey, yccComplaintAlipayTaskIdKey) +} + +func (m *defaultComplaintAlipayModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *ComplaintAlipay) error { + + oldVersion := newData.Version + newData.Version += 1 + + var sqlResult sql.Result + var err error + + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + yccComplaintAlipayAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayAlipayIdPrefix, data.AlipayId) + yccComplaintAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, data.Id) + yccComplaintAlipayTaskIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTaskIdPrefix, data.TaskId) + 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, complaintAlipayRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ComplaintId, newData.AlipayId, newData.TaskId, newData.OppositePid, newData.OppositeName, newData.ComplainAmount, newData.GmtComplain, newData.GmtProcess, newData.GmtOverdue, newData.ComplainContent, newData.TradeNo, newData.Status, newData.StatusDescription, newData.ProcessCode, newData.ProcessMessage, newData.ProcessRemark, newData.ProcessImgUrlList, newData.GmtRiskFinishTime, newData.ComplainUrl, newData.CertifyInfo, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ComplaintId, newData.AlipayId, newData.TaskId, newData.OppositePid, newData.OppositeName, newData.ComplainAmount, newData.GmtComplain, newData.GmtProcess, newData.GmtOverdue, newData.ComplainContent, newData.TradeNo, newData.Status, newData.StatusDescription, newData.ProcessCode, newData.ProcessMessage, newData.ProcessRemark, newData.ProcessImgUrlList, newData.GmtRiskFinishTime, newData.ComplainUrl, newData.CertifyInfo, newData.Id, oldVersion) + }, yccComplaintAlipayAlipayIdKey, yccComplaintAlipayIdKey, yccComplaintAlipayTaskIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultComplaintAlipayModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *ComplaintAlipay) 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 "), "ComplaintAlipayModel delete err : %+v", err) + } + return nil +} + +func (m *defaultComplaintAlipayModel) 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 *defaultComplaintAlipayModel) 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 *defaultComplaintAlipayModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*ComplaintAlipay, error) { + + builder = builder.Columns(complaintAlipayRows) + + 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 []*ComplaintAlipay + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipay, error) { + + builder = builder.Columns(complaintAlipayRows) + + 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 []*ComplaintAlipay + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipay, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(complaintAlipayRows) + + 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, total, err + } + + var resp []*ComplaintAlipay + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultComplaintAlipayModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintAlipay, error) { + + builder = builder.Columns(complaintAlipayRows) + + 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 []*ComplaintAlipay + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintAlipay, error) { + + builder = builder.Columns(complaintAlipayRows) + + 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 []*ComplaintAlipay + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayModel) 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 *defaultComplaintAlipayModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultComplaintAlipayModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + yccComplaintAlipayAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayAlipayIdPrefix, data.AlipayId) + yccComplaintAlipayIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, id) + yccComplaintAlipayTaskIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTaskIdPrefix, data.TaskId) + _, 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) + }, yccComplaintAlipayAlipayIdKey, yccComplaintAlipayIdKey, yccComplaintAlipayTaskIdKey) + return err +} +func (m *defaultComplaintAlipayModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccComplaintAlipayIdPrefix, primary) +} +func (m *defaultComplaintAlipayModel) 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", complaintAlipayRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultComplaintAlipayModel) tableName() string { + return m.table +} diff --git a/app/main/model/complaintAlipayTradeModel.go b/app/main/model/complaintAlipayTradeModel.go new file mode 100644 index 0000000..01c82a4 --- /dev/null +++ b/app/main/model/complaintAlipayTradeModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ ComplaintAlipayTradeModel = (*customComplaintAlipayTradeModel)(nil) + +type ( + // ComplaintAlipayTradeModel is an interface to be customized, add more methods here, + // and implement the added methods in customComplaintAlipayTradeModel. + ComplaintAlipayTradeModel interface { + complaintAlipayTradeModel + } + + customComplaintAlipayTradeModel struct { + *defaultComplaintAlipayTradeModel + } +) + +// NewComplaintAlipayTradeModel returns a model for the database table. +func NewComplaintAlipayTradeModel(conn sqlx.SqlConn, c cache.CacheConf) ComplaintAlipayTradeModel { + return &customComplaintAlipayTradeModel{ + defaultComplaintAlipayTradeModel: newComplaintAlipayTradeModel(conn, c), + } +} diff --git a/app/main/model/complaintAlipayTradeModel_gen.go b/app/main/model/complaintAlipayTradeModel_gen.go new file mode 100644 index 0000000..31e2db9 --- /dev/null +++ b/app/main/model/complaintAlipayTradeModel_gen.go @@ -0,0 +1,395 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + complaintAlipayTradeFieldNames = builder.RawFieldNames(&ComplaintAlipayTrade{}) + complaintAlipayTradeRows = strings.Join(complaintAlipayTradeFieldNames, ",") + complaintAlipayTradeRowsExpectAutoSet = strings.Join(stringx.Remove(complaintAlipayTradeFieldNames, "`create_time`", "`update_time`"), ",") + complaintAlipayTradeRowsWithPlaceHolder = strings.Join(stringx.Remove(complaintAlipayTradeFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccComplaintAlipayTradeIdPrefix = "cache:ycc:complaintAlipayTrade:id:" +) + +type ( + complaintAlipayTradeModel interface { + Insert(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) (sql.Result, error) + FindOne(ctx context.Context, id string) (*ComplaintAlipayTrade, error) + Update(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) 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 *ComplaintAlipayTrade) 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) ([]*ComplaintAlipayTrade, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipayTrade, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipayTrade, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintAlipayTrade, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintAlipayTrade, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultComplaintAlipayTradeModel struct { + sqlc.CachedConn + table string + } + + ComplaintAlipayTrade struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + ComplaintId string `db:"complaint_id"` // 投诉主表ID(关联complaint_alipay.id) + AlipayTradeId sql.NullInt64 `db:"alipay_trade_id"` // 交易信息表主键id(支付宝返回的id) + AlipayComplaintRecordId sql.NullInt64 `db:"alipay_complaint_record_id"` // 投诉主表id(支付宝返回的complaint_record_id) + TradeNo sql.NullString `db:"trade_no"` // 支付宝交易单号 + OutNo sql.NullString `db:"out_no"` // 商家订单号 + GmtTrade sql.NullTime `db:"gmt_trade"` // 交易时间 + GmtRefund sql.NullTime `db:"gmt_refund"` // 退款时间 + Status sql.NullString `db:"status"` // 交易投诉状态 + StatusDescription sql.NullString `db:"status_description"` // 交易投诉状态描述 + Amount sql.NullFloat64 `db:"amount"` // 交易单金额(单位:人民币元) + } +) + +func newComplaintAlipayTradeModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultComplaintAlipayTradeModel { + return &defaultComplaintAlipayTradeModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`complaint_alipay_trade`", + } +} + +func (m *defaultComplaintAlipayTradeModel) Insert(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccComplaintAlipayTradeIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, data.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, complaintAlipayTradeRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount) + }, yccComplaintAlipayTradeIdKey) +} +func (m *defaultComplaintAlipayTradeModel) insertUUID(data *ComplaintAlipayTrade) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultComplaintAlipayTradeModel) FindOne(ctx context.Context, id string) (*ComplaintAlipayTrade, error) { + yccComplaintAlipayTradeIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, id) + var resp ComplaintAlipayTrade + err := m.QueryRowCtx(ctx, &resp, yccComplaintAlipayTradeIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", complaintAlipayTradeRows, 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 *defaultComplaintAlipayTradeModel) Update(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) (sql.Result, error) { + yccComplaintAlipayTradeIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, 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, complaintAlipayTradeRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount, data.Id) + }, yccComplaintAlipayTradeIdKey) +} + +func (m *defaultComplaintAlipayTradeModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccComplaintAlipayTradeIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, 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, complaintAlipayTradeRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.AlipayTradeId, data.AlipayComplaintRecordId, data.TradeNo, data.OutNo, data.GmtTrade, data.GmtRefund, data.Status, data.StatusDescription, data.Amount, data.Id, oldVersion) + }, yccComplaintAlipayTradeIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultComplaintAlipayTradeModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *ComplaintAlipayTrade) 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 "), "ComplaintAlipayTradeModel delete err : %+v", err) + } + return nil +} + +func (m *defaultComplaintAlipayTradeModel) 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 *defaultComplaintAlipayTradeModel) 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 *defaultComplaintAlipayTradeModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*ComplaintAlipayTrade, error) { + + builder = builder.Columns(complaintAlipayTradeRows) + + 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 []*ComplaintAlipayTrade + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayTradeModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipayTrade, error) { + + builder = builder.Columns(complaintAlipayTradeRows) + + 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 []*ComplaintAlipayTrade + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayTradeModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintAlipayTrade, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(complaintAlipayTradeRows) + + 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, total, err + } + + var resp []*ComplaintAlipayTrade + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultComplaintAlipayTradeModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintAlipayTrade, error) { + + builder = builder.Columns(complaintAlipayTradeRows) + + 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 []*ComplaintAlipayTrade + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayTradeModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintAlipayTrade, error) { + + builder = builder.Columns(complaintAlipayTradeRows) + + 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 []*ComplaintAlipayTrade + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintAlipayTradeModel) 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 *defaultComplaintAlipayTradeModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultComplaintAlipayTradeModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + yccComplaintAlipayTradeIdKey := fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, 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) + }, yccComplaintAlipayTradeIdKey) + return err +} +func (m *defaultComplaintAlipayTradeModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccComplaintAlipayTradeIdPrefix, primary) +} +func (m *defaultComplaintAlipayTradeModel) 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", complaintAlipayTradeRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultComplaintAlipayTradeModel) tableName() string { + return m.table +} diff --git a/app/main/model/complaintMainModel.go b/app/main/model/complaintMainModel.go new file mode 100644 index 0000000..6391803 --- /dev/null +++ b/app/main/model/complaintMainModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ ComplaintMainModel = (*customComplaintMainModel)(nil) + +type ( + // ComplaintMainModel is an interface to be customized, add more methods here, + // and implement the added methods in customComplaintMainModel. + ComplaintMainModel interface { + complaintMainModel + } + + customComplaintMainModel struct { + *defaultComplaintMainModel + } +) + +// NewComplaintMainModel returns a model for the database table. +func NewComplaintMainModel(conn sqlx.SqlConn, c cache.CacheConf) ComplaintMainModel { + return &customComplaintMainModel{ + defaultComplaintMainModel: newComplaintMainModel(conn, c), + } +} diff --git a/app/main/model/complaintMainModel_gen.go b/app/main/model/complaintMainModel_gen.go new file mode 100644 index 0000000..6cb1074 --- /dev/null +++ b/app/main/model/complaintMainModel_gen.go @@ -0,0 +1,395 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + complaintMainFieldNames = builder.RawFieldNames(&ComplaintMain{}) + complaintMainRows = strings.Join(complaintMainFieldNames, ",") + complaintMainRowsExpectAutoSet = strings.Join(stringx.Remove(complaintMainFieldNames, "`create_time`", "`update_time`"), ",") + complaintMainRowsWithPlaceHolder = strings.Join(stringx.Remove(complaintMainFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccComplaintMainIdPrefix = "cache:ycc:complaintMain:id:" +) + +type ( + complaintMainModel interface { + Insert(ctx context.Context, session sqlx.Session, data *ComplaintMain) (sql.Result, error) + FindOne(ctx context.Context, id string) (*ComplaintMain, error) + Update(ctx context.Context, session sqlx.Session, data *ComplaintMain) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintMain) 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 *ComplaintMain) 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) ([]*ComplaintMain, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintMain, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintMain, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintMain, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintMain, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultComplaintMainModel struct { + sqlc.CachedConn + table string + } + + ComplaintMain struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + Type string `db:"type"` // 投诉类型:alipay=支付宝投诉,manual=主动投诉 + OrderId sql.NullString `db:"order_id"` // 关联订单ID(如果有相关订单) + Name sql.NullString `db:"name"` // 投诉人姓名 + Contact sql.NullString `db:"contact"` // 联系方式(手机号、邮箱等) + Content sql.NullString `db:"content"` // 投诉内容 + Status sql.NullString `db:"status"` // 投诉状态(通用状态,如:pending=待处理,processing=处理中,resolved=已解决,closed=已关闭) + StatusDescription sql.NullString `db:"status_description"` // 状态描述 + Remark sql.NullString `db:"remark"` // 处理备注 + HandlerId sql.NullString `db:"handler_id"` // 处理人ID(关联管理员或其他处理人员) + HandleTime sql.NullTime `db:"handle_time"` // 处理时间 + } +) + +func newComplaintMainModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultComplaintMainModel { + return &defaultComplaintMainModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`complaint_main`", + } +} + +func (m *defaultComplaintMainModel) Insert(ctx context.Context, session sqlx.Session, data *ComplaintMain) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccComplaintMainIdKey := fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, data.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, complaintMainRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime) + }, yccComplaintMainIdKey) +} +func (m *defaultComplaintMainModel) insertUUID(data *ComplaintMain) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultComplaintMainModel) FindOne(ctx context.Context, id string) (*ComplaintMain, error) { + yccComplaintMainIdKey := fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, id) + var resp ComplaintMain + err := m.QueryRowCtx(ctx, &resp, yccComplaintMainIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", complaintMainRows, 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 *defaultComplaintMainModel) Update(ctx context.Context, session sqlx.Session, data *ComplaintMain) (sql.Result, error) { + yccComplaintMainIdKey := fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, 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, complaintMainRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime, data.Id) + }, yccComplaintMainIdKey) +} + +func (m *defaultComplaintMainModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintMain) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccComplaintMainIdKey := fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, 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, complaintMainRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Type, data.OrderId, data.Name, data.Contact, data.Content, data.Status, data.StatusDescription, data.Remark, data.HandlerId, data.HandleTime, data.Id, oldVersion) + }, yccComplaintMainIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultComplaintMainModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *ComplaintMain) 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 "), "ComplaintMainModel delete err : %+v", err) + } + return nil +} + +func (m *defaultComplaintMainModel) 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 *defaultComplaintMainModel) 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 *defaultComplaintMainModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*ComplaintMain, error) { + + builder = builder.Columns(complaintMainRows) + + 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 []*ComplaintMain + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintMainModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintMain, error) { + + builder = builder.Columns(complaintMainRows) + + 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 []*ComplaintMain + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintMainModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintMain, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(complaintMainRows) + + 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, total, err + } + + var resp []*ComplaintMain + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultComplaintMainModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintMain, error) { + + builder = builder.Columns(complaintMainRows) + + 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 []*ComplaintMain + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintMainModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintMain, error) { + + builder = builder.Columns(complaintMainRows) + + 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 []*ComplaintMain + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintMainModel) 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 *defaultComplaintMainModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultComplaintMainModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + yccComplaintMainIdKey := fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, 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) + }, yccComplaintMainIdKey) + return err +} +func (m *defaultComplaintMainModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccComplaintMainIdPrefix, primary) +} +func (m *defaultComplaintMainModel) 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", complaintMainRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultComplaintMainModel) tableName() string { + return m.table +} diff --git a/app/main/model/complaintManualModel.go b/app/main/model/complaintManualModel.go new file mode 100644 index 0000000..a9a929e --- /dev/null +++ b/app/main/model/complaintManualModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ ComplaintManualModel = (*customComplaintManualModel)(nil) + +type ( + // ComplaintManualModel is an interface to be customized, add more methods here, + // and implement the added methods in customComplaintManualModel. + ComplaintManualModel interface { + complaintManualModel + } + + customComplaintManualModel struct { + *defaultComplaintManualModel + } +) + +// NewComplaintManualModel returns a model for the database table. +func NewComplaintManualModel(conn sqlx.SqlConn, c cache.CacheConf) ComplaintManualModel { + return &customComplaintManualModel{ + defaultComplaintManualModel: newComplaintManualModel(conn, c), + } +} diff --git a/app/main/model/complaintManualModel_gen.go b/app/main/model/complaintManualModel_gen.go new file mode 100644 index 0000000..328f4f6 --- /dev/null +++ b/app/main/model/complaintManualModel_gen.go @@ -0,0 +1,391 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + complaintManualFieldNames = builder.RawFieldNames(&ComplaintManual{}) + complaintManualRows = strings.Join(complaintManualFieldNames, ",") + complaintManualRowsExpectAutoSet = strings.Join(stringx.Remove(complaintManualFieldNames, "`create_time`", "`update_time`"), ",") + complaintManualRowsWithPlaceHolder = strings.Join(stringx.Remove(complaintManualFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccComplaintManualIdPrefix = "cache:ycc:complaintManual:id:" +) + +type ( + complaintManualModel interface { + Insert(ctx context.Context, session sqlx.Session, data *ComplaintManual) (sql.Result, error) + FindOne(ctx context.Context, id string) (*ComplaintManual, error) + Update(ctx context.Context, session sqlx.Session, data *ComplaintManual) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintManual) 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 *ComplaintManual) 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) ([]*ComplaintManual, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintManual, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintManual, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintManual, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintManual, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultComplaintManualModel struct { + sqlc.CachedConn + table string + } + + ComplaintManual struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + ComplaintId string `db:"complaint_id"` // 关联投诉主表ID(关联complaint_main.id) + UserId sql.NullString `db:"user_id"` // 关联用户ID(如果有相关用户) + Subject sql.NullString `db:"subject"` // 投诉主题 + Priority sql.NullString `db:"priority"` // 优先级:low=低,medium=中,high=高,urgent=紧急 + Source sql.NullString `db:"source"` // 投诉来源:web=网站,phone=电话,email=邮件,other=其他 + AttachmentUrls sql.NullString `db:"attachment_urls"` // 附件URL列表(JSON数组字符串) + } +) + +func newComplaintManualModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultComplaintManualModel { + return &defaultComplaintManualModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`complaint_manual`", + } +} + +func (m *defaultComplaintManualModel) Insert(ctx context.Context, session sqlx.Session, data *ComplaintManual) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccComplaintManualIdKey := fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, data.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, complaintManualRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls) + }, yccComplaintManualIdKey) +} +func (m *defaultComplaintManualModel) insertUUID(data *ComplaintManual) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultComplaintManualModel) FindOne(ctx context.Context, id string) (*ComplaintManual, error) { + yccComplaintManualIdKey := fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, id) + var resp ComplaintManual + err := m.QueryRowCtx(ctx, &resp, yccComplaintManualIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", complaintManualRows, 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 *defaultComplaintManualModel) Update(ctx context.Context, session sqlx.Session, data *ComplaintManual) (sql.Result, error) { + yccComplaintManualIdKey := fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, 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, complaintManualRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls, data.Id) + }, yccComplaintManualIdKey) +} + +func (m *defaultComplaintManualModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ComplaintManual) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccComplaintManualIdKey := fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, 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, complaintManualRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ComplaintId, data.UserId, data.Subject, data.Priority, data.Source, data.AttachmentUrls, data.Id, oldVersion) + }, yccComplaintManualIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultComplaintManualModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *ComplaintManual) 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 "), "ComplaintManualModel delete err : %+v", err) + } + return nil +} + +func (m *defaultComplaintManualModel) 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 *defaultComplaintManualModel) 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 *defaultComplaintManualModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*ComplaintManual, error) { + + builder = builder.Columns(complaintManualRows) + + 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 []*ComplaintManual + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintManualModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintManual, error) { + + builder = builder.Columns(complaintManualRows) + + 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 []*ComplaintManual + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintManualModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ComplaintManual, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(complaintManualRows) + + 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, total, err + } + + var resp []*ComplaintManual + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultComplaintManualModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ComplaintManual, error) { + + builder = builder.Columns(complaintManualRows) + + 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 []*ComplaintManual + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintManualModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ComplaintManual, error) { + + builder = builder.Columns(complaintManualRows) + + 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 []*ComplaintManual + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultComplaintManualModel) 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 *defaultComplaintManualModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultComplaintManualModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + yccComplaintManualIdKey := fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, 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) + }, yccComplaintManualIdKey) + return err +} +func (m *defaultComplaintManualModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccComplaintManualIdPrefix, primary) +} +func (m *defaultComplaintManualModel) 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", complaintManualRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultComplaintManualModel) tableName() string { + return m.table +} diff --git a/app/main/model/featureModel_gen.go b/app/main/model/featureModel_gen.go index b9a2d96..430e5a1 100644 --- a/app/main/model/featureModel_gen.go +++ b/app/main/model/featureModel_gen.go @@ -58,14 +58,15 @@ type ( } Feature struct { - Id string `db:"id"` - CreateTime time.Time `db:"create_time"` // 创建时间 - UpdateTime time.Time `db:"update_time"` // 更新时间 - DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 - DelState int64 `db:"del_state"` // 删除状态 - Version int64 `db:"version"` // 版本号 - ApiId string `db:"api_id"` // API标识 - Name string `db:"name"` // 描述 + Id string `db:"id"` + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + ApiId string `db:"api_id"` // API标识 + Name string `db:"name"` // 描述 + WhitelistPrice float64 `db:"whitelist_price"` // 白名单屏蔽价格(单位:元) } ) @@ -82,11 +83,11 @@ func (m *defaultFeatureModel) Insert(ctx context.Context, session sqlx.Session, yccFeatureApiIdKey := fmt.Sprintf("%s%v", cacheYccFeatureApiIdPrefix, data.ApiId) yccFeatureIdKey := fmt.Sprintf("%s%v", cacheYccFeatureIdPrefix, data.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, featureRowsExpectAutoSet) + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?)", m.table, featureRowsExpectAutoSet) if session != nil { - return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name) + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice) } - return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name) + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.WhitelistPrice) }, yccFeatureApiIdKey, yccFeatureIdKey) } func (m *defaultFeatureModel) insertUUID(data *Feature) { @@ -153,9 +154,9 @@ func (m *defaultFeatureModel) Update(ctx context.Context, session sqlx.Session, 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, featureRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.Id) + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id) } - return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.Id) + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id) }, yccFeatureApiIdKey, yccFeatureIdKey) } @@ -176,9 +177,9 @@ func (m *defaultFeatureModel) UpdateWithVersion(ctx context.Context, session sql 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, featureRowsWithPlaceHolder) if session != nil { - return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.Id, oldVersion) + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id, oldVersion) } - return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.Id, oldVersion) + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.WhitelistPrice, newData.Id, oldVersion) }, yccFeatureApiIdKey, yccFeatureIdKey) if err != nil { return err diff --git a/app/main/model/userFeatureWhitelistModel.go b/app/main/model/userFeatureWhitelistModel.go new file mode 100644 index 0000000..e601ff3 --- /dev/null +++ b/app/main/model/userFeatureWhitelistModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ UserFeatureWhitelistModel = (*customUserFeatureWhitelistModel)(nil) + +type ( + // UserFeatureWhitelistModel is an interface to be customized, add more methods here, + // and implement the added methods in customUserFeatureWhitelistModel. + UserFeatureWhitelistModel interface { + userFeatureWhitelistModel + } + + customUserFeatureWhitelistModel struct { + *defaultUserFeatureWhitelistModel + } +) + +// NewUserFeatureWhitelistModel returns a model for the database table. +func NewUserFeatureWhitelistModel(conn sqlx.SqlConn, c cache.CacheConf) UserFeatureWhitelistModel { + return &customUserFeatureWhitelistModel{ + defaultUserFeatureWhitelistModel: newUserFeatureWhitelistModel(conn, c), + } +} diff --git a/app/main/model/userFeatureWhitelistModel_gen.go b/app/main/model/userFeatureWhitelistModel_gen.go new file mode 100644 index 0000000..a2ea7c7 --- /dev/null +++ b/app/main/model/userFeatureWhitelistModel_gen.go @@ -0,0 +1,484 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + userFeatureWhitelistFieldNames = builder.RawFieldNames(&UserFeatureWhitelist{}) + userFeatureWhitelistRows = strings.Join(userFeatureWhitelistFieldNames, ",") + userFeatureWhitelistRowsExpectAutoSet = strings.Join(stringx.Remove(userFeatureWhitelistFieldNames, "`create_time`", "`update_time`"), ",") + userFeatureWhitelistRowsWithPlaceHolder = strings.Join(stringx.Remove(userFeatureWhitelistFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccUserFeatureWhitelistIdPrefix = "cache:ycc:userFeatureWhitelist:id:" + cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix = "cache:ycc:userFeatureWhitelist:idCard:featureApiId:delState:" + cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix = "cache:ycc:userFeatureWhitelist:idCard:featureId:delState:" + cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix = "cache:ycc:userFeatureWhitelist:whitelistOrderId:" +) + +type ( + userFeatureWhitelistModel interface { + Insert(ctx context.Context, session sqlx.Session, data *UserFeatureWhitelist) (sql.Result, error) + FindOne(ctx context.Context, id string) (*UserFeatureWhitelist, error) + FindOneByIdCardFeatureApiIdDelState(ctx context.Context, idCard string, featureApiId string, delState int64) (*UserFeatureWhitelist, error) + FindOneByIdCardFeatureIdDelState(ctx context.Context, idCard string, featureId string, delState int64) (*UserFeatureWhitelist, error) + FindOneByWhitelistOrderId(ctx context.Context, whitelistOrderId sql.NullString) (*UserFeatureWhitelist, error) + Update(ctx context.Context, session sqlx.Session, data *UserFeatureWhitelist) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *UserFeatureWhitelist) 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 *UserFeatureWhitelist) 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) ([]*UserFeatureWhitelist, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserFeatureWhitelist, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserFeatureWhitelist, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserFeatureWhitelist, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserFeatureWhitelist, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultUserFeatureWhitelistModel struct { + sqlc.CachedConn + table string + } + + UserFeatureWhitelist struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + IdCard string `db:"id_card"` // 身份证号(查询对象标识) + FeatureId string `db:"feature_id"` // Feature的UUID + FeatureApiId string `db:"feature_api_id"` // Feature的API标识(冗余字段,方便查询) + UserId string `db:"user_id"` // 操作用户ID(代理的user_id,以后可扩展为普通用户) + OrderId sql.NullString `db:"order_id"` // 关联的查询订单ID(可选,记录来源) + WhitelistOrderId sql.NullString `db:"whitelist_order_id"` // 关联的白名单订单ID(用于付费) + Amount float64 `db:"amount"` // 费用(单位:元) + Status int64 `db:"status"` // 状态:1=生效,2=已失效 + } +) + +func newUserFeatureWhitelistModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserFeatureWhitelistModel { + return &defaultUserFeatureWhitelistModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`user_feature_whitelist`", + } +} + +func (m *defaultUserFeatureWhitelistModel) Insert(ctx context.Context, session sqlx.Session, data *UserFeatureWhitelist) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix, data.IdCard, data.FeatureApiId, data.DelState) + yccUserFeatureWhitelistIdCardFeatureIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix, data.IdCard, data.FeatureId, data.DelState) + yccUserFeatureWhitelistIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, data.Id) + yccUserFeatureWhitelistWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix, data.WhitelistOrderId) + 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, userFeatureWhitelistRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.IdCard, data.FeatureId, data.FeatureApiId, data.UserId, data.OrderId, data.WhitelistOrderId, data.Amount, data.Status) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.IdCard, data.FeatureId, data.FeatureApiId, data.UserId, data.OrderId, data.WhitelistOrderId, data.Amount, data.Status) + }, yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey, yccUserFeatureWhitelistIdCardFeatureIdDelStateKey, yccUserFeatureWhitelistIdKey, yccUserFeatureWhitelistWhitelistOrderIdKey) +} +func (m *defaultUserFeatureWhitelistModel) insertUUID(data *UserFeatureWhitelist) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultUserFeatureWhitelistModel) FindOne(ctx context.Context, id string) (*UserFeatureWhitelist, error) { + yccUserFeatureWhitelistIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, id) + var resp UserFeatureWhitelist + err := m.QueryRowCtx(ctx, &resp, yccUserFeatureWhitelistIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userFeatureWhitelistRows, 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 *defaultUserFeatureWhitelistModel) FindOneByIdCardFeatureApiIdDelState(ctx context.Context, idCard string, featureApiId string, delState int64) (*UserFeatureWhitelist, error) { + yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix, idCard, featureApiId, delState) + var resp UserFeatureWhitelist + err := m.QueryRowIndexCtx(ctx, &resp, yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `id_card` = ? and `feature_api_id` = ? and `del_state` = ? and del_state = ? limit 1", userFeatureWhitelistRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, idCard, featureApiId, delState, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindOneByIdCardFeatureIdDelState(ctx context.Context, idCard string, featureId string, delState int64) (*UserFeatureWhitelist, error) { + yccUserFeatureWhitelistIdCardFeatureIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix, idCard, featureId, delState) + var resp UserFeatureWhitelist + err := m.QueryRowIndexCtx(ctx, &resp, yccUserFeatureWhitelistIdCardFeatureIdDelStateKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `id_card` = ? and `feature_id` = ? and `del_state` = ? and del_state = ? limit 1", userFeatureWhitelistRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, idCard, featureId, delState, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindOneByWhitelistOrderId(ctx context.Context, whitelistOrderId sql.NullString) (*UserFeatureWhitelist, error) { + yccUserFeatureWhitelistWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix, whitelistOrderId) + var resp UserFeatureWhitelist + err := m.QueryRowIndexCtx(ctx, &resp, yccUserFeatureWhitelistWhitelistOrderIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `whitelist_order_id` = ? and del_state = ? limit 1", userFeatureWhitelistRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, whitelistOrderId, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) Update(ctx context.Context, session sqlx.Session, newData *UserFeatureWhitelist) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix, data.IdCard, data.FeatureApiId, data.DelState) + yccUserFeatureWhitelistIdCardFeatureIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix, data.IdCard, data.FeatureId, data.DelState) + yccUserFeatureWhitelistIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, data.Id) + yccUserFeatureWhitelistWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix, data.WhitelistOrderId) + 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, userFeatureWhitelistRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.IdCard, newData.FeatureId, newData.FeatureApiId, newData.UserId, newData.OrderId, newData.WhitelistOrderId, newData.Amount, newData.Status, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.IdCard, newData.FeatureId, newData.FeatureApiId, newData.UserId, newData.OrderId, newData.WhitelistOrderId, newData.Amount, newData.Status, newData.Id) + }, yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey, yccUserFeatureWhitelistIdCardFeatureIdDelStateKey, yccUserFeatureWhitelistIdKey, yccUserFeatureWhitelistWhitelistOrderIdKey) +} + +func (m *defaultUserFeatureWhitelistModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *UserFeatureWhitelist) error { + + oldVersion := newData.Version + newData.Version += 1 + + var sqlResult sql.Result + var err error + + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix, data.IdCard, data.FeatureApiId, data.DelState) + yccUserFeatureWhitelistIdCardFeatureIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix, data.IdCard, data.FeatureId, data.DelState) + yccUserFeatureWhitelistIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, data.Id) + yccUserFeatureWhitelistWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix, data.WhitelistOrderId) + 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, userFeatureWhitelistRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.IdCard, newData.FeatureId, newData.FeatureApiId, newData.UserId, newData.OrderId, newData.WhitelistOrderId, newData.Amount, newData.Status, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.IdCard, newData.FeatureId, newData.FeatureApiId, newData.UserId, newData.OrderId, newData.WhitelistOrderId, newData.Amount, newData.Status, newData.Id, oldVersion) + }, yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey, yccUserFeatureWhitelistIdCardFeatureIdDelStateKey, yccUserFeatureWhitelistIdKey, yccUserFeatureWhitelistWhitelistOrderIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultUserFeatureWhitelistModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *UserFeatureWhitelist) 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 "), "UserFeatureWhitelistModel delete err : %+v", err) + } + return nil +} + +func (m *defaultUserFeatureWhitelistModel) 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 *defaultUserFeatureWhitelistModel) 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 *defaultUserFeatureWhitelistModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*UserFeatureWhitelist, error) { + + builder = builder.Columns(userFeatureWhitelistRows) + + 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 []*UserFeatureWhitelist + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserFeatureWhitelist, error) { + + builder = builder.Columns(userFeatureWhitelistRows) + + 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 []*UserFeatureWhitelist + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserFeatureWhitelist, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(userFeatureWhitelistRows) + + 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, total, err + } + + var resp []*UserFeatureWhitelist + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserFeatureWhitelist, error) { + + builder = builder.Columns(userFeatureWhitelistRows) + + 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 []*UserFeatureWhitelist + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserFeatureWhitelist, error) { + + builder = builder.Columns(userFeatureWhitelistRows) + + 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 []*UserFeatureWhitelist + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserFeatureWhitelistModel) 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 *defaultUserFeatureWhitelistModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultUserFeatureWhitelistModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureApiIdDelStatePrefix, data.IdCard, data.FeatureApiId, data.DelState) + yccUserFeatureWhitelistIdCardFeatureIdDelStateKey := fmt.Sprintf("%s%v:%v:%v", cacheYccUserFeatureWhitelistIdCardFeatureIdDelStatePrefix, data.IdCard, data.FeatureId, data.DelState) + yccUserFeatureWhitelistIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, id) + yccUserFeatureWhitelistWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistWhitelistOrderIdPrefix, data.WhitelistOrderId) + _, 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) + }, yccUserFeatureWhitelistIdCardFeatureApiIdDelStateKey, yccUserFeatureWhitelistIdCardFeatureIdDelStateKey, yccUserFeatureWhitelistIdKey, yccUserFeatureWhitelistWhitelistOrderIdKey) + return err +} +func (m *defaultUserFeatureWhitelistModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccUserFeatureWhitelistIdPrefix, primary) +} +func (m *defaultUserFeatureWhitelistModel) 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", userFeatureWhitelistRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultUserFeatureWhitelistModel) tableName() string { + return m.table +} diff --git a/app/main/model/whitelistOrderItemModel.go b/app/main/model/whitelistOrderItemModel.go new file mode 100644 index 0000000..77bc118 --- /dev/null +++ b/app/main/model/whitelistOrderItemModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ WhitelistOrderItemModel = (*customWhitelistOrderItemModel)(nil) + +type ( + // WhitelistOrderItemModel is an interface to be customized, add more methods here, + // and implement the added methods in customWhitelistOrderItemModel. + WhitelistOrderItemModel interface { + whitelistOrderItemModel + } + + customWhitelistOrderItemModel struct { + *defaultWhitelistOrderItemModel + } +) + +// NewWhitelistOrderItemModel returns a model for the database table. +func NewWhitelistOrderItemModel(conn sqlx.SqlConn, c cache.CacheConf) WhitelistOrderItemModel { + return &customWhitelistOrderItemModel{ + defaultWhitelistOrderItemModel: newWhitelistOrderItemModel(conn, c), + } +} diff --git a/app/main/model/whitelistOrderItemModel_gen.go b/app/main/model/whitelistOrderItemModel_gen.go new file mode 100644 index 0000000..02dfdd7 --- /dev/null +++ b/app/main/model/whitelistOrderItemModel_gen.go @@ -0,0 +1,390 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + whitelistOrderItemFieldNames = builder.RawFieldNames(&WhitelistOrderItem{}) + whitelistOrderItemRows = strings.Join(whitelistOrderItemFieldNames, ",") + whitelistOrderItemRowsExpectAutoSet = strings.Join(stringx.Remove(whitelistOrderItemFieldNames, "`create_time`", "`update_time`"), ",") + whitelistOrderItemRowsWithPlaceHolder = strings.Join(stringx.Remove(whitelistOrderItemFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccWhitelistOrderItemIdPrefix = "cache:ycc:whitelistOrderItem:id:" +) + +type ( + whitelistOrderItemModel interface { + Insert(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) (sql.Result, error) + FindOne(ctx context.Context, id string) (*WhitelistOrderItem, error) + Update(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) 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 *WhitelistOrderItem) 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) ([]*WhitelistOrderItem, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrderItem, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrderItem, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*WhitelistOrderItem, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*WhitelistOrderItem, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultWhitelistOrderItemModel struct { + sqlc.CachedConn + table string + } + + WhitelistOrderItem struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + OrderId string `db:"order_id"` // 订单ID(关联whitelist_order.id) + FeatureId string `db:"feature_id"` // Feature的UUID + FeatureApiId string `db:"feature_api_id"` // Feature的API标识(冗余) + FeatureName string `db:"feature_name"` // Feature的名称(冗余) + Price float64 `db:"price"` // 单价(单位:元) + } +) + +func newWhitelistOrderItemModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultWhitelistOrderItemModel { + return &defaultWhitelistOrderItemModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`whitelist_order_item`", + } +} + +func (m *defaultWhitelistOrderItemModel) Insert(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccWhitelistOrderItemIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, data.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, whitelistOrderItemRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price) + }, yccWhitelistOrderItemIdKey) +} +func (m *defaultWhitelistOrderItemModel) insertUUID(data *WhitelistOrderItem) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultWhitelistOrderItemModel) FindOne(ctx context.Context, id string) (*WhitelistOrderItem, error) { + yccWhitelistOrderItemIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, id) + var resp WhitelistOrderItem + err := m.QueryRowCtx(ctx, &resp, yccWhitelistOrderItemIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", whitelistOrderItemRows, 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 *defaultWhitelistOrderItemModel) Update(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) (sql.Result, error) { + yccWhitelistOrderItemIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, 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, whitelistOrderItemRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price, data.Id) + }, yccWhitelistOrderItemIdKey) +} + +func (m *defaultWhitelistOrderItemModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + yccWhitelistOrderItemIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, 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, whitelistOrderItemRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.OrderId, data.FeatureId, data.FeatureApiId, data.FeatureName, data.Price, data.Id, oldVersion) + }, yccWhitelistOrderItemIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultWhitelistOrderItemModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *WhitelistOrderItem) 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 "), "WhitelistOrderItemModel delete err : %+v", err) + } + return nil +} + +func (m *defaultWhitelistOrderItemModel) 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 *defaultWhitelistOrderItemModel) 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 *defaultWhitelistOrderItemModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*WhitelistOrderItem, error) { + + builder = builder.Columns(whitelistOrderItemRows) + + 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 []*WhitelistOrderItem + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderItemModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrderItem, error) { + + builder = builder.Columns(whitelistOrderItemRows) + + 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 []*WhitelistOrderItem + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderItemModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrderItem, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(whitelistOrderItemRows) + + 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, total, err + } + + var resp []*WhitelistOrderItem + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultWhitelistOrderItemModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*WhitelistOrderItem, error) { + + builder = builder.Columns(whitelistOrderItemRows) + + 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 []*WhitelistOrderItem + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderItemModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*WhitelistOrderItem, error) { + + builder = builder.Columns(whitelistOrderItemRows) + + 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 []*WhitelistOrderItem + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderItemModel) 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 *defaultWhitelistOrderItemModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultWhitelistOrderItemModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + yccWhitelistOrderItemIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, 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) + }, yccWhitelistOrderItemIdKey) + return err +} +func (m *defaultWhitelistOrderItemModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccWhitelistOrderItemIdPrefix, primary) +} +func (m *defaultWhitelistOrderItemModel) 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", whitelistOrderItemRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultWhitelistOrderItemModel) tableName() string { + return m.table +} diff --git a/app/main/model/whitelistOrderModel.go b/app/main/model/whitelistOrderModel.go new file mode 100644 index 0000000..eae263d --- /dev/null +++ b/app/main/model/whitelistOrderModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ WhitelistOrderModel = (*customWhitelistOrderModel)(nil) + +type ( + // WhitelistOrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customWhitelistOrderModel. + WhitelistOrderModel interface { + whitelistOrderModel + } + + customWhitelistOrderModel struct { + *defaultWhitelistOrderModel + } +) + +// NewWhitelistOrderModel returns a model for the database table. +func NewWhitelistOrderModel(conn sqlx.SqlConn, c cache.CacheConf) WhitelistOrderModel { + return &customWhitelistOrderModel{ + defaultWhitelistOrderModel: newWhitelistOrderModel(conn, c), + } +} diff --git a/app/main/model/whitelistOrderModel_gen.go b/app/main/model/whitelistOrderModel_gen.go new file mode 100644 index 0000000..18137fa --- /dev/null +++ b/app/main/model/whitelistOrderModel_gen.go @@ -0,0 +1,433 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "reflect" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "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" + "ycc-server/common/globalkey" +) + +var ( + whitelistOrderFieldNames = builder.RawFieldNames(&WhitelistOrder{}) + whitelistOrderRows = strings.Join(whitelistOrderFieldNames, ",") + whitelistOrderRowsExpectAutoSet = strings.Join(stringx.Remove(whitelistOrderFieldNames, "`create_time`", "`update_time`"), ",") + whitelistOrderRowsWithPlaceHolder = strings.Join(stringx.Remove(whitelistOrderFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheYccWhitelistOrderIdPrefix = "cache:ycc:whitelistOrder:id:" + cacheYccWhitelistOrderOrderNoPrefix = "cache:ycc:whitelistOrder:orderNo:" +) + +type ( + whitelistOrderModel interface { + Insert(ctx context.Context, session sqlx.Session, data *WhitelistOrder) (sql.Result, error) + FindOne(ctx context.Context, id string) (*WhitelistOrder, error) + FindOneByOrderNo(ctx context.Context, orderNo string) (*WhitelistOrder, error) + Update(ctx context.Context, session sqlx.Session, data *WhitelistOrder) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *WhitelistOrder) 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 *WhitelistOrder) 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) ([]*WhitelistOrder, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrder, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrder, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*WhitelistOrder, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*WhitelistOrder, error) + Delete(ctx context.Context, session sqlx.Session, id string) error + } + + defaultWhitelistOrderModel struct { + sqlc.CachedConn + table string + } + + WhitelistOrder struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号(乐观锁) + OrderNo string `db:"order_no"` // 订单号(唯一) + UserId string `db:"user_id"` // 用户ID(代理的user_id) + IdCard string `db:"id_card"` // 身份证号(查询对象标识) + TotalAmount float64 `db:"total_amount"` // 总金额(单位:元) + Status int64 `db:"status"` // 订单状态:1=待支付,2=已支付,3=已取消 + PaymentMethod sql.NullString `db:"payment_method"` // 支付方式:wechat, alipay, appleiap等 + PaymentPlatform sql.NullString `db:"payment_platform"` // 支付平台:alipay, wechat, appleiap等 + PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号(第三方平台的订单号) + PayTime sql.NullTime `db:"pay_time"` // 支付时间 + } +) + +func newWhitelistOrderModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultWhitelistOrderModel { + return &defaultWhitelistOrderModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`whitelist_order`", + } +} + +func (m *defaultWhitelistOrderModel) Insert(ctx context.Context, session sqlx.Session, data *WhitelistOrder) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + m.insertUUID(data) + yccWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, data.Id) + yccWhitelistOrderOrderNoKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderOrderNoPrefix, data.OrderNo) + 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, whitelistOrderRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.OrderNo, data.UserId, data.IdCard, data.TotalAmount, data.Status, data.PaymentMethod, data.PaymentPlatform, data.PlatformOrderId, data.PayTime) + } + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.OrderNo, data.UserId, data.IdCard, data.TotalAmount, data.Status, data.PaymentMethod, data.PaymentPlatform, data.PlatformOrderId, data.PayTime) + }, yccWhitelistOrderIdKey, yccWhitelistOrderOrderNoKey) +} +func (m *defaultWhitelistOrderModel) insertUUID(data *WhitelistOrder) { + t := reflect.TypeOf(data).Elem() + v := reflect.ValueOf(data).Elem() + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + if sf.Tag.Get("db") == "id" { + f := v.Field(i) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + if f.String() == "" { + f.SetString(uuid.NewString()) + } + } + break + } + } +} + +func (m *defaultWhitelistOrderModel) FindOne(ctx context.Context, id string) (*WhitelistOrder, error) { + yccWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, id) + var resp WhitelistOrder + err := m.QueryRowCtx(ctx, &resp, yccWhitelistOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", whitelistOrderRows, 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 *defaultWhitelistOrderModel) FindOneByOrderNo(ctx context.Context, orderNo string) (*WhitelistOrder, error) { + yccWhitelistOrderOrderNoKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderOrderNoPrefix, orderNo) + var resp WhitelistOrder + err := m.QueryRowIndexCtx(ctx, &resp, yccWhitelistOrderOrderNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `order_no` = ? and del_state = ? limit 1", whitelistOrderRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, orderNo, globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderModel) Update(ctx context.Context, session sqlx.Session, newData *WhitelistOrder) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + yccWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, data.Id) + yccWhitelistOrderOrderNoKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderOrderNoPrefix, data.OrderNo) + 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, whitelistOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.OrderNo, newData.UserId, newData.IdCard, newData.TotalAmount, newData.Status, newData.PaymentMethod, newData.PaymentPlatform, newData.PlatformOrderId, newData.PayTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.OrderNo, newData.UserId, newData.IdCard, newData.TotalAmount, newData.Status, newData.PaymentMethod, newData.PaymentPlatform, newData.PlatformOrderId, newData.PayTime, newData.Id) + }, yccWhitelistOrderIdKey, yccWhitelistOrderOrderNoKey) +} + +func (m *defaultWhitelistOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *WhitelistOrder) error { + + oldVersion := newData.Version + newData.Version += 1 + + var sqlResult sql.Result + var err error + + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + yccWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, data.Id) + yccWhitelistOrderOrderNoKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderOrderNoPrefix, data.OrderNo) + 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, whitelistOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.OrderNo, newData.UserId, newData.IdCard, newData.TotalAmount, newData.Status, newData.PaymentMethod, newData.PaymentPlatform, newData.PlatformOrderId, newData.PayTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.OrderNo, newData.UserId, newData.IdCard, newData.TotalAmount, newData.Status, newData.PaymentMethod, newData.PaymentPlatform, newData.PlatformOrderId, newData.PayTime, newData.Id, oldVersion) + }, yccWhitelistOrderIdKey, yccWhitelistOrderOrderNoKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultWhitelistOrderModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *WhitelistOrder) 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 "), "WhitelistOrderModel delete err : %+v", err) + } + return nil +} + +func (m *defaultWhitelistOrderModel) 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 *defaultWhitelistOrderModel) 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 *defaultWhitelistOrderModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*WhitelistOrder, error) { + + builder = builder.Columns(whitelistOrderRows) + + 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 []*WhitelistOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrder, error) { + + builder = builder.Columns(whitelistOrderRows) + + 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 []*WhitelistOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*WhitelistOrder, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(whitelistOrderRows) + + 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, total, err + } + + var resp []*WhitelistOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultWhitelistOrderModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*WhitelistOrder, error) { + + builder = builder.Columns(whitelistOrderRows) + + 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 []*WhitelistOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*WhitelistOrder, error) { + + builder = builder.Columns(whitelistOrderRows) + + 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 []*WhitelistOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultWhitelistOrderModel) 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 *defaultWhitelistOrderModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultWhitelistOrderModel) Delete(ctx context.Context, session sqlx.Session, id string) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + yccWhitelistOrderIdKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, id) + yccWhitelistOrderOrderNoKey := fmt.Sprintf("%s%v", cacheYccWhitelistOrderOrderNoPrefix, data.OrderNo) + _, 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) + }, yccWhitelistOrderIdKey, yccWhitelistOrderOrderNoKey) + return err +} +func (m *defaultWhitelistOrderModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheYccWhitelistOrderIdPrefix, primary) +} +func (m *defaultWhitelistOrderModel) 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", whitelistOrderRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultWhitelistOrderModel) tableName() string { + return m.table +} diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index e171a87..90dde89 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -28,7 +28,7 @@ $tables = @( # "admin_user_role", # "agent", # "agent_commission", - "agent_config" + # "agent_config" # "agent_freeze_task", # "agent_invite_code", # "agent_invite_code_usage", @@ -41,11 +41,12 @@ $tables = @( # "agent_short_link", # "agent_upgrade", # "agent_wallet", - # "agent_withdrawal", + # "agent_withdrawal" # "agent_withdrawal_tax", # "authorization_document", # "example", - # "feature", + # "feature" + # "alipay_from_callback" # "global_notifications", # "order", # "order_refund", @@ -57,6 +58,13 @@ $tables = @( # "query_cleanup_log", # "user", # "user_auth" + # "whitelist_order", + # "whitelist_order_item", + # "user_feature_whitelist" + "complaint_main", + "complaint_alipay", + "complaint_alipay_trade", + "complaint_manual" ) # 为每个表生成模型 diff --git a/deploy/sql/alipay_from_callback_migration.sql b/deploy/sql/alipay_from_callback_migration.sql new file mode 100644 index 0000000..2b67006 --- /dev/null +++ b/deploy/sql/alipay_from_callback_migration.sql @@ -0,0 +1,40 @@ +-- ============================================ +-- 支付宝from消息回调记录表 - 创建表 +-- ============================================ +-- 说明:用于记录和存储支付宝from消息回调的所有记录 +-- 执行时间:2025-XX-XX +-- ============================================ + +CREATE TABLE `alipay_from_callback` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ +`msg_method` varchar(100) NOT NULL COMMENT '消息类型,如:alipay.merchant.tradecomplain.changed, alipay.security.risk.complaints.merchants.notify', + `app_id` varchar(50) NOT NULL COMMENT '应用ID', + `notify_id` varchar(100) DEFAULT NULL COMMENT '通知ID(支付宝返回的)', + `biz_content` text NOT NULL COMMENT '业务内容(JSON字符串,存储完整的biz_content)', + `status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '处理状态:pending=待处理,processed=已处理,failed=处理失败', + `error_message` text DEFAULT NULL COMMENT '错误信息(如果处理失败)', + + PRIMARY KEY (`id`), + KEY `idx_msg_method` (`msg_method`) COMMENT '优化按消息类型查询', + KEY `idx_app_id` (`app_id`) COMMENT '优化按应用ID查询', + KEY `idx_notify_id` (`notify_id`) COMMENT '优化按通知ID查询(用于去重)', + KEY `idx_status` (`status`) COMMENT '优化按处理状态查询', + KEY `idx_create_time` (`create_time`) COMMENT '优化按创建时间查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='支付宝from消息回调记录表'; + +-- ============================================ +-- 说明: +-- 1. 此表用于记录所有支付宝from消息回调的原始数据 +-- 2. biz_content 存储完整的JSON字符串,便于后续查看和调试 +-- 3. status 字段用于标记回调的处理状态 +-- 4. notify_id 可用于去重,避免重复处理同一条回调 +-- 5. 支持软删除(del_state) +-- 6. 由于回调类型多样,不解析biz_content,统一存储JSON字符串 +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/complaint_system_migration.sql b/deploy/sql/complaint_system_migration.sql new file mode 100644 index 0000000..d386e7a --- /dev/null +++ b/deploy/sql/complaint_system_migration.sql @@ -0,0 +1,173 @@ +-- ============================================ +-- 投诉系统表结构 - 统一创建 +-- ============================================ +-- 说明:投诉系统统一使用 complaint_ 前缀 +-- 执行时间:2025-XX-XX +-- ============================================ + +-- ============================================ +-- 1. 投诉主表(complaint_main) +-- ============================================ +-- 作用:统一管理所有类型的投诉,包含投诉人的基本信息和通用状态 +CREATE TABLE `complaint_main` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`type` varchar(20) NOT NULL COMMENT '投诉类型:alipay=支付宝投诉,manual=主动投诉', + `order_id` CHAR(36) DEFAULT NULL COMMENT '关联订单ID(如果有相关订单)', + `name` varchar(100) DEFAULT NULL COMMENT '投诉人姓名', + `contact` varchar(50) DEFAULT NULL COMMENT '联系方式(手机号、邮箱等)', + `content` text DEFAULT NULL COMMENT '投诉内容', + `status` varchar(50) DEFAULT NULL COMMENT '投诉状态(通用状态,如:pending=待处理,processing=处理中,resolved=已解决,closed=已关闭)', + `status_description` varchar(200) DEFAULT NULL COMMENT '状态描述', + `remark` text DEFAULT NULL COMMENT '处理备注', + `handler_id` CHAR(36) DEFAULT NULL COMMENT '处理人ID(关联管理员或其他处理人员)', + `handle_time` datetime DEFAULT NULL COMMENT '处理时间', + + PRIMARY KEY (`id`), + KEY `idx_type` (`type`) COMMENT '优化按投诉类型查询', + KEY `idx_order_id` (`order_id`) COMMENT '优化按订单查询', + KEY `idx_status` (`status`) COMMENT '优化按状态查询', + KEY `idx_name` (`name`) COMMENT '优化按姓名查询', + KEY `idx_contact` (`contact`) COMMENT '优化按联系方式查询', + KEY `idx_create_time` (`create_time`) COMMENT '优化按创建时间查询', + KEY `idx_handler_id` (`handler_id`) COMMENT '优化按处理人查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='投诉主表'; + +-- ============================================ +-- 2. 支付宝投诉表(complaint_alipay) +-- ============================================ +-- 作用:存储支付宝投诉的详细信息 +CREATE TABLE `complaint_alipay` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`complaint_id` CHAR(36) NOT NULL COMMENT '关联投诉主表ID(关联complaint_main.id)', + `alipay_id` bigint NOT NULL COMMENT '支付宝投诉主表的主键id(支付宝返回的id)', + `task_id` varchar(100) NOT NULL COMMENT '投诉单号id(支付宝返回的task_id,用于唯一标识)', + `opposite_pid` varchar(50) DEFAULT NULL COMMENT '被投诉人pid', + `opposite_name` varchar(200) DEFAULT NULL COMMENT '投诉单中被投诉方的名称', + `complain_amount` decimal(10,2) DEFAULT NULL COMMENT '投诉单涉及交易总金额(单位:人民币元)', + `gmt_complain` datetime DEFAULT NULL COMMENT '投诉时间', + `gmt_process` datetime DEFAULT NULL COMMENT '处理时间', + `gmt_overdue` datetime DEFAULT NULL COMMENT '过期时间', + `complain_content` text DEFAULT NULL COMMENT '用户投诉内容', + `trade_no` varchar(100) DEFAULT NULL COMMENT '投诉交易单号', + `status` varchar(50) DEFAULT NULL COMMENT '投诉状态(支付宝返回的状态)', + `status_description` varchar(200) DEFAULT NULL COMMENT '投诉单状态枚举值描述', + `process_code` varchar(50) DEFAULT NULL COMMENT '商家处理结果码', + `process_message` varchar(500) DEFAULT NULL COMMENT '商家处理结果码对应描述', + `process_remark` text DEFAULT NULL COMMENT '商家处理备注', + `process_img_url_list` text DEFAULT NULL COMMENT '商家处理备注图片url列表(JSON数组字符串)', + `gmt_risk_finish_time` datetime DEFAULT NULL COMMENT '推送时间', + `complain_url` varchar(500) DEFAULT NULL COMMENT '投诉网址', + `certify_info` text DEFAULT NULL COMMENT '投诉凭证图片信息(JSON数组字符串)', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_task_id` (`task_id`) COMMENT '投诉单号唯一索引', + UNIQUE KEY `uk_alipay_id` (`alipay_id`) COMMENT '支付宝id唯一索引', + KEY `idx_complaint_id` (`complaint_id`) COMMENT '优化按投诉主表查询', + KEY `idx_opposite_pid` (`opposite_pid`) COMMENT '优化按被投诉人查询', + KEY `idx_trade_no` (`trade_no`) COMMENT '优化按交易单号查询', + KEY `idx_status` (`status`) COMMENT '优化按状态查询', + KEY `idx_gmt_complain` (`gmt_complain`) COMMENT '优化按投诉时间查询(用于轮询新投诉)', + KEY `idx_create_time` (`create_time`) COMMENT '优化按创建时间查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='支付宝投诉表'; + +-- ============================================ +-- 3. 支付宝投诉交易信息表(complaint_alipay_trade) +-- ============================================ +-- 作用:存储支付宝投诉涉及的交易信息(一个投诉可能涉及多个交易) +CREATE TABLE `complaint_alipay_trade` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`complaint_id` CHAR(36) NOT NULL COMMENT '投诉主表ID(关联complaint_alipay.id)', + `alipay_trade_id` bigint DEFAULT NULL COMMENT '交易信息表主键id(支付宝返回的id)', + `alipay_complaint_record_id` bigint DEFAULT NULL COMMENT '投诉主表id(支付宝返回的complaint_record_id)', + `trade_no` varchar(100) DEFAULT NULL COMMENT '支付宝交易单号', + `out_no` varchar(100) DEFAULT NULL COMMENT '商家订单号', + `gmt_trade` datetime DEFAULT NULL COMMENT '交易时间', + `gmt_refund` datetime DEFAULT NULL COMMENT '退款时间', + `status` varchar(50) DEFAULT NULL COMMENT '交易投诉状态', + `status_description` varchar(200) DEFAULT NULL COMMENT '交易投诉状态描述', + `amount` decimal(10,2) DEFAULT NULL COMMENT '交易单金额(单位:人民币元)', + + PRIMARY KEY (`id`), + KEY `idx_complaint_id` (`complaint_id`) COMMENT '优化按投诉主表查询', + KEY `idx_trade_no` (`trade_no`) COMMENT '优化按交易单号查询', + KEY `idx_out_no` (`out_no`) COMMENT '优化按商家订单号查询', + KEY `idx_status` (`status`) COMMENT '优化按状态查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='支付宝投诉交易信息表'; + +-- ============================================ +-- 4. 主动投诉表(complaint_manual) +-- ============================================ +-- 作用:存储主动投诉的详细信息 +CREATE TABLE `complaint_manual` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`complaint_id` CHAR(36) NOT NULL COMMENT '关联投诉主表ID(关联complaint_main.id)', + `user_id` CHAR(36) DEFAULT NULL COMMENT '关联用户ID(如果有相关用户)', + `subject` varchar(200) DEFAULT NULL COMMENT '投诉主题', + `priority` varchar(20) DEFAULT NULL COMMENT '优先级:low=低,medium=中,high=高,urgent=紧急', + `source` varchar(50) DEFAULT NULL COMMENT '投诉来源:web=网站,phone=电话,email=邮件,other=其他', + `attachment_urls` text DEFAULT NULL COMMENT '附件URL列表(JSON数组字符串)', + + PRIMARY KEY (`id`), + KEY `idx_complaint_id` (`complaint_id`) COMMENT '优化按投诉主表查询', + KEY `idx_user_id` (`user_id`) COMMENT '优化按用户查询', + KEY `idx_priority` (`priority`) COMMENT '优化按优先级查询', + KEY `idx_source` (`source`) COMMENT '优化按来源查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='主动投诉表'; + +-- ============================================ +-- 表关系说明 +-- ============================================ +-- 1. complaint_main (投诉主表) +-- ├── 包含:投诉类型、订单ID、姓名、联系方式、投诉内容、状态等通用信息 +-- │ +-- ├── type: alipay +-- │ └── complaint_alipay (支付宝投诉表) +-- │ └── complaint_alipay_trade (支付宝投诉交易信息表) +-- │ +-- └── type: manual +-- └── complaint_manual (主动投诉表) +-- +-- 2. 关联关系: +-- - complaint_main.order_id -> order.id (订单关联) +-- - complaint_alipay.complaint_id -> complaint_main.id +-- - complaint_alipay_trade.complaint_id -> complaint_alipay.id +-- - complaint_manual.complaint_id -> complaint_main.id +-- - complaint_manual.user_id -> user.id (用户关联) +-- +-- 3. 字段说明: +-- - 主表统一存储:姓名、联系方式、投诉内容、通用状态 +-- - 子表存储:特定类型的详细信息 +-- - 订单关联:在主表中统一管理,方便查询 +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/feature_whitelist_price_migration.sql b/deploy/sql/feature_whitelist_price_migration.sql new file mode 100644 index 0000000..080b145 --- /dev/null +++ b/deploy/sql/feature_whitelist_price_migration.sql @@ -0,0 +1,17 @@ +-- ============================================ +-- Feature表 - 添加白名单屏蔽价格字段 +-- ============================================ +-- 说明:为 feature 表添加白名单屏蔽价格字段 +-- 执行时间:2024-XX-XX +-- ============================================ + +-- 添加白名单屏蔽价格字段 +ALTER TABLE `feature` +ADD COLUMN `whitelist_price` decimal(10, 2) DEFAULT 0.00 COMMENT '白名单屏蔽价格(单位:元)' AFTER `name`; + +-- ============================================ +-- 说明: +-- 1. whitelist_price 默认值为 0.00,表示该feature不支持白名单屏蔽 +-- 2. 如果 whitelist_price > 0,表示该feature可以加入白名单,价格为 whitelist_price +-- 3. 后台可以在"功能管理"页面配置每个feature的屏蔽价格 +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/refund_commission_cancel_migration.sql b/deploy/sql/refund_commission_cancel_migration.sql new file mode 100644 index 0000000..8daa233 --- /dev/null +++ b/deploy/sql/refund_commission_cancel_migration.sql @@ -0,0 +1,75 @@ +-- 退款收回代理佣金功能 - 数据库迁移SQL +-- 说明:此功能使用现有的 status 字段来标记撤销状态 +-- +-- 状态字段含义(根据代码和模型注释): +-- agent_commission.status: 1=已发放,2=已冻结,3=已取消(撤销) +-- agent_rebate.status: 1=已发放,2=已冻结,3=已取消(撤销) +-- agent_freeze_task.status: 1=待解冻,2=已解冻,3=已取消(撤销) +-- +-- 注意:如果数据库中这三个表的 status 字段含义不同(例如:1=待解冻,2=已解冻,3=已取消), +-- 需要确认是否有冲突。当前实现使用 status=3 表示已撤销。 +-- 如果 status=3 与其他业务冲突,请使用方案2添加新字段。 + +-- 检查字段是否存在(可选,用于验证) +SELECT + COLUMN_NAME, + COLUMN_TYPE, + COLUMN_COMMENT +FROM INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN ( + 'agent_commission', + 'agent_rebate', + 'agent_freeze_task' + ) + AND COLUMN_NAME = 'status'; + +-- ========== 方案1:使用现有 status 字段(当前实现,推荐) ========== +-- 如果 status=3 的含义是"已取消",可以直接使用,无需修改数据库 +-- 代码中已实现:撤销时将 status 设置为 3 +-- 注意:需要确认数据库中这三个表的 status 字段实际含义是否与代码一致 + +-- ========== 方案2:添加新字段(如果 status=3 与其他业务冲突) ========== +-- 如果 status 字段的含义不同(例如:1=待解冻,2=已解冻,3=已取消,但3不是用于撤销), +-- 可以使用以下SQL添加新字段: +-- +-- ALTER TABLE `agent_commission` +-- ADD COLUMN `cancel_status` TINYINT DEFAULT 0 COMMENT '撤销状态:0=正常,1=已撤销' AFTER `status`; +-- +-- ALTER TABLE `agent_rebate` +-- ADD COLUMN `cancel_status` TINYINT DEFAULT 0 COMMENT '撤销状态:0=正常,1=已撤销' AFTER `status`; +-- +-- ALTER TABLE `agent_freeze_task` +-- ADD COLUMN `cancel_status` TINYINT DEFAULT 0 COMMENT '撤销状态:0=正常,1=已撤销' AFTER `status`; +-- +-- 如果使用方案2,需要修改代码: +-- 1. 在 CancelAgentCommission 方法中,使用 cancel_status 字段而不是 status 字段 +-- 2. 查询时使用 cancel_status = 1 来筛选已撤销的记录 + +-- 当前实现不需要修改数据库结构,使用现有的 status 字段即可 +-- 如果后续需要查询已撤销的记录,可以使用: +SELECT * FROM agent_commission WHERE status = 3 AND del_state = 0; + +SELECT * FROM agent_rebate WHERE status = 3 AND del_state = 0; + +SELECT * FROM agent_freeze_task WHERE status = 3 AND del_state = 0; + +-- 如果需要添加索引优化查询(可选): +CREATE INDEX `idx_order_id_status` ON `agent_commission` ( + `order_id`, + `status`, + `del_state` +); + +CREATE INDEX `idx_order_id_status` ON `agent_rebate` ( + `order_id`, + `status`, + `del_state` +); + +CREATE INDEX `idx_order_id_status` ON `agent_freeze_task` ( + `order_id`, + `status`, + `del_state` +); \ No newline at end of file diff --git a/deploy/sql/user_feature_whitelist_migration.sql b/deploy/sql/user_feature_whitelist_migration.sql new file mode 100644 index 0000000..5946844 --- /dev/null +++ b/deploy/sql/user_feature_whitelist_migration.sql @@ -0,0 +1,44 @@ +-- ============================================ +-- 用户模块白名单表 - 创建表 +-- ============================================ +-- 说明:创建 user_feature_whitelist 表,用于记录用户身份证号对应的feature屏蔽白名单 +-- 执行时间:2024-XX-XX +-- ============================================ + +CREATE TABLE `user_feature_whitelist` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`id_card` varchar(50) NOT NULL COMMENT '身份证号(查询对象标识)', + `feature_id` CHAR(36) NOT NULL COMMENT 'Feature的UUID', + `feature_api_id` varchar(100) NOT NULL COMMENT 'Feature的API标识(冗余字段,方便查询)', + `user_id` CHAR(36) NOT NULL COMMENT '操作用户ID(代理的user_id,以后可扩展为普通用户)', + `order_id` CHAR(36) DEFAULT NULL COMMENT '关联的查询订单ID(可选,记录来源)', + `whitelist_order_id` CHAR(36) DEFAULT NULL COMMENT '关联的白名单订单ID(用于付费)', + `amount` decimal(10,2) NOT NULL COMMENT '费用(单位:元)', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1=生效,2=已失效', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_id_card_feature_id` (`id_card`, `feature_id`, `del_state`) COMMENT '身份证号和feature_id联合唯一索引', + UNIQUE KEY `uk_id_card_feature_api_id` (`id_card`, `feature_api_id`, `del_state`) COMMENT '身份证号和feature_api_id联合唯一索引', + UNIQUE KEY `uk_whitelist_order_id` (`whitelist_order_id`) COMMENT '白名单订单ID唯一索引', + KEY `idx_id_card` (`id_card`) COMMENT '优化按身份证号查询', + KEY `idx_feature_id` (`feature_id`) COMMENT '优化按feature查询', + KEY `idx_feature_api_id` (`feature_api_id`) COMMENT '优化按API标识查询', + KEY `idx_user_id` (`user_id`) COMMENT '优化按用户查询', + KEY `idx_status` (`status`) COMMENT '优化按状态查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户模块白名单表'; + +-- ============================================ +-- 说明: +-- 1. 以身份证号(id_card)+ feature_id 为单位,记录白名单屏蔽关系 +-- 2. user_id 使用操作用户的ID(代理的user_id),便于以后扩展为普通用户操作 +-- 3. status=1表示生效,status=2表示已失效(可手动失效或系统失效) +-- 4. 一旦生效,该身份证号+feature组合永久屏蔽,除非手动失效 +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/whitelist_order_migration.sql b/deploy/sql/whitelist_order_migration.sql new file mode 100644 index 0000000..69d7338 --- /dev/null +++ b/deploy/sql/whitelist_order_migration.sql @@ -0,0 +1,70 @@ +-- ============================================ +-- 白名单订单表 - 创建表 +-- ============================================ +-- 说明:创建 whitelist_order 和 whitelist_order_item 表,用于白名单订单管理 +-- 执行时间:2024-XX-XX +-- ============================================ + +-- 1. 创建白名单订单表 +CREATE TABLE `whitelist_order` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ +`order_no` varchar(50) NOT NULL COMMENT '订单号(唯一)', + `user_id` CHAR(36) NOT NULL COMMENT '用户ID(代理的user_id)', + `id_card` varchar(50) NOT NULL COMMENT '身份证号(查询对象标识)', + `total_amount` decimal(10,2) NOT NULL COMMENT '总金额(单位:元)', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '订单状态:1=待支付,2=已支付,3=已取消', + `payment_method` varchar(20) DEFAULT NULL COMMENT '支付方式:wechat, alipay, appleiap等', + `payment_platform` varchar(20) DEFAULT NULL COMMENT '支付平台:alipay, wechat, appleiap等', + `platform_order_id` varchar(100) DEFAULT NULL COMMENT '支付平台订单号(第三方平台的订单号)', + `pay_time` datetime DEFAULT NULL COMMENT '支付时间', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_order_no` (`order_no`) COMMENT '订单号唯一索引', + KEY `idx_user_id` (`user_id`) COMMENT '优化按用户查询', + KEY `idx_id_card` (`id_card`) COMMENT '优化按身份证号查询', + KEY `idx_status` (`status`) COMMENT '优化按状态查询', + KEY `idx_create_time` (`create_time`) COMMENT '优化按创建时间查询', + KEY `idx_platform_order_id` (`platform_order_id`) COMMENT '优化按支付平台订单号查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='白名单订单表'; + +-- 2. 创建白名单订单明细表 +CREATE TABLE `whitelist_order_item` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + +/* 业务字段 */ + +`order_id` CHAR(36) NOT NULL COMMENT '订单ID(关联whitelist_order.id)', + `feature_id` CHAR(36) NOT NULL COMMENT 'Feature的UUID', + `feature_api_id` varchar(100) NOT NULL COMMENT 'Feature的API标识(冗余)', + `feature_name` varchar(200) NOT NULL COMMENT 'Feature的名称(冗余)', + `price` decimal(10,2) NOT NULL COMMENT '单价(单位:元)', + + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`) COMMENT '优化按订单查询', + KEY `idx_feature_id` (`feature_id`) COMMENT '优化按feature查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='白名单订单明细表'; + +-- ============================================ +-- 说明: +-- 1. whitelist_order 记录白名单订单主信息 +-- 2. whitelist_order_item 记录订单明细(每个feature一条记录) +-- - 如果订单只包含一个feature,可以考虑去掉明细表,将feature信息直接放在主表 +-- - 如果订单可能包含多个feature,建议保留明细表,便于记录每个feature的单价和名称 +-- 3. 订单状态:1=待支付,2=已支付,3=已取消 +-- 4. 支付成功后,会创建对应的 user_feature_whitelist 记录 +-- 5. payment_method: 支付方式(wechat, alipay, appleiap等) +-- 6. payment_platform: 支付平台(alipay, wechat, appleiap等) +-- 7. platform_order_id: 支付平台的订单号(第三方平台的订单号) +-- ============================================ \ No newline at end of file diff --git a/deploy/sql/withdrawal_alipay_quota_migration.sql b/deploy/sql/withdrawal_alipay_quota_migration.sql new file mode 100644 index 0000000..7956f1b --- /dev/null +++ b/deploy/sql/withdrawal_alipay_quota_migration.sql @@ -0,0 +1,26 @@ +-- ============================================ +-- 支付宝月度提现额度配置 +-- ============================================ +-- 说明:为代理系统增加支付宝每月提现额度配置,默认 800 元 +-- 执行时间:2026-01-06 +-- ============================================ + +INSERT INTO + `agent_config` ( + `id`, + `config_key`, + `config_value`, + `config_type`, + `description` + ) +VALUES ( + 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + 'alipay_month_quota', + '800.00', + 'withdrawal', + '支付宝每月提现额度(单位:元)' + ) +ON DUPLICATE KEY UPDATE + `config_value` = VALUES(`config_value`), + `config_type` = VALUES(`config_type`), + `description` = VALUES(`description`); \ No newline at end of file diff --git a/deploy/sql/withdrawal_bank_card_migration.sql b/deploy/sql/withdrawal_bank_card_migration.sql new file mode 100644 index 0000000..a471e8b --- /dev/null +++ b/deploy/sql/withdrawal_bank_card_migration.sql @@ -0,0 +1,34 @@ +-- ============================================ +-- 代理提现表 - 添加银行卡提现支持 +-- ============================================ +-- 说明:为 agent_withdrawal 表添加提现方式字段和银行卡相关字段 +-- 执行时间:2024-XX-XX +-- ============================================ + +-- 1. 添加提现方式字段(1=支付宝,2=银行卡) +ALTER TABLE `agent_withdrawal` +ADD COLUMN `withdrawal_type` tinyint NOT NULL DEFAULT 1 COMMENT '提现方式:1=支付宝,2=银行卡' AFTER `agent_id`; + +-- 2. 添加银行卡号字段(银行卡提现时使用) +ALTER TABLE `agent_withdrawal` +ADD COLUMN `bank_card_no` varchar(100) DEFAULT NULL COMMENT '银行卡号(银行卡提现时使用)' AFTER `payee_account`; + +-- 3. 添加开户行名称字段(银行卡提现时使用) +ALTER TABLE `agent_withdrawal` +ADD COLUMN `bank_name` varchar(100) DEFAULT NULL COMMENT '开户行名称(银行卡提现时使用)' AFTER `bank_card_no`; + +-- 4. 为提现方式字段添加索引(用于按提现方式查询) +ALTER TABLE `agent_withdrawal` +ADD INDEX `idx_withdrawal_type` (`withdrawal_type`); + +-- 5. 为提现方式和状态添加复合索引(用于后台审核查询) +ALTER TABLE `agent_withdrawal` +ADD INDEX `idx_type_status` (`withdrawal_type`, `status`); + +-- ============================================ +-- 说明: +-- 1. withdrawal_type 默认值为 1(支付宝),兼容历史数据 +-- 2. bank_card_no 和 bank_name 为可选字段,支付宝提现时为空 +-- 3. 对于支付宝提现,payee_account 存储支付宝账号 +-- 4. 对于银行卡提现,payee_account 可以存储银行卡号(与 bank_card_no 保持一致),或留空 +-- ============================================ \ No newline at end of file