commit 55fcaf62dcbd463c16a28744157a06fb363e8bae Author: liangzai <2440983361@qq.com> Date: Wed Apr 1 15:43:01 2026 +0800 first commit diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..c9dd67c --- /dev/null +++ b/.cursorrules @@ -0,0 +1,202 @@ +你是一位精通Go-Zero框架的AI编程助手,专门帮助开发基于Go-Zero的微服务API项目。 + +熟悉Go-Zero的项目结构和架构模式,包括: +- api服务开发 +- rpc服务开发 +- model层数据库操作 +- 中间件实现 +- 配置文件管理 +- JWT认证体系 +- 分布式事务处理 +- 使用goctl工具生成代码 + +项目目录结构说明: +``` +bdrp-server/ # 项目根目录 +├── app/ # 应用服务目录 +│ └── user/ # 用户服务 +│ ├── cmd/ # 服务启动入口 +│ │ ├── api/ # API服务 +│ │ │ ├── desc/ # API接口定义目录 +│ │ │ │ ├── user/ # 用户模块API定义 +│ │ │ │ │ └── user.api # 用户API类型定义 +│ │ │ │ └── main.api # 主API文件 +│ │ │ ├── etc/ # 配置文件目录 +│ │ │ │ └── user.yaml # 服务配置文件 +│ │ │ └── internal/ # 内部代码 +│ │ │ ├── config/ # 配置结构定义 +│ │ │ ├── handler/ # HTTP处理器 +│ │ │ ├── logic/ # 业务逻辑 +│ │ │ ├── middleware/ # 中间件 +│ │ │ ├── svc/ # 服务上下文 +│ │ │ └── types/ # 类型定义 +│ │ └── rpc/ # RPC服务(如果有) +│ └── model/ # 数据库模型 +├── common/ # 公共代码 +│ ├── ctxdata/ # 上下文数据处理 +│ ├── globalkey/ # 全局键值定义 +│ ├── interceptor/ # 拦截器 +│ ├── jwt/ # JWT认证 +│ ├── kqueue/ # 消息队列 +│ ├── middleware/ # 中间件 +│ ├── result/ # 统一返回结果 +│ ├── tool/ # 工具函数 +│ ├── uniqueid/ # 唯一ID生成 +│ ├── wxminisub/ # 微信小程序订阅 +│ └── xerr/ # 错误处理 +├── data/ # 数据文件目录 +├── deploy/ # 部署相关文件 +│ └── template/ # goctl模板 +├── pkg/ # 可复用的包 +│ └── lzkit/ # 工具包 +└── tmp/ # 临时文件目录 +``` + +目录作用说明: +1. app/user/cmd/api/:API服务目录 + - desc/:API接口定义,包含各模块的API文件 + - main.api:主API文件,导入所有模块API并定义路由 + - user/user.api:用户模块的请求响应参数定义 + - order/order.api:订单模块的请求响应参数定义 + - 其他模块API定义文件 + - etc/:配置文件目录,存放yaml配置 + - user.yaml:包含数据库、缓存、JWT等配置信息 + - internal/:服务内部代码 + - config/:配置结构定义,对应etc下的yaml文件 + - handler/:HTTP请求处理器,负责解析请求和返回响应 + - logic/:业务逻辑实现,处理具体业务 + - middleware/:HTTP中间件,如认证、日志等 + - svc/:服务上下文,管理服务依赖(如DB、Cache等) + - types/:请求响应的结构体定义,由goctl根据API文件生成(不允许自己修改) + +2. app/user/model/:数据库模型层 + - userModel.go:用户表模型定义及CRUD方法 + - userModel_gen.go:goctl工具生成的基础数据库操作代码(不允许自己修改) + - vars.go:定义数据库相关变量和常量 + - 其他数据表模型文件 + +3. common/:存放公共代码 + - ctxdata/:上下文数据处理 + - globalkey/:全局键值定义 + - interceptor/:拦截器 + - jwt/:JWT认证相关 + - kqueue/:消息队列处理 + - middleware/:全局中间件 + - result/:统一返回结果处理 + - tool/:通用工具函数 + - uniqueid/:唯一ID生成器 + - wxminisub/:微信小程序订阅功能 + - xerr/:统一错误处理 + +4. pkg/:可在多个服务间共享的包 + - lzkit/:通用工具包 + +5. deploy/:部署相关文件 + - template/:goctl代码生成模板 + +6. data/:数据文件目录 + - 用于存放项目相关的数据文件 + +7. tmp/:临时文件目录 + - 用于存放临时生成的文件 + +使用goctl生成API服务的步骤: +1. 首先确保API定义目录存在: + ```bash + mkdir -p app/user/cmd/api/desc/user + ``` + +2. API文件组织结构(单体服务模式): + ``` + app/user/cmd/api/desc/ + ├── user/ + │ └── user.api # 用户模块的请求响应参数定义 + ├── order/ + │ └── order.api # 订单模块的请求响应参数定义 + └── main.api # 主API文件,集中管理所有模块的API定义 + ``` + +3. 在app/user/cmd/api/desc/main.api中集中管理所有API: + ``` + syntax = "v1" + + info( + title: "单体服务API" + desc: "集中管理所有模块的API" + author: "team" + version: "v1" + ) + + // 导入各模块的类型定义 + import "user/user.api" + import "order/order.api" + + // 各模块下定义路由,例如user模块 desc/user.api + @server ( + prefix: api/v1 + group: user + ) + service main { + // 用户模块接口 + @handler Login + post /login (LoginReq) returns (LoginResp) + } + ``` + +4. 各模块在下一层定义类型,例如在app/user/cmd/api/desc/user/user.api中只定义用户模块的接口的类型: + ``` + type ( + LoginReq { + Username string `json:"username"` + Password string `json:"password"` + } + + LoginResp { + Token string `json:"token"` + ExpireAt int64 `json:"expireAt"` + } + ) + ``` + +5. 使用goctl生成API代码(始终使用main.api): + ```bash + goctl api go -api app/user/cmd/api/desc/main.api -dir app/user/cmd/api --home ./deploy/template + ``` + +注意:无论修改哪个模块的API文件,都需要执行main.api来生成代码,因为这是单体服务模式。 +6. goctl生成的文件和目录结构: + ``` + app/user/cmd/api/ + ├── desc/ # API接口定义目录(已存在) + ├── etc/ # 配置文件目录 + │ └── main.yaml # 服务配置文件 + ├── internal/ # 内部代码 + │ ├── config/ # 配置结构定义 + │ │ └── config.go # 配置结构体 + │ ├── handler/ # HTTP处理器 + │ │ ├── routes.go # 路由注册 + │ │ └── user/ # 用户模块处理器 + │ │ └── login_handler.go # 登录处理器 + │ ├── logic/ # 业务逻辑 + │ │ └── user/ # 用户模块逻辑 + │ │ └── login_logic.go # 登录逻辑 + │ ├── middleware/ # 中间件 + │ ├── svc/ # 服务上下文 + │ │ └── service_context.go # 服务上下文定义 + │ └── types/ # 类型定义 + │ └── types.go # 请求响应类型定义 + └── main.go # 服务入口文件 + ``` + +7. 生成代码后,才能够实现具体的业务逻辑,例如: + - user.api中的`mobileLogin`接口生成的逻辑文件在`app/user/cmd/api/internal/logic/user/mobile_login_logic.go` + - user.api中的`wxMiniAuth`接口生成的逻辑文件在`app/user/cmd/api/internal/logic/user/wx_mini_auth_logic.go` + - query.api中的`queryService`接口生成的逻辑文件在`app/user/cmd/api/internal/logic/query/query_service_logic.go` + + 生成的逻辑文件中需要实现`Logic`结构体的`XXX`方法(方法名与接口名对应),这是业务逻辑的核心部分。 + +代码说明尽量简洁,但关键逻辑和go-zero特有模式需要添加注释。 + +始终关注性能、安全性和可维护性。 + +在回答问题时,优先考虑Go-Zero的特性和设计理念,而不是通用的Go编程模式。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78894b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# idea +.idea +.idea/ +*.iml +.DS_Store +**/.DS_Store + +#deploy data + +data/* +!data/.gitkeep + +# gitlab ci +.cache + +#vscode +.vscode +.vscode/ + +/tmp/ + +/app/api + +# debug binary (Delve) +_debug_bin.* +*.exe + +# authorization documents (PDF files) +app/main/api/data/authorization_docs/ +**/authorization_docs/**/*.pdf + +# local merchant certs and local configs +app/main/api/etc/merchant/ +app/main/api/etc/main.dev.yaml +app/main/api/etc/main.yaml \ No newline at end of file diff --git a/app/main/api/Dockerfile b/app/main/api/Dockerfile new file mode 100644 index 0000000..12f447e --- /dev/null +++ b/app/main/api/Dockerfile @@ -0,0 +1,33 @@ +FROM golang:1.23.4-alpine AS builder + +LABEL stage=gobuilder + +ENV CGO_ENABLED 0 +ENV GOPROXY https://goproxy.cn,direct +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories + +RUN apk update --no-cache && apk add --no-cache tzdata + +WORKDIR /build + +ADD go.mod . +ADD go.sum . +RUN go mod download +COPY . . +COPY app/main/api/etc /app/etc +COPY app/main/api/static /app/static +RUN go build -ldflags="-s -w" -o /app/main app/main/api/main.go + + +FROM scratch + +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai +ENV TZ Asia/Shanghai + +WORKDIR /app +COPY --from=builder /app/main /app/main +COPY --from=builder /app/etc /app/etc +COPY --from=builder /app/static /app/static + +CMD ["./main", "-f", "etc/main.yaml"] diff --git a/app/main/api/desc/admin/admin_agent.api b/app/main/api/desc/admin/admin_agent.api new file mode 100644 index 0000000..ad93a8c --- /dev/null +++ b/app/main/api/desc/admin/admin_agent.api @@ -0,0 +1,599 @@ +syntax = "v1" + +info ( + title: "后台代理管理服务" + desc: "后台代理相关接口" + author: "team" + version: "v1" +) + +// 代理管理接口 +@server( + prefix: /api/v1/admin/agent + group: admin_agent + middleware: AdminAuthInterceptor +) +service main { + // 代理分页查询 + @handler AdminGetAgentList + get /list (AdminGetAgentListReq) returns (AdminGetAgentListResp) + + // 代理推广链接分页查询 + @handler AdminGetAgentLinkList + get /agent-link/list (AdminGetAgentLinkListReq) returns (AdminGetAgentLinkListResp) + + // 代理佣金分页查询 + @handler AdminGetAgentCommissionList + get /agent-commission/list (AdminGetAgentCommissionListReq) returns (AdminGetAgentCommissionListResp) + + // 代理佣金状态更新 + @handler AdminUpdateAgentCommissionStatus + post /agent-commission/update-status (AdminUpdateAgentCommissionStatusReq) returns (AdminUpdateAgentCommissionStatusResp) + + // 批量解冻代理佣金 + @handler AdminBatchUnfreezeAgentCommission + post /agent-commission/batch-unfreeze (AdminBatchUnfreezeAgentCommissionReq) returns (AdminBatchUnfreezeAgentCommissionResp) + + // 代理奖励分页查询 + @handler AdminGetAgentRewardList + get /agent-reward/list (AdminGetAgentRewardListReq) returns (AdminGetAgentRewardListResp) + + // 代理提现分页查询 + @handler AdminGetAgentWithdrawalList + get /agent-withdrawal/list (AdminGetAgentWithdrawalListReq) returns (AdminGetAgentWithdrawalListResp) + + // 代理上级抽佣分页查询 + @handler AdminGetAgentCommissionDeductionList + get /agent-commission-deduction/list (AdminGetAgentCommissionDeductionListReq) returns (AdminGetAgentCommissionDeductionListResp) + + // 获取代理钱包信息 + @handler AdminGetAgentWallet + get /wallet/:agent_id (AdminGetAgentWalletReq) returns (AdminGetAgentWalletResp) + + // 修改代理钱包余额 + @handler AdminUpdateAgentWalletBalance + post /wallet/update-balance (AdminUpdateAgentWalletBalanceReq) returns (AdminUpdateAgentWalletBalanceResp) + + // 平台抽佣分页查询 + @handler AdminGetAgentPlatformDeductionList + get /agent-platform-deduction/list (AdminGetAgentPlatformDeductionListReq) returns (AdminGetAgentPlatformDeductionListResp) + + // 代理产品配置分页查询 + @handler AdminGetAgentProductionConfigList + get /agent-production-config/list (AdminGetAgentProductionConfigListReq) returns (AdminGetAgentProductionConfigListResp) + + // 代理产品配置编辑 + @handler AdminUpdateAgentProductionConfig + post /agent-production-config/update (AdminUpdateAgentProductionConfigReq) returns (AdminUpdateAgentProductionConfigResp) + + // 代理会员充值订单分页查询 + @handler AdminGetAgentMembershipRechargeOrderList + get /agent-membership-recharge-order/list (AdminGetAgentMembershipRechargeOrderListReq) returns (AdminGetAgentMembershipRechargeOrderListResp) + + // 代理会员配置分页查询 + @handler AdminGetAgentMembershipConfigList + get /agent-membership-config/list (AdminGetAgentMembershipConfigListReq) returns (AdminGetAgentMembershipConfigListResp) + + // 代理会员配置编辑 + @handler AdminUpdateAgentMembershipConfig + post /agent-membership-config/update (AdminUpdateAgentMembershipConfigReq) returns (AdminUpdateAgentMembershipConfigResp) + + // 银行卡提现审核(确认/拒绝) + @handler AdminReviewBankCardWithdrawal + post /agent-withdrawal/bank-card/review (AdminReviewBankCardWithdrawalReq) returns (AdminReviewBankCardWithdrawalResp) + + // 获取提现统计数据 + @handler AdminGetWithdrawalStatistics + get /agent-withdrawal/statistics (AdminGetWithdrawalStatisticsReq) returns (AdminGetWithdrawalStatisticsResp) + + // 获取代理订单统计数据 + @handler AdminGetAgentOrderStatistics + get /agent-order/statistics (AdminGetAgentOrderStatisticsReq) returns (AdminGetAgentOrderStatisticsResp) + + // 获取代理统计数据 + @handler AdminGetAgentStatistics + get /statistics (AdminGetAgentStatisticsReq) returns (AdminGetAgentStatisticsResp) + + // 获取代理链接产品统计 + @handler AdminGetAgentLinkProductStatistics + get /agent-link/product-statistics (AdminGetAgentLinkProductStatisticsReq) returns (AdminGetAgentLinkProductStatisticsResp) + + // 获取系统配置 + @handler AdminGetSystemConfig + get /system-config (AdminGetSystemConfigResp) + + // 更新系统配置 + @handler AdminUpdateSystemConfig + post /system-config (AdminUpdateSystemConfigReq) returns (AdminUpdateSystemConfigResp) + + // 代理钱包流水分页查询 + @handler AdminGetAgentWalletTransactionList + get /wallet-transaction/list (AdminGetAgentWalletTransactionListReq) returns (AdminGetAgentWalletTransactionListResp) + + } + +type ( + // 代理分页查询请求 + AdminGetAgentListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + Mobile *string `form:"mobile,optional"` // 手机号(可选) + Region *string `form:"region,optional"` // 区域(可选) + ParentAgentId *int64 `form:"parent_agent_id,optional"` // 上级代理ID(可选) + } + + // 代理列表项 + AgentListItem { + Id int64 `json:"id"` // 主键 + UserId int64 `json:"user_id"` // 用户ID + ParentAgentId int64 `json:"parent_agent_id"` // 上级代理ID + LevelName string `json:"level_name"` // 等级名称 + Region string `json:"region"` // 区域 + Mobile string `json:"mobile"` // 手机号 + MembershipExpiryTime string `json:"membership_expiry_time"` // 会员到期时间 + Balance float64 `json:"balance"` // 钱包余额 + TotalEarnings float64 `json:"total_earnings"` // 累计收益 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + WithdrawnAmount float64 `json:"withdrawn_amount"` // 提现总额 + CreateTime string `json:"create_time"` // 创建时间 + IsRealNameVerified bool `json:"is_real_name_verified"` // 是否已实名认证 + RealName string `json:"real_name"` // 实名姓名 + IdCard string `json:"id_card"` // 身份证号 + RealNameStatus string `json:"real_name_status"` // 实名状态(pending/approved/rejected) + } + + // 代理分页查询响应 + AdminGetAgentListResp { + Total int64 `json:"total"` // 总数 + Items []AgentListItem `json:"items"` // 列表数据 + } + + // 代理推广链接分页查询请求 + AdminGetAgentLinkListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选) + } + + // 代理推广链接列表项 + AgentLinkListItem { + AgentId int64 `json:"agent_id"` // 代理ID + ProductName string `json:"product_name"` // 产品名 + Price float64 `json:"price"` // 价格 + LinkIdentifier string `json:"link_identifier"` // 推广码 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理推广链接分页查询响应 + AdminGetAgentLinkListResp { + Total int64 `json:"total"` // 总数 + Items []AgentLinkListItem `json:"items"` // 列表数据 + } + + // 代理佣金分页查询请求 + AdminGetAgentCommissionListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + OrderId *int64 `form:"order_id,optional"` // 订单ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + CreateTimeStart *string `form:"create_time_start,optional"` // 创建时间开始(可选) + CreateTimeEnd *string `form:"create_time_end,optional"` // 创建时间结束(可选) + } + + // 代理佣金列表项 + AgentCommissionListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + OrderId int64 `json:"order_id"` // 订单ID + Amount float64 `json:"amount"` // 金额 + ProductName string `json:"product_name"` // 产品名 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理佣金分页查询响应 + AdminGetAgentCommissionListResp { + Total int64 `json:"total"` // 总数 + Items []AgentCommissionListItem `json:"items"` // 列表数据 + } + + // 代理佣金状态更新请求 + AdminUpdateAgentCommissionStatusReq { + Id int64 `json:"id"` // 佣金记录ID + Status int64 `json:"status"` // 状态:0-已结算,1-冻结中,2-已取消 + } + + // 代理佣金状态更新响应 + AdminUpdateAgentCommissionStatusResp { + Success bool `json:"success"` // 是否成功 + } + + // 批量解冻代理佣金请求 + AdminBatchUnfreezeAgentCommissionReq { + AgentId *int64 `json:"agent_id,optional"` // 代理ID,可选。如果不传则解冻所有冻结中的佣金 + } + + // 批量解冻代理佣金响应 + AdminBatchUnfreezeAgentCommissionResp { + Success bool `json:"success"` // 是否成功 + Count int64 `json:"count"` // 解冻的数量 + Amount float64 `json:"amount"` // 解冻的总金额 + } + + // 代理奖励分页查询请求 + AdminGetAgentRewardListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + RelationAgentId *int64 `form:"relation_agent_id,optional"` // 关联代理ID(可选) + Type *string `form:"type,optional"` // 奖励类型(可选) + } + + // 代理奖励列表项 + AgentRewardListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + RelationAgentId int64 `json:"relation_agent_id"` // 关联代理ID + Amount float64 `json:"amount"` // 金额 + Type string `json:"type"` // 奖励类型 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理奖励分页查询响应 + AdminGetAgentRewardListResp { + Total int64 `json:"total"` // 总数 + Items []AgentRewardListItem `json:"items"` // 列表数据 + } + + // 代理提现分页查询请求 + AdminGetAgentWithdrawalListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选) + WithdrawType *int64 `form:"withdraw_type,optional"` // 提现类型(可选)1-支付宝,2-银行卡 + } + + // 代理提现列表项 + AgentWithdrawalListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + WithdrawNo string `json:"withdraw_no"` // 提现单号 + Amount float64 `json:"amount"` // 金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后) + TaxAmount float64 `json:"tax_amount"` // 扣税金额 + Status int64 `json:"status"` // 状态 + PayeeAccount string `json:"payee_account"` // 收款账户 + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 + WithdrawType int64 `json:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡 + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + PayeeName string `json:"payee_name"` // 收款人姓名 + } + + // 代理提现分页查询响应 + AdminGetAgentWithdrawalListResp { + Total int64 `json:"total"` // 总数 + Items []AgentWithdrawalListItem `json:"items"` // 列表数据 + } + + // 代理抽佣分页查询请求 + AdminGetAgentCommissionDeductionListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Type *string `form:"type,optional"` // 类型(cost/pricing,可选) + Status *int64 `form:"status,optional"` // 状态(可选) + } + + // 代理抽佣列表项 + AgentCommissionDeductionListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + DeductedAgentId int64 `json:"deducted_agent_id"` // 被扣代理ID + Amount float64 `json:"amount"` // 金额 + ProductName string `json:"product_name"` // 产品名 + Type string `json:"type"` // 类型(cost/pricing) + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理抽佣分页查询响应 + AdminGetAgentCommissionDeductionListResp { + Total int64 `json:"total"` // 总数 + Items []AgentCommissionDeductionListItem `json:"items"` // 列表数据 + } + + // 平台抽佣分页查询请求 + AdminGetAgentPlatformDeductionListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + Type *string `form:"type,optional"` // 类型(cost/pricing,可选) + Status *int64 `form:"status,optional"` // 状态(可选) + } + + // 平台抽佣列表项 + AgentPlatformDeductionListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + Amount float64 `json:"amount"` // 金额 + Type string `json:"type"` // 类型(cost/pricing) + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 平台抽佣分页查询响应 + AdminGetAgentPlatformDeductionListResp { + Total int64 `json:"total"` // 总数 + Items []AgentPlatformDeductionListItem `json:"items"` // 列表数据 + } + + // 代理产品配置分页查询请求 + AdminGetAgentProductionConfigListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Id *int64 `form:"id,optional"` // 配置ID(可选) + } + + // 代理产品配置分页查询响应 + AdminGetAgentProductionConfigListResp { + Total int64 `json:"total"` // 总数 + Items []AgentProductionConfigItem `json:"items"` // 列表数据 + } + + // 代理产品配置列表项 + AgentProductionConfigItem { + Id int64 `json:"id"` // 主键 + ProductName string `json:"product_name"` // 产品名 + CostPrice float64 `json:"cost_price"` // 成本 + PriceRangeMin float64 `json:"price_range_min"` // 最低定价 + PriceRangeMax float64 `json:"price_range_max"` // 最高定价 + PricingStandard float64 `json:"pricing_standard"` // 定价标准 + OverpricingRatio float64 `json:"overpricing_ratio"` // 超价比例 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理产品配置编辑请求 + AdminUpdateAgentProductionConfigReq { + Id int64 `json:"id"` // 主键 + CostPrice float64 `json:"cost_price"` // 成本 + PriceRangeMin float64 `json:"price_range_min"` // 最低定价 + PriceRangeMax float64 `json:"price_range_max"` // 最高定价 + PricingStandard float64 `json:"pricing_standard"` // 定价标准 + OverpricingRatio float64 `json:"overpricing_ratio"` // 超价比例 + } + + // 代理产品配置编辑响应 + AdminUpdateAgentProductionConfigResp { + Success bool `json:"success"` // 是否成功 + } + + // 代理会员充值订单分页查询请求 + AdminGetAgentMembershipRechargeOrderListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + UserId *int64 `form:"user_id,optional"` // 用户ID(可选) + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + OrderNo *string `form:"order_no,optional"` // 订单号(可选) + PlatformOrderId *string `form:"platform_order_id,optional"` // 平台订单号(可选) + Status *string `form:"status,optional"` // 状态(可选) + PaymentMethod *string `form:"payment_method,optional"` // 支付方式(可选) + } + + // 代理会员充值订单列表项 + AgentMembershipRechargeOrderListItem { + Id int64 `json:"id"` // 主键 + UserId int64 `json:"user_id"` // 用户ID + AgentId int64 `json:"agent_id"` // 代理ID + LevelName string `json:"level_name"` // 等级名称 + Amount float64 `json:"amount"` // 金额 + PaymentMethod string `json:"payment_method"` // 支付方式 + OrderNo string `json:"order_no"` // 订单号 + PlatformOrderId string `json:"platform_order_id"` // 平台订单号 + Status string `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理会员充值订单分页查询响应 + AdminGetAgentMembershipRechargeOrderListResp { + Total int64 `json:"total"` // 总数 + Items []AgentMembershipRechargeOrderListItem `json:"items"` // 列表数据 + } + + // 代理会员配置分页查询请求 + AdminGetAgentMembershipConfigListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + LevelName *string `form:"level_name,optional"` // 会员级别名称(可选) + } + + // 代理会员配置分页查询响应 + AdminGetAgentMembershipConfigListResp { + Total int64 `json:"total"` // 总数 + Items []AgentMembershipConfigListItem `json:"items"` // 列表数据 + } + + // 代理会员配置列表项 + AgentMembershipConfigListItem { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price *float64 `json:"price"` // 会员年费 + ReportCommission *float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward *float64 `json:"lower_activity_reward"` // 下级活跃奖励金额 + NewActivityReward *float64 `json:"new_activity_reward"` // 新增活跃奖励金额 + LowerStandardCount *int64 `json:"lower_standard_count"` // 活跃下级达标个数 + NewLowerStandardCount *int64 `json:"new_lower_standard_count"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio *float64 `json:"lower_withdraw_reward_ratio"` // 下级提现奖励比例 + LowerConvertVipReward *float64 `json:"lower_convert_vip_reward"` // 下级转化VIP奖励 + LowerConvertSvipReward *float64 `json:"lower_convert_svip_reward"` // 下级转化SVIP奖励 + ExemptionAmount *float64 `json:"exemption_amount"` // 免责金额 + PriceIncreaseMax *float64 `json:"price_increase_max"` // 提价最高金额 + PriceRatio *float64 `json:"price_ratio"` // 提价区间收取比例 + PriceIncreaseAmount *float64 `json:"price_increase_amount"` // 在原本成本上加价的金额 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理会员配置编辑请求 + AdminUpdateAgentMembershipConfigReq { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price float64 `json:"price"` // 会员年费 + ReportCommission float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward *float64 `json:"lower_activity_reward,optional,omitempty"` // 下级活跃奖励金额 + NewActivityReward *float64 `json:"new_activity_reward,optional,omitempty"` // 新增活跃奖励金额 + LowerStandardCount *int64 `json:"lower_standard_count,optional,omitempty"` // 活跃下级达标个数 + NewLowerStandardCount *int64 `json:"new_lower_standard_count,optional,omitempty"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio *float64 `json:"lower_withdraw_reward_ratio,optional,omitempty"` // 下级提现奖励比例 + LowerConvertVipReward *float64 `json:"lower_convert_vip_reward,optional,omitempty"` // 下级转化VIP奖励 + LowerConvertSvipReward *float64 `json:"lower_convert_svip_reward,optional,omitempty"` // 下级转化SVIP奖励 + ExemptionAmount *float64 `json:"exemption_amount,optional,omitempty"` // 免责金额 + PriceIncreaseMax *float64 `json:"price_increase_max,optional,omitempty"` // 提价最高金额 + PriceRatio *float64 `json:"price_ratio,optional,omitempty"` // 提价区间收取比例 + PriceIncreaseAmount *float64 `json:"price_increase_amount,optional,omitempty"` // 在原本成本上加价的金额 + } + + // 代理会员配置编辑响应 + AdminUpdateAgentMembershipConfigResp { + Success bool `json:"success"` // 是否成功 + } + + // 银行卡提现审核请求 + AdminReviewBankCardWithdrawalReq { + WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID + Action int64 `json:"action"` // 操作:1-确认,2-拒绝 + Remark string `json:"remark"` // 备注(拒绝时必填) + } + + // 银行卡提现审核响应 + AdminReviewBankCardWithdrawalResp { + Success bool `json:"success"` // 是否成功 + } + + // 获取提现统计数据请求 + AdminGetWithdrawalStatisticsReq { + } + + // 获取提现统计数据响应 + AdminGetWithdrawalStatisticsResp { + TotalWithdrawalAmount float64 `json:"total_withdrawal_amount"` // 总提现金额 + TodayWithdrawalAmount float64 `json:"today_withdrawal_amount"` // 今日提现金额 + TotalActualAmount float64 `json:"total_actual_amount"` // 总实际到账金额 + TotalTaxAmount float64 `json:"total_tax_amount"` // 总扣税金额 + } + + // 获取代理订单统计数据请求 + AdminGetAgentOrderStatisticsReq { + } + + // 获取代理订单统计数据响应 + AdminGetAgentOrderStatisticsResp { + TotalAgentOrderCount int64 `json:"total_agent_order_count"` // 总代理订单数 + TodayAgentOrderCount int64 `json:"today_agent_order_count"` // 今日代理订单数 + } + + // 获取代理统计数据请求 + AdminGetAgentStatisticsReq { + } + + // 获取代理统计数据响应 + AdminGetAgentStatisticsResp { + TotalAgentCount int64 `json:"total_agent_count"` // 总代理数 + TodayAgentCount int64 `json:"today_agent_count"` // 今日新增代理数 + } + + // 获取代理链接产品统计请求 + AdminGetAgentLinkProductStatisticsReq { + } + + // 代理链接产品统计列表项 + AgentLinkProductStatisticsItem { + ProductName string `json:"product_name"` // 产品名称 + LinkCount int64 `json:"link_count"` // 推广链接数量 + } + + // 获取代理链接产品统计响应 + AdminGetAgentLinkProductStatisticsResp { + Items []AgentLinkProductStatisticsItem `json:"items"` // 列表数据 + } + + // 获取代理钱包信息请求 + AdminGetAgentWalletReq { + AgentId int64 `path:"agent_id"` // 代理ID + } + + // 获取代理钱包信息响应 + AdminGetAgentWalletResp { + Balance float64 `json:"balance"` // 可用余额 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + TotalEarnings float64 `json:"total_earnings"` // 总收益 + } + + // 修改代理钱包余额请求 + AdminUpdateAgentWalletBalanceReq { + AgentId int64 `json:"agent_id"` // 代理ID + Amount float64 `json:"amount"` // 修改金额(正数增加,负数减少) + } + + // 修改代理钱包余额响应 + AdminUpdateAgentWalletBalanceResp { + Success bool `json:"success"` // 是否成功 + Balance float64 `json:"balance"` // 修改后的余额 + } + + // 更新系统配置请求 + AdminUpdateSystemConfigReq { + CommissionSafeMode *bool `json:"commission_safe_mode,optional"` // 佣金安全防御模式:true-冻结模式,false-直接结算模式 + } + + // 更新系统配置响应 + AdminUpdateSystemConfigResp { + Success bool `json:"success"` // 是否成功 + } + + // 获取系统配置响应 + AdminGetSystemConfigResp { + CommissionSafeMode bool `json:"commission_safe_mode"` // 佣金安全防御模式 + } + + // 代理钱包流水分页查询请求 + AdminGetAgentWalletTransactionListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId int64 `form:"agent_id"` // 代理ID + TransactionType *string `form:"transaction_type,optional"` // 交易类型(可选) + CreateTimeStart *string `form:"create_time_start,optional"` // 创建时间开始(可选) + CreateTimeEnd *string `form:"create_time_end,optional"` // 创建时间结束(可选) + } + + // 代理钱包流水列表项 + AgentWalletTransactionListItem { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + TransactionType string `json:"transaction_type"` // 交易类型 + Amount float64 `json:"amount"` // 变动金额 + BalanceBefore float64 `json:"balance_before"` // 变动前余额 + BalanceAfter float64 `json:"balance_after"` // 变动后余额 + FrozenBalanceBefore float64 `json:"frozen_balance_before"` // 变动前冻结余额 + FrozenBalanceAfter float64 `json:"frozen_balance_after"` // 变动后冻结余额 + TransactionId *string `json:"transaction_id"` // 关联交易ID + RelatedUserId *int64 `json:"related_user_id"` // 关联用户ID + Remark *string `json:"remark"` // 备注说明 + CreateTime string `json:"create_time"` // 创建时间 + } + + // 代理钱包流水分页查询响应 + AdminGetAgentWalletTransactionListResp { + Total int64 `json:"total"` // 总数 + Items []AgentWalletTransactionListItem `json:"items"` // 列表数据 + } + +) \ No newline at end of file diff --git a/app/main/api/desc/admin/admin_api.api b/app/main/api/desc/admin/admin_api.api new file mode 100644 index 0000000..4ad224b --- /dev/null +++ b/app/main/api/desc/admin/admin_api.api @@ -0,0 +1,131 @@ +syntax = "v1" + +info( + title: "Admin API管理" + desc: "管理员API管理接口" + author: "team" + version: "v1" +) + +type ( + // API列表请求 + AdminGetApiListReq { + Page int64 `form:"page,default=1"` + PageSize int64 `form:"page_size,default=20"` + ApiName string `form:"api_name,optional"` + Method string `form:"method,optional"` + Status int64 `form:"status,optional"` + } + + // API列表响应 + AdminGetApiListResp { + Items []AdminApiInfo `json:"items"` + Total int64 `json:"total"` + } + + // API信息 + AdminApiInfo { + Id int64 `json:"id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` + } + + // API详情请求 + AdminGetApiDetailReq { + Id int64 `path:"id"` + } + + // API详情响应 + AdminGetApiDetailResp { + AdminApiInfo + } + + // 创建API请求 + AdminCreateApiReq { + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status,default=1"` + Description string `json:"description,optional"` + } + + // 创建API响应 + AdminCreateApiResp { + Id int64 `json:"id"` + } + + // 更新API请求 + AdminUpdateApiReq { + Id int64 `path:"id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description,optional"` + } + + // 更新API响应 + AdminUpdateApiResp { + Success bool `json:"success"` + } + + // 删除API请求 + AdminDeleteApiReq { + Id int64 `path:"id"` + } + + // 删除API响应 + AdminDeleteApiResp { + Success bool `json:"success"` + } + + // 批量更新API状态请求 + AdminBatchUpdateApiStatusReq { + Ids []int64 `json:"ids"` + Status int64 `json:"status"` + } + + // 批量更新API状态响应 + AdminBatchUpdateApiStatusResp { + Success bool `json:"success"` + } +) + +@server ( + prefix: api/v1 + group: admin_api + middleware: AdminAuthInterceptor +) +service main { + // 获取API列表 + @handler AdminGetApiList + get /admin/api/list (AdminGetApiListReq) returns (AdminGetApiListResp) + + // 获取API详情 + @handler AdminGetApiDetail + get /admin/api/detail/:id (AdminGetApiDetailReq) returns (AdminGetApiDetailResp) + + // 创建API + @handler AdminCreateApi + post /admin/api/create (AdminCreateApiReq) returns (AdminCreateApiResp) + + // 更新API + @handler AdminUpdateApi + put /admin/api/update/:id (AdminUpdateApiReq) returns (AdminUpdateApiResp) + + // 删除API + @handler AdminDeleteApi + delete /admin/api/delete/:id (AdminDeleteApiReq) returns (AdminDeleteApiResp) + + // 批量更新API状态 + @handler AdminBatchUpdateApiStatus + put /admin/api/batch-update-status (AdminBatchUpdateApiStatusReq) returns (AdminBatchUpdateApiStatusResp) +} diff --git a/app/main/api/desc/admin/admin_feature.api b/app/main/api/desc/admin/admin_feature.api new file mode 100644 index 0000000..57a0e9a --- /dev/null +++ b/app/main/api/desc/admin/admin_feature.api @@ -0,0 +1,132 @@ +syntax = "v1" + +info ( + title: "后台功能管理服务" + desc: "后台功能管理相关接口" + version: "v1" +) + +// 功能管理接口 +@server ( + prefix: /api/v1/admin/feature + group: admin_feature + middleware: AdminAuthInterceptor +) +service main { + // 创建功能 + @handler AdminCreateFeature + post /create (AdminCreateFeatureReq) returns (AdminCreateFeatureResp) + + // 更新功能 + @handler AdminUpdateFeature + put /update/:id (AdminUpdateFeatureReq) returns (AdminUpdateFeatureResp) + + // 删除功能 + @handler AdminDeleteFeature + delete /delete/:id (AdminDeleteFeatureReq) returns (AdminDeleteFeatureResp) + + // 获取功能列表 + @handler AdminGetFeatureList + get /list (AdminGetFeatureListReq) returns (AdminGetFeatureListResp) + + // 获取功能详情 + @handler AdminGetFeatureDetail + get /detail/:id (AdminGetFeatureDetailReq) returns (AdminGetFeatureDetailResp) + + // 配置功能示例数据 + @handler AdminConfigFeatureExample + post /config-example (AdminConfigFeatureExampleReq) returns (AdminConfigFeatureExampleResp) + + // 查看功能示例数据 + @handler AdminGetFeatureExample + get /example/:feature_id (AdminGetFeatureExampleReq) returns (AdminGetFeatureExampleResp) +} + +type ( + // 创建功能请求 + AdminCreateFeatureReq { + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 + } + // 创建功能响应 + AdminCreateFeatureResp { + Id int64 `json:"id"` // 功能ID + } + // 更新功能请求 + AdminUpdateFeatureReq { + Id int64 `path:"id"` // 功能ID + ApiId *string `json:"api_id,optional"` // API标识 + Name *string `json:"name,optional"` // 描述 + CostPrice *float64 `json:"cost_price,optional"` // 成本价 + } + // 更新功能响应 + AdminUpdateFeatureResp { + Success bool `json:"success"` // 是否成功 + } + // 删除功能请求 + AdminDeleteFeatureReq { + Id int64 `path:"id"` // 功能ID + } + // 删除功能响应 + AdminDeleteFeatureResp { + Success bool `json:"success"` // 是否成功 + } + // 获取功能列表请求 + AdminGetFeatureListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ApiId *string `form:"api_id,optional"` // API标识 + Name *string `form:"name,optional"` // 描述 + } + // 功能列表项 + FeatureListItem { + Id int64 `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + // 获取功能列表响应 + AdminGetFeatureListResp { + Total int64 `json:"total"` // 总数 + Items []FeatureListItem `json:"items"` // 列表数据 + } + // 获取功能详情请求 + AdminGetFeatureDetailReq { + Id int64 `path:"id"` // 功能ID + } + // 获取功能详情响应 + AdminGetFeatureDetailResp { + Id int64 `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + // 配置功能示例数据请求 + AdminConfigFeatureExampleReq { + FeatureId int64 `json:"feature_id"` // 功能ID + Data string `json:"data"` // 示例数据JSON + } + // 配置功能示例数据响应 + AdminConfigFeatureExampleResp { + Success bool `json:"success"` // 是否成功 + } + // 查看功能示例数据请求 + AdminGetFeatureExampleReq { + FeatureId int64 `path:"feature_id"` // 功能ID + } + // 查看功能示例数据响应 + AdminGetFeatureExampleResp { + Id int64 `json:"id"` // 示例数据ID + FeatureId int64 `json:"feature_id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Data string `json:"data"` // 示例数据JSON + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } +) + diff --git a/app/main/api/desc/admin/admin_product.api b/app/main/api/desc/admin/admin_product.api new file mode 100644 index 0000000..b8fe7ba --- /dev/null +++ b/app/main/api/desc/admin/admin_product.api @@ -0,0 +1,174 @@ +syntax = "v1" + +info ( + title: "后台产品管理服务" + desc: "后台产品管理相关接口" + version: "v1" +) + +// 产品管理接口 +@server( + prefix: /api/v1/admin/product + group: admin_product + middleware: AdminAuthInterceptor +) +service main { + // 创建产品 + @handler AdminCreateProduct + post /create (AdminCreateProductReq) returns (AdminCreateProductResp) + + // 更新产品 + @handler AdminUpdateProduct + put /update/:id (AdminUpdateProductReq) returns (AdminUpdateProductResp) + + // 删除产品 + @handler AdminDeleteProduct + delete /delete/:id (AdminDeleteProductReq) returns (AdminDeleteProductResp) + + // 获取产品列表 + @handler AdminGetProductList + get /list (AdminGetProductListReq) returns (AdminGetProductListResp) + + // 获取产品详情 + @handler AdminGetProductDetail + get /detail/:id (AdminGetProductDetailReq) returns (AdminGetProductDetailResp) + + // 获取产品功能列表 + @handler AdminGetProductFeatureList + get /feature/list/:product_id (AdminGetProductFeatureListReq) returns ([]AdminGetProductFeatureListResp) + + // 更新产品功能关联(批量) + @handler AdminUpdateProductFeatures + put /feature/update/:product_id (AdminUpdateProductFeaturesReq) returns (AdminUpdateProductFeaturesResp) +} + +type ( + // 创建产品请求 + AdminCreateProductReq { + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes,optional"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 + } + + // 创建产品响应 + AdminCreateProductResp { + Id int64 `json:"id"` // 产品ID + } + + // 更新产品请求 + AdminUpdateProductReq { + Id int64 `path:"id"` // 产品ID + ProductName *string `json:"product_name,optional"` // 服务名 + ProductEn *string `json:"product_en,optional"` // 英文名 + Description *string `json:"description,optional"` // 描述 + Notes *string `json:"notes,optional"` // 备注 + CostPrice *float64 `json:"cost_price,optional"` // 成本 + SellPrice *float64 `json:"sell_price,optional"` // 售价 + } + + // 更新产品响应 + AdminUpdateProductResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除产品请求 + AdminDeleteProductReq { + Id int64 `path:"id"` // 产品ID + } + + // 删除产品响应 + AdminDeleteProductResp { + Success bool `json:"success"` // 是否成功 + } + + // 获取产品列表请求 + AdminGetProductListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ProductName *string `form:"product_name,optional"` // 服务名 + ProductEn *string `form:"product_en,optional"` // 英文名 + } + + // 产品列表项 + ProductListItem { + Id int64 `json:"id"` // 产品ID + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 获取产品列表响应 + AdminGetProductListResp { + Total int64 `json:"total"` // 总数 + Items []ProductListItem `json:"items"` // 列表数据 + } + + // 获取产品详情请求 + AdminGetProductDetailReq { + Id int64 `path:"id"` // 产品ID + } + + // 获取产品详情响应 + AdminGetProductDetailResp { + Id int64 `json:"id"` // 产品ID + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 获取产品功能列表请求 + AdminGetProductFeatureListReq { + ProductId int64 `path:"product_id"` // 产品ID + } + + // 获取产品功能列表响应Item + AdminGetProductFeatureListResp { + Id int64 `json:"id"` // 关联ID + ProductId int64 `json:"product_id"` // 产品ID + FeatureId int64 `json:"feature_id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 功能描述 + Sort int64 `json:"sort"` // 排序 + Enable int64 `json:"enable"` // 是否启用 + IsImportant int64 `json:"is_important"` // 是否重要 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // // 获取产品功能列表响应 + // AdminGetProductFeatureListResp { + // Items []ProductFeatureListItem `json:"items"` // 列表数据 + // } + + // 产品功能关联项 + ProductFeatureItem { + FeatureId int64 `json:"feature_id"` // 功能ID + Sort int64 `json:"sort"` // 排序 + Enable int64 `json:"enable"` // 是否启用 + IsImportant int64 `json:"is_important"` // 是否重要 + } + + // 更新产品功能关联请求(批量) + AdminUpdateProductFeaturesReq { + ProductId int64 `path:"product_id"` // 产品ID + Features []ProductFeatureItem `json:"features"` // 功能列表 + } + + // 更新产品功能关联响应 + AdminUpdateProductFeaturesResp { + Success bool `json:"success"` // 是否成功 + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/admin_query.api b/app/main/api/desc/admin/admin_query.api new file mode 100644 index 0000000..ab3db31 --- /dev/null +++ b/app/main/api/desc/admin/admin_query.api @@ -0,0 +1,133 @@ +syntax = "v1" + +info ( + title: "查询服务" + desc: "查询服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/query + group: admin_query + middleware: AdminAuthInterceptor +) +service main { + @doc "获取查询详情" + @handler AdminGetQueryDetailByOrderId + get /detail/:order_id (AdminGetQueryDetailByOrderIdReq) returns (AdminGetQueryDetailByOrderIdResp) + + @doc "获取清理日志列表" + @handler AdminGetQueryCleanupLogList + get /cleanup/logs (AdminGetQueryCleanupLogListReq) returns (AdminGetQueryCleanupLogListResp) + + @doc "获取清理详情列表" + @handler AdminGetQueryCleanupDetailList + get /cleanup/details/:log_id (AdminGetQueryCleanupDetailListReq) returns (AdminGetQueryCleanupDetailListResp) + + @doc "获取清理配置列表" + @handler AdminGetQueryCleanupConfigList + get /cleanup/configs (AdminGetQueryCleanupConfigListReq) returns (AdminGetQueryCleanupConfigListResp) + + @doc "更新清理配置" + @handler AdminUpdateQueryCleanupConfig + put /cleanup/config (AdminUpdateQueryCleanupConfigReq) returns (AdminUpdateQueryCleanupConfigResp) +} + +type AdminGetQueryDetailByOrderIdReq { + OrderId int64 `path:"order_id"` +} + +type AdminGetQueryDetailByOrderIdResp { + Id int64 `json:"id"` // 主键ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品ID + QueryParams map[string]interface{} `json:"query_params"` + QueryData []AdminQueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 +} + +type AdminQueryItem { + Feature interface{} `json:"feature"` + Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct +} + +// 清理日志相关请求响应定义 +type AdminGetQueryCleanupLogListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"page_size,default=20"` // 每页数量 + Status int64 `form:"status,optional"` // 状态:1-成功,2-失败 + StartTime string `form:"start_time,optional"` // 开始时间 + EndTime string `form:"end_time,optional"` // 结束时间 +} + +type AdminGetQueryCleanupLogListResp { + Total int64 `json:"total"` // 总数 + Items []QueryCleanupLogItem `json:"items"` // 列表 +} + +type QueryCleanupLogItem { + Id int64 `json:"id"` // 主键ID + CleanupTime string `json:"cleanup_time"` // 清理时间 + CleanupBefore string `json:"cleanup_before"` // 清理截止时间 + Status int64 `json:"status"` // 状态:1-成功,2-失败 + AffectedRows int64 `json:"affected_rows"` // 影响行数 + ErrorMsg string `json:"error_msg"` // 错误信息 + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 +} + +// 清理详情相关请求响应定义 +type AdminGetQueryCleanupDetailListReq { + LogId int64 `path:"log_id"` // 清理日志ID + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"page_size,default=20"` // 每页数量 +} + +type AdminGetQueryCleanupDetailListResp { + Total int64 `json:"total"` // 总数 + Items []QueryCleanupDetailItem `json:"items"` // 列表 +} + +type QueryCleanupDetailItem { + Id int64 `json:"id"` // 主键ID + CleanupLogId int64 `json:"cleanup_log_id"` // 清理日志ID + QueryId int64 `json:"query_id"` // 查询ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品名称 + QueryState string `json:"query_state"` // 查询状态 + CreateTimeOld string `json:"create_time_old"` // 原创建时间 + CreateTime string `json:"create_time"` // 创建时间 +} + +// 清理配置相关请求响应定义 +type AdminGetQueryCleanupConfigListReq { + Status int64 `form:"status,optional"` // 状态:1-启用,0-禁用 +} + +type AdminGetQueryCleanupConfigListResp { + Items []QueryCleanupConfigItem `json:"items"` // 配置列表 +} + +type QueryCleanupConfigItem { + Id int64 `json:"id"` // 主键ID + ConfigKey string `json:"config_key"` // 配置键 + ConfigValue string `json:"config_value"` // 配置值 + ConfigDesc string `json:"config_desc"` // 配置描述 + Status int64 `json:"status"` // 状态:1-启用,0-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminUpdateQueryCleanupConfigReq { + Id int64 `json:"id"` // 主键ID + ConfigValue string `json:"config_value"` // 配置值 + Status int64 `json:"status"` // 状态:1-启用,0-禁用 +} + +type AdminUpdateQueryCleanupConfigResp { + Success bool `json:"success"` // 是否成功 +} \ No newline at end of file diff --git a/app/main/api/desc/admin/admin_queue.api b/app/main/api/desc/admin/admin_queue.api new file mode 100644 index 0000000..e86841b --- /dev/null +++ b/app/main/api/desc/admin/admin_queue.api @@ -0,0 +1,21 @@ +syntax = "v1" + +info( + title: "Admin Queue管理" + desc: "管理员队列管理接口" + author: "team" + version: "v1" +) + +type ( + +) + +@server ( + prefix: api/v1 + group: admin_queue + middleware: AdminAuthInterceptor +) +service main { + +} diff --git a/app/main/api/desc/admin/admin_role_api.api b/app/main/api/desc/admin/admin_role_api.api new file mode 100644 index 0000000..2659a8e --- /dev/null +++ b/app/main/api/desc/admin/admin_role_api.api @@ -0,0 +1,103 @@ +syntax = "v1" + +info( + title: "Admin 角色API权限管理" + desc: "管理员角色API权限管理接口" + author: "team" + version: "v1" +) + +type ( + // 获取角色API权限列表请求 + AdminGetRoleApiListReq { + RoleId int64 `path:"role_id"` + } + + // 获取角色API权限列表响应 + AdminGetRoleApiListResp { + Items []AdminRoleApiInfo `json:"items"` + } + + // 角色API权限信息 + AdminRoleApiInfo { + Id int64 `json:"id"` + RoleId int64 `json:"role_id"` + ApiId int64 `json:"api_id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description"` + } + + // 分配角色API权限请求 + AdminAssignRoleApiReq { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` + } + + // 分配角色API权限响应 + AdminAssignRoleApiResp { + Success bool `json:"success"` + } + + // 移除角色API权限请求 + AdminRemoveRoleApiReq { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` + } + + // 移除角色API权限响应 + AdminRemoveRoleApiResp { + Success bool `json:"success"` + } + + // 更新角色API权限请求 + AdminUpdateRoleApiReq { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` + } + + // 更新角色API权限响应 + AdminUpdateRoleApiResp { + Success bool `json:"success"` + } + + // 获取所有API列表(用于权限分配) + AdminGetAllApiListReq { + Status int64 `form:"status,optional,default=1"` + } + + // 获取所有API列表响应 + AdminGetAllApiListResp { + Items []AdminRoleApiInfo `json:"items"` + } +) + +@server ( + prefix: api/v1 + group: admin_role_api + middleware: AdminAuthInterceptor +) +service main { + // 获取角色API权限列表 + @handler AdminGetRoleApiList + get /admin/role/:role_id/api/list (AdminGetRoleApiListReq) returns (AdminGetRoleApiListResp) + + // 分配角色API权限 + @handler AdminAssignRoleApi + post /admin/role/api/assign (AdminAssignRoleApiReq) returns (AdminAssignRoleApiResp) + + // 移除角色API权限 + @handler AdminRemoveRoleApi + post /admin/role/api/remove (AdminRemoveRoleApiReq) returns (AdminRemoveRoleApiResp) + + // 更新角色API权限 + @handler AdminUpdateRoleApi + put /admin/role/api/update (AdminUpdateRoleApiReq) returns (AdminUpdateRoleApiResp) + + // 获取所有API列表(用于权限分配) + @handler AdminGetAllApiList + get /admin/api/all (AdminGetAllApiListReq) returns (AdminGetAllApiListResp) +} diff --git a/app/main/api/desc/admin/admin_user.api b/app/main/api/desc/admin/admin_user.api new file mode 100644 index 0000000..8df538e --- /dev/null +++ b/app/main/api/desc/admin/admin_user.api @@ -0,0 +1,144 @@ +syntax = "v1" + +info ( + title: "后台用户中心服务" + desc: "后台用户中心服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/user + group: admin_user + middleware: AdminAuthInterceptor +) +service main { + @doc "获取用户列表" + @handler AdminGetUserList + get /list (AdminGetUserListReq) returns (AdminGetUserListResp) + + @doc "获取用户详情" + @handler AdminGetUserDetail + get /detail/:id (AdminGetUserDetailReq) returns (AdminGetUserDetailResp) + + @doc "创建用户" + @handler AdminCreateUser + post /create (AdminCreateUserReq) returns (AdminCreateUserResp) + + @doc "更新用户" + @handler AdminUpdateUser + put /update/:id (AdminUpdateUserReq) returns (AdminUpdateUserResp) + + @doc "删除用户" + @handler AdminDeleteUser + delete /delete/:id (AdminDeleteUserReq) returns (AdminDeleteUserResp) + + @doc "用户信息" + @handler AdminUserInfo + get /info (AdminUserInfoReq) returns (AdminUserInfoResp) + + @doc "重置管理员密码" + @handler AdminResetPassword + put /reset-password/:id (AdminResetPasswordReq) returns (AdminResetPasswordResp) +} + +type ( + // 列表请求 + AdminGetUserListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Username string `form:"username,optional"` // 用户名 + RealName string `form:"real_name,optional"` // 真实姓名 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 + } + + // 列表响应 + AdminGetUserListResp { + Total int64 `json:"total"` // 总数 + Items []AdminUserListItem `json:"items"` // 列表 + } + + // 列表项 + AdminUserListItem { + Id int64 `json:"id"` // 用户ID + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + CreateTime string `json:"create_time"` // 创建时间 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 + } + + // 详情请求 + AdminGetUserDetailReq { + Id int64 `path:"id"` // 用户ID + } + + // 详情响应 + AdminGetUserDetailResp { + Id int64 `json:"id"` // 用户ID + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 + } + + // 创建请求 + AdminCreateUserReq { + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status,default=1"` // 状态:0-禁用,1-启用 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 + } + + // 创建响应 + AdminCreateUserResp { + Id int64 `json:"id"` // 用户ID + } + + // 更新请求 + AdminUpdateUserReq { + Id int64 `path:"id"` // 用户ID + Username *string `json:"username,optional"` // 用户名 + RealName *string `json:"real_name,optional"` // 真实姓名 + Status *int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + RoleIds []int64 `json:"role_ids,optional"` // 关联的角色ID列表 + } + + // 更新响应 + AdminUpdateUserResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除请求 + AdminDeleteUserReq { + Id int64 `path:"id"` // 用户ID + } + + // 删除响应 + AdminDeleteUserResp { + Success bool `json:"success"` // 是否成功 + } + + // 用户信息请求 + AdminUserInfoReq { + } + + // 用户信息响应 + AdminUserInfoResp { + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Roles []string `json:"roles"` // 角色编码列表 + } + + // 重置密码请求 + AdminResetPasswordReq { + Id int64 `path:"id"` // 用户ID + Password string `json:"password"` // 新密码 + } + + // 重置密码响应 + AdminResetPasswordResp { + Success bool `json:"success"` // 是否成功 + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/auth.api b/app/main/api/desc/admin/auth.api new file mode 100644 index 0000000..71c79a9 --- /dev/null +++ b/app/main/api/desc/admin/auth.api @@ -0,0 +1,32 @@ +syntax = "v1" + +info ( + title: "认证中心服务" + desc: "认证中心服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/auth + group: admin_auth +) +service main { + @doc "登录" + @handler AdminLogin + post /login (AdminLoginReq) returns (AdminLoginResp) + +} + +type ( + AdminLoginReq { + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Captcha bool `json:"captcha" validate:"required"` + } + AdminLoginResp { + AccessToken string `json:"access_token"` + AccessExpire int64 `json:"access_expire"` + RefreshAfter int64 `json:"refresh_after"` + Roles []string `json:"roles"` + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/menu.api b/app/main/api/desc/admin/menu.api new file mode 100644 index 0000000..792539c --- /dev/null +++ b/app/main/api/desc/admin/menu.api @@ -0,0 +1,147 @@ +syntax = "v1" + +info ( + title: "菜单中心服务" + desc: "菜单中心服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/menu + group: admin_menu + middleware: AdminAuthInterceptor +) +service main { + @doc "获取菜单列表" + @handler GetMenuList + get /list (GetMenuListReq) returns ([]MenuListItem) + + @doc "获取菜单详情" + @handler GetMenuDetail + get /detail/:id (GetMenuDetailReq) returns (GetMenuDetailResp) + + @doc "创建菜单" + @handler CreateMenu + post /create (CreateMenuReq) returns (CreateMenuResp) + + @doc "更新菜单" + @handler UpdateMenu + put /update/:id (UpdateMenuReq) returns (UpdateMenuResp) + + @doc "删除菜单" + @handler DeleteMenu + delete /delete/:id (DeleteMenuReq) returns (DeleteMenuResp) + + @doc "获取所有菜单(树形结构)" + @handler GetMenuAll + get /all (GetMenuAllReq) returns ([]GetMenuAllResp) +} + +type ( + // 列表请求 + GetMenuListReq { + Name string `form:"name,optional"` // 菜单名称 + Path string `form:"path,optional"` // 路由路径 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 + Type string `form:"type,optional"` // 类型 + } + + // 列表项 + MenuListItem { + Id int64 `json:"id"` // 菜单ID + Pid int64 `json:"pid"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path"` // 路由路径 + Component string `json:"component"` // 组件路径 + Redirect string `json:"redirect"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"createTime"` // 创建时间 + Children []MenuListItem `json:"children"` // 子菜单 + } + + // 详情请求 + GetMenuDetailReq { + Id int64 `path:"id"` // 菜单ID + } + + // 详情响应 + GetMenuDetailResp { + Id int64 `json:"id"` // 菜单ID + Pid int64 `json:"pid"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path"` // 路由路径 + Component string `json:"component"` // 组件路径 + Redirect string `json:"redirect"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"createTime"` // 创建时间 + UpdateTime string `json:"updateTime"` // 更新时间 + } + + // 创建请求 + CreateMenuReq { + Pid int64 `json:"pid,optional"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path,optional"` // 路由路径 + Component string `json:"component,optional"` // 组件路径 + Redirect string `json:"redirect,optional"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status,optional,default=1"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort,optional"` // 排序 + } + + // 创建响应 + CreateMenuResp { + Id int64 `json:"id"` // 菜单ID + } + + // 更新请求 + UpdateMenuReq { + Id int64 `path:"id"` // 菜单ID + Pid int64 `json:"pid,optional"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path,optional"` // 路由路径 + Component string `json:"component,optional"` // 组件路径 + Redirect string `json:"redirect,optional"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort,optional"` // 排序 + } + + // 更新响应 + UpdateMenuResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除请求 + DeleteMenuReq { + Id int64 `path:"id"` // 菜单ID + } + + // 删除响应 + DeleteMenuResp { + Success bool `json:"success"` // 是否成功 + } + + // 获取所有菜单请求 + GetMenuAllReq { + } + + // 获取所有菜单响应 + GetMenuAllResp { + Name string `json:"name"` + Path string `json:"path"` + Redirect string `json:"redirect,omitempty"` + Component string `json:"component,omitempty"` + Sort int64 `json:"sort"` + Meta map[string]interface{} `json:"meta"` + Children []GetMenuAllResp `json:"children"` + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/notification.api b/app/main/api/desc/admin/notification.api new file mode 100644 index 0000000..a66dc31 --- /dev/null +++ b/app/main/api/desc/admin/notification.api @@ -0,0 +1,128 @@ +syntax = "v1" + +type ( + // 创建通知请求 + AdminCreateNotificationReq { + Title string `json:"title"` // 通知标题 + NotificationPage string `json:"notification_page"` // 通知页面 + Content string `json:"content"` // 通知内容 + StartDate string `json:"start_date"` // 生效开始日期(yyyy-MM-dd) + StartTime string `json:"start_time"` // 生效开始时间(HH:mm:ss) + EndDate string `json:"end_date"` // 生效结束日期(yyyy-MM-dd) + EndTime string `json:"end_time"` // 生效结束时间(HH:mm:ss) + Status int64 `json:"status"` // 状态:1-启用,0-禁用 + } + + // 创建通知响应 + AdminCreateNotificationResp { + Id int64 `json:"id"` // 通知ID + } + + // 更新通知请求 + AdminUpdateNotificationReq { + Id int64 `path:"id"` // 通知ID + Title *string `json:"title,optional"` // 通知标题 + Content *string `json:"content,optional"` // 通知内容 + NotificationPage *string `json:"notification_page,optional"` // 通知页面 + StartDate *string `json:"start_date,optional"` // 生效开始日期 + StartTime *string `json:"start_time,optional"` // 生效开始时间 + EndDate *string `json:"end_date,optional"` // 生效结束日期 + EndTime *string `json:"end_time,optional"` // 生效结束时间 + Status *int64 `json:"status,optional"` // 状态 + } + + // 更新通知响应 + AdminUpdateNotificationResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除通知请求 + AdminDeleteNotificationReq { + Id int64 `path:"id"` // 通知ID + } + + // 删除通知响应 + AdminDeleteNotificationResp { + Success bool `json:"success"` // 是否成功 + } + + // 获取通知详情请求 + AdminGetNotificationDetailReq { + Id int64 `path:"id"` // 通知ID + } + + // 获取通知详情响应 + AdminGetNotificationDetailResp { + Id int64 `json:"id"` // 通知ID + Title string `json:"title"` // 通知标题 + Content string `json:"content"` // 通知内容 + NotificationPage string `json:"notification_page"` // 通知页面 + StartDate string `json:"start_date"` // 生效开始日期 + StartTime string `json:"start_time"` // 生效开始时间 + EndDate string `json:"end_date"` // 生效结束日期 + EndTime string `json:"end_time"` // 生效结束时间 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 获取通知列表请求 + AdminGetNotificationListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + Title *string `form:"title,optional"` // 通知标题(可选) + NotificationPage *string `form:"notification_page,optional"` // 通知页面(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + StartDate *string `form:"start_date,optional"` // 开始日期范围(可选) + EndDate *string `form:"end_date,optional"` // 结束日期范围(可选) + } + + // 通知列表项 + NotificationListItem { + Id int64 `json:"id"` // 通知ID + Title string `json:"title"` // 通知标题 + NotificationPage string `json:"notification_page"` // 通知页面 + Content string `json:"content"` // 通知内容 + StartDate string `json:"start_date"` // 生效开始日期 + StartTime string `json:"start_time"` // 生效开始时间 + EndDate string `json:"end_date"` // 生效结束日期 + EndTime string `json:"end_time"` // 生效结束时间 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 获取通知列表响应 + AdminGetNotificationListResp { + Total int64 `json:"total"` // 总数 + Items []NotificationListItem `json:"items"` // 列表数据 + } +) + +// 通知管理接口 +@server( + prefix: /api/v1/admin/notification + group: admin_notification + middleware: AdminAuthInterceptor +) +service main { + // 创建通知 + @handler AdminCreateNotification + post /create (AdminCreateNotificationReq) returns (AdminCreateNotificationResp) + + // 更新通知 + @handler AdminUpdateNotification + put /update/:id (AdminUpdateNotificationReq) returns (AdminUpdateNotificationResp) + + // 删除通知 + @handler AdminDeleteNotification + delete /delete/:id (AdminDeleteNotificationReq) returns (AdminDeleteNotificationResp) + + // 获取通知详情 + @handler AdminGetNotificationDetail + get /detail/:id (AdminGetNotificationDetailReq) returns (AdminGetNotificationDetailResp) + + // 获取通知列表 + @handler AdminGetNotificationList + get /list (AdminGetNotificationListReq) returns (AdminGetNotificationListResp) +} \ No newline at end of file diff --git a/app/main/api/desc/admin/order.api b/app/main/api/desc/admin/order.api new file mode 100644 index 0000000..ffe2265 --- /dev/null +++ b/app/main/api/desc/admin/order.api @@ -0,0 +1,250 @@ +syntax = "v1" + +info ( + title: "订单服务" + desc: "订单服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/order + group: admin_order + middleware: AdminAuthInterceptor +) +service main { + @doc "获取订单列表" + @handler AdminGetOrderList + get /list (AdminGetOrderListReq) returns (AdminGetOrderListResp) + + @doc "获取订单详情" + @handler AdminGetOrderDetail + get /detail/:id (AdminGetOrderDetailReq) returns (AdminGetOrderDetailResp) + + @doc "创建订单" + @handler AdminCreateOrder + post /create (AdminCreateOrderReq) returns (AdminCreateOrderResp) + + @doc "更新订单" + @handler AdminUpdateOrder + put /update/:id (AdminUpdateOrderReq) returns (AdminUpdateOrderResp) + + @doc "删除订单" + @handler AdminDeleteOrder + delete /delete/:id (AdminDeleteOrderReq) returns (AdminDeleteOrderResp) + + @doc "订单退款" + @handler AdminRefundOrder + post /refund/:id (AdminRefundOrderReq) returns (AdminRefundOrderResp) + + @doc "重新执行代理处理" + @handler AdminRetryAgentProcess + post /retry-agent-process/:id (AdminRetryAgentProcessReq) returns (AdminRetryAgentProcessResp) + + @doc "获取退款统计数据" + @handler AdminGetRefundStatistics + get /refund-statistics (AdminGetRefundStatisticsReq) returns (AdminGetRefundStatisticsResp) + + @doc "获取收入和利润统计数据" + @handler AdminGetRevenueStatistics + get /revenue-statistics (AdminGetRevenueStatisticsReq) returns (AdminGetRevenueStatisticsResp) + + @doc "获取订单来源统计数据" + @handler AdminGetOrderSourceStatistics + get /source-statistics (AdminGetOrderSourceStatisticsReq) returns (AdminGetOrderSourceStatisticsResp) + + @doc "获取订单统计数据" + @handler AdminGetOrderStatistics + get /statistics (AdminGetOrderStatisticsReq) returns (AdminGetOrderStatisticsResp) +} + +type ( + // 列表请求 + AdminGetOrderListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + OrderNo string `form:"order_no,optional"` // 商户订单号 + PlatformOrderId string `form:"platform_order_id,optional"` // 支付订单号 + ProductName string `form:"product_name,optional"` // 产品名称 + PaymentPlatform string `form:"payment_platform,optional"` // 支付方式 + PaymentScene string `form:"payment_scene,optional"` // 支付平台 + Amount float64 `form:"amount,optional"` // 金额 + Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + IsPromotion int64 `form:"is_promotion,optional,default=-1"` // 是否推广订单:0-否,1-是 + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始 + PayTimeEnd string `form:"pay_time_end,optional"` // 支付时间结束 + RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 + RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 + SalesCost float64 `form:"sales_cost,optional"` // 成本价 + QueryName string `form:"query_name,optional"` // 被查询人姓名(通过 query_user_record 表追溯订单) + QueryIdCard string `form:"query_id_card,optional"` // 被查询人身份证(通过 query_user_record 表追溯订单) + QueryMobile string `form:"query_mobile,optional"` // 被查询人手机号(通过 query_user_record 表追溯订单) + } + // 列表响应 + AdminGetOrderListResp { + Total int64 `json:"total"` // 总数 + Items []OrderListItem `json:"items"` // 列表 + } + // 列表项 + OrderListItem { + Id int64 `json:"id"` // 订单ID + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + SalesCost float64 `json:"sales_cost"` // 成本价 + Status string `json:"status"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + QueryState string `json:"query_state"` // 查询状态:pending-待查询,success-查询成功,failed-查询失败 processing-查询中 + CreateTime string `json:"create_time"` // 创建时间 + PayTime string `json:"pay_time"` // 支付时间 + RefundTime string `json:"refund_time"` // 退款时间 + IsPromotion int64 `json:"is_promotion"` // 是否推广订单:0-否,1-是 + IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 + AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理 + + } + // 详情请求 + AdminGetOrderDetailReq { + Id int64 `path:"id"` // 订单ID + } + // 详情响应 + AdminGetOrderDetailResp { + Id int64 `json:"id"` // 订单ID + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + SalesCost float64 `json:"sales_cost"` // 成本价 + Status string `json:"status"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + QueryState string `json:"query_state"` // 查询状态:pending-待查询,success-查询成功,failed-查询失败 processing-查询中 + CreateTime string `json:"create_time"` // 创建时间 + PayTime string `json:"pay_time"` // 支付时间 + RefundTime string `json:"refund_time"` // 退款时间 + IsPromotion int64 `json:"is_promotion"` // 是否推广订单:0-否,1-是 + UpdateTime string `json:"update_time"` // 更新时间 + IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 + AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理 + } + // 创建请求 + AdminCreateOrderReq { + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + Status string `json:"status,default=pending"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + IsPromotion int64 `json:"is_promotion,default=0"` // 是否推广订单:0-否,1-是 + } + // 创建响应 + AdminCreateOrderResp { + Id int64 `json:"id"` // 订单ID + } + // 更新请求 + AdminUpdateOrderReq { + Id int64 `path:"id"` // 订单ID + OrderNo *string `json:"order_no,optional"` // 商户订单号 + PlatformOrderId *string `json:"platform_order_id,optional"` // 支付订单号 + ProductName *string `json:"product_name,optional"` // 产品名称 + PaymentPlatform *string `json:"payment_platform,optional"` // 支付方式 + PaymentScene *string `json:"payment_scene,optional"` // 支付平台 + Amount *float64 `json:"amount,optional"` // 金额 + Status *string `json:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + PayTime *string `json:"pay_time,optional"` // 支付时间 + RefundTime *string `json:"refund_time,optional"` // 退款时间 + IsPromotion *int64 `json:"is_promotion,optional"` // 是否推广订单:0-否,1-是 + } + // 更新响应 + AdminUpdateOrderResp { + Success bool `json:"success"` // 是否成功 + } + // 删除请求 + AdminDeleteOrderReq { + Id int64 `path:"id"` // 订单ID + } + // 删除响应 + AdminDeleteOrderResp { + Success bool `json:"success"` // 是否成功 + } + // 退款请求 + AdminRefundOrderReq { + Id int64 `path:"id"` // 订单ID + RefundAmount float64 `json:"refund_amount"` // 退款金额 + RefundReason string `json:"refund_reason"` // 退款原因 + } + // 退款响应 + AdminRefundOrderResp { + Status string `json:"status"` // 退款状态 + RefundNo string `json:"refund_no"` // 退款单号 + Amount float64 `json:"amount"` // 退款金额 + } + // 重新执行代理处理请求 + AdminRetryAgentProcessReq { + Id int64 `path:"id"` // 订单ID + } + // 重新执行代理处理响应 + AdminRetryAgentProcessResp { + Status string `json:"status"` // 执行状态:success-成功,already_processed-已处理,failed-失败 + Message string `json:"message"` // 执行结果消息 + ProcessedAt string `json:"processed_at"` // 处理时间 + } + + // 获取退款统计数据请求 + AdminGetRefundStatisticsReq { + } + + // 获取退款统计数据响应 + AdminGetRefundStatisticsResp { + TotalRefundAmount float64 `json:"total_refund_amount"` // 总退款金额 + TodayRefundAmount float64 `json:"today_refund_amount"` // 今日退款金额 + } + + // 获取收入和利润统计数据请求 + AdminGetRevenueStatisticsReq { + } + + // 获取收入和利润统计数据响应 + AdminGetRevenueStatisticsResp { + TotalRevenueAmount float64 `json:"total_revenue_amount"` // 总收入金额 + TodayRevenueAmount float64 `json:"today_revenue_amount"` // 今日收入金额 + TotalProfitAmount float64 `json:"total_profit_amount"` // 总利润金额 + TodayProfitAmount float64 `json:"today_profit_amount"` // 今日利润金额 + } + + // 获取订单来源统计数据请求 + AdminGetOrderSourceStatisticsReq { + } + + // 订单来源统计项 + OrderSourceStatisticsItem { + ProductName string `json:"product_name"` // 产品名称 + OrderCount int64 `json:"order_count"` // 订单数量 + } + + // 获取订单来源统计数据响应 + AdminGetOrderSourceStatisticsResp { + Items []OrderSourceStatisticsItem `json:"items"` // 订单来源统计列表 + } + + // 获取订单统计数据请求 + AdminGetOrderStatisticsReq { + Dimension string `form:"dimension"` // 时间维度:day-日(当月1号到今天),month-月(今年1月到当月),year-年(过去5年),all-全部(按日统计) + } + + // 订单统计项 + OrderStatisticsItem { + Date string `json:"date"` // 日期 + Count int64 `json:"count"` // 订单数量 + Amount float64 `json:"amount"` // 订单金额 + } + + // 获取订单统计数据响应 + AdminGetOrderStatisticsResp { + Items []OrderStatisticsItem `json:"items"` // 订单统计列表 + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/platform_user.api b/app/main/api/desc/admin/platform_user.api new file mode 100644 index 0000000..435ba93 --- /dev/null +++ b/app/main/api/desc/admin/platform_user.api @@ -0,0 +1,125 @@ +syntax = "v1" + +info ( + title: "平台用户管理" + desc: "平台用户管理" + version: "v1" +) + +// 平台用户管理接口 +@server( + prefix: /api/v1/admin/platform_user + group: admin_platform_user + middleware: AdminAuthInterceptor +) +service main { + // 创建平台用户 + @handler AdminCreatePlatformUser + post /create (AdminCreatePlatformUserReq) returns (AdminCreatePlatformUserResp) + + // 更新平台用户 + @handler AdminUpdatePlatformUser + put /update/:id (AdminUpdatePlatformUserReq) returns (AdminUpdatePlatformUserResp) + + // 删除平台用户 + @handler AdminDeletePlatformUser + delete /delete/:id (AdminDeletePlatformUserReq) returns (AdminDeletePlatformUserResp) + + // 获取平台用户分页列表 + @handler AdminGetPlatformUserList + get /list (AdminGetPlatformUserListReq) returns (AdminGetPlatformUserListResp) + + // 获取平台用户详情 + @handler AdminGetPlatformUserDetail + get /detail/:id (AdminGetPlatformUserDetailReq) returns (AdminGetPlatformUserDetailResp) +} + +type ( + // 分页列表请求 + AdminGetPlatformUserListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Mobile string `form:"mobile,optional"` // 手机号 + Nickname string `form:"nickname,optional"` // 昵称 + Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否 + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + OrderBy string `form:"order_by,optional"` // 排序字段 + OrderType string `form:"order_type,optional"` // 排序类型 + } + + // 分页列表响应 + AdminGetPlatformUserListResp { + Total int64 `json:"total"` // 总数 + Items []PlatformUserListItem `json:"items"` // 列表 + } + + // 列表项 + PlatformUserListItem { + Id int64 `json:"id"` // 用户ID + Mobile string `json:"mobile"` // 手机号 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 + Disable int64 `json:"disable"` // 封禁状态 0-可用 1-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 详情请求 + AdminGetPlatformUserDetailReq { + Id int64 `path:"id"` // 用户ID + } + + // 详情响应 + AdminGetPlatformUserDetailResp { + Id int64 `json:"id"` // 用户ID + Mobile string `json:"mobile"` // 手机号 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 + Disable int64 `json:"disable"` // 封禁状态 0-可用 1-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + } + + // 创建请求 + AdminCreatePlatformUserReq { + Mobile string `json:"mobile"` // 手机号 + Password string `json:"password"` // 密码 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 + } + + // 创建响应 + AdminCreatePlatformUserResp { + Id int64 `json:"id"` // 用户ID + } + + // 更新请求 + AdminUpdatePlatformUserReq { + Id int64 `path:"id"` // 用户ID + Mobile *string `json:"mobile,optional"` // 手机号 + Password *string `json:"password,optional"` // 密码 + Nickname *string `json:"nickname,optional"` // 昵称 + Info *string `json:"info,optional"` // 备注信息 + Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否 + Disable *int64 `json:"disable,optional"` // 封禁状态 0-可用 1-禁用 + } + + // 更新响应 + AdminUpdatePlatformUserResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除请求 + AdminDeletePlatformUserReq { + Id int64 `path:"id"` // 用户ID + } + + // 删除响应 + AdminDeletePlatformUserResp { + Success bool `json:"success"` // 是否成功 + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/promotion.api b/app/main/api/desc/admin/promotion.api new file mode 100644 index 0000000..9c0a03e --- /dev/null +++ b/app/main/api/desc/admin/promotion.api @@ -0,0 +1,182 @@ +syntax = "v1" + +info ( + title: "推广服务" + desc: "推广服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/promotion/link + group: admin_promotion + middleware: AdminAuthInterceptor +) +service main { + @doc "获取推广链接列表" + @handler GetPromotionLinkList + get /list (GetPromotionLinkListReq) returns (GetPromotionLinkListResp) + + @doc "获取推广链接详情" + @handler GetPromotionLinkDetail + get /detail/:id (GetPromotionLinkDetailReq) returns (GetPromotionLinkDetailResp) + + @doc "创建推广链接" + @handler CreatePromotionLink + post /create (CreatePromotionLinkReq) returns (CreatePromotionLinkResp) + + @doc "更新推广链接" + @handler UpdatePromotionLink + put /update/:id (UpdatePromotionLinkReq) returns (UpdatePromotionLinkResp) + + @doc "删除推广链接" + @handler DeletePromotionLink + delete /delete/:id (DeletePromotionLinkReq) returns (DeletePromotionLinkResp) +} + +type ( + // 列表请求 + GetPromotionLinkListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Name string `form:"name,optional"` // 链接名称 + Url string `form:"url,optional"` // 推广链接URL + } + + // 列表响应 + GetPromotionLinkListResp { + Total int64 `json:"total"` // 总数 + Items []PromotionLinkItem `json:"items"` // 列表 + } + + // 列表项 + PromotionLinkItem { + Id int64 `json:"id"` // 链接ID + Name string `json:"name"` // 链接名称 + Url string `json:"url"` // 推广链接URL + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + PayAmount string `json:"pay_amount"` // 付费金额 + CreateTime string `json:"create_time"` // 创建时间 + LastClickTime string `json:"last_click_time,optional"` // 最后点击时间 + LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间 + } + + // 详情请求 + GetPromotionLinkDetailReq { + Id int64 `path:"id"` // 链接ID + } + + // 详情响应 + GetPromotionLinkDetailResp { + Name string `json:"name"` // 链接名称 + Url string `json:"url"` // 推广链接URL + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + PayAmount string `json:"pay_amount"` // 付费金额 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + LastClickTime string `json:"last_click_time,optional"` // 最后点击时间 + LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间 + } + + // 创建请求 + CreatePromotionLinkReq { + Name string `json:"name"` // 链接名称 + } + + // 创建响应 + CreatePromotionLinkResp { + Id int64 `json:"id"` // 链接ID + Url string `json:"url"` // 生成的推广链接URL + } + + // 更新请求 + UpdatePromotionLinkReq { + Id int64 `path:"id"` // 链接ID + Name *string `json:"name,optional"` // 链接名称 + } + + // 更新响应 + UpdatePromotionLinkResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除请求 + DeletePromotionLinkReq { + Id int64 `path:"id"` // 链接ID + } + + // 删除响应 + DeletePromotionLinkResp { + Success bool `json:"success"` // 是否成功 + } +) + +@server ( + prefix: api/v1/admin/promotion/link + group: admin_promotion + middleware: AdminAuthInterceptor +) +service main { + @doc "记录链接点击" + @handler RecordLinkClick + get /record/:path (RecordLinkClickReq) returns (RecordLinkClickResp) +} + +type ( + // 记录链接点击请求 + RecordLinkClickReq { + Path string `path:"path"` // 链接路径 + } + + // 记录链接点击响应 + RecordLinkClickResp { + Success bool `json:"success"` // 是否成功 + } +) +@server ( + prefix: api/v1/admin/promotion/stats + group: admin_promotion + middleware: AdminAuthInterceptor +) +service main { + @doc "获取推广历史记录" + @handler GetPromotionStatsHistory + get /history (GetPromotionStatsHistoryReq) returns ([]PromotionStatsHistoryItem) + + @doc "获取推广总统计" + @handler GetPromotionStatsTotal + get /total (GetPromotionStatsTotalReq) returns (GetPromotionStatsTotalResp) +} + +type ( + // 获取推广历史记录请求 + GetPromotionStatsHistoryReq { + StartDate string `form:"start_date"` // 开始日期,格式:YYYY-MM-DD + EndDate string `form:"end_date"` // 结束日期,格式:YYYY-MM-DD + } + + // 推广历史记录项 + PromotionStatsHistoryItem { + Id int64 `json:"id"` // 记录ID + LinkId int64 `json:"link_id"` // 链接ID + PayAmount float64 `json:"pay_amount"` // 金额 + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + StatsDate string `json:"stats_date"` // 统计日期 + } + + // 获取推广总统计请求 + GetPromotionStatsTotalReq { + } + + // 获取推广总统计响应 + GetPromotionStatsTotalResp { + TodayPayAmount float64 `json:"today_pay_amount"` // 今日金额 + TodayClickCount int64 `json:"today_click_count"` // 今日点击数 + TodayPayCount int64 `json:"today_pay_count"` // 今日付费次数 + TotalPayAmount float64 `json:"total_pay_amount"` // 总金额 + TotalClickCount int64 `json:"total_click_count"` // 总点击数 + TotalPayCount int64 `json:"total_pay_count"` // 总付费次数 + } +) \ No newline at end of file diff --git a/app/main/api/desc/admin/role.api b/app/main/api/desc/admin/role.api new file mode 100644 index 0000000..99dd121 --- /dev/null +++ b/app/main/api/desc/admin/role.api @@ -0,0 +1,122 @@ +syntax = "v1" + +info ( + title: "角色服务" + desc: "角色服务" + version: "v1" +) + +@server ( + prefix: api/v1/admin/role + group: admin_role + middleware: AdminAuthInterceptor +) +service main { + @doc "获取角色列表" + @handler GetRoleList + get /list (GetRoleListReq) returns (GetRoleListResp) + + @doc "获取角色详情" + @handler GetRoleDetail + get /detail/:id (GetRoleDetailReq) returns (GetRoleDetailResp) + + @doc "创建角色" + @handler CreateRole + post /create (CreateRoleReq) returns (CreateRoleResp) + + @doc "更新角色" + @handler UpdateRole + put /update/:id (UpdateRoleReq) returns (UpdateRoleResp) + + @doc "删除角色" + @handler DeleteRole + delete /delete/:id (DeleteRoleReq) returns (DeleteRoleResp) +} + +type ( + // 列表请求 + GetRoleListReq { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Name string `form:"name,optional"` // 角色名称 + Code string `form:"code,optional"` // 角色编码 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 + } + + // 列表响应 + GetRoleListResp { + Total int64 `json:"total"` // 总数 + Items []RoleListItem `json:"items"` // 列表 + } + + // 列表项 + RoleListItem { + Id int64 `json:"id"` // 角色ID + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"create_time"` // 创建时间 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 + } + + // 详情请求 + GetRoleDetailReq { + Id int64 `path:"id"` // 角色ID + } + + // 详情响应 + GetRoleDetailResp { + Id int64 `json:"id"` // 角色ID + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 + } + + // 创建请求 + CreateRoleReq { + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status,default=1"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort,default=0"` // 排序 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 + } + + // 创建响应 + CreateRoleResp { + Id int64 `json:"id"` // 角色ID + } + + // 更新请求 + UpdateRoleReq { + Id int64 `path:"id"` // 角色ID + RoleName *string `json:"role_name,optional"` // 角色名称 + RoleCode *string `json:"role_code,optional"` // 角色编码 + Description *string `json:"description,optional"` // 角色描述 + Status *int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + Sort *int64 `json:"sort,optional"` // 排序 + MenuIds []int64 `json:"menu_ids,optional"` // 关联的菜单ID列表 + } + + // 更新响应 + UpdateRoleResp { + Success bool `json:"success"` // 是否成功 + } + + // 删除请求 + DeleteRoleReq { + Id int64 `path:"id"` // 角色ID + } + + // 删除响应 + DeleteRoleResp { + Success bool `json:"success"` // 是否成功 + } +) \ No newline at end of file diff --git a/app/main/api/desc/front/agent.api b/app/main/api/desc/front/agent.api new file mode 100644 index 0000000..12ac05d --- /dev/null +++ b/app/main/api/desc/front/agent.api @@ -0,0 +1,451 @@ +syntax = "v1" + +info ( + title: "代理服务" + desc: "代理服务接口" + version: "v1" +) + +@server ( + prefix: api/v1/agent + group: agent +) +service main { + // 获取推广二维码海报 + @handler GetAgentPromotionQrcode + get /promotion/qrcode (GetAgentPromotionQrcodeReq) + + // 获取会员开通信息 + @handler GetMembershipInfo + get /membership/info returns (GetMembershipInfoResp) +} + +type ( + GetAgentPromotionQrcodeReq { + QrcodeType string `form:"qrcode_type"` + QrcodeUrl string `form:"qrcode_url"` + } + // 会员配置信息 + MembershipConfigInfo { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price float64 `json:"price"` // 会员年费 + ReportCommission float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward float64 `json:"lower_activity_reward"` // 下级活跃奖励金额 + NewActivityReward float64 `json:"new_activity_reward"` // 新增活跃奖励金额 + LowerStandardCount int64 `json:"lower_standard_count"` // 活跃下级达标个数 + NewLowerStandardCount int64 `json:"new_lower_standard_count"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio float64 `json:"lower_withdraw_reward_ratio"` // 下级提现奖励比例 + LowerConvertVipReward float64 `json:"lower_convert_vip_reward"` // 下级转化VIP奖励 + LowerConvertSvipReward float64 `json:"lower_convert_svip_reward"` // 下级转化SVIP奖励 + ExemptionAmount float64 `json:"exemption_amount"` // 免审核金额 + PriceIncreaseMax float64 `json:"price_increase_max"` // 提价最高金额 + PriceRatio float64 `json:"price_ratio"` // 提价区间收取比例 + PriceIncreaseAmount float64 `json:"price_increase_amount"` // 在原本成本上加价的金额 + } + // 获取会员开通信息响应 + GetMembershipInfoResp { + NormalConfig MembershipConfigInfo `json:"normal_config"` // 普通代理配置 + VipConfig MembershipConfigInfo `json:"vip_config"` // VIP会员配置 + SvipConfig MembershipConfigInfo `json:"svip_config"` // SVIP会员配置 + } +) + +// 代理服务基本类型定义 +type AgentProductConfig { + ProductID int64 `json:"product_id"` + CostPrice float64 `json:"cost_price"` + PriceRangeMin float64 `json:"price_range_min"` + PriceRangeMax float64 `json:"price_range_max"` + PPricingStandard float64 `json:"p_pricing_standard"` + POverpricingRatio float64 `json:"p_overpricing_ratio"` + APricingStandard float64 `json:"a_pricing_standard"` + APricingEnd float64 `json:"a_pricing_end"` + AOverpricingRatio float64 `json:"a_overpricing_ratio"` +} + +type AgentMembershipUserConfig { + ProductID int64 `json:"product_id"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRangeFrom float64 `json:"price_range_from"` + PriceRangeTo float64 `json:"price_range_to"` + PriceRatio float64 `json:"price_ratio"` +} + +type ProductConfig { + ProductID int64 `json:"product_id"` + CostPrice float64 `json:"cost_price"` + PriceRangeMin float64 `json:"price_range_min"` + PriceRangeMax float64 `json:"price_range_max"` +} + +@server ( + prefix: api/v1/agent + group: agent + jwt: JwtAuth +) +service main { + // 查看代理信息 + @handler GetAgentInfo + get /info returns (AgentInfoResp) + + @handler GetAgentRevenueInfo + get /revenue (GetAgentRevenueInfoReq) returns (GetAgentRevenueInfoResp) +} + +@server ( + prefix: api/v1/agent + group: agent + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { + // 查询代理申请状态 + @handler GetAgentAuditStatus + get /audit/status returns (AgentAuditStatusResp) + + // 生成推广标识 + @handler GeneratingLink + post /generating_link (AgentGeneratingLinkReq) returns (AgentGeneratingLinkResp) + + // 获取推广定价配置 + @handler GetAgentProductConfig + get /product_config returns (AgentProductConfigResp) + + // 获取下级分页列表 + @handler GetAgentSubordinateList + get /subordinate/list (GetAgentSubordinateListReq) returns (GetAgentSubordinateListResp) + + // 下级贡献详情 + @handler GetAgentSubordinateContributionDetail + get /subordinate/contribution/detail (GetAgentSubordinateContributionDetailReq) returns (GetAgentSubordinateContributionDetailResp) + + @handler AgentRealName + post /real_name (AgentRealNameReq) returns (AgentRealNameResp) +} + +type ( + AgentInfoResp { + status int64 `json:"status"` // 0=待审核,1=审核通过,2=审核未通过,3=未申请 + isAgent bool `json:"is_agent"` + agentID int64 `json:"agent_id"` + level string `json:"level"` + region string `json:"region"` + mobile string `json:"mobile"` + expiryTime string `json:"expiry_time"` + isRealName bool `json:"is_real_name"` + } + // 查询代理申请状态响应 + AgentAuditStatusResp { + Status int64 `json:"status"` // 0=待审核,1=审核通过,2=审核未通过 + AuditReason string `json:"audit_reason"` + } + AgentGeneratingLinkReq { + Product string `json:"product"` + Price string `json:"price"` + } + AgentGeneratingLinkResp { + LinkIdentifier string `json:"link_identifier"` + } + AgentProductConfigResp { + AgentProductConfig []AgentProductConfig + } + GetAgentSubordinateListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + } + GetAgentSubordinateListResp { + Total int64 `json:"total"` // 总记录数 + List []AgentSubordinateList `json:"list"` // 查询列表 + } + AgentSubordinateList { + ID int64 `json:"id"` + Mobile string `json:"mobile"` + CreateTime string `json:"create_time"` + LevelName string `json:"level_name"` + TotalOrders int64 `json:"total_orders"` // 总单量 + TotalEarnings float64 `json:"total_earnings"` // 总金额 + TotalContribution float64 `json:"total_contribution"` // 总贡献 + } + GetAgentSubordinateContributionDetailReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + SubordinateID int64 `form:"subordinate_id"` // 下级ID + } + GetAgentSubordinateContributionDetailResp { + Mobile string `json:"mobile"` + Total int64 `json:"total"` // 总记录数 + CreateTime string `json:"create_time"` + TotalEarnings float64 `json:"total_earnings"` // 总金额 + TotalContribution float64 `json:"total_contribution"` // 总贡献 + TotalOrders int64 `json:"total_orders"` // 总单量 + LevelName string `json:"level_name"` // 等级名称 + List []AgentSubordinateContributionDetail `json:"list"` // 查询列表 + Stats AgentSubordinateContributionStats `json:"stats"` // 统计数据 + } + AgentSubordinateContributionDetail { + ID int64 `json:"id"` + CreateTime string `json:"create_time"` + Amount float64 `json:"amount"` + Type string `json:"type"` + } + AgentSubordinateContributionStats { + CostCount int64 `json:"cost_count"` // 成本扣除次数 + CostAmount float64 `json:"cost_amount"` // 成本扣除总额 + PricingCount int64 `json:"pricing_count"` // 定价扣除次数 + PricingAmount float64 `json:"pricing_amount"` // 定价扣除总额 + DescendantPromotionCount int64 `json:"descendant_promotion_count"` // 下级推广次数 + DescendantPromotionAmount float64 `json:"descendant_promotion_amount"` // 下级推广总额 + DescendantUpgradeVipCount int64 `json:"descendant_upgrade_vip_count"` // 下级升级VIP次数 + DescendantUpgradeVipAmount float64 `json:"descendant_upgrade_vip_amount"` // 下级升级VIP总额 + DescendantUpgradeSvipCount int64 `json:"descendant_upgrade_svip_count"` // 下级升级SVIP次数 + DescendantUpgradeSvipAmount float64 `json:"descendant_upgrade_svip_amount"` // 下级升级SVIP总额 + DescendantStayActiveCount int64 `json:"descendant_stay_active_count"` // 下级保持活跃次数 + DescendantStayActiveAmount float64 `json:"descendant_stay_active_amount"` // 下级保持活跃总额 + DescendantNewActiveCount int64 `json:"descendant_new_active_count"` // 下级新增活跃次数 + DescendantNewActiveAmount float64 `json:"descendant_new_active_amount"` // 下级新增活跃总额 + DescendantWithdrawCount int64 `json:"descendant_withdraw_count"` // 下级提现次数 + DescendantWithdrawAmount float64 `json:"descendant_withdraw_amount"` // 下级提现总额 + } + AgentRealNameReq { + Name string `json:"name"` + IDCard string `json:"id_card"` + Mobile string `json:"mobile"` + Code string `json:"code"` + } + AgentRealNameResp { + Status string `json:"status"` + } +) + +@server ( + prefix: api/v1/agent + group: agent + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { + @handler GetAgentMembershipProductConfig + get /membership/user_config (AgentMembershipProductConfigReq) returns (AgentMembershipProductConfigResp) + + @handler SaveAgentMembershipUserConfig + post /membership/save_user_config (SaveAgentMembershipUserConfigReq) +} + +type ( + // 获取会员当前配置 + AgentMembershipProductConfigReq { + ProductID int64 `form:"product_id"` + } + // 获取会员当前配置 + AgentMembershipProductConfigResp { + AgentMembershipUserConfig AgentMembershipUserConfig `json:"agent_membership_user_config"` + ProductConfig ProductConfig `json:"product_config"` + PriceIncreaseMax float64 `json:"price_increase_max"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRatio float64 `json:"price_ratio"` + } + SaveAgentMembershipUserConfigReq { + ProductID int64 `json:"product_id"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRangeFrom float64 `json:"price_range_from"` + PriceRangeTo float64 `json:"price_range_to"` + PriceRatio float64 `json:"price_ratio"` + } +) + +@server ( + prefix: api/v1/agent + group: agent + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { + @handler GetAgentCommission + get /commission (GetCommissionReq) returns (GetCommissionResp) + + @handler GetAgentRewards + get /rewards (GetRewardsReq) returns (GetRewardsResp) + + @handler GetAgentWithdrawal + get /withdrawal (GetWithdrawalReq) returns (GetWithdrawalResp) + + @handler AgentWithdrawal + post /withdrawal (WithdrawalReq) returns (WithdrawalResp) + + @handler ActivateAgentMembership + post /membership/activate (AgentActivateMembershipReq) returns (AgentActivateMembershipResp) + + @handler GetAgentWithdrawalTaxExemption + get /withdrawal/tax/exemption (GetWithdrawalTaxExemptionReq) returns (GetWithdrawalTaxExemptionResp) + + // 银行卡提现申请 + @handler BankCardWithdrawal + post /withdrawal/bank-card (BankCardWithdrawalReq) returns (WithdrawalResp) + + // 获取历史银行卡信息 + @handler GetBankCardInfo + get /withdrawal/bank-card/info (GetBankCardInfoReq) returns (GetBankCardInfoResp) +} + +type ( + // 收益信息 + GetAgentRevenueInfoReq {} + GetAgentRevenueInfoResp { + Balance float64 `json:"balance"` + FrozenBalance float64 `json:"frozen_balance"` + TotalEarnings float64 `json:"total_earnings"` + DirectPush DirectPushReport `json:"direct_push"` // 直推报告数据 + ActiveReward ActiveReward `json:"active_reward"` // 活跃下级奖励数据 + } + // 直推报告数据结构 + DirectPushReport { + TotalCommission float64 `json:"total_commission"` + TotalReport int `json:"total_report"` + Today TimeRangeReport `json:"today"` // 近24小时数据 + Last7D TimeRangeReport `json:"last7d"` // 近7天数据 + Last30D TimeRangeReport `json:"last30d"` // 近30天数据 + } + // 活跃下级奖励数据结构 + ActiveReward { + TotalReward float64 `json:"total_reward"` + Today ActiveRewardData `json:"today"` // 今日数据 + Last7D ActiveRewardData `json:"last7d"` // 近7天数据 + Last30D ActiveRewardData `json:"last30d"` // 近30天数据 + } + // 通用时间范围报告结构 + TimeRangeReport { + Commission float64 `json:"commission"` // 佣金 + Report int `json:"report"` // 报告量 + } + // 活跃奖励专用结构 + ActiveRewardData { + NewActiveReward float64 `json:"active_reward"` + SubPromoteReward float64 `json:"sub_promote_reward"` + SubUpgradeReward float64 `json:"sub_upgrade_reward"` + SubWithdrawReward float64 `json:"sub_withdraw_reward"` + } + Commission { + OrderId string `json:"order_id"` // 订单号 + ProductName string `json:"product_name"` + Amount float64 `json:"amount"` // 原始佣金金额 + RefundedAmount float64 `json:"refunded_amount"` // 已退款佣金金额 + NetAmount float64 `json:"net_amount"` // 剩余净佣金金额 = amount - refunded_amount + Status int64 `json:"status"` // 状态:0-已结算,1-冻结中,2-已退款 + CreateTime string `json:"create_time"` + QueryParams map[string]interface{} `json:"query_params,omitempty"` + } + GetCommissionReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + } + GetCommissionResp { + Total int64 `json:"total"` // 总记录数 + List []Commission `json:"list"` // 查询列表 + } + Rewards { + Type string `json:"type"` + Amount float64 `json:"amount"` + CreateTime string `json:"create_time"` + } + GetRewardsReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + } + GetRewardsResp { + Total int64 `json:"total"` // 总记录数 + List []Rewards `json:"list"` // 查询列表 + } + Withdrawal { + Status int64 `json:"status"` + Amount float64 `json:"amount"` + WithdrawalNo string `json:"withdrawal_no"` + Remark string `json:"remark"` + payeeAccount string `json:"payee_account"` + CreateTime string `json:"create_time"` + } + GetWithdrawalReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + } + GetWithdrawalResp { + Total int64 `json:"total"` // 总记录数 + List []Withdrawal `json:"list"` // 查询列表 + } + WithdrawalReq { + Amount float64 `json:"amount"` // 提现金额 + payeeAccount string `json:"payee_account"` + payeeName string `json:"payee_name"` + } + WithdrawalResp { + Status int64 `json:"status"` // 1申请中 2成功 3失败 + failMsg string `json:"fail_msg"` + } + // 开通代理会员请求参数 + AgentActivateMembershipReq { + Type string `json:"type,oneof=VIP SVIP"` // 会员类型:vip/svip + } + // 开通代理会员响应 + AgentActivateMembershipResp { + Id string `json:"id"` + } + GetWithdrawalTaxExemptionReq {} + GetWithdrawalTaxExemptionResp { + TotalExemptionAmount float64 `json:"total_exemption_amount"` + UsedExemptionAmount float64 `json:"used_exemption_amount"` + RemainingExemptionAmount float64 `json:"remaining_exemption_amount"` + TaxRate float64 `json:"tax_rate"` + } + // 银行卡提现申请请求 + BankCardWithdrawalReq { + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + Amount float64 `json:"amount"` // 提现金额 + } + // 获取历史银行卡信息请求 + GetBankCardInfoReq {} + // 获取历史银行卡信息响应 + GetBankCardInfoResp { + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + PayeeName string `json:"payee_name"` // 收款人姓名 + IdCard string `json:"id_card"` // 身份证号 + } +) + +@server ( + prefix: api/v1/agent + group: agent + middleware: AuthInterceptor +) +service main { + // 提交代理申请 + @handler ApplyForAgent + post /apply (AgentApplyReq) returns (AgentApplyResp) + + // 获取推广标识数据 + @handler GetLinkData + get /link (GetLinkDataReq) returns (GetLinkDataResp) +} + +type ( + // 代理申请请求参数 + AgentApplyReq { + Region string `json:"region"` + Mobile string `json:"mobile"` + Code string `json:"code"` + Ancestor string `json:"ancestor,optional"` + } + AgentApplyResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } + GetLinkDataReq { + LinkIdentifier string `form:"link_identifier"` + } + GetLinkDataResp { + Product + } +) + diff --git a/app/main/api/desc/front/app.api b/app/main/api/desc/front/app.api new file mode 100644 index 0000000..1882504 --- /dev/null +++ b/app/main/api/desc/front/app.api @@ -0,0 +1,37 @@ +syntax = "v1" + +info ( + title: "APP服务" + desc: "APP服务" + version: "v1" +) + +@server ( + prefix: api/v1 + group: app +) +service main { + @doc( + summary: "心跳检测接口" + ) + @handler healthCheck + get /health/check returns (HealthCheckResp) + + @handler getAppVersion + get /app/version returns (getAppVersionResp) +} + +type ( + // 心跳检测响应 + HealthCheckResp { + Status string `json:"status"` // 服务状态 + Message string `json:"message"` // 状态信息 + } +) + +type ( + getAppVersionResp { + Version string `json:"version"` + WgtUrl string `json:"wgtUrl"` + } +) \ No newline at end of file diff --git a/app/main/api/desc/front/authorization.api b/app/main/api/desc/front/authorization.api new file mode 100644 index 0000000..54d8ed3 --- /dev/null +++ b/app/main/api/desc/front/authorization.api @@ -0,0 +1,83 @@ +type ( + // GetAuthorizationDocumentReq 获取授权书请求 + GetAuthorizationDocumentReq { + DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID + } + + // GetAuthorizationDocumentResp 获取授权书响应 + GetAuthorizationDocumentResp { + DocumentId int64 `json:"documentId"` // 授权书ID + UserId int64 `json:"userId"` // 用户ID + OrderId int64 `json:"orderId"` // 订单ID + QueryId int64 `json:"queryId"` // 查询ID + FileName string `json:"fileName"` // 文件名 + FileUrl string `json:"fileUrl"` // 文件访问URL + FileSize int64 `json:"fileSize"` // 文件大小 + FileType string `json:"fileType"` // 文件类型 + Status string `json:"status"` // 状态 + CreateTime string `json:"createTime"` // 创建时间 + } + + // GetAuthorizationDocumentByOrderReq 根据订单ID获取授权书请求 + GetAuthorizationDocumentByOrderReq { + OrderId int64 `json:"orderId" validate:"required"` // 订单ID + } + + // GetAuthorizationDocumentByOrderResp 根据订单ID获取授权书响应 + GetAuthorizationDocumentByOrderResp { + Documents []AuthorizationDocumentInfo `json:"documents"` // 授权书列表 + } + + // AuthorizationDocumentInfo 授权书信息 + AuthorizationDocumentInfo { + DocumentId int64 `json:"documentId"` // 授权书ID + UserId int64 `json:"userId"` // 用户ID + OrderId int64 `json:"orderId"` // 订单ID + QueryId int64 `json:"queryId"` // 查询ID + FileName string `json:"fileName"` // 文件名 + FileUrl string `json:"fileUrl"` // 文件访问URL + FileSize int64 `json:"fileSize"` // 文件大小 + FileType string `json:"fileType"` // 文件类型 + Status string `json:"status"` // 状态 + CreateTime string `json:"createTime"` // 创建时间 + } + + // DownloadAuthorizationDocumentReq 下载授权书请求 + DownloadAuthorizationDocumentReq { + DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID + } + + // DownloadAuthorizationDocumentByNameReq 通过文件名下载授权书请求 + DownloadAuthorizationDocumentByNameReq { + FileName string `path:"fileName" validate:"required"` // 授权书文件名 + } + + // DownloadAuthorizationDocumentResp 下载授权书响应 + DownloadAuthorizationDocumentResp { + FileName string `json:"fileName"` // 文件名 + FilePath string `json:"filePath"` // 文件存储路径 + } +) + +// 授权书相关接口 +@server ( + prefix: api/v1 + group: authorization +) +service main { + // 获取授权书信息 + @handler GetAuthorizationDocument + get /authorization/document/:documentId (GetAuthorizationDocumentReq) returns (GetAuthorizationDocumentResp) + + // 根据订单ID获取授权书列表 + @handler GetAuthorizationDocumentByOrder + get /authorization/document/order/:orderId (GetAuthorizationDocumentByOrderReq) returns (GetAuthorizationDocumentByOrderResp) + + // 下载授权书文件 + @handler DownloadAuthorizationDocument + get /authorization/download/:documentId (DownloadAuthorizationDocumentReq) returns (DownloadAuthorizationDocumentResp) + + // 通过文件名下载授权书文件 + @handler DownloadAuthorizationDocumentByName + get /authorization/download/file/:fileName (DownloadAuthorizationDocumentByNameReq) returns (DownloadAuthorizationDocumentResp) +} diff --git a/app/main/api/desc/front/pay.api b/app/main/api/desc/front/pay.api new file mode 100644 index 0000000..154e8b0 --- /dev/null +++ b/app/main/api/desc/front/pay.api @@ -0,0 +1,71 @@ +syntax = "v1" + +info ( + title: "支付服务" + desc: "支付服务" + version: "v1" +) + +@server ( + prefix: api/v1 + group: pay +) +service main { + // 微信支付回调 + @handler WechatPayCallback + post /pay/wechat/callback + + // 支付宝支付回调 + @handler AlipayCallback + post /pay/alipay/callback + + // 微信退款回调 + @handler WechatPayRefundCallback + post /pay/wechat/refund_callback +} + +@server ( + prefix: api/v1 + group: pay + jwt: JwtAuth + middleware: UserAuthInterceptor + +) +service main { + // 支付 + @handler Payment + post /pay/payment (PaymentReq) returns (PaymentResp) + + @handler IapCallback + post /pay/iap_callback (IapCallbackReq) + + @handler PaymentCheck + post /pay/check (PaymentCheckReq) returns (PaymentCheckResp) +} + +type ( + PaymentReq { + Id string `json:"id"` + PayMethod string `json:"pay_method"` + PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"` + } + PaymentResp { + PrepayData interface{} `json:"prepay_data"` + PrepayId string `json:"prepay_id"` + OrderNo string `json:"order_no"` + } + PaymentCheckReq { + OrderNo string `json:"order_no" validate:"required"` + } + PaymentCheckResp { + Type string `json:"type"` + Status string `json:"status"` + } +) + +type ( + IapCallbackReq { + OrderID int64 `json:"order_id" validate:"required"` + TransactionReceipt string `json:"transaction_receipt" validate:"required"` + } +) \ No newline at end of file diff --git a/app/main/api/desc/front/product.api b/app/main/api/desc/front/product.api new file mode 100644 index 0000000..f642df5 --- /dev/null +++ b/app/main/api/desc/front/product.api @@ -0,0 +1,55 @@ +syntax = "v1" + +info ( + title: "产品服务" + desc: "产品服务" + version: "v1" +) +type Feature { + ID int64 `json:"id"` // 功能ID + ApiID string `json:"api_id"` // API标识 + Name string `json:"name"` // 功能描述 +} +// 产品基本类型定义 +type Product { + ProductName string `json:"product_name"` + ProductEn string `json:"product_en"` + Description string `json:"description"` + Notes string `json:"notes,optional"` + SellPrice float64 `json:"sell_price"` + Features []Feature `json:"features"` // 关联功能列表 +} + +@server ( + prefix: api/v1/product + group: product + +) +service main { + @handler GetProductByID + get /:id (GetProductByIDRequest) returns (ProductResponse) + + @handler GetProductByEn + get /en/:product_en (GetProductByEnRequest) returns (ProductResponse) +} + +type GetProductByIDRequest { + Id int64 `path:"id"` +} + +type GetProductByEnRequest { + ProductEn string `path:"product_en"` +} + +type ProductResponse { + Product +} + +@server ( + prefix: api/v1/product + group: product +) +service main { + @handler GetProductAppByEn + get /app_en/:product_en (GetProductByEnRequest) returns (ProductResponse) +} \ No newline at end of file diff --git a/app/main/api/desc/front/query.api b/app/main/api/desc/front/query.api new file mode 100644 index 0000000..4bcd368 --- /dev/null +++ b/app/main/api/desc/front/query.api @@ -0,0 +1,232 @@ +syntax = "v1" + +info ( + title: "产品查询服务" + desc: "产品查询服务" + version: "v1" +) + +//============================> query v1 <============================ +// 查询基本类型定义 +type Query { + Id int64 `json:"id"` // 主键ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + Product string `json:"product"` // 产品ID + ProductName string `json:"product_name"` // 产品ID + QueryParams map[string]interface{} `json:"query_params"` + QueryData []QueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 +} + +type QueryItem { + Feature interface{} `json:"feature"` + Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct +} + +@server ( + prefix: api/v1 + group: query + middleware: AuthInterceptor +) +service main { + @doc "query service agent" + @handler queryServiceAgent + post /query/service_agent/:product (QueryServiceReq) returns (QueryServiceResp) + + @handler queryServiceApp + post /query/service_app/:product (QueryServiceReq) returns (QueryServiceResp) +} + +type ( + QueryReq { + Data string `json:"data" validate:"required"` + } + QueryResp { + Id string `json:"id"` + } +) + +type ( + QueryServiceReq { + Product string `path:"product"` + Data string `json:"data" validate:"required"` + AgentIdentifier string `json:"agent_identifier,optional"` + App bool `json:"app,optional"` + } + QueryServiceResp { + Id string `json:"id"` + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } +) + +@server ( + prefix: api/v1 + group: query + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { + @doc "query service" + @handler queryService + post /query/service/:product (QueryServiceReq) returns (QueryServiceResp) +} + +@server ( + prefix: api/v1 + group: query + jwt: JwtAuth + middleware: UserAuthInterceptor +) +service main { + @doc "获取查询临时订单" + @handler queryProvisionalOrder + get /query/provisional_order/:id (QueryProvisionalOrderReq) returns (QueryProvisionalOrderResp) + + @doc "查询列表" + @handler queryList + get /query/list (QueryListReq) returns (QueryListResp) + + @doc "查询详情 按订单号 付款查询时" + @handler queryDetailByOrderId + get /query/orderId/:order_id (QueryDetailByOrderIdReq) returns (QueryDetailByOrderIdResp) + + @doc "查询详情 按订单号" + @handler queryDetailByOrderNo + get /query/orderNo/:order_no (QueryDetailByOrderNoReq) returns (QueryDetailByOrderNoResp) + + @doc "重试查询" + @handler queryRetry + post /query/retry/:id (QueryRetryReq) returns (QueryRetryResp) + + @doc "更新查询数据" + @handler updateQueryData + post /query/update_data (UpdateQueryDataReq) returns (UpdateQueryDataResp) + + @doc "生成分享链接" + @handler QueryGenerateShareLink + post /query/generate_share_link (QueryGenerateShareLinkReq) returns (QueryGenerateShareLinkResp) +} + +type ( + QueryGenerateShareLinkReq { + OrderId *int64 `json:"order_id,optional"` + OrderNo *string `json:"order_no,optional"` + } + QueryGenerateShareLinkResp { + ShareLink string `json:"share_link"` + } +) + +// 获取查询临时订单 +type ( + QueryProvisionalOrderReq { + Id string `path:"id"` + } + QueryProvisionalOrderResp { + Name string `json:"name"` + IdCard string `json:"id_card"` + Mobile string `json:"mobile"` + Product Product `json:"product"` + } +) + +type ( + QueryListReq { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + } + QueryListResp { + Total int64 `json:"total"` // 总记录数 + List []Query `json:"list"` // 查询列表 + } +) + +type ( + QueryExampleReq { + Feature string `form:"feature"` + } + QueryExampleResp { + Query + } +) + +type ( + QueryDetailByOrderIdReq { + OrderId int64 `path:"order_id"` + } + QueryDetailByOrderIdResp { + Query + } +) + +type ( + QueryDetailByOrderNoReq { + OrderNo string `path:"order_no"` + } + QueryDetailByOrderNoResp { + Query + } +) + +type ( + QueryRetryReq { + Id int64 `path:"id"` + } + QueryRetryResp { + Query + } +) + +type ( + UpdateQueryDataReq { + Id int64 `json:"id"` // 查询ID + QueryData string `json:"query_data"` // 查询数据(未加密的JSON) + } + UpdateQueryDataResp { + Id int64 `json:"id"` + UpdatedAt string `json:"updated_at"` // 更新时间 + } +) + +@server ( + prefix: api/v1 + group: query +) +service main { + @handler querySingleTest + post /query/single/test (QuerySingleTestReq) returns (QuerySingleTestResp) + + @doc "查询详情" + @handler queryShareDetail + get /query/share/:id (QueryShareDetailReq) returns (QueryShareDetailResp) + + @doc "查询示例" + @handler queryExample + get /query/example (QueryExampleReq) returns (QueryExampleResp) +} + +type ( + QueryShareDetailReq { + Id string `path:"id"` + } + QueryShareDetailResp { + Status string `json:"status"` + Query + } +) + +type QuerySingleTestReq { + Params map[string]interface{} `json:"params"` + Api string `json:"api"` +} + +type QuerySingleTestResp { + Data interface{} `json:"data"` + Api string `json:"api"` +} + diff --git a/app/main/api/desc/front/user.api b/app/main/api/desc/front/user.api new file mode 100644 index 0000000..34b93ec --- /dev/null +++ b/app/main/api/desc/front/user.api @@ -0,0 +1,178 @@ +syntax = "v1" + +info ( + title: "用户中心服务" + desc: "用户中心服务" + version: "v1" +) + +//============================> user v1 <============================ +// 用户基本类型定义 +type User { + Id int64 `json:"id"` + Mobile string `json:"mobile"` + NickName string `json:"nickName"` + UserType int64 `json:"userType"` +} + +//no need login +@server ( + prefix: api/v1 + group: user +) +service main { + @doc "mobile code login" + @handler mobileCodeLogin + post /user/mobileCodeLogin (MobileCodeLoginReq) returns (MobileCodeLoginResp) + + @doc "wechat mini auth" + @handler wxMiniAuth + post /user/wxMiniAuth (WXMiniAuthReq) returns (WXMiniAuthResp) + + @doc "wechat h5 auth" + @handler wxH5Auth + post /user/wxh5Auth (WXH5AuthReq) returns (WXH5AuthResp) + + @handler getSignature + post /wechat/getSignature (GetSignatureReq) returns (GetSignatureResp) +} + +type ( + MobileCodeLoginReq { + Mobile string `json:"mobile"` + Code string `json:"code" validate:"required"` + } + MobileCodeLoginResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } +) + +type ( + WXMiniAuthReq { + Code string `json:"code"` + } + WXMiniAuthResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } +) + +type ( + GetSignatureReq { + Url string `json:"url"` + } + GetSignatureResp { + AppId string `json:"appId"` + Timestamp int64 `json:"timestamp"` + NonceStr string `json:"nonceStr"` + Signature string `json:"signature"` + } +) + +type ( + WXH5AuthReq { + Code string `json:"code"` + } + WXH5AuthResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } +) + +@server ( + prefix: api/v1 + group: user + middleware: AuthInterceptor +) +service main { + @doc "绑定手机号" + @handler bindMobile + post /user/bindMobile (BindMobileReq) returns (BindMobileResp) +} + +//need login +@server ( + prefix: api/v1 + group: user + jwt: JwtAuth +) +service main { + @doc "get user info" + @handler detail + get /user/detail returns (UserInfoResp) + + @doc "get new token" + @handler getToken + post /user/getToken returns (MobileCodeLoginResp) + + @handler cancelOut + post /user/cancelOut +} + +type ( + UserInfoResp { + UserInfo User `json:"userInfo"` + } + BindMobileReq { + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` + } + BindMobileResp { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` + } +) + +//============================> auth v1 <============================ +@server ( + prefix: api/v1 + group: auth +) +service main { + @doc "get mobile verify code" + @handler sendSms + post /auth/sendSms (sendSmsReq) +} + +type ( + sendSmsReq { + Mobile string `json:"mobile" validate:"required,mobile"` + CaptchaVerifyParam string `json:"captchaVerifyParam"` + ActionType string `json:"actionType,optional" validate:"oneof=login register query agentApply realName bindMobile"` + } +) + +//============================> notification v1 <============================ +@server ( + prefix: api/v1 + group: notification +) +service main { + @doc "get notifications" + @handler getNotifications + get /notification/list returns (GetNotificationsResp) +} + +type Notification { + Title string `json:"title"` // 通知标题 + Content string `json:"content"` // 通知内容 (富文本) + NotificationPage string `json:"notificationPage"` // 通知页面 + StartDate string `json:"startDate"` // 通知开始日期,格式 "YYYY-MM-DD" + EndDate string `json:"endDate"` // 通知结束日期,格式 "YYYY-MM-DD" + StartTime string `json:"startTime"` // 每天通知开始时间,格式 "HH:MM:SS" + EndTime string `json:"endTime"` // 每天通知结束时间,格式 "HH:MM:SS" +} + +type ( + // 获取通知响应体(分页) + GetNotificationsResp { + Notifications []Notification `json:"notifications"` // 通知列表 + Total int64 `json:"total"` // 总记录数 + } +) + diff --git a/app/main/api/desc/main.api b/app/main/api/desc/main.api new file mode 100644 index 0000000..afb7210 --- /dev/null +++ b/app/main/api/desc/main.api @@ -0,0 +1,32 @@ +syntax = "v1" + +info ( + title: "单体服务中心" + desc: "单体服务中心" + version: "v1" +) + +// 前台 +import "./front/user.api" +import "./front/query.api" +import "./front/pay.api" +import "./front/product.api" +import "./front/agent.api" +import "./front/app.api" +import "./front/authorization.api" +// 后台 +import "./admin/auth.api" +import "./admin/menu.api" +import "./admin/role.api" +import "./admin/promotion.api" +import "./admin/order.api" +import "./admin/admin_user.api" +import "./admin/platform_user.api" +import "./admin/notification.api" +import "./admin/admin_product.api" +import "./admin/admin_feature.api" +import "./admin/admin_query.api" +import "./admin/admin_agent.api" +import "./admin/admin_api.api" +import "./admin/admin_role_api.api" +import "./admin/admin_queue.api" diff --git a/app/main/api/etc/main.example.yaml b/app/main/api/etc/main.example.yaml new file mode 100644 index 0000000..f4428fe --- /dev/null +++ b/app/main/api/etc/main.example.yaml @@ -0,0 +1,89 @@ +Name: main +Host: 0.0.0.0 +Port: 8888 +DataSource: "bdrp:d7X7E1KSxrZC@tcp(bdrp_mysql:3306)/bdrp?charset=utf8mb4&parseTime=True&loc=Local" +CacheRedis: + - Host: "bdrp_redis:6379" + Pass: "3m3WsgyCKWqz" # Redis 密码,如果未设置则留空 + Type: "node" # 单节点模式 + +JwtAuth: + AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" + AccessExpire: 2592000 + RefreshAfter: 1296000 + +VerifyCode: + AccessKeyID: "xxxx" + AccessKeySecret: "xxxx" + EndpointURL: "dysmsapi.aliyuncs.com" + SignName: "xxxx" + TemplateCode: "xxxx" + ValidTime: 300 + +Captcha: + Enable: false + AccessKeyID: "xxxx" + AccessKeySecret: "xxxx" + EndpointURL: "captcha.cn-shanghai.aliyuncs.com" + SceneID: "xxxx" + EKey: "" + +Encrypt: + SecretKey: "xxxx" +Alipay: + AppID: "xxxx" + PrivateKey: "xxxx" + AlipayPublicKey: "xxxx" + AppCertPath: "etc/merchant/appCertPublicKey_xxxxxxxxxxx.crt" + AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt" + AlipayRootCertPath: "etc/merchant/alipayRootCert.crt" + IsProduction: true + NotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/pay/alipay/callback" + ReturnURL: "https://www.XXXXXXXXXXXXXX.com/payment/result" +Wxpay: + AppID: "xxxx" + MchID: "xxxx" + MchCertificateSerialNumber: "xxxx" + MchApiv3Key: "xxxx" + MchPrivateKeyPath: "etc/merchant/wxpay/XXXXXXXXXXXX_XXXXXXXXXXXX_cert/apiclient_key.pem" + MchPublicKeyID: "xxxx" + MchPublicKeyPath: "etc/merchant/wxpay/XXXXXXXXXXXX_XXXXXXXXXXXX_cert/pub_key.pem" + MchPlatformRAS: "xxxx" + NotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/pay/wechat/callback" + RefundNotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/wechat/refund_callback" +Applepay: + ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt" + SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt" + Sandbox: true + BundleID: "xxxx" + IssuerID: "xxxx" + KeyID: "xxxx" + LoadPrivateKeyPath: "etc/merchant/AuthKey_XXXXXXXXXXXX.p8" +SystemConfig: + ThreeVerify: true + CommissionSafeMode: false # 佣金安全防御模式:true-冻结模式,false-直接结算模式 +WechatH5: + AppID: "xxxx" + AppSecret: "xxxx" +WechatMini: + AppID: "xxxx" # 小程序的AppID + AppSecret: "xxxx" # 小程序的AppSecret + TycAppID: "xxxx" + TycAppSecret: "xxxx" +Query: + ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒 +AdminConfig: + AccessSecret: "xxxx" + AccessExpire: 604800 + RefreshAfter: 302400 +TaxConfig: + TaxRate: 0.06 + TaxExemptionAmount: 0.00 +Tianyuanapi: + AccessID: "xxxx" + Key: "xxxx" + BaseURL: "https://api.tianyuanapi.com" + Timeout: 60 +Authorization: + FileBaseURL: "https://www.XXXXXXXXXXXXXX.com/api/v1/auth-docs" # 授权书文件访问基础URL +ExtensionTime: 24 # 佣金解冻延迟时间,单位:24小时 diff --git a/app/main/api/internal/config/config.go b/app/main/api/internal/config/config.go new file mode 100644 index 0000000..caf387b --- /dev/null +++ b/app/main/api/internal/config/config.go @@ -0,0 +1,139 @@ +package config + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/rest" +) + +type Config struct { + rest.RestConf + DataSource string + CacheRedis cache.CacheConf + JwtAuth JwtAuth // JWT 鉴权相关配置 + VerifyCode VerifyCode + Captcha CaptchaConfig + Encrypt Encrypt + Alipay AlipayConfig + Wxpay WxpayConfig + Applepay ApplepayConfig + Tianyuanapi TianyuanapiConfig + SystemConfig SystemConfig + WechatH5 WechatH5Config + Authorization AuthorizationConfig // 授权书配置 + WechatMini WechatMiniConfig + Query QueryConfig + AdminConfig AdminConfig + TaxConfig TaxConfig + ExtensionTime int64 +} + +// JwtAuth 用于 JWT 鉴权配置 +type JwtAuth struct { + AccessSecret string // JWT 密钥,用于签发 Token + AccessExpire int64 // Token 过期时间,单位为秒 + RefreshAfter int64 +} +type VerifyCode struct { + AccessKeyID string + AccessKeySecret string + EndpointURL string + SignName string + TemplateCode string + ValidTime int +} + +type CaptchaConfig struct { + Enable bool + AccessKeyID string + AccessKeySecret string + EndpointURL string + SceneID string + EKey string +} +type Encrypt struct { + SecretKey string +} + +type AlipayConfig struct { + AppID string + PrivateKey string + AlipayPublicKey string + AppCertPath string // 应用公钥证书路径 + AlipayCertPath string // 支付宝公钥证书路径 + AlipayRootCertPath string // 根证书路径 + IsProduction bool + NotifyUrl string + ReturnURL string +} +type WxpayConfig struct { + AppID string + MchID string + MchCertificateSerialNumber string + MchApiv3Key string + MchPrivateKeyPath string + MchPublicKeyID string + MchPublicKeyPath string + NotifyUrl string + RefundNotifyUrl string +} +type AliConfig struct { + Code string +} +type ApplepayConfig struct { + ProductionVerifyURL string + SandboxVerifyURL string // 沙盒环境的验证 URL + Sandbox bool + BundleID string + IssuerID string + KeyID string + LoadPrivateKeyPath string +} +type WestConfig struct { + Url string + Key string + SecretId string + SecretSecondId string +} +type YushanConfig struct { + ApiKey string + AcctID string + Url string +} +type SystemConfig struct { + ThreeVerify bool // 是否开启三级实名认证 + CommissionSafeMode bool // 佣金安全防御模式:true-冻结模式(status=1,进入frozen_balance),false-直接结算(status=0,进入balance) +} +type WechatH5Config struct { + AppID string + AppSecret string +} +type WechatMiniConfig struct { + AppID string + AppSecret string +} +type QueryConfig struct { + ShareLinkExpire int64 +} +type AdminConfig struct { + AccessSecret string + AccessExpire int64 + RefreshAfter int64 +} + +type AdminPromotion struct { + URLDomain string +} +type TaxConfig struct { + TaxRate float64 + TaxExemptionAmount float64 +} +type TianyuanapiConfig struct { + AccessID string + Key string + BaseURL string + Timeout int64 +} + +type AuthorizationConfig struct { + FileBaseURL string // 授权书文件访问基础URL +} diff --git a/app/main/api/internal/handler/admin_agent/adminbatchunfreezeagentcommissionhandler.go b/app/main/api/internal/handler/admin_agent/adminbatchunfreezeagentcommissionhandler.go new file mode 100644 index 0000000..971d3c9 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminbatchunfreezeagentcommissionhandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminBatchUnfreezeAgentCommissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminBatchUnfreezeAgentCommissionReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminBatchUnfreezeAgentCommissionLogic(r.Context(), svcCtx) + resp, err := l.AdminBatchUnfreezeAgentCommission(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentcommissiondeductionlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentcommissiondeductionlisthandler.go new file mode 100644 index 0000000..a210766 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentcommissiondeductionlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentCommissionDeductionListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentCommissionDeductionListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentCommissionDeductionListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentCommissionDeductionList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentcommissionlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentcommissionlisthandler.go new file mode 100644 index 0000000..f50eb84 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentcommissionlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentCommissionListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentCommissionListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentCommissionListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentCommissionList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentlinklisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentlinklisthandler.go new file mode 100644 index 0000000..91edb6f --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentlinklisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentLinkListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentLinkListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentLinkListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentLinkList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentlinkproductstatisticshandler.go b/app/main/api/internal/handler/admin_agent/admingetagentlinkproductstatisticshandler.go new file mode 100644 index 0000000..0a1fdf8 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentlinkproductstatisticshandler.go @@ -0,0 +1,29 @@ +package admin_agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetAgentLinkProductStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentLinkProductStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentLinkProductStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentLinkProductStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentlisthandler.go new file mode 100644 index 0000000..2562b63 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentmembershipconfiglisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentmembershipconfiglisthandler.go new file mode 100644 index 0000000..0190462 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentmembershipconfiglisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentMembershipConfigListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentMembershipConfigListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentMembershipConfigListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentMembershipConfigList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentmembershiprechargeorderlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentmembershiprechargeorderlisthandler.go new file mode 100644 index 0000000..43c05ea --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentmembershiprechargeorderlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentMembershipRechargeOrderListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentMembershipRechargeOrderListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentMembershipRechargeOrderListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentMembershipRechargeOrderList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentorderstatisticshandler.go b/app/main/api/internal/handler/admin_agent/admingetagentorderstatisticshandler.go new file mode 100644 index 0000000..85a3269 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentorderstatisticshandler.go @@ -0,0 +1,29 @@ +package admin_agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetAgentOrderStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentOrderStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentOrderStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentOrderStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentplatformdeductionlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentplatformdeductionlisthandler.go new file mode 100644 index 0000000..7982ef2 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentplatformdeductionlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentPlatformDeductionListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentPlatformDeductionListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentPlatformDeductionListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentPlatformDeductionList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentproductionconfiglisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentproductionconfiglisthandler.go new file mode 100644 index 0000000..3528c76 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentproductionconfiglisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentProductionConfigListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentProductionConfigListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentProductionConfigListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentProductionConfigList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentrewardlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentrewardlisthandler.go new file mode 100644 index 0000000..53725a1 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentrewardlisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentRewardListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentRewardListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentRewardListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentRewardList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentstatisticshandler.go b/app/main/api/internal/handler/admin_agent/admingetagentstatisticshandler.go new file mode 100644 index 0000000..c56a857 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentstatisticshandler.go @@ -0,0 +1,29 @@ +package admin_agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetAgentStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentwallethandler.go b/app/main/api/internal/handler/admin_agent/admingetagentwallethandler.go new file mode 100644 index 0000000..67243ae --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentwallethandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentWalletHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentWalletReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentWalletLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentWallet(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentwallettransactionlisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentwallettransactionlisthandler.go new file mode 100644 index 0000000..df0e286 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentwallettransactionlisthandler.go @@ -0,0 +1,29 @@ +package admin_agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetAgentWalletTransactionListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentWalletTransactionListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentWalletTransactionListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentWalletTransactionList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetagentwithdrawallisthandler.go b/app/main/api/internal/handler/admin_agent/admingetagentwithdrawallisthandler.go new file mode 100644 index 0000000..602e88a --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetagentwithdrawallisthandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAgentWithdrawalListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAgentWithdrawalListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetAgentWithdrawalListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAgentWithdrawalList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetsystemconfighandler.go b/app/main/api/internal/handler/admin_agent/admingetsystemconfighandler.go new file mode 100644 index 0000000..fcfd501 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetsystemconfighandler.go @@ -0,0 +1,17 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func AdminGetSystemConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := admin_agent.NewAdminGetSystemConfigLogic(r.Context(), svcCtx) + resp, err := l.AdminGetSystemConfig() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/admingetwithdrawalstatisticshandler.go b/app/main/api/internal/handler/admin_agent/admingetwithdrawalstatisticshandler.go new file mode 100644 index 0000000..8c4bdd9 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/admingetwithdrawalstatisticshandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetWithdrawalStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetWithdrawalStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminGetWithdrawalStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetWithdrawalStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminreviewbankcardwithdrawalhandler.go b/app/main/api/internal/handler/admin_agent/adminreviewbankcardwithdrawalhandler.go new file mode 100644 index 0000000..831be55 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminreviewbankcardwithdrawalhandler.go @@ -0,0 +1,29 @@ +package admin_agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminReviewBankCardWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminReviewBankCardWithdrawalReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminReviewBankCardWithdrawalLogic(r.Context(), svcCtx) + resp, err := l.AdminReviewBankCardWithdrawal(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminupdateagentcommissionstatushandler.go b/app/main/api/internal/handler/admin_agent/adminupdateagentcommissionstatushandler.go new file mode 100644 index 0000000..691230b --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminupdateagentcommissionstatushandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateAgentCommissionStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateAgentCommissionStatusReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminUpdateAgentCommissionStatusLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateAgentCommissionStatus(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminupdateagentmembershipconfighandler.go b/app/main/api/internal/handler/admin_agent/adminupdateagentmembershipconfighandler.go new file mode 100644 index 0000000..d9c2646 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminupdateagentmembershipconfighandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateAgentMembershipConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateAgentMembershipConfigReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminUpdateAgentMembershipConfigLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateAgentMembershipConfig(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminupdateagentproductionconfighandler.go b/app/main/api/internal/handler/admin_agent/adminupdateagentproductionconfighandler.go new file mode 100644 index 0000000..be00941 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminupdateagentproductionconfighandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateAgentProductionConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateAgentProductionConfigReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminUpdateAgentProductionConfigLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateAgentProductionConfig(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminupdateagentwalletbalancehandler.go b/app/main/api/internal/handler/admin_agent/adminupdateagentwalletbalancehandler.go new file mode 100644 index 0000000..87eed75 --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminupdateagentwalletbalancehandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateAgentWalletBalanceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateAgentWalletBalanceReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminUpdateAgentWalletBalanceLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateAgentWalletBalance(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_agent/adminupdatesystemconfighandler.go b/app/main/api/internal/handler/admin_agent/adminupdatesystemconfighandler.go new file mode 100644 index 0000000..350ea9e --- /dev/null +++ b/app/main/api/internal/handler/admin_agent/adminupdatesystemconfighandler.go @@ -0,0 +1,30 @@ +package admin_agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateSystemConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateSystemConfigReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_agent.NewAdminUpdateSystemConfigLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateSystemConfig(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/adminbatchupdateapistatushandler.go b/app/main/api/internal/handler/admin_api/adminbatchupdateapistatushandler.go new file mode 100644 index 0000000..3555ae5 --- /dev/null +++ b/app/main/api/internal/handler/admin_api/adminbatchupdateapistatushandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminBatchUpdateApiStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminBatchUpdateApiStatusReq + 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_api.NewAdminBatchUpdateApiStatusLogic(r.Context(), svcCtx) + resp, err := l.AdminBatchUpdateApiStatus(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/admincreateapihandler.go b/app/main/api/internal/handler/admin_api/admincreateapihandler.go new file mode 100644 index 0000000..70a38e9 --- /dev/null +++ b/app/main/api/internal/handler/admin_api/admincreateapihandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateApiReq + 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_api.NewAdminCreateApiLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/admindeleteapihandler.go b/app/main/api/internal/handler/admin_api/admindeleteapihandler.go new file mode 100644 index 0000000..2f7b42e --- /dev/null +++ b/app/main/api/internal/handler/admin_api/admindeleteapihandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteApiReq + 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_api.NewAdminDeleteApiLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/admingetapidetailhandler.go b/app/main/api/internal/handler/admin_api/admingetapidetailhandler.go new file mode 100644 index 0000000..4d3f47b --- /dev/null +++ b/app/main/api/internal/handler/admin_api/admingetapidetailhandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetApiDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetApiDetailReq + 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_api.NewAdminGetApiDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetApiDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/admingetapilisthandler.go b/app/main/api/internal/handler/admin_api/admingetapilisthandler.go new file mode 100644 index 0000000..c06c764 --- /dev/null +++ b/app/main/api/internal/handler/admin_api/admingetapilisthandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetApiListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetApiListReq + 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_api.NewAdminGetApiListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetApiList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_api/adminupdateapihandler.go b/app/main/api/internal/handler/admin_api/adminupdateapihandler.go new file mode 100644 index 0000000..7257e94 --- /dev/null +++ b/app/main/api/internal/handler/admin_api/adminupdateapihandler.go @@ -0,0 +1,30 @@ +package admin_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateApiReq + 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_api.NewAdminUpdateApiLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_auth/adminloginhandler.go b/app/main/api/internal/handler/admin_auth/adminloginhandler.go new file mode 100644 index 0000000..276b0a2 --- /dev/null +++ b/app/main/api/internal/handler/admin_auth/adminloginhandler.go @@ -0,0 +1,30 @@ +package admin_auth + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_auth" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminLoginReq + 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_auth.NewAdminLoginLogic(r.Context(), svcCtx) + resp, err := l.AdminLogin(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/adminconfigfeatureexamplehandler.go b/app/main/api/internal/handler/admin_feature/adminconfigfeatureexamplehandler.go new file mode 100644 index 0000000..2020406 --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/adminconfigfeatureexamplehandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminConfigFeatureExampleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminConfigFeatureExampleReq + 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_feature.NewAdminConfigFeatureExampleLogic(r.Context(), svcCtx) + resp, err := l.AdminConfigFeatureExample(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/admincreatefeaturehandler.go b/app/main/api/internal/handler/admin_feature/admincreatefeaturehandler.go new file mode 100644 index 0000000..862c953 --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/admincreatefeaturehandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateFeatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateFeatureReq + 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_feature.NewAdminCreateFeatureLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateFeature(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/admindeletefeaturehandler.go b/app/main/api/internal/handler/admin_feature/admindeletefeaturehandler.go new file mode 100644 index 0000000..dafa584 --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/admindeletefeaturehandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteFeatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteFeatureReq + 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_feature.NewAdminDeleteFeatureLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteFeature(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/admingetfeaturedetailhandler.go b/app/main/api/internal/handler/admin_feature/admingetfeaturedetailhandler.go new file mode 100644 index 0000000..d5f07ab --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/admingetfeaturedetailhandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetFeatureDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetFeatureDetailReq + 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_feature.NewAdminGetFeatureDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetFeatureDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/admingetfeatureexamplehandler.go b/app/main/api/internal/handler/admin_feature/admingetfeatureexamplehandler.go new file mode 100644 index 0000000..909cafd --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/admingetfeatureexamplehandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetFeatureExampleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetFeatureExampleReq + 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_feature.NewAdminGetFeatureExampleLogic(r.Context(), svcCtx) + resp, err := l.AdminGetFeatureExample(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/admingetfeaturelisthandler.go b/app/main/api/internal/handler/admin_feature/admingetfeaturelisthandler.go new file mode 100644 index 0000000..785e343 --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/admingetfeaturelisthandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetFeatureListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetFeatureListReq + 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_feature.NewAdminGetFeatureListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetFeatureList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_feature/adminupdatefeaturehandler.go b/app/main/api/internal/handler/admin_feature/adminupdatefeaturehandler.go new file mode 100644 index 0000000..05a99c6 --- /dev/null +++ b/app/main/api/internal/handler/admin_feature/adminupdatefeaturehandler.go @@ -0,0 +1,30 @@ +package admin_feature + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_feature" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateFeatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateFeatureReq + 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_feature.NewAdminUpdateFeatureLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateFeature(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/createmenuhandler.go b/app/main/api/internal/handler/admin_menu/createmenuhandler.go new file mode 100644 index 0000000..6e6fae8 --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/createmenuhandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CreateMenuHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreateMenuReq + 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_menu.NewCreateMenuLogic(r.Context(), svcCtx) + resp, err := l.CreateMenu(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/deletemenuhandler.go b/app/main/api/internal/handler/admin_menu/deletemenuhandler.go new file mode 100644 index 0000000..5d30616 --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/deletemenuhandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DeleteMenuHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeleteMenuReq + 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_menu.NewDeleteMenuLogic(r.Context(), svcCtx) + resp, err := l.DeleteMenu(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/getmenuallhandler.go b/app/main/api/internal/handler/admin_menu/getmenuallhandler.go new file mode 100644 index 0000000..cadd74f --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/getmenuallhandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetMenuAllHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetMenuAllReq + 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_menu.NewGetMenuAllLogic(r.Context(), svcCtx) + resp, err := l.GetMenuAll(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/getmenudetailhandler.go b/app/main/api/internal/handler/admin_menu/getmenudetailhandler.go new file mode 100644 index 0000000..a7548d7 --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/getmenudetailhandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetMenuDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetMenuDetailReq + 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_menu.NewGetMenuDetailLogic(r.Context(), svcCtx) + resp, err := l.GetMenuDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/getmenulisthandler.go b/app/main/api/internal/handler/admin_menu/getmenulisthandler.go new file mode 100644 index 0000000..331d907 --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/getmenulisthandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetMenuListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetMenuListReq + 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_menu.NewGetMenuListLogic(r.Context(), svcCtx) + resp, err := l.GetMenuList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_menu/updatemenuhandler.go b/app/main/api/internal/handler/admin_menu/updatemenuhandler.go new file mode 100644 index 0000000..31f9cb5 --- /dev/null +++ b/app/main/api/internal/handler/admin_menu/updatemenuhandler.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_menu" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func UpdateMenuHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateMenuReq + 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_menu.NewUpdateMenuLogic(r.Context(), svcCtx) + resp, err := l.UpdateMenu(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_notification/admincreatenotificationhandler.go b/app/main/api/internal/handler/admin_notification/admincreatenotificationhandler.go new file mode 100644 index 0000000..2f1f1df --- /dev/null +++ b/app/main/api/internal/handler/admin_notification/admincreatenotificationhandler.go @@ -0,0 +1,30 @@ +package admin_notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateNotificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateNotificationReq + 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_notification.NewAdminCreateNotificationLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateNotification(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_notification/admindeletenotificationhandler.go b/app/main/api/internal/handler/admin_notification/admindeletenotificationhandler.go new file mode 100644 index 0000000..ec1e0a3 --- /dev/null +++ b/app/main/api/internal/handler/admin_notification/admindeletenotificationhandler.go @@ -0,0 +1,30 @@ +package admin_notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteNotificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteNotificationReq + 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_notification.NewAdminDeleteNotificationLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteNotification(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_notification/admingetnotificationdetailhandler.go b/app/main/api/internal/handler/admin_notification/admingetnotificationdetailhandler.go new file mode 100644 index 0000000..298dbb2 --- /dev/null +++ b/app/main/api/internal/handler/admin_notification/admingetnotificationdetailhandler.go @@ -0,0 +1,30 @@ +package admin_notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetNotificationDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetNotificationDetailReq + 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_notification.NewAdminGetNotificationDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetNotificationDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_notification/admingetnotificationlisthandler.go b/app/main/api/internal/handler/admin_notification/admingetnotificationlisthandler.go new file mode 100644 index 0000000..081baa0 --- /dev/null +++ b/app/main/api/internal/handler/admin_notification/admingetnotificationlisthandler.go @@ -0,0 +1,30 @@ +package admin_notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetNotificationListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetNotificationListReq + 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_notification.NewAdminGetNotificationListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetNotificationList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_notification/adminupdatenotificationhandler.go b/app/main/api/internal/handler/admin_notification/adminupdatenotificationhandler.go new file mode 100644 index 0000000..f5dff21 --- /dev/null +++ b/app/main/api/internal/handler/admin_notification/adminupdatenotificationhandler.go @@ -0,0 +1,30 @@ +package admin_notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateNotificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateNotificationReq + 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_notification.NewAdminUpdateNotificationLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateNotification(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admincreateorderhandler.go b/app/main/api/internal/handler/admin_order/admincreateorderhandler.go new file mode 100644 index 0000000..e2af747 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admincreateorderhandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateOrderReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminCreateOrderLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admindeleteorderhandler.go b/app/main/api/internal/handler/admin_order/admindeleteorderhandler.go new file mode 100644 index 0000000..7f204ec --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admindeleteorderhandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteOrderReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminDeleteOrderLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetorderdetailhandler.go b/app/main/api/internal/handler/admin_order/admingetorderdetailhandler.go new file mode 100644 index 0000000..9171982 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetorderdetailhandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetOrderDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetOrderDetailReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetOrderDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetOrderDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetorderlisthandler.go b/app/main/api/internal/handler/admin_order/admingetorderlisthandler.go new file mode 100644 index 0000000..e16027b --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetorderlisthandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetOrderListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetOrderListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetOrderListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetOrderList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetordersourcestatisticshandler.go b/app/main/api/internal/handler/admin_order/admingetordersourcestatisticshandler.go new file mode 100644 index 0000000..2d969f9 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetordersourcestatisticshandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetOrderSourceStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetOrderSourceStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetOrderSourceStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetOrderSourceStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetorderstatisticshandler.go b/app/main/api/internal/handler/admin_order/admingetorderstatisticshandler.go new file mode 100644 index 0000000..a243b35 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetorderstatisticshandler.go @@ -0,0 +1,29 @@ +package admin_order + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetOrderStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetOrderStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetOrderStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetOrderStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetrefundstatisticshandler.go b/app/main/api/internal/handler/admin_order/admingetrefundstatisticshandler.go new file mode 100644 index 0000000..04e26c6 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetrefundstatisticshandler.go @@ -0,0 +1,29 @@ +package admin_order + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminGetRefundStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetRefundStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetRefundStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetRefundStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/admingetrevenuestatisticshandler.go b/app/main/api/internal/handler/admin_order/admingetrevenuestatisticshandler.go new file mode 100644 index 0000000..08295e4 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/admingetrevenuestatisticshandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetRevenueStatisticsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetRevenueStatisticsReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminGetRevenueStatisticsLogic(r.Context(), svcCtx) + resp, err := l.AdminGetRevenueStatistics(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/adminrefundorderhandler.go b/app/main/api/internal/handler/admin_order/adminrefundorderhandler.go new file mode 100644 index 0000000..769ea35 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/adminrefundorderhandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminRefundOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminRefundOrderReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminRefundOrderLogic(r.Context(), svcCtx) + resp, err := l.AdminRefundOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/adminretryagentprocesshandler.go b/app/main/api/internal/handler/admin_order/adminretryagentprocesshandler.go new file mode 100644 index 0000000..58688e3 --- /dev/null +++ b/app/main/api/internal/handler/admin_order/adminretryagentprocesshandler.go @@ -0,0 +1,29 @@ +package admin_order + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminRetryAgentProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminRetryAgentProcessReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminRetryAgentProcessLogic(r.Context(), svcCtx) + resp, err := l.AdminRetryAgentProcess(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_order/adminupdateorderhandler.go b/app/main/api/internal/handler/admin_order/adminupdateorderhandler.go new file mode 100644 index 0000000..11787dd --- /dev/null +++ b/app/main/api/internal/handler/admin_order/adminupdateorderhandler.go @@ -0,0 +1,30 @@ +package admin_order + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_order" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateOrderReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_order.NewAdminUpdateOrderLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_platform_user/admincreateplatformuserhandler.go b/app/main/api/internal/handler/admin_platform_user/admincreateplatformuserhandler.go new file mode 100644 index 0000000..ba177e9 --- /dev/null +++ b/app/main/api/internal/handler/admin_platform_user/admincreateplatformuserhandler.go @@ -0,0 +1,30 @@ +package admin_platform_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_platform_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreatePlatformUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreatePlatformUserReq + 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_platform_user.NewAdminCreatePlatformUserLogic(r.Context(), svcCtx) + resp, err := l.AdminCreatePlatformUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_platform_user/admindeleteplatformuserhandler.go b/app/main/api/internal/handler/admin_platform_user/admindeleteplatformuserhandler.go new file mode 100644 index 0000000..4900c70 --- /dev/null +++ b/app/main/api/internal/handler/admin_platform_user/admindeleteplatformuserhandler.go @@ -0,0 +1,30 @@ +package admin_platform_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_platform_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeletePlatformUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeletePlatformUserReq + 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_platform_user.NewAdminDeletePlatformUserLogic(r.Context(), svcCtx) + resp, err := l.AdminDeletePlatformUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_platform_user/admingetplatformuserdetailhandler.go b/app/main/api/internal/handler/admin_platform_user/admingetplatformuserdetailhandler.go new file mode 100644 index 0000000..ab700af --- /dev/null +++ b/app/main/api/internal/handler/admin_platform_user/admingetplatformuserdetailhandler.go @@ -0,0 +1,30 @@ +package admin_platform_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_platform_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetPlatformUserDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetPlatformUserDetailReq + 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_platform_user.NewAdminGetPlatformUserDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetPlatformUserDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_platform_user/admingetplatformuserlisthandler.go b/app/main/api/internal/handler/admin_platform_user/admingetplatformuserlisthandler.go new file mode 100644 index 0000000..be8c983 --- /dev/null +++ b/app/main/api/internal/handler/admin_platform_user/admingetplatformuserlisthandler.go @@ -0,0 +1,30 @@ +package admin_platform_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_platform_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetPlatformUserListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetPlatformUserListReq + 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_platform_user.NewAdminGetPlatformUserListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetPlatformUserList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_platform_user/adminupdateplatformuserhandler.go b/app/main/api/internal/handler/admin_platform_user/adminupdateplatformuserhandler.go new file mode 100644 index 0000000..ccc81d0 --- /dev/null +++ b/app/main/api/internal/handler/admin_platform_user/adminupdateplatformuserhandler.go @@ -0,0 +1,30 @@ +package admin_platform_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_platform_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdatePlatformUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdatePlatformUserReq + 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_platform_user.NewAdminUpdatePlatformUserLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdatePlatformUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/admincreateproducthandler.go b/app/main/api/internal/handler/admin_product/admincreateproducthandler.go new file mode 100644 index 0000000..29aec9e --- /dev/null +++ b/app/main/api/internal/handler/admin_product/admincreateproducthandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateProductReq + 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_product.NewAdminCreateProductLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateProduct(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/admindeleteproducthandler.go b/app/main/api/internal/handler/admin_product/admindeleteproducthandler.go new file mode 100644 index 0000000..707e062 --- /dev/null +++ b/app/main/api/internal/handler/admin_product/admindeleteproducthandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteProductReq + 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_product.NewAdminDeleteProductLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteProduct(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/admingetproductdetailhandler.go b/app/main/api/internal/handler/admin_product/admingetproductdetailhandler.go new file mode 100644 index 0000000..702a911 --- /dev/null +++ b/app/main/api/internal/handler/admin_product/admingetproductdetailhandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetProductDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetProductDetailReq + 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_product.NewAdminGetProductDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetProductDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/admingetproductfeaturelisthandler.go b/app/main/api/internal/handler/admin_product/admingetproductfeaturelisthandler.go new file mode 100644 index 0000000..3e62bf5 --- /dev/null +++ b/app/main/api/internal/handler/admin_product/admingetproductfeaturelisthandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetProductFeatureListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetProductFeatureListReq + 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_product.NewAdminGetProductFeatureListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetProductFeatureList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/admingetproductlisthandler.go b/app/main/api/internal/handler/admin_product/admingetproductlisthandler.go new file mode 100644 index 0000000..9e678d2 --- /dev/null +++ b/app/main/api/internal/handler/admin_product/admingetproductlisthandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetProductListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetProductListReq + 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_product.NewAdminGetProductListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetProductList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/adminupdateproductfeatureshandler.go b/app/main/api/internal/handler/admin_product/adminupdateproductfeatureshandler.go new file mode 100644 index 0000000..56c2273 --- /dev/null +++ b/app/main/api/internal/handler/admin_product/adminupdateproductfeatureshandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateProductFeaturesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateProductFeaturesReq + 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_product.NewAdminUpdateProductFeaturesLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateProductFeatures(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_product/adminupdateproducthandler.go b/app/main/api/internal/handler/admin_product/adminupdateproducthandler.go new file mode 100644 index 0000000..37b1ffc --- /dev/null +++ b/app/main/api/internal/handler/admin_product/adminupdateproducthandler.go @@ -0,0 +1,30 @@ +package admin_product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateProductReq + 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_product.NewAdminUpdateProductLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateProduct(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/createpromotionlinkhandler.go b/app/main/api/internal/handler/admin_promotion/createpromotionlinkhandler.go new file mode 100644 index 0000000..7931ea7 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/createpromotionlinkhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CreatePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreatePromotionLinkReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewCreatePromotionLinkLogic(r.Context(), svcCtx) + resp, err := l.CreatePromotionLink(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/deletepromotionlinkhandler.go b/app/main/api/internal/handler/admin_promotion/deletepromotionlinkhandler.go new file mode 100644 index 0000000..41a101a --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/deletepromotionlinkhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DeletePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeletePromotionLinkReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewDeletePromotionLinkLogic(r.Context(), svcCtx) + err := l.DeletePromotionLink(&req) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/getpromotionlinkdetailhandler.go b/app/main/api/internal/handler/admin_promotion/getpromotionlinkdetailhandler.go new file mode 100644 index 0000000..d4e2f51 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/getpromotionlinkdetailhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetPromotionLinkDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetPromotionLinkDetailReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewGetPromotionLinkDetailLogic(r.Context(), svcCtx) + resp, err := l.GetPromotionLinkDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/getpromotionlinklisthandler.go b/app/main/api/internal/handler/admin_promotion/getpromotionlinklisthandler.go new file mode 100644 index 0000000..2226a3c --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/getpromotionlinklisthandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetPromotionLinkListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetPromotionLinkListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewGetPromotionLinkListLogic(r.Context(), svcCtx) + resp, err := l.GetPromotionLinkList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/getpromotionstatshistoryhandler.go b/app/main/api/internal/handler/admin_promotion/getpromotionstatshistoryhandler.go new file mode 100644 index 0000000..172ff14 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/getpromotionstatshistoryhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetPromotionStatsHistoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetPromotionStatsHistoryReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewGetPromotionStatsHistoryLogic(r.Context(), svcCtx) + resp, err := l.GetPromotionStatsHistory(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/getpromotionstatstotalhandler.go b/app/main/api/internal/handler/admin_promotion/getpromotionstatstotalhandler.go new file mode 100644 index 0000000..3b9ce22 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/getpromotionstatstotalhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetPromotionStatsTotalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetPromotionStatsTotalReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewGetPromotionStatsTotalLogic(r.Context(), svcCtx) + resp, err := l.GetPromotionStatsTotal(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/recordlinkclickhandler.go b/app/main/api/internal/handler/admin_promotion/recordlinkclickhandler.go new file mode 100644 index 0000000..dc41196 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/recordlinkclickhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func RecordLinkClickHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.RecordLinkClickReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewRecordLinkClickLogic(r.Context(), svcCtx) + resp, err := l.RecordLinkClick(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_promotion/updatepromotionlinkhandler.go b/app/main/api/internal/handler/admin_promotion/updatepromotionlinkhandler.go new file mode 100644 index 0000000..8ab6408 --- /dev/null +++ b/app/main/api/internal/handler/admin_promotion/updatepromotionlinkhandler.go @@ -0,0 +1,30 @@ +package admin_promotion + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_promotion" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func UpdatePromotionLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdatePromotionLinkReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_promotion.NewUpdatePromotionLinkLogic(r.Context(), svcCtx) + err := l.UpdatePromotionLink(&req) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/admin_query/admingetquerycleanupconfiglisthandler.go b/app/main/api/internal/handler/admin_query/admingetquerycleanupconfiglisthandler.go new file mode 100644 index 0000000..53917d7 --- /dev/null +++ b/app/main/api/internal/handler/admin_query/admingetquerycleanupconfiglisthandler.go @@ -0,0 +1,30 @@ +package admin_query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetQueryCleanupConfigListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetQueryCleanupConfigListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_query.NewAdminGetQueryCleanupConfigListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetQueryCleanupConfigList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_query/admingetquerycleanupdetaillisthandler.go b/app/main/api/internal/handler/admin_query/admingetquerycleanupdetaillisthandler.go new file mode 100644 index 0000000..a9d06bf --- /dev/null +++ b/app/main/api/internal/handler/admin_query/admingetquerycleanupdetaillisthandler.go @@ -0,0 +1,30 @@ +package admin_query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetQueryCleanupDetailListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetQueryCleanupDetailListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_query.NewAdminGetQueryCleanupDetailListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetQueryCleanupDetailList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_query/admingetquerycleanuploglisthandler.go b/app/main/api/internal/handler/admin_query/admingetquerycleanuploglisthandler.go new file mode 100644 index 0000000..7c30183 --- /dev/null +++ b/app/main/api/internal/handler/admin_query/admingetquerycleanuploglisthandler.go @@ -0,0 +1,30 @@ +package admin_query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetQueryCleanupLogListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetQueryCleanupLogListReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_query.NewAdminGetQueryCleanupLogListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetQueryCleanupLogList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_query/admingetquerydetailbyorderidhandler.go b/app/main/api/internal/handler/admin_query/admingetquerydetailbyorderidhandler.go new file mode 100644 index 0000000..c001539 --- /dev/null +++ b/app/main/api/internal/handler/admin_query/admingetquerydetailbyorderidhandler.go @@ -0,0 +1,30 @@ +package admin_query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetQueryDetailByOrderIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetQueryDetailByOrderIdReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_query.NewAdminGetQueryDetailByOrderIdLogic(r.Context(), svcCtx) + resp, err := l.AdminGetQueryDetailByOrderId(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_query/adminupdatequerycleanupconfighandler.go b/app/main/api/internal/handler/admin_query/adminupdatequerycleanupconfighandler.go new file mode 100644 index 0000000..88f2ada --- /dev/null +++ b/app/main/api/internal/handler/admin_query/adminupdatequerycleanupconfighandler.go @@ -0,0 +1,30 @@ +package admin_query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateQueryCleanupConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateQueryCleanupConfigReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := admin_query.NewAdminUpdateQueryCleanupConfigLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateQueryCleanupConfig(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role/createrolehandler.go b/app/main/api/internal/handler/admin_role/createrolehandler.go new file mode 100644 index 0000000..e82f2b1 --- /dev/null +++ b/app/main/api/internal/handler/admin_role/createrolehandler.go @@ -0,0 +1,30 @@ +package admin_role + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CreateRoleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreateRoleReq + 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_role.NewCreateRoleLogic(r.Context(), svcCtx) + resp, err := l.CreateRole(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role/deleterolehandler.go b/app/main/api/internal/handler/admin_role/deleterolehandler.go new file mode 100644 index 0000000..6078bdb --- /dev/null +++ b/app/main/api/internal/handler/admin_role/deleterolehandler.go @@ -0,0 +1,30 @@ +package admin_role + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DeleteRoleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeleteRoleReq + 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_role.NewDeleteRoleLogic(r.Context(), svcCtx) + resp, err := l.DeleteRole(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role/getroledetailhandler.go b/app/main/api/internal/handler/admin_role/getroledetailhandler.go new file mode 100644 index 0000000..ad5637f --- /dev/null +++ b/app/main/api/internal/handler/admin_role/getroledetailhandler.go @@ -0,0 +1,30 @@ +package admin_role + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetRoleDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetRoleDetailReq + 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_role.NewGetRoleDetailLogic(r.Context(), svcCtx) + resp, err := l.GetRoleDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role/getrolelisthandler.go b/app/main/api/internal/handler/admin_role/getrolelisthandler.go new file mode 100644 index 0000000..62ba3c7 --- /dev/null +++ b/app/main/api/internal/handler/admin_role/getrolelisthandler.go @@ -0,0 +1,30 @@ +package admin_role + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetRoleListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetRoleListReq + 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_role.NewGetRoleListLogic(r.Context(), svcCtx) + resp, err := l.GetRoleList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role/updaterolehandler.go b/app/main/api/internal/handler/admin_role/updaterolehandler.go new file mode 100644 index 0000000..e4c31b0 --- /dev/null +++ b/app/main/api/internal/handler/admin_role/updaterolehandler.go @@ -0,0 +1,30 @@ +package admin_role + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func UpdateRoleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateRoleReq + 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_role.NewUpdateRoleLogic(r.Context(), svcCtx) + resp, err := l.UpdateRole(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role_api/adminassignroleapihandler.go b/app/main/api/internal/handler/admin_role_api/adminassignroleapihandler.go new file mode 100644 index 0000000..898b454 --- /dev/null +++ b/app/main/api/internal/handler/admin_role_api/adminassignroleapihandler.go @@ -0,0 +1,30 @@ +package admin_role_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminAssignRoleApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminAssignRoleApiReq + 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_role_api.NewAdminAssignRoleApiLogic(r.Context(), svcCtx) + resp, err := l.AdminAssignRoleApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role_api/admingetallapilisthandler.go b/app/main/api/internal/handler/admin_role_api/admingetallapilisthandler.go new file mode 100644 index 0000000..f0a34f9 --- /dev/null +++ b/app/main/api/internal/handler/admin_role_api/admingetallapilisthandler.go @@ -0,0 +1,30 @@ +package admin_role_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetAllApiListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetAllApiListReq + 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_role_api.NewAdminGetAllApiListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetAllApiList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role_api/admingetroleapilisthandler.go b/app/main/api/internal/handler/admin_role_api/admingetroleapilisthandler.go new file mode 100644 index 0000000..f584650 --- /dev/null +++ b/app/main/api/internal/handler/admin_role_api/admingetroleapilisthandler.go @@ -0,0 +1,30 @@ +package admin_role_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetRoleApiListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetRoleApiListReq + 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_role_api.NewAdminGetRoleApiListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetRoleApiList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role_api/adminremoveroleapihandler.go b/app/main/api/internal/handler/admin_role_api/adminremoveroleapihandler.go new file mode 100644 index 0000000..223b8bb --- /dev/null +++ b/app/main/api/internal/handler/admin_role_api/adminremoveroleapihandler.go @@ -0,0 +1,30 @@ +package admin_role_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminRemoveRoleApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminRemoveRoleApiReq + 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_role_api.NewAdminRemoveRoleApiLogic(r.Context(), svcCtx) + resp, err := l.AdminRemoveRoleApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_role_api/adminupdateroleapihandler.go b/app/main/api/internal/handler/admin_role_api/adminupdateroleapihandler.go new file mode 100644 index 0000000..df6ea45 --- /dev/null +++ b/app/main/api/internal/handler/admin_role_api/adminupdateroleapihandler.go @@ -0,0 +1,30 @@ +package admin_role_api + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_role_api" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateRoleApiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateRoleApiReq + 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_role_api.NewAdminUpdateRoleApiLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateRoleApi(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/admincreateuserhandler.go b/app/main/api/internal/handler/admin_user/admincreateuserhandler.go new file mode 100644 index 0000000..b8dbf0b --- /dev/null +++ b/app/main/api/internal/handler/admin_user/admincreateuserhandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminCreateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminCreateUserReq + 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_user.NewAdminCreateUserLogic(r.Context(), svcCtx) + resp, err := l.AdminCreateUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/admindeleteuserhandler.go b/app/main/api/internal/handler/admin_user/admindeleteuserhandler.go new file mode 100644 index 0000000..1be9ed6 --- /dev/null +++ b/app/main/api/internal/handler/admin_user/admindeleteuserhandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminDeleteUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminDeleteUserReq + 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_user.NewAdminDeleteUserLogic(r.Context(), svcCtx) + resp, err := l.AdminDeleteUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/admingetuserdetailhandler.go b/app/main/api/internal/handler/admin_user/admingetuserdetailhandler.go new file mode 100644 index 0000000..606b95c --- /dev/null +++ b/app/main/api/internal/handler/admin_user/admingetuserdetailhandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetUserDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetUserDetailReq + 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_user.NewAdminGetUserDetailLogic(r.Context(), svcCtx) + resp, err := l.AdminGetUserDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/admingetuserlisthandler.go b/app/main/api/internal/handler/admin_user/admingetuserlisthandler.go new file mode 100644 index 0000000..f554bb1 --- /dev/null +++ b/app/main/api/internal/handler/admin_user/admingetuserlisthandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminGetUserListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminGetUserListReq + 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_user.NewAdminGetUserListLogic(r.Context(), svcCtx) + resp, err := l.AdminGetUserList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/adminresetpasswordhandler.go b/app/main/api/internal/handler/admin_user/adminresetpasswordhandler.go new file mode 100644 index 0000000..43dd33b --- /dev/null +++ b/app/main/api/internal/handler/admin_user/adminresetpasswordhandler.go @@ -0,0 +1,29 @@ +package admin_user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func AdminResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminResetPasswordReq + 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_user.NewAdminResetPasswordLogic(r.Context(), svcCtx) + resp, err := l.AdminResetPassword(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/adminupdateuserhandler.go b/app/main/api/internal/handler/admin_user/adminupdateuserhandler.go new file mode 100644 index 0000000..ee06f25 --- /dev/null +++ b/app/main/api/internal/handler/admin_user/adminupdateuserhandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUpdateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUpdateUserReq + 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_user.NewAdminUpdateUserLogic(r.Context(), svcCtx) + resp, err := l.AdminUpdateUser(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/admin_user/adminuserinfohandler.go b/app/main/api/internal/handler/admin_user/adminuserinfohandler.go new file mode 100644 index 0000000..0427e8b --- /dev/null +++ b/app/main/api/internal/handler/admin_user/adminuserinfohandler.go @@ -0,0 +1,30 @@ +package admin_user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/admin_user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AdminUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AdminUserInfoReq + 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_user.NewAdminUserInfoLogic(r.Context(), svcCtx) + resp, err := l.AdminUserInfo(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/activateagentmembershiphandler.go b/app/main/api/internal/handler/agent/activateagentmembershiphandler.go new file mode 100644 index 0000000..d981ed0 --- /dev/null +++ b/app/main/api/internal/handler/agent/activateagentmembershiphandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func ActivateAgentMembershipHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentActivateMembershipReq + 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.NewActivateAgentMembershipLogic(r.Context(), svcCtx) + resp, err := l.ActivateAgentMembership(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/agentrealnamehandler.go b/app/main/api/internal/handler/agent/agentrealnamehandler.go new file mode 100644 index 0000000..f9e70ae --- /dev/null +++ b/app/main/api/internal/handler/agent/agentrealnamehandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AgentRealNameHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentRealNameReq + 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.NewAgentRealNameLogic(r.Context(), svcCtx) + resp, err := l.AgentRealName(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/agentwithdrawalhandler.go b/app/main/api/internal/handler/agent/agentwithdrawalhandler.go new file mode 100644 index 0000000..6a50503 --- /dev/null +++ b/app/main/api/internal/handler/agent/agentwithdrawalhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func AgentWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.WithdrawalReq + 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.NewAgentWithdrawalLogic(r.Context(), svcCtx) + resp, err := l.AgentWithdrawal(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/applyforagenthandler.go b/app/main/api/internal/handler/agent/applyforagenthandler.go new file mode 100644 index 0000000..c1b3f9d --- /dev/null +++ b/app/main/api/internal/handler/agent/applyforagenthandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func ApplyForAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentApplyReq + 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.NewApplyForAgentLogic(r.Context(), svcCtx) + resp, err := l.ApplyForAgent(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/bankcardwithdrawalhandler.go b/app/main/api/internal/handler/agent/bankcardwithdrawalhandler.go new file mode 100644 index 0000000..4101295 --- /dev/null +++ b/app/main/api/internal/handler/agent/bankcardwithdrawalhandler.go @@ -0,0 +1,29 @@ +package agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func BankCardWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.BankCardWithdrawalReq + 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.NewBankCardWithdrawalLogic(r.Context(), svcCtx) + resp, err := l.BankCardWithdrawal(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/generatinglinkhandler.go b/app/main/api/internal/handler/agent/generatinglinkhandler.go new file mode 100644 index 0000000..770d8ed --- /dev/null +++ b/app/main/api/internal/handler/agent/generatinglinkhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GeneratingLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentGeneratingLinkReq + 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.NewGeneratingLinkLogic(r.Context(), svcCtx) + resp, err := l.GeneratingLink(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentauditstatushandler.go b/app/main/api/internal/handler/agent/getagentauditstatushandler.go new file mode 100644 index 0000000..0e46897 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentauditstatushandler.go @@ -0,0 +1,17 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetAgentAuditStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := agent.NewGetAgentAuditStatusLogic(r.Context(), svcCtx) + resp, err := l.GetAgentAuditStatus() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentcommissionhandler.go b/app/main/api/internal/handler/agent/getagentcommissionhandler.go new file mode 100644 index 0000000..c7f13fc --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentcommissionhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentCommissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetCommissionReq + 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.NewGetAgentCommissionLogic(r.Context(), svcCtx) + resp, err := l.GetAgentCommission(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentinfohandler.go b/app/main/api/internal/handler/agent/getagentinfohandler.go new file mode 100644 index 0000000..5c5eaea --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentinfohandler.go @@ -0,0 +1,17 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetAgentInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := agent.NewGetAgentInfoLogic(r.Context(), svcCtx) + resp, err := l.GetAgentInfo() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentmembershipproductconfighandler.go b/app/main/api/internal/handler/agent/getagentmembershipproductconfighandler.go new file mode 100644 index 0000000..883f8f2 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentmembershipproductconfighandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentMembershipProductConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentMembershipProductConfigReq + 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.NewGetAgentMembershipProductConfigLogic(r.Context(), svcCtx) + resp, err := l.GetAgentMembershipProductConfig(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentproductconfighandler.go b/app/main/api/internal/handler/agent/getagentproductconfighandler.go new file mode 100644 index 0000000..7a813b1 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentproductconfighandler.go @@ -0,0 +1,17 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetAgentProductConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := agent.NewGetAgentProductConfigLogic(r.Context(), svcCtx) + resp, err := l.GetAgentProductConfig() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go b/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go new file mode 100644 index 0000000..62aae83 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentpromotionqrcodehandler.go @@ -0,0 +1,36 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentPromotionQrcodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAgentPromotionQrcodeReq + 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 + } + + // 注意:这里传入了 ResponseWriter,用于直接写入图片数据 + l := agent.NewGetAgentPromotionQrcodeLogic(r.Context(), svcCtx, w) + err := l.GetAgentPromotionQrcode(&req) + if err != nil { + // 如果处理过程中出错,返回JSON错误响应 + result.HttpResult(r, w, nil, err) + } + // 成功时,图片数据已经通过logic直接写入ResponseWriter,不需要额外处理 + } +} diff --git a/app/main/api/internal/handler/agent/getagentrevenueinfohandler.go b/app/main/api/internal/handler/agent/getagentrevenueinfohandler.go new file mode 100644 index 0000000..5ffa6d3 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentrevenueinfohandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentRevenueInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAgentRevenueInfoReq + 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.NewGetAgentRevenueInfoLogic(r.Context(), svcCtx) + resp, err := l.GetAgentRevenueInfo(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentrewardshandler.go b/app/main/api/internal/handler/agent/getagentrewardshandler.go new file mode 100644 index 0000000..258a066 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentrewardshandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentRewardsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetRewardsReq + 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.NewGetAgentRewardsLogic(r.Context(), svcCtx) + resp, err := l.GetAgentRewards(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentsubordinatecontributiondetailhandler.go b/app/main/api/internal/handler/agent/getagentsubordinatecontributiondetailhandler.go new file mode 100644 index 0000000..e700f12 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentsubordinatecontributiondetailhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentSubordinateContributionDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAgentSubordinateContributionDetailReq + 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.NewGetAgentSubordinateContributionDetailLogic(r.Context(), svcCtx) + resp, err := l.GetAgentSubordinateContributionDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentsubordinatelisthandler.go b/app/main/api/internal/handler/agent/getagentsubordinatelisthandler.go new file mode 100644 index 0000000..a635bdb --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentsubordinatelisthandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentSubordinateListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAgentSubordinateListReq + 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.NewGetAgentSubordinateListLogic(r.Context(), svcCtx) + resp, err := l.GetAgentSubordinateList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentwithdrawalhandler.go b/app/main/api/internal/handler/agent/getagentwithdrawalhandler.go new file mode 100644 index 0000000..f66b216 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentwithdrawalhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetWithdrawalReq + 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.NewGetAgentWithdrawalLogic(r.Context(), svcCtx) + resp, err := l.GetAgentWithdrawal(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getagentwithdrawaltaxexemptionhandler.go b/app/main/api/internal/handler/agent/getagentwithdrawaltaxexemptionhandler.go new file mode 100644 index 0000000..22c83a2 --- /dev/null +++ b/app/main/api/internal/handler/agent/getagentwithdrawaltaxexemptionhandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAgentWithdrawalTaxExemptionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetWithdrawalTaxExemptionReq + 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.NewGetAgentWithdrawalTaxExemptionLogic(r.Context(), svcCtx) + resp, err := l.GetAgentWithdrawalTaxExemption(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getbankcardinfohandler.go b/app/main/api/internal/handler/agent/getbankcardinfohandler.go new file mode 100644 index 0000000..90d8592 --- /dev/null +++ b/app/main/api/internal/handler/agent/getbankcardinfohandler.go @@ -0,0 +1,29 @@ +package agent + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func GetBankCardInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetBankCardInfoReq + 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.NewGetBankCardInfoLogic(r.Context(), svcCtx) + resp, err := l.GetBankCardInfo(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getlinkdatahandler.go b/app/main/api/internal/handler/agent/getlinkdatahandler.go new file mode 100644 index 0000000..3a8e0d5 --- /dev/null +++ b/app/main/api/internal/handler/agent/getlinkdatahandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetLinkDataHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetLinkDataReq + 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.NewGetLinkDataLogic(r.Context(), svcCtx) + resp, err := l.GetLinkData(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/getmembershipinfohandler.go b/app/main/api/internal/handler/agent/getmembershipinfohandler.go new file mode 100644 index 0000000..be22fd5 --- /dev/null +++ b/app/main/api/internal/handler/agent/getmembershipinfohandler.go @@ -0,0 +1,17 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetMembershipInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := agent.NewGetMembershipInfoLogic(r.Context(), svcCtx) + resp, err := l.GetMembershipInfo() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/agent/saveagentmembershipuserconfighandler.go b/app/main/api/internal/handler/agent/saveagentmembershipuserconfighandler.go new file mode 100644 index 0000000..0fd7e0f --- /dev/null +++ b/app/main/api/internal/handler/agent/saveagentmembershipuserconfighandler.go @@ -0,0 +1,30 @@ +package agent + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/agent" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func SaveAgentMembershipUserConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.SaveAgentMembershipUserConfigReq + 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.NewSaveAgentMembershipUserConfigLogic(r.Context(), svcCtx) + err := l.SaveAgentMembershipUserConfig(&req) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/app/getappversionhandler.go b/app/main/api/internal/handler/app/getappversionhandler.go new file mode 100644 index 0000000..22bd32a --- /dev/null +++ b/app/main/api/internal/handler/app/getappversionhandler.go @@ -0,0 +1,17 @@ +package app + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/app" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetAppVersionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := app.NewGetAppVersionLogic(r.Context(), svcCtx) + resp, err := l.GetAppVersion() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/app/healthcheckhandler.go b/app/main/api/internal/handler/app/healthcheckhandler.go new file mode 100644 index 0000000..ecd96b4 --- /dev/null +++ b/app/main/api/internal/handler/app/healthcheckhandler.go @@ -0,0 +1,17 @@ +package app + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/app" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func HealthCheckHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := app.NewHealthCheckLogic(r.Context(), svcCtx) + resp, err := l.HealthCheck() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/auth/sendsmshandler.go b/app/main/api/internal/handler/auth/sendsmshandler.go new file mode 100644 index 0000000..f8c52b6 --- /dev/null +++ b/app/main/api/internal/handler/auth/sendsmshandler.go @@ -0,0 +1,34 @@ +package auth + +import ( + "context" + "net/http" + + "bdrp-server/app/main/api/internal/logic/auth" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/captcha" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.SendSmsReq + 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 + } + // 将 request 注入 context,供 captcha 包判断微信等环境时跳过图形验证 + ctx := context.WithValue(r.Context(), captcha.HTTPRequestContextKey, r) + l := auth.NewSendSmsLogic(ctx, svcCtx) + err := l.SendSms(&req) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/authorization/downloadauthorizationdocumentbynamehandler.go b/app/main/api/internal/handler/authorization/downloadauthorizationdocumentbynamehandler.go new file mode 100644 index 0000000..244cff9 --- /dev/null +++ b/app/main/api/internal/handler/authorization/downloadauthorizationdocumentbynamehandler.go @@ -0,0 +1,67 @@ +package authorization + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "os" + "strconv" + + "bdrp-server/app/main/api/internal/logic/authorization" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/result" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DownloadAuthorizationDocumentByNameHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DownloadAuthorizationDocumentByNameReq + 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 := authorization.NewDownloadAuthorizationDocumentByNameLogic(r.Context(), svcCtx) + resp, err := l.DownloadAuthorizationDocumentByName(&req) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + httpx.WriteJson(w, http.StatusNotFound, result.Error(xerr.CUSTOM_ERROR, "授权书不存在")) + return + } + result.HttpResult(r, w, nil, err) + return + } + + file, openErr := os.Open(resp.FilePath) + if openErr != nil { + logx.Errorf("打开授权书文件失败: fileName=%s, filePath=%s, error=%v", req.FileName, resp.FilePath, openErr) + result.HttpResult(r, w, nil, errors.New("授权书文件不存在")) + return + } + defer file.Close() + + fileInfo, statErr := file.Stat() + if statErr != nil { + logx.Errorf("读取授权书文件信息失败: fileName=%s, filePath=%s, error=%v", req.FileName, resp.FilePath, statErr) + result.HttpResult(r, w, nil, errors.New("授权书文件不可用")) + return + } + + escapedName := url.PathEscape(resp.FileName) + w.Header().Set("Content-Type", "application/pdf") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", escapedName, escapedName)) + w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10)) + + http.ServeContent(w, r, resp.FileName, fileInfo.ModTime(), file) + } +} diff --git a/app/main/api/internal/handler/authorization/downloadauthorizationdocumenthandler.go b/app/main/api/internal/handler/authorization/downloadauthorizationdocumenthandler.go new file mode 100644 index 0000000..0ff0796 --- /dev/null +++ b/app/main/api/internal/handler/authorization/downloadauthorizationdocumenthandler.go @@ -0,0 +1,67 @@ +package authorization + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "os" + "strconv" + + "bdrp-server/app/main/api/internal/logic/authorization" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/result" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DownloadAuthorizationDocumentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DownloadAuthorizationDocumentReq + 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 := authorization.NewDownloadAuthorizationDocumentLogic(r.Context(), svcCtx) + resp, err := l.DownloadAuthorizationDocument(&req) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + httpx.WriteJson(w, http.StatusNotFound, result.Error(xerr.CUSTOM_ERROR, "授权书不存在")) + return + } + result.HttpResult(r, w, nil, err) + return + } + + file, openErr := os.Open(resp.FilePath) + if openErr != nil { + logx.Errorf("打开授权书文件失败: filePath=%s, error=%v", resp.FilePath, openErr) + result.HttpResult(r, w, nil, errors.New("授权书文件不存在")) + return + } + defer file.Close() + + fileInfo, statErr := file.Stat() + if statErr != nil { + logx.Errorf("读取授权书文件信息失败: filePath=%s, error=%v", resp.FilePath, statErr) + result.HttpResult(r, w, nil, errors.New("授权书文件不可用")) + return + } + + escapedName := url.PathEscape(resp.FileName) + w.Header().Set("Content-Type", "application/pdf") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", escapedName, escapedName)) + w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10)) + + http.ServeContent(w, r, resp.FileName, fileInfo.ModTime(), file) + } +} diff --git a/app/main/api/internal/handler/authorization/getauthorizationdocumentbyorderhandler.go b/app/main/api/internal/handler/authorization/getauthorizationdocumentbyorderhandler.go new file mode 100644 index 0000000..2f031cc --- /dev/null +++ b/app/main/api/internal/handler/authorization/getauthorizationdocumentbyorderhandler.go @@ -0,0 +1,30 @@ +package authorization + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/authorization" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAuthorizationDocumentByOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAuthorizationDocumentByOrderReq + 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 := authorization.NewGetAuthorizationDocumentByOrderLogic(r.Context(), svcCtx) + resp, err := l.GetAuthorizationDocumentByOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/authorization/getauthorizationdocumenthandler.go b/app/main/api/internal/handler/authorization/getauthorizationdocumenthandler.go new file mode 100644 index 0000000..a070c16 --- /dev/null +++ b/app/main/api/internal/handler/authorization/getauthorizationdocumenthandler.go @@ -0,0 +1,30 @@ +package authorization + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/authorization" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetAuthorizationDocumentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetAuthorizationDocumentReq + 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 := authorization.NewGetAuthorizationDocumentLogic(r.Context(), svcCtx) + resp, err := l.GetAuthorizationDocument(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/notification/getnotificationshandler.go b/app/main/api/internal/handler/notification/getnotificationshandler.go new file mode 100644 index 0000000..69716f9 --- /dev/null +++ b/app/main/api/internal/handler/notification/getnotificationshandler.go @@ -0,0 +1,17 @@ +package notification + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/notification" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetNotificationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := notification.NewGetNotificationsLogic(r.Context(), svcCtx) + resp, err := l.GetNotifications() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/pay/alipaycallbackhandler.go b/app/main/api/internal/handler/pay/alipaycallbackhandler.go new file mode 100644 index 0000000..c9f2d76 --- /dev/null +++ b/app/main/api/internal/handler/pay/alipaycallbackhandler.go @@ -0,0 +1,17 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func AlipayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := pay.NewAlipayCallbackLogic(r.Context(), svcCtx) + err := l.AlipayCallback(w, r) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/pay/iapcallbackhandler.go b/app/main/api/internal/handler/pay/iapcallbackhandler.go new file mode 100644 index 0000000..9608645 --- /dev/null +++ b/app/main/api/internal/handler/pay/iapcallbackhandler.go @@ -0,0 +1,30 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func IapCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.IapCallbackReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := pay.NewIapCallbackLogic(r.Context(), svcCtx) + err := l.IapCallback(&req) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/pay/paymentcheckhandler.go b/app/main/api/internal/handler/pay/paymentcheckhandler.go new file mode 100644 index 0000000..e67f526 --- /dev/null +++ b/app/main/api/internal/handler/pay/paymentcheckhandler.go @@ -0,0 +1,30 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func PaymentCheckHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.PaymentCheckReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := pay.NewPaymentCheckLogic(r.Context(), svcCtx) + resp, err := l.PaymentCheck(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/pay/paymenthandler.go b/app/main/api/internal/handler/pay/paymenthandler.go new file mode 100644 index 0000000..d0363e4 --- /dev/null +++ b/app/main/api/internal/handler/pay/paymenthandler.go @@ -0,0 +1,30 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func PaymentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.PaymentReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := pay.NewPaymentLogic(r.Context(), svcCtx) + resp, err := l.Payment(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/pay/wechatpaycallbackhandler.go b/app/main/api/internal/handler/pay/wechatpaycallbackhandler.go new file mode 100644 index 0000000..986f82a --- /dev/null +++ b/app/main/api/internal/handler/pay/wechatpaycallbackhandler.go @@ -0,0 +1,17 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func WechatPayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := pay.NewWechatPayCallbackLogic(r.Context(), svcCtx) + err := l.WechatPayCallback(w, r) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/pay/wechatpayrefundcallbackhandler.go b/app/main/api/internal/handler/pay/wechatpayrefundcallbackhandler.go new file mode 100644 index 0000000..208fc4e --- /dev/null +++ b/app/main/api/internal/handler/pay/wechatpayrefundcallbackhandler.go @@ -0,0 +1,17 @@ +package pay + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func WechatPayRefundCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := pay.NewWechatPayRefundCallbackLogic(r.Context(), svcCtx) + err := l.WechatPayRefundCallback(w, r) + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/product/getproductappbyenhandler.go b/app/main/api/internal/handler/product/getproductappbyenhandler.go new file mode 100644 index 0000000..5267898 --- /dev/null +++ b/app/main/api/internal/handler/product/getproductappbyenhandler.go @@ -0,0 +1,30 @@ +package product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetProductAppByEnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetProductByEnRequest + 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 := product.NewGetProductAppByEnLogic(r.Context(), svcCtx) + resp, err := l.GetProductAppByEn(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/product/getproductbyenhandler.go b/app/main/api/internal/handler/product/getproductbyenhandler.go new file mode 100644 index 0000000..fe48d6c --- /dev/null +++ b/app/main/api/internal/handler/product/getproductbyenhandler.go @@ -0,0 +1,30 @@ +package product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetProductByEnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetProductByEnRequest + 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 := product.NewGetProductByEnLogic(r.Context(), svcCtx) + resp, err := l.GetProductByEn(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/product/getproductbyidhandler.go b/app/main/api/internal/handler/product/getproductbyidhandler.go new file mode 100644 index 0000000..249390f --- /dev/null +++ b/app/main/api/internal/handler/product/getproductbyidhandler.go @@ -0,0 +1,30 @@ +package product + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/product" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetProductByIDHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetProductByIDRequest + 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 := product.NewGetProductByIDLogic(r.Context(), svcCtx) + resp, err := l.GetProductByID(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querydetailbyorderidhandler.go b/app/main/api/internal/handler/query/querydetailbyorderidhandler.go new file mode 100644 index 0000000..f046b14 --- /dev/null +++ b/app/main/api/internal/handler/query/querydetailbyorderidhandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryDetailByOrderIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryDetailByOrderIdReq + 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 := query.NewQueryDetailByOrderIdLogic(r.Context(), svcCtx) + resp, err := l.QueryDetailByOrderId(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querydetailbyordernohandler.go b/app/main/api/internal/handler/query/querydetailbyordernohandler.go new file mode 100644 index 0000000..224f55e --- /dev/null +++ b/app/main/api/internal/handler/query/querydetailbyordernohandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryDetailByOrderNoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryDetailByOrderNoReq + 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 := query.NewQueryDetailByOrderNoLogic(r.Context(), svcCtx) + resp, err := l.QueryDetailByOrderNo(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryexamplehandler.go b/app/main/api/internal/handler/query/queryexamplehandler.go new file mode 100644 index 0000000..082ed9f --- /dev/null +++ b/app/main/api/internal/handler/query/queryexamplehandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryExampleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryExampleReq + 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 := query.NewQueryExampleLogic(r.Context(), svcCtx) + resp, err := l.QueryExample(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querygeneratesharelinkhandler.go b/app/main/api/internal/handler/query/querygeneratesharelinkhandler.go new file mode 100644 index 0000000..3c8a48f --- /dev/null +++ b/app/main/api/internal/handler/query/querygeneratesharelinkhandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryGenerateShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryGenerateShareLinkReq + 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 := query.NewQueryGenerateShareLinkLogic(r.Context(), svcCtx) + resp, err := l.QueryGenerateShareLink(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querylisthandler.go b/app/main/api/internal/handler/query/querylisthandler.go new file mode 100644 index 0000000..b81db3c --- /dev/null +++ b/app/main/api/internal/handler/query/querylisthandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryListReq + 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 := query.NewQueryListLogic(r.Context(), svcCtx) + resp, err := l.QueryList(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryprovisionalorderhandler.go b/app/main/api/internal/handler/query/queryprovisionalorderhandler.go new file mode 100644 index 0000000..f33f31b --- /dev/null +++ b/app/main/api/internal/handler/query/queryprovisionalorderhandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryProvisionalOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryProvisionalOrderReq + 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 := query.NewQueryProvisionalOrderLogic(r.Context(), svcCtx) + resp, err := l.QueryProvisionalOrder(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryretryhandler.go b/app/main/api/internal/handler/query/queryretryhandler.go new file mode 100644 index 0000000..be08f9f --- /dev/null +++ b/app/main/api/internal/handler/query/queryretryhandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryRetryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryRetryReq + 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 := query.NewQueryRetryLogic(r.Context(), svcCtx) + resp, err := l.QueryRetry(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryserviceagenthandler.go b/app/main/api/internal/handler/query/queryserviceagenthandler.go new file mode 100644 index 0000000..760cb58 --- /dev/null +++ b/app/main/api/internal/handler/query/queryserviceagenthandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryServiceAgentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryServiceReq + 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 := query.NewQueryServiceLogic(r.Context(), svcCtx) + resp, err := l.QueryService(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryserviceapphandler.go b/app/main/api/internal/handler/query/queryserviceapphandler.go new file mode 100644 index 0000000..8556d17 --- /dev/null +++ b/app/main/api/internal/handler/query/queryserviceapphandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryServiceAppHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryServiceReq + 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 := query.NewQueryServiceLogic(r.Context(), svcCtx) + resp, err := l.QueryService(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/queryservicehandler.go b/app/main/api/internal/handler/query/queryservicehandler.go new file mode 100644 index 0000000..d943059 --- /dev/null +++ b/app/main/api/internal/handler/query/queryservicehandler.go @@ -0,0 +1,25 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryServiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryServiceReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + l := query.NewQueryServiceLogic(r.Context(), svcCtx) + resp, err := l.QueryService(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querysharedetailhandler.go b/app/main/api/internal/handler/query/querysharedetailhandler.go new file mode 100644 index 0000000..444e0bf --- /dev/null +++ b/app/main/api/internal/handler/query/querysharedetailhandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QueryShareDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QueryShareDetailReq + 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 := query.NewQueryShareDetailLogic(r.Context(), svcCtx) + resp, err := l.QueryShareDetail(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/querysingletesthandler.go b/app/main/api/internal/handler/query/querysingletesthandler.go new file mode 100644 index 0000000..a01be97 --- /dev/null +++ b/app/main/api/internal/handler/query/querysingletesthandler.go @@ -0,0 +1,30 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func QuerySingleTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.QuerySingleTestReq + 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 := query.NewQuerySingleTestLogic(r.Context(), svcCtx) + resp, err := l.QuerySingleTest(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/query/updatequerydatahandler.go b/app/main/api/internal/handler/query/updatequerydatahandler.go new file mode 100644 index 0000000..3932888 --- /dev/null +++ b/app/main/api/internal/handler/query/updatequerydatahandler.go @@ -0,0 +1,31 @@ +package query + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/query" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +// 更新查询数据 +func UpdateQueryDataHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateQueryDataReq + 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 := query.NewUpdateQueryDataLogic(r.Context(), svcCtx) + resp, err := l.UpdateQueryData(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go new file mode 100644 index 0000000..3247ff7 --- /dev/null +++ b/app/main/api/internal/handler/routes.go @@ -0,0 +1,1218 @@ +// Code generated by goctl. DO NOT EDIT. +package handler + +import ( + "net/http" + + admin_agent "bdrp-server/app/main/api/internal/handler/admin_agent" + admin_api "bdrp-server/app/main/api/internal/handler/admin_api" + admin_auth "bdrp-server/app/main/api/internal/handler/admin_auth" + admin_feature "bdrp-server/app/main/api/internal/handler/admin_feature" + admin_menu "bdrp-server/app/main/api/internal/handler/admin_menu" + admin_notification "bdrp-server/app/main/api/internal/handler/admin_notification" + admin_order "bdrp-server/app/main/api/internal/handler/admin_order" + admin_platform_user "bdrp-server/app/main/api/internal/handler/admin_platform_user" + admin_product "bdrp-server/app/main/api/internal/handler/admin_product" + admin_promotion "bdrp-server/app/main/api/internal/handler/admin_promotion" + admin_query "bdrp-server/app/main/api/internal/handler/admin_query" + admin_role "bdrp-server/app/main/api/internal/handler/admin_role" + admin_role_api "bdrp-server/app/main/api/internal/handler/admin_role_api" + admin_user "bdrp-server/app/main/api/internal/handler/admin_user" + agent "bdrp-server/app/main/api/internal/handler/agent" + app "bdrp-server/app/main/api/internal/handler/app" + auth "bdrp-server/app/main/api/internal/handler/auth" + authorization "bdrp-server/app/main/api/internal/handler/authorization" + notification "bdrp-server/app/main/api/internal/handler/notification" + pay "bdrp-server/app/main/api/internal/handler/pay" + product "bdrp-server/app/main/api/internal/handler/product" + query "bdrp-server/app/main/api/internal/handler/query" + user "bdrp-server/app/main/api/internal/handler/user" + "bdrp-server/app/main/api/internal/svc" + + "github.com/zeromicro/go-zero/rest" +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/agent-commission-deduction/list", + Handler: admin_agent.AdminGetAgentCommissionDeductionListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/agent-commission/batch-unfreeze", + Handler: admin_agent.AdminBatchUnfreezeAgentCommissionHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-commission/list", + Handler: admin_agent.AdminGetAgentCommissionListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/agent-commission/update-status", + Handler: admin_agent.AdminUpdateAgentCommissionStatusHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-link/list", + Handler: admin_agent.AdminGetAgentLinkListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-link/product-statistics", + Handler: admin_agent.AdminGetAgentLinkProductStatisticsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-membership-config/list", + Handler: admin_agent.AdminGetAgentMembershipConfigListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/agent-membership-config/update", + Handler: admin_agent.AdminUpdateAgentMembershipConfigHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-membership-recharge-order/list", + Handler: admin_agent.AdminGetAgentMembershipRechargeOrderListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-order/statistics", + Handler: admin_agent.AdminGetAgentOrderStatisticsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-platform-deduction/list", + Handler: admin_agent.AdminGetAgentPlatformDeductionListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-production-config/list", + Handler: admin_agent.AdminGetAgentProductionConfigListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/agent-production-config/update", + Handler: admin_agent.AdminUpdateAgentProductionConfigHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-reward/list", + Handler: admin_agent.AdminGetAgentRewardListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/agent-withdrawal/bank-card/review", + Handler: admin_agent.AdminReviewBankCardWithdrawalHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-withdrawal/list", + Handler: admin_agent.AdminGetAgentWithdrawalListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/agent-withdrawal/statistics", + Handler: admin_agent.AdminGetWithdrawalStatisticsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/list", + Handler: admin_agent.AdminGetAgentListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/statistics", + Handler: admin_agent.AdminGetAgentStatisticsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/system-config", + Handler: admin_agent.AdminGetSystemConfigHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/system-config", + Handler: admin_agent.AdminUpdateSystemConfigHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/wallet-transaction/list", + Handler: admin_agent.AdminGetAgentWalletTransactionListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/wallet/:agent_id", + Handler: admin_agent.AdminGetAgentWalletHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/wallet/update-balance", + Handler: admin_agent.AdminUpdateAgentWalletBalanceHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPut, + Path: "/admin/api/batch-update-status", + Handler: admin_api.AdminBatchUpdateApiStatusHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/admin/api/create", + Handler: admin_api.AdminCreateApiHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/admin/api/delete/:id", + Handler: admin_api.AdminDeleteApiHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/admin/api/detail/:id", + Handler: admin_api.AdminGetApiDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/admin/api/list", + Handler: admin_api.AdminGetApiListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/admin/api/update/:id", + Handler: admin_api.AdminUpdateApiHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 登录 + Method: http.MethodPost, + Path: "/login", + Handler: admin_auth.AdminLoginHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/admin/auth"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/config-example", + Handler: admin_feature.AdminConfigFeatureExampleHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/create", + Handler: admin_feature.AdminCreateFeatureHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_feature.AdminDeleteFeatureHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_feature.AdminGetFeatureDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/example/:feature_id", + Handler: admin_feature.AdminGetFeatureExampleHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/list", + Handler: admin_feature.AdminGetFeatureListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_feature.AdminUpdateFeatureHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/feature"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 获取所有菜单(树形结构) + Method: http.MethodGet, + Path: "/all", + Handler: admin_menu.GetMenuAllHandler(serverCtx), + }, + { + // 创建菜单 + Method: http.MethodPost, + Path: "/create", + Handler: admin_menu.CreateMenuHandler(serverCtx), + }, + { + // 删除菜单 + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_menu.DeleteMenuHandler(serverCtx), + }, + { + // 获取菜单详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_menu.GetMenuDetailHandler(serverCtx), + }, + { + // 获取菜单列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_menu.GetMenuListHandler(serverCtx), + }, + { + // 更新菜单 + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_menu.UpdateMenuHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/menu"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/create", + Handler: admin_notification.AdminCreateNotificationHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_notification.AdminDeleteNotificationHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_notification.AdminGetNotificationDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/list", + Handler: admin_notification.AdminGetNotificationListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_notification.AdminUpdateNotificationHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/notification"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 创建订单 + Method: http.MethodPost, + Path: "/create", + Handler: admin_order.AdminCreateOrderHandler(serverCtx), + }, + { + // 删除订单 + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_order.AdminDeleteOrderHandler(serverCtx), + }, + { + // 获取订单详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_order.AdminGetOrderDetailHandler(serverCtx), + }, + { + // 获取订单列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_order.AdminGetOrderListHandler(serverCtx), + }, + { + // 获取退款统计数据 + Method: http.MethodGet, + Path: "/refund-statistics", + Handler: admin_order.AdminGetRefundStatisticsHandler(serverCtx), + }, + { + // 订单退款 + Method: http.MethodPost, + Path: "/refund/:id", + Handler: admin_order.AdminRefundOrderHandler(serverCtx), + }, + { + // 重新执行代理处理 + Method: http.MethodPost, + Path: "/retry-agent-process/:id", + Handler: admin_order.AdminRetryAgentProcessHandler(serverCtx), + }, + { + // 获取收入和利润统计数据 + Method: http.MethodGet, + Path: "/revenue-statistics", + Handler: admin_order.AdminGetRevenueStatisticsHandler(serverCtx), + }, + { + // 获取订单来源统计数据 + Method: http.MethodGet, + Path: "/source-statistics", + Handler: admin_order.AdminGetOrderSourceStatisticsHandler(serverCtx), + }, + { + // 获取订单统计数据 + Method: http.MethodGet, + Path: "/statistics", + Handler: admin_order.AdminGetOrderStatisticsHandler(serverCtx), + }, + { + // 更新订单 + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_order.AdminUpdateOrderHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/order"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/create", + Handler: admin_platform_user.AdminCreatePlatformUserHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_platform_user.AdminDeletePlatformUserHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_platform_user.AdminGetPlatformUserDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/list", + Handler: admin_platform_user.AdminGetPlatformUserListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_platform_user.AdminUpdatePlatformUserHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/platform_user"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/create", + Handler: admin_product.AdminCreateProductHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_product.AdminDeleteProductHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_product.AdminGetProductDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/feature/list/:product_id", + Handler: admin_product.AdminGetProductFeatureListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/feature/update/:product_id", + Handler: admin_product.AdminUpdateProductFeaturesHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/list", + Handler: admin_product.AdminGetProductListHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_product.AdminUpdateProductHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/product"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 创建推广链接 + Method: http.MethodPost, + Path: "/create", + Handler: admin_promotion.CreatePromotionLinkHandler(serverCtx), + }, + { + // 删除推广链接 + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_promotion.DeletePromotionLinkHandler(serverCtx), + }, + { + // 获取推广链接详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_promotion.GetPromotionLinkDetailHandler(serverCtx), + }, + { + // 获取推广链接列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_promotion.GetPromotionLinkListHandler(serverCtx), + }, + { + // 更新推广链接 + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_promotion.UpdatePromotionLinkHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/promotion/link"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 记录链接点击 + Method: http.MethodGet, + Path: "/record/:path", + Handler: admin_promotion.RecordLinkClickHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/promotion/link"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 获取推广历史记录 + Method: http.MethodGet, + Path: "/history", + Handler: admin_promotion.GetPromotionStatsHistoryHandler(serverCtx), + }, + { + // 获取推广总统计 + Method: http.MethodGet, + Path: "/total", + Handler: admin_promotion.GetPromotionStatsTotalHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/promotion/stats"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 更新清理配置 + Method: http.MethodPut, + Path: "/cleanup/config", + Handler: admin_query.AdminUpdateQueryCleanupConfigHandler(serverCtx), + }, + { + // 获取清理配置列表 + Method: http.MethodGet, + Path: "/cleanup/configs", + Handler: admin_query.AdminGetQueryCleanupConfigListHandler(serverCtx), + }, + { + // 获取清理详情列表 + Method: http.MethodGet, + Path: "/cleanup/details/:log_id", + Handler: admin_query.AdminGetQueryCleanupDetailListHandler(serverCtx), + }, + { + // 获取清理日志列表 + Method: http.MethodGet, + Path: "/cleanup/logs", + Handler: admin_query.AdminGetQueryCleanupLogListHandler(serverCtx), + }, + { + // 获取查询详情 + Method: http.MethodGet, + Path: "/detail/:order_id", + Handler: admin_query.AdminGetQueryDetailByOrderIdHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/query"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{}..., + ), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 创建角色 + Method: http.MethodPost, + Path: "/create", + Handler: admin_role.CreateRoleHandler(serverCtx), + }, + { + // 删除角色 + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_role.DeleteRoleHandler(serverCtx), + }, + { + // 获取角色详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_role.GetRoleDetailHandler(serverCtx), + }, + { + // 获取角色列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_role.GetRoleListHandler(serverCtx), + }, + { + // 更新角色 + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_role.UpdateRoleHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/role"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/admin/api/all", + Handler: admin_role_api.AdminGetAllApiListHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/admin/role/:role_id/api/list", + Handler: admin_role_api.AdminGetRoleApiListHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/admin/role/api/assign", + Handler: admin_role_api.AdminAssignRoleApiHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/admin/role/api/remove", + Handler: admin_role_api.AdminRemoveRoleApiHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/admin/role/api/update", + Handler: admin_role_api.AdminUpdateRoleApiHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AdminAuthInterceptor}, + []rest.Route{ + { + // 创建用户 + Method: http.MethodPost, + Path: "/create", + Handler: admin_user.AdminCreateUserHandler(serverCtx), + }, + { + // 删除用户 + Method: http.MethodDelete, + Path: "/delete/:id", + Handler: admin_user.AdminDeleteUserHandler(serverCtx), + }, + { + // 获取用户详情 + Method: http.MethodGet, + Path: "/detail/:id", + Handler: admin_user.AdminGetUserDetailHandler(serverCtx), + }, + { + // 用户信息 + Method: http.MethodGet, + Path: "/info", + Handler: admin_user.AdminUserInfoHandler(serverCtx), + }, + { + // 获取用户列表 + Method: http.MethodGet, + Path: "/list", + Handler: admin_user.AdminGetUserListHandler(serverCtx), + }, + { + // 重置管理员密码 + Method: http.MethodPut, + Path: "/reset-password/:id", + Handler: admin_user.AdminResetPasswordHandler(serverCtx), + }, + { + // 更新用户 + Method: http.MethodPut, + Path: "/update/:id", + Handler: admin_user.AdminUpdateUserHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/admin/user"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/membership/info", + Handler: agent.GetMembershipInfoHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/promotion/qrcode", + Handler: agent.GetAgentPromotionQrcodeHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/info", + Handler: agent.GetAgentInfoHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/revenue", + Handler: agent.GetAgentRevenueInfoHandler(serverCtx), + }, + }, + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/audit/status", + Handler: agent.GetAgentAuditStatusHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/generating_link", + Handler: agent.GeneratingLinkHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/product_config", + Handler: agent.GetAgentProductConfigHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/real_name", + Handler: agent.AgentRealNameHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/subordinate/contribution/detail", + Handler: agent.GetAgentSubordinateContributionDetailHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/subordinate/list", + Handler: agent.GetAgentSubordinateListHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/membership/save_user_config", + Handler: agent.SaveAgentMembershipUserConfigHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/membership/user_config", + Handler: agent.GetAgentMembershipProductConfigHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/commission", + Handler: agent.GetAgentCommissionHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/membership/activate", + Handler: agent.ActivateAgentMembershipHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/rewards", + Handler: agent.GetAgentRewardsHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/withdrawal", + Handler: agent.GetAgentWithdrawalHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/withdrawal", + Handler: agent.AgentWithdrawalHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/withdrawal/bank-card", + Handler: agent.BankCardWithdrawalHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/withdrawal/bank-card/info", + Handler: agent.GetBankCardInfoHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/withdrawal/tax/exemption", + Handler: agent.GetAgentWithdrawalTaxExemptionHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/apply", + Handler: agent.ApplyForAgentHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/link", + Handler: agent.GetLinkDataHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/agent"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/app/version", + Handler: app.GetAppVersionHandler(serverCtx), + }, + { + // 心跳检测接口 + Method: http.MethodGet, + Path: "/health/check", + Handler: app.HealthCheckHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // get mobile verify code + Method: http.MethodPost, + Path: "/auth/sendSms", + Handler: auth.SendSmsHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/authorization/document/:documentId", + Handler: authorization.GetAuthorizationDocumentHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/authorization/document/order/:orderId", + Handler: authorization.GetAuthorizationDocumentByOrderHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/authorization/download/:documentId", + Handler: authorization.DownloadAuthorizationDocumentHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/authorization/download/file/:fileName", + Handler: authorization.DownloadAuthorizationDocumentByNameHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // get notifications + Method: http.MethodGet, + Path: "/notification/list", + Handler: notification.GetNotificationsHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodPost, + Path: "/pay/alipay/callback", + Handler: pay.AlipayCallbackHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/pay/wechat/callback", + Handler: pay.WechatPayCallbackHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/pay/wechat/refund_callback", + Handler: pay.WechatPayRefundCallbackHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/pay/check", + Handler: pay.PaymentCheckHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/pay/iap_callback", + Handler: pay.IapCallbackHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/pay/payment", + Handler: pay.PaymentHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/:id", + Handler: product.GetProductByIDHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/en/:product_en", + Handler: product.GetProductByEnHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/product"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/app_en/:product_en", + Handler: product.GetProductAppByEnHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/product"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + // query service agent + Method: http.MethodPost, + Path: "/query/service_agent/:product", + Handler: query.QueryServiceAgentHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/query/service_app/:product", + Handler: query.QueryServiceAppHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + // query service + Method: http.MethodPost, + Path: "/query/service/:product", + Handler: query.QueryServiceHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.UserAuthInterceptor}, + []rest.Route{ + { + // 生成分享链接 + Method: http.MethodPost, + Path: "/query/generate_share_link", + Handler: query.QueryGenerateShareLinkHandler(serverCtx), + }, + { + // 查询列表 + Method: http.MethodGet, + Path: "/query/list", + Handler: query.QueryListHandler(serverCtx), + }, + { + // 查询详情 按订单号 付款查询时 + Method: http.MethodGet, + Path: "/query/orderId/:order_id", + Handler: query.QueryDetailByOrderIdHandler(serverCtx), + }, + { + // 查询详情 按订单号 + Method: http.MethodGet, + Path: "/query/orderNo/:order_no", + Handler: query.QueryDetailByOrderNoHandler(serverCtx), + }, + { + // 获取查询临时订单 + Method: http.MethodGet, + Path: "/query/provisional_order/:id", + Handler: query.QueryProvisionalOrderHandler(serverCtx), + }, + { + // 重试查询 + Method: http.MethodPost, + Path: "/query/retry/:id", + Handler: query.QueryRetryHandler(serverCtx), + }, + { + // 更新查询数据 + Method: http.MethodPost, + Path: "/query/update_data", + Handler: query.UpdateQueryDataHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 查询示例 + Method: http.MethodGet, + Path: "/query/example", + Handler: query.QueryExampleHandler(serverCtx), + }, + { + // 查询详情 + Method: http.MethodGet, + Path: "/query/share/:id", + Handler: query.QueryShareDetailHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/query/single/test", + Handler: query.QuerySingleTestHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // mobile code login + Method: http.MethodPost, + Path: "/user/mobileCodeLogin", + Handler: user.MobileCodeLoginHandler(serverCtx), + }, + { + // wechat mini auth + Method: http.MethodPost, + Path: "/user/wxMiniAuth", + Handler: user.WxMiniAuthHandler(serverCtx), + }, + { + // wechat h5 auth + Method: http.MethodPost, + Path: "/user/wxh5Auth", + Handler: user.WxH5AuthHandler(serverCtx), + }, + { + Method: http.MethodPost, + Path: "/wechat/getSignature", + Handler: user.GetSignatureHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + // 绑定手机号 + Method: http.MethodPost, + Path: "/user/bindMobile", + Handler: user.BindMobileHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodPost, + Path: "/user/cancelOut", + Handler: user.CancelOutHandler(serverCtx), + }, + { + // get user info + Method: http.MethodGet, + Path: "/user/detail", + Handler: user.DetailHandler(serverCtx), + }, + { + // get new token + Method: http.MethodPost, + Path: "/user/getToken", + Handler: user.GetTokenHandler(serverCtx), + }, + }, + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1"), + ) +} diff --git a/app/main/api/internal/handler/user/bindmobilehandler.go b/app/main/api/internal/handler/user/bindmobilehandler.go new file mode 100644 index 0000000..157d784 --- /dev/null +++ b/app/main/api/internal/handler/user/bindmobilehandler.go @@ -0,0 +1,30 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func BindMobileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.BindMobileReq + 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 := user.NewBindMobileLogic(r.Context(), svcCtx) + resp, err := l.BindMobile(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/cancelouthandler.go b/app/main/api/internal/handler/user/cancelouthandler.go new file mode 100644 index 0000000..3ebeab2 --- /dev/null +++ b/app/main/api/internal/handler/user/cancelouthandler.go @@ -0,0 +1,17 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func CancelOutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := user.NewCancelOutLogic(r.Context(), svcCtx) + err := l.CancelOut() + result.HttpResult(r, w, nil, err) + } +} diff --git a/app/main/api/internal/handler/user/detailhandler.go b/app/main/api/internal/handler/user/detailhandler.go new file mode 100644 index 0000000..777cb19 --- /dev/null +++ b/app/main/api/internal/handler/user/detailhandler.go @@ -0,0 +1,17 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func DetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := user.NewDetailLogic(r.Context(), svcCtx) + resp, err := l.Detail() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/getsignaturehandler.go b/app/main/api/internal/handler/user/getsignaturehandler.go new file mode 100644 index 0000000..34dbfd7 --- /dev/null +++ b/app/main/api/internal/handler/user/getsignaturehandler.go @@ -0,0 +1,29 @@ +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" +) + +func GetSignatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetSignatureReq + 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 := user.NewGetSignatureLogic(r.Context(), svcCtx) + resp, err := l.GetSignature(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/gettokenhandler.go b/app/main/api/internal/handler/user/gettokenhandler.go new file mode 100644 index 0000000..675971a --- /dev/null +++ b/app/main/api/internal/handler/user/gettokenhandler.go @@ -0,0 +1,17 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/common/result" +) + +func GetTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := user.NewGetTokenLogic(r.Context(), svcCtx) + resp, err := l.GetToken() + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/mobilecodeloginhandler.go b/app/main/api/internal/handler/user/mobilecodeloginhandler.go new file mode 100644 index 0000000..535ada8 --- /dev/null +++ b/app/main/api/internal/handler/user/mobilecodeloginhandler.go @@ -0,0 +1,30 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func MobileCodeLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.MobileCodeLoginReq + 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 := user.NewMobileCodeLoginLogic(r.Context(), svcCtx) + resp, err := l.MobileCodeLogin(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/wxh5authhandler.go b/app/main/api/internal/handler/user/wxh5authhandler.go new file mode 100644 index 0000000..c26f507 --- /dev/null +++ b/app/main/api/internal/handler/user/wxh5authhandler.go @@ -0,0 +1,30 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func WxH5AuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.WXH5AuthReq + 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 := user.NewWxH5AuthLogic(r.Context(), svcCtx) + resp, err := l.WxH5Auth(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/user/wxminiauthhandler.go b/app/main/api/internal/handler/user/wxminiauthhandler.go new file mode 100644 index 0000000..1d9967b --- /dev/null +++ b/app/main/api/internal/handler/user/wxminiauthhandler.go @@ -0,0 +1,30 @@ +package user + +import ( + "net/http" + + "bdrp-server/app/main/api/internal/logic/user" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func WxMiniAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.WXMiniAuthReq + 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 := user.NewWxMiniAuthLogic(r.Context(), svcCtx) + resp, err := l.WxMiniAuth(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/logic/admin_agent/adminbatchunfreezeagentcommissionlogic.go b/app/main/api/internal/logic/admin_agent/adminbatchunfreezeagentcommissionlogic.go new file mode 100644 index 0000000..82d5cb1 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminbatchunfreezeagentcommissionlogic.go @@ -0,0 +1,149 @@ +package admin_agent + +import ( + "context" + "errors" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminBatchUnfreezeAgentCommissionLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminBatchUnfreezeAgentCommissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminBatchUnfreezeAgentCommissionLogic { + return &AdminBatchUnfreezeAgentCommissionLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminBatchUnfreezeAgentCommissionLogic) AdminBatchUnfreezeAgentCommission(req *types.AdminBatchUnfreezeAgentCommissionReq) (resp *types.AdminBatchUnfreezeAgentCommissionResp, err error) { + // 构建查询条件:状态为1(冻结中) + builder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{"status": 1}) + + // 如果指定了代理商ID,则只查询该代理商的冻结佣金 + if req.AgentId != nil && *req.AgentId > 0 { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + + // 查询所有冻结中的佣金记录 + commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, err + } + + // 如果没有冻结的佣金,直接返回 + if len(commissions) == 0 { + resp = &types.AdminBatchUnfreezeAgentCommissionResp{ + Success: true, + Count: 0, + Amount: 0, + } + return + } + + // 计算总金额 + var totalAmount float64 + for _, commission := range commissions { + totalAmount += commission.Amount + } + + // 开始事务 + err = l.svcCtx.AgentCommissionModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 按代理商分组更新钱包余额 + agentWalletMap := make(map[int64]*model.AgentWallet) + + // 遍历所有冻结的佣金,更新状态 + for _, commission := range commissions { + // 更新佣金状态为已结算 + commission.Status = 0 + err := l.svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, commission) + if err != nil { + // 如果是版本冲突错误,重新查询最新的数据后重试 + if errors.Is(err, model.ErrNoRowsUpdate) { + latestCommission, findErr := l.svcCtx.AgentCommissionModel.FindOne(ctx, commission.Id) + if findErr != nil { + return findErr + } + // 检查状态是否已被其他操作修改 + if latestCommission.Status != 1 { + return xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, fmt.Sprintf("佣金 %d 的状态已被其他操作修改,当前状态: %d", commission.Id, latestCommission.Status)) + } + // 重新更新状态 + latestCommission.Status = 0 + updateErr := l.svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, latestCommission) + if updateErr != nil { + return updateErr + } + // 更新引用,使用最新的数据 + commission.Version = latestCommission.Version + } else { + return err + } + } + + // 累加到对应代理商的钱包数据 + if wallet, exists := agentWalletMap[commission.AgentId]; exists { + wallet.Balance += commission.Amount + wallet.FrozenBalance -= commission.Amount + } else { + // 查询该代理商的钱包 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId) + if err != nil { + return err + } + wallet.Balance += commission.Amount + wallet.FrozenBalance -= commission.Amount + agentWalletMap[commission.AgentId] = wallet + } + } + + // 更新所有受影响代理商的钱包 + for _, wallet := range agentWalletMap { + err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) + if err != nil { + // 如果是版本冲突错误,重新查询最新的数据后重试 + if errors.Is(err, model.ErrNoRowsUpdate) { + latestWallet, findErr := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, wallet.AgentId) + if findErr != nil { + return findErr + } + // 重新累加金额 + latestWallet.Balance = wallet.Balance + latestWallet.FrozenBalance = wallet.FrozenBalance + updateErr := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, latestWallet) + if updateErr != nil { + return updateErr + } + } else { + return err + } + } + } + + return nil + }) + + if err != nil { + return nil, xerr.NewErrMsg("批量解冻失败: " + err.Error()) + } + + resp = &types.AdminBatchUnfreezeAgentCommissionResp{ + Success: true, + Count: int64(len(commissions)), + Amount: totalAmount, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentcommissiondeductionlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentcommissiondeductionlistlogic.go new file mode 100644 index 0000000..ae2802f --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentcommissiondeductionlistlogic.go @@ -0,0 +1,84 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentCommissionDeductionListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentCommissionDeductionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentCommissionDeductionListLogic { + return &AdminGetAgentCommissionDeductionListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentCommissionDeductionListLogic) AdminGetAgentCommissionDeductionList(req *types.AdminGetAgentCommissionDeductionListReq) (resp *types.AdminGetAgentCommissionDeductionListResp, err error) { + builder := l.svcCtx.AgentCommissionDeductionModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.Type != nil && *req.Type != "" { + builder = builder.Where(squirrel.Eq{"type": *req.Type}) + } + if req.Status != nil { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } + // 产品名筛选需先查product_id + if req.ProductName != nil && *req.ProductName != "" { + products, err := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"product_name": *req.ProductName}), "") + if err != nil || len(products) == 0 { + return &types.AdminGetAgentCommissionDeductionListResp{Total: 0, Items: []types.AgentCommissionDeductionListItem{}}, nil + } + builder = builder.Where("product_id = ?", products[0].Id) + } + + list, total, err := l.svcCtx.AgentCommissionDeductionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + + // 批量查product_id->name + productIds := make(map[int64]struct{}) + for _, v := range list { + productIds[v.ProductId] = struct{}{} + } + productIdArr := make([]int64, 0, len(productIds)) + for id := range productIds { + productIdArr = append(productIdArr, id) + } + productNameMap := make(map[int64]string) + if len(productIdArr) > 0 { + build := l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": productIdArr}) + products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, build, "") + for _, p := range products { + productNameMap[p.Id] = p.ProductName + } + } + + items := make([]types.AgentCommissionDeductionListItem, 0, len(list)) + for _, v := range list { + item := types.AgentCommissionDeductionListItem{} + _ = copier.Copy(&item, v) + item.ProductName = productNameMap[v.ProductId] + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentCommissionDeductionListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentcommissionlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentcommissionlistlogic.go new file mode 100644 index 0000000..f1251f3 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentcommissionlistlogic.go @@ -0,0 +1,105 @@ +package admin_agent + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentCommissionListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentCommissionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentCommissionListLogic { + return &AdminGetAgentCommissionListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentCommissionListLogic) AdminGetAgentCommissionList(req *types.AdminGetAgentCommissionListReq) (resp *types.AdminGetAgentCommissionListResp, err error) { + builder := l.svcCtx.AgentCommissionModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.OrderId != nil { + builder = builder.Where(squirrel.Eq{"order_id": *req.OrderId}) + } + if req.Status != nil { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } + + // 时间范围筛选 + if req.CreateTimeStart != nil && *req.CreateTimeStart != "" { + startTime, err := time.Parse("2006-01-02 15:04:05", *req.CreateTimeStart) + if err == nil { + builder = builder.Where(squirrel.GtOrEq{"create_time": startTime}) + } + } + if req.CreateTimeEnd != nil && *req.CreateTimeEnd != "" { + endTime, err := time.Parse("2006-01-02 15:04:05", *req.CreateTimeEnd) + if err == nil { + builder = builder.Where(squirrel.LtOrEq{"create_time": endTime}) + } + } + + // 先查出所有product_id对应的product_name(如有product_name筛选,需反查id) + if req.ProductName != nil && *req.ProductName != "" { + // 支持模糊匹配产品名称 + products, err := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Like{"product_name": "%" + *req.ProductName + "%"}), "") + if err != nil || len(products) == 0 { + return &types.AdminGetAgentCommissionListResp{Total: 0, Items: []types.AgentCommissionListItem{}}, nil + } + productIds := make([]int64, 0, len(products)) + for _, p := range products { + productIds = append(productIds, p.Id) + } + builder = builder.Where(squirrel.Eq{"product_id": productIds}) + } + + list, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + + // 批量查product_name + productIds := make(map[int64]struct{}) + for _, v := range list { + productIds[v.ProductId] = struct{}{} + } + productNameMap := make(map[int64]string) + if len(productIds) > 0 { + ids := make([]int64, 0, len(productIds)) + for id := range productIds { + ids = append(ids, id) + } + builder := l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": ids}) + products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, builder, "") + for _, p := range products { + productNameMap[p.Id] = p.ProductName + } + } + + items := make([]types.AgentCommissionListItem, 0, len(list)) + for _, v := range list { + item := types.AgentCommissionListItem{} + _ = copier.Copy(&item, v) + item.ProductName = productNameMap[v.ProductId] + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentCommissionListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentlinklistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentlinklistlogic.go new file mode 100644 index 0000000..78274c2 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentlinklistlogic.go @@ -0,0 +1,86 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentLinkListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentLinkListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentLinkListLogic { + return &AdminGetAgentLinkListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentLinkListLogic) AdminGetAgentLinkList(req *types.AdminGetAgentLinkListReq) (resp *types.AdminGetAgentLinkListResp, err error) { + builder := l.svcCtx.AgentLinkModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where("agent_id = ?", *req.AgentId) + } + if req.LinkIdentifier != nil && *req.LinkIdentifier != "" { + builder = builder.Where("link_identifier = ?", *req.LinkIdentifier) + } + + // 先查出所有product_id对应的product_name(如有product_name筛选,需反查id) + var productIdFilter int64 + if req.ProductName != nil && *req.ProductName != "" { + // 只支持精确匹配,如需模糊可扩展 + products, err := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"product_name": *req.ProductName}), "") + if err != nil || len(products) == 0 { + return &types.AdminGetAgentLinkListResp{Total: 0, Items: []types.AgentLinkListItem{}}, nil + } + productIdFilter = products[0].Id + builder = builder.Where("product_id = ?", productIdFilter) + } + + links, total, err := l.svcCtx.AgentLinkModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, err + } + + // 批量查product_id->name,避免N+1 + productIdSet := make(map[int64]struct{}) + for _, link := range links { + productIdSet[link.ProductId] = struct{}{} + } + productIdList := make([]int64, 0, len(productIdSet)) + for id := range productIdSet { + productIdList = append(productIdList, id) + } + productNameMap := make(map[int64]string) + if len(productIdList) > 0 { + products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": productIdList}), "") + for _, p := range products { + productNameMap[p.Id] = p.ProductName + } + } + + items := make([]types.AgentLinkListItem, 0, len(links)) + for _, link := range links { + items = append(items, types.AgentLinkListItem{ + AgentId: link.AgentId, + ProductName: productNameMap[link.ProductId], + Price: link.Price, + LinkIdentifier: link.LinkIdentifier, + CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"), + }) + } + + resp = &types.AdminGetAgentLinkListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentlinkproductstatisticslogic.go b/app/main/api/internal/logic/admin_agent/admingetagentlinkproductstatisticslogic.go new file mode 100644 index 0000000..0c76898 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentlinkproductstatisticslogic.go @@ -0,0 +1,100 @@ +package admin_agent + +import ( + "context" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentLinkProductStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentLinkProductStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentLinkProductStatisticsLogic { + return &AdminGetAgentLinkProductStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentLinkProductStatisticsLogic) AdminGetAgentLinkProductStatistics(req *types.AdminGetAgentLinkProductStatisticsReq) (resp *types.AdminGetAgentLinkProductStatisticsResp, err error) { + // 构建查询 + query := squirrel.Select( + "p.product_name", + "COUNT(al.id) as link_count", + ). + From("agent_link al"). + Join("product p ON al.product_id = p.id"). + Where(squirrel.Eq{"al.del_state": 0}). + Where(squirrel.Eq{"p.del_state": 0}). + GroupBy("p.product_name"). + OrderBy("link_count DESC") + + // 执行查询 + sql, args, err := query.ToSql() + if err != nil { + return nil, err + } + + type Result struct { + ProductName string `db:"product_name"` + LinkCount int64 `db:"link_count"` + } + + var results []Result + // 使用模型的方法执行查询 + // 通过反射获取底层的QueryRowsNoCacheCtx方法,避免直接类型断言 + if agentLinkModel, ok := l.svcCtx.AgentLinkModel.(interface { + QueryRowsNoCacheCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error + }); ok { + err = agentLinkModel.QueryRowsNoCacheCtx(l.ctx, &results, sql, args...) + } else { + // 如果无法使用模型的方法,则使用原始的连接方式(安全地获取连接) + if cachedConn, ok := l.svcCtx.AgentLinkModel.(interface { + GetConn() interface{} + }); ok { + conn := cachedConn.GetConn() + if sqlxConn, ok := conn.(interface { + QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error + }); ok { + err = sqlxConn.QueryRowsCtx(l.ctx, &results, sql, args...) + } else { + return nil, fmt.Errorf("无法获取数据库连接") + } + } else { + return nil, fmt.Errorf("无法获取数据库连接") + } + } + + if err != nil { + return nil, err + } + + // 处理空结果 + if len(results) == 0 { + return &types.AdminGetAgentLinkProductStatisticsResp{ + Items: []types.AgentLinkProductStatisticsItem{}, + }, nil + } + + // 转换为返回结果 + items := make([]types.AgentLinkProductStatisticsItem, 0, len(results)) + for _, r := range results { + items = append(items, types.AgentLinkProductStatisticsItem{ + ProductName: r.ProductName, + LinkCount: r.LinkCount, + }) + } + + return &types.AdminGetAgentLinkProductStatisticsResp{ + Items: items, + }, nil +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentlistlogic.go new file mode 100644 index 0000000..72ef216 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentlistlogic.go @@ -0,0 +1,122 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/tool" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentListLogic { + return &AdminGetAgentListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentListLogic) AdminGetAgentList(req *types.AdminGetAgentListReq) (resp *types.AdminGetAgentListResp, err error) { + builder := l.svcCtx.AgentModel.SelectBuilder() + if req.Mobile != nil && *req.Mobile != "" { + builder = builder.Where("mobile = ?", *req.Mobile) + } + if req.Region != nil && *req.Region != "" { + builder = builder.Where("region = ?", *req.Region) + } + + // 新增:如果传入ParentAgentId,则查找其所有1级下级代理 + if req.ParentAgentId != nil { + closureBuilder := l.svcCtx.AgentClosureModel.SelectBuilder().Where("ancestor_id = ? AND depth = 1", *req.ParentAgentId) + closures, cerr := l.svcCtx.AgentClosureModel.FindAll(l.ctx, closureBuilder, "") + if cerr != nil { + return nil, cerr + } + if len(closures) == 0 { + resp = &types.AdminGetAgentListResp{Total: 0, Items: []types.AgentListItem{}} + return resp, nil + } + ids := make([]int64, 0, len(closures)) + for _, c := range closures { + ids = append(ids, c.DescendantId) + } + // 将int64切片转换为interface{}切片,以便squirrel正确处理IN查询 + interfaceIds := make([]interface{}, len(ids)) + for i, id := range ids { + interfaceIds[i] = id + } + // 使用项目中的InPlaceholders函数生成正确数量的占位符 + builder = builder.Where("id IN ("+tool.InPlaceholders(len(interfaceIds))+")", interfaceIds...) + } + + agents, total, err := l.svcCtx.AgentModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, err + } + + items := make([]types.AgentListItem, 0, len(agents)) + + for _, agent := range agents { + item := types.AgentListItem{ + Id: agent.Id, + UserId: agent.UserId, + LevelName: agent.LevelName, + Region: agent.Region, + CreateTime: agent.CreateTime.Format("2006-01-02 15:04:05"), + } + if req.ParentAgentId != nil { + item.ParentAgentId = *req.ParentAgentId + } + agent.Mobile, err = crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err) + } + item.Mobile = agent.Mobile + if agent.MembershipExpiryTime.Valid { + item.MembershipExpiryTime = agent.MembershipExpiryTime.Time.Format("2006-01-02 15:04:05") + } + + // 查询钱包信息 + wallet, _ := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agent.Id) + if wallet != nil { + item.Balance = wallet.Balance + item.TotalEarnings = wallet.TotalEarnings + item.FrozenBalance = wallet.FrozenBalance + item.WithdrawnAmount = wallet.WithdrawnAmount + } + + // 查询实名认证信息 + realNameInfo, _ := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id) + if realNameInfo != nil { + item.IsRealNameVerified = realNameInfo.Status == model.AgentRealNameStatusApproved + item.RealName = realNameInfo.Name + item.IdCard = realNameInfo.IdCard + item.RealNameStatus = realNameInfo.Status + } else { + item.IsRealNameVerified = false + item.RealName = "" + item.IdCard = "" + item.RealNameStatus = "" + } + + items = append(items, item) + } + + resp = &types.AdminGetAgentListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentmembershipconfiglistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentmembershipconfiglistlogic.go new file mode 100644 index 0000000..6853eaa --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentmembershipconfiglistlogic.go @@ -0,0 +1,51 @@ +package admin_agent + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentMembershipConfigListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentMembershipConfigListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentMembershipConfigListLogic { + return &AdminGetAgentMembershipConfigListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentMembershipConfigListLogic) AdminGetAgentMembershipConfigList(req *types.AdminGetAgentMembershipConfigListReq) (resp *types.AdminGetAgentMembershipConfigListResp, err error) { + builder := l.svcCtx.AgentMembershipConfigModel.SelectBuilder() + if req.LevelName != nil && *req.LevelName != "" { + builder = builder.Where(squirrel.Eq{"level_name": *req.LevelName}) + } + list, total, err := l.svcCtx.AgentMembershipConfigModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, err + } + items := make([]types.AgentMembershipConfigListItem, 0, len(list)) + for _, v := range list { + var item types.AgentMembershipConfigListItem + if err := copier.Copy(&item, v); err != nil { + l.Logger.Errorf("copy error: %v", err) + continue + } + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentMembershipConfigListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentmembershiprechargeorderlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentmembershiprechargeorderlistlogic.go new file mode 100644 index 0000000..aacd1cd --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentmembershiprechargeorderlistlogic.go @@ -0,0 +1,64 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentMembershipRechargeOrderListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentMembershipRechargeOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentMembershipRechargeOrderListLogic { + return &AdminGetAgentMembershipRechargeOrderListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentMembershipRechargeOrderListLogic) AdminGetAgentMembershipRechargeOrderList(req *types.AdminGetAgentMembershipRechargeOrderListReq) (resp *types.AdminGetAgentMembershipRechargeOrderListResp, err error) { + builder := l.svcCtx.AgentMembershipRechargeOrderModel.SelectBuilder() + if req.UserId != nil { + builder = builder.Where(squirrel.Eq{"user_id": *req.UserId}) + } + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.OrderNo != nil && *req.OrderNo != "" { + builder = builder.Where(squirrel.Eq{"order_no": *req.OrderNo}) + } + if req.PlatformOrderId != nil && *req.PlatformOrderId != "" { + builder = builder.Where(squirrel.Eq{"platform_order_id": *req.PlatformOrderId}) + } + if req.Status != nil && *req.Status != "" { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } + if req.PaymentMethod != nil && *req.PaymentMethod != "" { + builder = builder.Where(squirrel.Eq{"payment_method": *req.PaymentMethod}) + } + list, total, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + items := make([]types.AgentMembershipRechargeOrderListItem, 0, len(list)) + for _, v := range list { + item := types.AgentMembershipRechargeOrderListItem{} + _ = copier.Copy(&item, v) + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentMembershipRechargeOrderListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentorderstatisticslogic.go b/app/main/api/internal/logic/admin_agent/admingetagentorderstatisticslogic.go new file mode 100644 index 0000000..450284b --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentorderstatisticslogic.go @@ -0,0 +1,60 @@ +package admin_agent + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentOrderStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentOrderStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentOrderStatisticsLogic { + return &AdminGetAgentOrderStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentOrderStatisticsLogic) AdminGetAgentOrderStatistics(req *types.AdminGetAgentOrderStatisticsReq) (resp *types.AdminGetAgentOrderStatisticsResp, err error) { + // 获取今日的开始和结束时间 + today := time.Now() + startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + + // 构建查询条件 + builder := l.svcCtx.AgentOrderModel.SelectBuilder() + + // 查询总代理订单数 + totalBuilder := builder + totalAgentOrderCount, err := l.svcCtx.AgentOrderModel.FindCount(l.ctx, totalBuilder, "id") + if err != nil { + logx.Errorf("查询总代理订单数失败: %v", err) + return nil, fmt.Errorf("查询总代理订单数失败: %w", err) + } + + // 查询今日代理订单数 + todayBuilder := builder.Where("create_time >= ? AND create_time < ?", startOfDay, endOfDay) + todayAgentOrderCount, err := l.svcCtx.AgentOrderModel.FindCount(l.ctx, todayBuilder, "id") + if err != nil { + logx.Errorf("查询今日代理订单数失败: %v", err) + return nil, fmt.Errorf("查询今日代理订单数失败: %w", err) + } + + // 构建响应 + resp = &types.AdminGetAgentOrderStatisticsResp{ + TotalAgentOrderCount: totalAgentOrderCount, + TodayAgentOrderCount: todayAgentOrderCount, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentplatformdeductionlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentplatformdeductionlistlogic.go new file mode 100644 index 0000000..8664c73 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentplatformdeductionlistlogic.go @@ -0,0 +1,57 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentPlatformDeductionListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentPlatformDeductionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentPlatformDeductionListLogic { + return &AdminGetAgentPlatformDeductionListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentPlatformDeductionListLogic) AdminGetAgentPlatformDeductionList(req *types.AdminGetAgentPlatformDeductionListReq) (resp *types.AdminGetAgentPlatformDeductionListResp, err error) { + builder := l.svcCtx.AgentPlatformDeductionModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.Type != nil && *req.Type != "" { + builder = builder.Where(squirrel.Eq{"type": *req.Type}) + } + if req.Status != nil { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } + + list, total, err := l.svcCtx.AgentPlatformDeductionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + + items := make([]types.AgentPlatformDeductionListItem, 0, len(list)) + for _, v := range list { + item := types.AgentPlatformDeductionListItem{} + _ = copier.Copy(&item, v) + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentPlatformDeductionListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentproductionconfiglistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentproductionconfiglistlogic.go new file mode 100644 index 0000000..2fb0638 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentproductionconfiglistlogic.go @@ -0,0 +1,74 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentProductionConfigListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentProductionConfigListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentProductionConfigListLogic { + return &AdminGetAgentProductionConfigListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentProductionConfigListLogic) AdminGetAgentProductionConfigList(req *types.AdminGetAgentProductionConfigListReq) (resp *types.AdminGetAgentProductionConfigListResp, err error) { + builder := l.svcCtx.AgentProductConfigModel.SelectBuilder() + if req.ProductName != nil && *req.ProductName != "" { + products, err := l.svcCtx.ProductModel.FindAll(l.ctx, l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"product_name": *req.ProductName}), "") + if err != nil || len(products) == 0 { + return &types.AdminGetAgentProductionConfigListResp{Total: 0, Items: []types.AgentProductionConfigItem{}}, nil + } + builder = builder.Where(squirrel.Eq{"product_id": products[0].Id}) + } + if req.Id != nil { + builder = builder.Where(squirrel.Eq{"id": *req.Id}) + } + list, total, err := l.svcCtx.AgentProductConfigModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + // 查询所有涉及到的product_id对应的product_name + productIdSet := make(map[int64]struct{}) + for _, v := range list { + productIdSet[v.ProductId] = struct{}{} + } + productIdArr := make([]int64, 0, len(productIdSet)) + for id := range productIdSet { + productIdArr = append(productIdArr, id) + } + productNameMap := make(map[int64]string) + if len(productIdArr) > 0 { + build := l.svcCtx.ProductModel.SelectBuilder().Where(squirrel.Eq{"id": productIdArr}) + products, _ := l.svcCtx.ProductModel.FindAll(l.ctx, build, "") + for _, p := range products { + productNameMap[p.Id] = p.ProductName + } + } + items := make([]types.AgentProductionConfigItem, 0, len(list)) + for _, v := range list { + item := types.AgentProductionConfigItem{} + _ = copier.Copy(&item, v) + item.ProductName = productNameMap[v.ProductId] + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + items = append(items, item) + } + resp = &types.AdminGetAgentProductionConfigListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go new file mode 100644 index 0000000..3174b84 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentrewardlistlogic.go @@ -0,0 +1,58 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentRewardListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentRewardListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentRewardListLogic { + return &AdminGetAgentRewardListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentRewardListLogic) AdminGetAgentRewardList(req *types.AdminGetAgentRewardListReq) (resp *types.AdminGetAgentRewardListResp, err error) { + builder := l.svcCtx.AgentRewardsModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.RelationAgentId != nil { + builder = builder.Where(squirrel.Eq{"relation_agent_id": *req.RelationAgentId}) + } + if req.Type != nil && *req.Type != "" { + builder = builder.Where(squirrel.Eq{"type": *req.Type}) + } + list, total, err := l.svcCtx.AgentRewardsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + items := make([]types.AgentRewardListItem, 0, len(list)) + for _, v := range list { + item := types.AgentRewardListItem{} + _ = copier.Copy(&item, v) + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + if v.RelationAgentId.Valid { + item.RelationAgentId = v.RelationAgentId.Int64 + } + items = append(items, item) + } + resp = &types.AdminGetAgentRewardListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentstatisticslogic.go b/app/main/api/internal/logic/admin_agent/admingetagentstatisticslogic.go new file mode 100644 index 0000000..2203999 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentstatisticslogic.go @@ -0,0 +1,56 @@ +package admin_agent + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentStatisticsLogic { + return &AdminGetAgentStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentStatisticsLogic) AdminGetAgentStatistics(req *types.AdminGetAgentStatisticsReq) (resp *types.AdminGetAgentStatisticsResp, err error) { + // 使用AgentModel的SelectBuilder和FindCount方法获取总代理数 + totalBuilder := l.svcCtx.AgentModel.SelectBuilder() + totalAgentCount, err := l.svcCtx.AgentModel.FindCount(l.ctx, totalBuilder, "id") + if err != nil { + logx.Errorf("获取总代理数失败: %v", err) + return nil, fmt.Errorf("获取总代理数失败: %w", err) + } + + // 获取今日新增代理数 + todayBuilder := l.svcCtx.AgentModel.SelectBuilder() + today := time.Now() + startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + todayBuilder = todayBuilder.Where("create_time >= ? AND create_time < ?", startOfDay, endOfDay) + + todayAgentCount, err := l.svcCtx.AgentModel.FindCount(l.ctx, todayBuilder, "id") + if err != nil { + logx.Errorf("获取今日新增代理数失败: %v", err) + return nil, fmt.Errorf("获取今日新增代理数失败: %w", err) + } + + resp = &types.AdminGetAgentStatisticsResp{ + TotalAgentCount: totalAgentCount, + TodayAgentCount: todayAgentCount, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentwalletlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentwalletlogic.go new file mode 100644 index 0000000..5b3c203 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentwalletlogic.go @@ -0,0 +1,45 @@ +package admin_agent + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentWalletLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentWalletLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentWalletLogic { + return &AdminGetAgentWalletLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentWalletLogic) AdminGetAgentWallet(req *types.AdminGetAgentWalletReq) (resp *types.AdminGetAgentWalletResp, err error) { + // 查询代理钱包信息 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, req.AgentId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, xerr.NewErrMsg("代理钱包不存在") + } + return nil, err + } + + resp = &types.AdminGetAgentWalletResp{ + Balance: wallet.Balance, + FrozenBalance: wallet.FrozenBalance, + TotalEarnings: wallet.TotalEarnings, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentwallettransactionlistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentwallettransactionlistlogic.go new file mode 100644 index 0000000..c9e8d5c --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentwallettransactionlistlogic.go @@ -0,0 +1,84 @@ +package admin_agent + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentWalletTransactionListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentWalletTransactionListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentWalletTransactionListLogic { + return &AdminGetAgentWalletTransactionListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentWalletTransactionListLogic) AdminGetAgentWalletTransactionList(req *types.AdminGetAgentWalletTransactionListReq) (resp *types.AdminGetAgentWalletTransactionListResp, err error) { + builder := l.svcCtx.AgentWalletTransactionModel.SelectBuilder() + + // 必须传入代理ID + if req.AgentId == 0 { + return nil, errors.New("代理ID不能为空") + } + + builder = builder.Where(squirrel.Eq{"agent_id": req.AgentId}) + + // 可选条件 + if req.TransactionType != nil && *req.TransactionType != "" { + builder = builder.Where(squirrel.Eq{"transaction_type": *req.TransactionType}) + } + if req.CreateTimeStart != nil && *req.CreateTimeStart != "" { + builder = builder.Where(squirrel.GtOrEq{"create_time": *req.CreateTimeStart}) + } + if req.CreateTimeEnd != nil && *req.CreateTimeEnd != "" { + builder = builder.Where(squirrel.LtOrEq{"create_time": *req.CreateTimeEnd}) + } + + list, total, err := l.svcCtx.AgentWalletTransactionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + + items := make([]types.AgentWalletTransactionListItem, 0, len(list)) + for _, v := range list { + item := types.AgentWalletTransactionListItem{ + Id: v.Id, + AgentId: v.AgentId, + TransactionType: v.TransactionType, + Amount: v.Amount, + BalanceBefore: v.BalanceBefore, + BalanceAfter: v.BalanceAfter, + FrozenBalanceBefore: v.FrozenBalanceBefore, + FrozenBalanceAfter: v.FrozenBalanceAfter, + CreateTime: v.CreateTime.Format("2006-01-02 15:04:05"), + } + if v.TransactionId.Valid { + item.TransactionId = &v.TransactionId.String + } + if v.RelatedUserId.Valid { + item.RelatedUserId = &v.RelatedUserId.Int64 + } + if v.Remark.Valid { + item.Remark = &v.Remark.String + } + items = append(items, item) + } + + resp = &types.AdminGetAgentWalletTransactionListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go b/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go new file mode 100644 index 0000000..909315f --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetagentwithdrawallistlogic.go @@ -0,0 +1,75 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAgentWithdrawalListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAgentWithdrawalListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAgentWithdrawalListLogic { + return &AdminGetAgentWithdrawalListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *types.AdminGetAgentWithdrawalListReq) (resp *types.AdminGetAgentWithdrawalListResp, err error) { + builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder() + if req.AgentId != nil { + builder = builder.Where(squirrel.Eq{"agent_id": *req.AgentId}) + } + if req.Status != nil { + builder = builder.Where(squirrel.Eq{"status": *req.Status}) + } + if req.WithdrawNo != nil && *req.WithdrawNo != "" { + builder = builder.Where(squirrel.Eq{"withdraw_no": *req.WithdrawNo}) + } + if req.WithdrawType != nil { + builder = builder.Where(squirrel.Eq{"withdraw_type": *req.WithdrawType}) + } + list, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, err + } + items := make([]types.AgentWithdrawalListItem, 0, len(list)) + for _, v := range list { + item := types.AgentWithdrawalListItem{} + _ = copier.Copy(&item, v) + item.Remark = "" + if v.Remark.Valid { + item.Remark = v.Remark.String + } + item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05") + + // 手动设置银行卡信息(copier不会自动处理sql.NullString) + item.WithdrawType = v.WithdrawType + if v.BankCardNo.Valid { + item.BankCardNo = v.BankCardNo.String + } + if v.BankName.Valid { + item.BankName = v.BankName.String + } + if v.PayeeName.Valid { + item.PayeeName = v.PayeeName.String + } + + items = append(items, item) + } + resp = &types.AdminGetAgentWithdrawalListResp{ + Total: total, + Items: items, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go b/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go new file mode 100644 index 0000000..ec4c6fb --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetsystemconfiglogic.go @@ -0,0 +1,31 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetSystemConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetSystemConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetSystemConfigLogic { + return &AdminGetSystemConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetSystemConfigLogic) AdminGetSystemConfig() (resp *types.AdminGetSystemConfigResp, err error) { + resp = &types.AdminGetSystemConfigResp{ + CommissionSafeMode: l.svcCtx.Config.SystemConfig.CommissionSafeMode, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/admingetwithdrawalstatisticslogic.go b/app/main/api/internal/logic/admin_agent/admingetwithdrawalstatisticslogic.go new file mode 100644 index 0000000..99e7c8e --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/admingetwithdrawalstatisticslogic.go @@ -0,0 +1,76 @@ +package admin_agent + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetWithdrawalStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetWithdrawalStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetWithdrawalStatisticsLogic { + return &AdminGetWithdrawalStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetWithdrawalStatisticsLogic) AdminGetWithdrawalStatistics(req *types.AdminGetWithdrawalStatisticsReq) (resp *types.AdminGetWithdrawalStatisticsResp, err error) { + // 获取今日的开始和结束时间 + today := time.Now() + startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + + // 构建查询条件 + builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder() + + // 查询总提现金额(status=2表示成功) + totalBuilder := builder.Where("status = ?", 2) + totalWithdrawalAmount, err := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, totalBuilder, "amount") + if err != nil { + logx.Errorf("查询总提现金额失败: %v", err) + return nil, fmt.Errorf("查询总提现金额失败: %w", err) + } + + // 查询今日提现金额(status=2表示成功) + todayBuilder := builder.Where("status = ? AND create_time >= ? AND create_time < ?", 2, startOfDay, endOfDay) + todayWithdrawalAmount, err := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, todayBuilder, "amount") + if err != nil { + logx.Errorf("查询今日提现金额失败: %v", err) + return nil, fmt.Errorf("查询今日提现金额失败: %w", err) + } + + // 查询总实际到账金额(status=2表示成功) + totalActualAmount, err := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, totalBuilder, "actual_amount") + if err != nil { + logx.Errorf("查询总实际到账金额失败: %v", err) + return nil, fmt.Errorf("查询总实际到账金额失败: %w", err) + } + + // 查询总扣税金额(status=2表示成功) + totalTaxAmount, err := l.svcCtx.AgentWithdrawalModel.FindSum(l.ctx, totalBuilder, "tax_amount") + if err != nil { + logx.Errorf("查询总扣税金额失败: %v", err) + return nil, fmt.Errorf("查询总扣税金额失败: %w", err) + } + + // 构建响应 + resp = &types.AdminGetWithdrawalStatisticsResp{ + TotalWithdrawalAmount: totalWithdrawalAmount, + TodayWithdrawalAmount: todayWithdrawalAmount, + TotalActualAmount: totalActualAmount, + TotalTaxAmount: totalTaxAmount, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_agent/adminreviewbankcardwithdrawallogic.go b/app/main/api/internal/logic/admin_agent/adminreviewbankcardwithdrawallogic.go new file mode 100644 index 0000000..cd2b680 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminreviewbankcardwithdrawallogic.go @@ -0,0 +1,447 @@ +package admin_agent + +import ( + "context" + "database/sql" + "time" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +// 审核操作常量 +const ( + ReviewActionApprove = 1 // 确认 + ReviewActionReject = 2 // 拒绝 +) + +// 状态常量 +const ( + StatusPending = 1 // 申请中/处理中 + StatusSuccess = 2 // 成功 + StatusFailed = 3 // 失败 +) + +// 提现类型常量 +const ( + WithdrawTypeAlipay = 1 // 支付宝提现 + WithdrawTypeBankCard = 2 // 银行卡提现 +) + +type AdminReviewBankCardWithdrawalLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminReviewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminReviewBankCardWithdrawalLogic { + return &AdminReviewBankCardWithdrawalLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminReviewBankCardWithdrawalLogic) AdminReviewBankCardWithdrawal(req *types.AdminReviewBankCardWithdrawalReq) (resp *types.AdminReviewBankCardWithdrawalResp, err error) { + // 验证操作类型 + if req.Action != ReviewActionApprove && req.Action != ReviewActionReject { + return nil, errors.Wrapf(xerr.NewErrMsg("操作类型不正确"), "操作类型验证失败") + } + + // 拒绝操作必须填写备注 + if req.Action == ReviewActionReject && req.Remark == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("拒绝提现必须填写拒绝原因"), "拒绝原因验证失败") + } + + resp = &types.AdminReviewBankCardWithdrawalResp{ + Success: false, + } + + // 使用事务处理审核操作 + err = l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 获取提现记录 + record, err := l.svcCtx.AgentWithdrawalModel.FindOne(ctx, req.WithdrawalId) + 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 record.Status != StatusPending { + return errors.Wrapf(xerr.NewErrMsg("该提现记录已处理,无法重复操作"), "状态验证失败") + } + + // 验证提现类型(支持银行卡和支付宝提现) + if record.WithdrawType != WithdrawTypeBankCard && record.WithdrawType != WithdrawTypeAlipay { + return errors.Wrapf(xerr.NewErrMsg("提现类型不正确"), "提现类型验证失败") + } + + if req.Action == ReviewActionApprove { + // 确认提现 + return l.approveWithdrawal(ctx, session, record) + } else { + // 拒绝提现 + return l.rejectWithdrawal(ctx, session, record, req.Remark) + } + }) + + if err != nil { + return nil, err + } + + resp.Success = true + return resp, nil +} + +// 确认提现 +func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { + // 根据提现类型执行不同的操作 + if record.WithdrawType == WithdrawTypeAlipay { + // 支付宝提现:先调用支付宝转账接口 + return l.approveAlipayWithdrawal(ctx, session, record) + } else { + // 银行卡提现:直接更新状态为成功(线下转账) + return l.approveBankCardWithdrawal(ctx, session, record) + } +} + +// 确认支付宝提现 +func (l *AdminReviewBankCardWithdrawalLogic) approveAlipayWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { + // 同步调用支付宝转账 + transferResp, err := l.svcCtx.AlipayService.AliTransfer(ctx, record.PayeeAccount, record.PayeeName.String, record.ActualAmount, "公司提现", record.WithdrawNo) + if err != nil { + l.Logger.Errorf("【支付宝转账失败】withdrawNo:%s error:%v", record.WithdrawNo, err) + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "支付宝接口调用失败: %v", err) + } + + switch { + case transferResp.Status == "SUCCESS": + // 立即处理成功状态 + return l.completeWithdrawalSuccess(ctx, session, record) + case transferResp.Status == "FAIL" || transferResp.SubCode != "": + // 处理明确失败 + errorMsg := l.mapAlipayError(transferResp.SubCode) + return l.completeWithdrawalFailure(ctx, session, record, errorMsg) + case transferResp.Status == "DEALING": + // 处理中状态,更新为处理中但不标记为最终状态 + record.Remark = sql.NullString{String: "支付宝转账处理中", Valid: true} + if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err) + } + l.Logger.Infof("支付宝提现审核通过,转账处理中 withdrawalId:%d withdrawNo:%s", record.Id, record.WithdrawNo) + return nil + default: + // 未知状态按失败处理 + return l.completeWithdrawalFailure(ctx, session, record, "支付宝返回未知状态") + } +} + +// 确认银行卡提现 +func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { + // 更新提现记录状态为成功 + record.Status = StatusSuccess + record.Remark = sql.NullString{String: "管理员确认提现", Valid: true} + if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err) + } + + // 解冻资金并扣除(FrozenBalance -= amount, Balance不变) + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err) + } + + // 记录变动前的冻结余额 + frozenBalanceBefore := wallet.FrozenBalance + + // 更新钱包(减少冻结余额) + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err) + } + + // 记录交易流水(提现成功) + err = l.svcCtx.AgentService.CreateWalletTransaction( + ctx, + session, + wallet.AgentId, + model.WalletTransactionTypeWithdraw, + -record.Amount, // 变动金额(负数表示减少) + wallet.Balance, // 变动前余额(不变) + wallet.Balance, // 变动后余额(不变) + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额 + record.WithdrawNo, // 关联交易ID + 0, // 关联用户ID + "提现审核通过", // 备注 + ) + if err != nil { + return err + } + + // 更新扣税记录状态为成功 + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + + // 提现成功后,给上级代理发放提现奖励 + withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session) + if withdrawRewardErr != nil { + l.Logger.Errorf("发放提现奖励失败,代理ID:%d,提现金额:%f,错误:%+v", record.AgentId, record.Amount, withdrawRewardErr) + // 提现奖励失败不影响主流程,只记录日志 + } else { + l.Logger.Infof("发放提现奖励成功,代理ID:%d,提现金额:%f", record.AgentId, record.Amount) + } + + l.Logger.Infof("银行卡提现确认成功 withdrawalId:%d amount:%f", record.Id, record.Amount) + return nil +} + +// 拒绝提现 +func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, remark string) error { + // 更新提现记录状态为失败 + record.Status = StatusFailed + record.Remark = sql.NullString{String: "管理员拒绝:" + remark, Valid: true} + if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err) + } + + // 解冻资金(FrozenBalance -= amount, Balance += amount) + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err) + } + + // 记录变动前的余额 + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + // 更新钱包(余额增加,冻结余额减少) + wallet.Balance += record.Amount + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err) + } + + // 记录交易流水(解冻) + err = l.svcCtx.AgentService.CreateWalletTransaction( + ctx, + session, + wallet.AgentId, + model.WalletTransactionTypeUnfreeze, + record.Amount, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + wallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额 + record.WithdrawNo, // 关联交易ID + 0, // 关联用户ID + "提现拒绝,解冻资金", // 备注 + ) + if err != nil { + return err + } + + // 更新扣税记录状态为失败 + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + + l.Logger.Infof("银行卡提现拒绝 withdrawalId:%d amount:%f reason:%s", record.Id, record.Amount, remark) + return nil +} + +// 完成提现成功(支付宝转账成功后调用) +func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error { + // 更新提现记录状态为成功 + record.Status = StatusSuccess + record.Remark = sql.NullString{String: "支付宝转账成功", Valid: true} + if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err) + } + + // 解冻资金并扣除(FrozenBalance -= amount, Balance不变) + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err) + } + + // 记录变动前的冻结余额 + frozenBalanceBefore := wallet.FrozenBalance + + // 更新钱包(减少冻结余额) + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err) + } + + // 记录交易流水(提现成功) + err = l.svcCtx.AgentService.CreateWalletTransaction( + ctx, + session, + wallet.AgentId, + model.WalletTransactionTypeWithdraw, + -record.Amount, // 变动金额(负数表示减少) + wallet.Balance, // 变动前余额(不变) + wallet.Balance, // 变动后余额(不变) + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额 + record.WithdrawNo, // 关联交易ID + 0, // 关联用户ID + "提现成功", // 备注 + ) + if err != nil { + return err + } + + // 更新扣税记录状态为成功 + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + + // 提现成功后,给上级代理发放提现奖励 + withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session) + if withdrawRewardErr != nil { + l.Logger.Errorf("发放提现奖励失败,代理ID:%d,提现金额:%f,错误:%+v", record.AgentId, record.Amount, withdrawRewardErr) + // 提现奖励失败不影响主流程,只记录日志 + } else { + l.Logger.Infof("发放提现奖励成功,代理ID:%d,提现金额:%f", record.AgentId, record.Amount) + } + + l.Logger.Infof("支付宝提现成功 withdrawalId:%d withdrawNo:%s", record.Id, record.WithdrawNo) + return nil +} + +// 完成提现失败(支付宝转账失败后调用) +func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, errorMsg string) error { + // 更新提现记录状态为失败 + record.Status = StatusFailed + record.Remark = sql.NullString{String: errorMsg, Valid: true} + if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err) + } + + // 解冻资金(FrozenBalance -= amount, Balance += amount) + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err) + } + + // 记录变动前的余额 + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + // 更新钱包(余额增加,冻结余额减少) + wallet.Balance += record.Amount + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err) + } + + // 记录交易流水(解冻) + err = l.svcCtx.AgentService.CreateWalletTransaction( + ctx, + session, + wallet.AgentId, + model.WalletTransactionTypeUnfreeze, + record.Amount, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + wallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额 + record.WithdrawNo, // 关联交易ID + 0, // 关联用户ID + "提现失败,解冻资金", // 备注 + ) + if err != nil { + return err + } + + // 更新扣税记录状态为失败 + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", err) + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + + l.Logger.Infof("支付宝提现失败 withdrawalId:%d withdrawNo:%s reason:%s", record.Id, record.WithdrawNo, errorMsg) + return nil +} + +// 错误类型映射 +func (l *AdminReviewBankCardWithdrawalLogic) mapAlipayError(code string) string { + errorMapping := map[string]string{ + // 账户存在性错误 + "PAYEE_ACCOUNT_NOT_EXSIT": "收款账户不存在,请检查账号是否正确", + "PAYEE_NOT_EXIST": "收款账户不存在或姓名有误,请核实信息", + "PAYEE_ACC_OCUPIED": "收款账号存在多个账户,无法确认唯一性", + "PAYEE_MID_CANNOT_SAME": "收款方和中间方不能是同一个人,请修改收款方或者中间方信息", + + // 实名认证问题 + "PAYEE_CERTIFY_LEVEL_LIMIT": "收款方未完成实名认证", + "PAYEE_NOT_RELNAME_CERTIFY": "收款方未完成实名认证", + "PAYEE_CERT_INFO_ERROR": "收款方证件信息不匹配", + + // 账户状态异常 + "PAYEE_ACCOUNT_STATUS_ERROR": "收款账户状态异常,请更换账号", + "PAYEE_USERINFO_STATUS_ERROR": "收款账户状态异常,无法收款", + "PERMIT_LIMIT_PAYEE": "收款账户异常,请更换账号", + "BLOCK_USER_FORBBIDEN_RECIEVE": "账户冻结无法收款", + "PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT": "收款方托管子户累计收款金额超限", + + // 账户信息错误 + "PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配", + "PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致", + "PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配", + "PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能,请更换收款账号", + "PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现,请更换收款账号", + + // 权限与限制 + "PAYEE_RECEIVE_COUNT_EXCEED_LIMIT": "收款次数超限,请明日再试", + "PAYEE_OUT_PERMLIMIT_CHECK_FAILURE": "收款方权限校验不通过", + "PERMIT_NON_BANK_LIMIT_PAYEE": "收款方未完善身份信息,无法收款", + } + if msg, ok := errorMapping[code]; ok { + return msg + } + return "系统错误,请联系客服" +} diff --git a/app/main/api/internal/logic/admin_agent/adminupdateagentcommissionstatuslogic.go b/app/main/api/internal/logic/admin_agent/adminupdateagentcommissionstatuslogic.go new file mode 100644 index 0000000..08ecab8 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminupdateagentcommissionstatuslogic.go @@ -0,0 +1,109 @@ +package admin_agent + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateAgentCommissionStatusLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateAgentCommissionStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentCommissionStatusLogic { + return &AdminUpdateAgentCommissionStatusLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateAgentCommissionStatusLogic) AdminUpdateAgentCommissionStatus(req *types.AdminUpdateAgentCommissionStatusReq) (resp *types.AdminUpdateAgentCommissionStatusResp, err error) { + // 验证状态值:不允许手动设置为2(已取消),只能设置为0或1 + if req.Status != 0 && req.Status != 1 { + return nil, xerr.NewErrMsg("无效的状态值,状态必须为0(已结算)或1(冻结中)") + } + commission, err := l.svcCtx.AgentCommissionModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, xerr.NewErrMsg("佣金记录不存在") + } + return nil, err + } + + // 检查状态转换是否合法 + // 0(已结算) <-> 1(冻结中):允许冻结和解冻相互转换 + // 2(已取消):已取消的状态无法转换到其他状态(由订单退款自动触发) + if commission.Status == req.Status { + return nil, xerr.NewErrMsg("状态未发生变化") + } + + // 已取消的状态不能再转换 + if commission.Status == 2 { + return nil, xerr.NewErrMsg("已取消的佣金状态不能转换") + } + + // 开始事务 + err = l.svcCtx.AgentCommissionModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 保存原始状态用于判断 + originalStatus := commission.Status + + // 更新佣金状态 + commission.Status = req.Status + err = l.svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, commission) + if err != nil { + return err + } + + // 查询代理钱包 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId) + if err != nil { + return err + } + + // 根据状态转换更新钱包 + if originalStatus == 0 && req.Status == 1 { + // 已结算 -> 冻结中:增加冻结金额,减少钱包余额 + // 检查钱包余额是否足够 + if wallet.Balance < commission.Amount { + return xerr.NewErrMsg("钱包余额不足,无法冻结") + } + wallet.FrozenBalance += commission.Amount + wallet.Balance -= commission.Amount + + } else if originalStatus == 1 && req.Status == 0 { + // 冻结中 -> 已结算:减少冻结金额,增加钱包余额 + // 检查冻结余额是否足够 + if wallet.FrozenBalance < commission.Amount { + return xerr.NewErrMsg("冻结余额不足,无法解冻") + } + wallet.FrozenBalance -= commission.Amount + wallet.Balance += commission.Amount + } + + err = l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + resp = &types.AdminUpdateAgentCommissionStatusResp{ + Success: true, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/adminupdateagentmembershipconfiglogic.go b/app/main/api/internal/logic/admin_agent/adminupdateagentmembershipconfiglogic.go new file mode 100644 index 0000000..42741e6 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminupdateagentmembershipconfiglogic.go @@ -0,0 +1,96 @@ +package admin_agent + +import ( + "context" + "database/sql" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateAgentMembershipConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateAgentMembershipConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentMembershipConfigLogic { + return &AdminUpdateAgentMembershipConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateAgentMembershipConfigLogic) AdminUpdateAgentMembershipConfig(req *types.AdminUpdateAgentMembershipConfigReq) (resp *types.AdminUpdateAgentMembershipConfigResp, err error) { + cfg, err := l.svcCtx.AgentMembershipConfigModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, err + } + cfg.LevelName = req.LevelName + cfg.Price = sql.NullFloat64{Float64: req.Price, Valid: true} + cfg.ReportCommission = sql.NullFloat64{Float64: req.ReportCommission, Valid: true} + if req.LowerActivityReward == nil { + cfg.LowerActivityReward = sql.NullFloat64{Valid: false} + } else { + cfg.LowerActivityReward = sql.NullFloat64{Float64: *req.LowerActivityReward, Valid: true} + } + if req.NewActivityReward == nil { + cfg.NewActivityReward = sql.NullFloat64{Valid: false} + } else { + cfg.NewActivityReward = sql.NullFloat64{Float64: *req.NewActivityReward, Valid: true} + } + if req.LowerStandardCount == nil { + cfg.LowerStandardCount = sql.NullInt64{Valid: false} + } else { + cfg.LowerStandardCount = sql.NullInt64{Int64: *req.LowerStandardCount, Valid: true} + } + if req.NewLowerStandardCount == nil { + cfg.NewLowerStandardCount = sql.NullInt64{Valid: false} + } else { + cfg.NewLowerStandardCount = sql.NullInt64{Int64: *req.NewLowerStandardCount, Valid: true} + } + if req.LowerWithdrawRewardRatio == nil { + cfg.LowerWithdrawRewardRatio = sql.NullFloat64{Valid: false} + } else { + cfg.LowerWithdrawRewardRatio = sql.NullFloat64{Float64: *req.LowerWithdrawRewardRatio, Valid: true} + } + if req.LowerConvertVipReward == nil { + cfg.LowerConvertVipReward = sql.NullFloat64{Valid: false} + } else { + cfg.LowerConvertVipReward = sql.NullFloat64{Float64: *req.LowerConvertVipReward, Valid: true} + } + if req.LowerConvertSvipReward == nil { + cfg.LowerConvertSvipReward = sql.NullFloat64{Valid: false} + } else { + cfg.LowerConvertSvipReward = sql.NullFloat64{Float64: *req.LowerConvertSvipReward, Valid: true} + } + if req.ExemptionAmount == nil { + cfg.ExemptionAmount = sql.NullFloat64{Valid: false} + } else { + cfg.ExemptionAmount = sql.NullFloat64{Float64: *req.ExemptionAmount, Valid: true} + } + if req.PriceIncreaseMax == nil { + cfg.PriceIncreaseMax = sql.NullFloat64{Valid: false} + } else { + cfg.PriceIncreaseMax = sql.NullFloat64{Float64: *req.PriceIncreaseMax, Valid: true} + } + if req.PriceRatio == nil { + cfg.PriceRatio = sql.NullFloat64{Valid: false} + } else { + cfg.PriceRatio = sql.NullFloat64{Float64: *req.PriceRatio, Valid: true} + } + if req.PriceIncreaseAmount == nil { + cfg.PriceIncreaseAmount = sql.NullFloat64{Valid: false} + } else { + cfg.PriceIncreaseAmount = sql.NullFloat64{Float64: *req.PriceIncreaseAmount, Valid: true} + } + _, err = l.svcCtx.AgentMembershipConfigModel.Update(l.ctx, nil, cfg) + if err != nil { + return nil, err + } + resp = &types.AdminUpdateAgentMembershipConfigResp{Success: true} + return +} diff --git a/app/main/api/internal/logic/admin_agent/adminupdateagentproductionconfiglogic.go b/app/main/api/internal/logic/admin_agent/adminupdateagentproductionconfiglogic.go new file mode 100644 index 0000000..9afa523 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminupdateagentproductionconfiglogic.go @@ -0,0 +1,42 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateAgentProductionConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateAgentProductionConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentProductionConfigLogic { + return &AdminUpdateAgentProductionConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateAgentProductionConfigLogic) AdminUpdateAgentProductionConfig(req *types.AdminUpdateAgentProductionConfigReq) (resp *types.AdminUpdateAgentProductionConfigResp, err error) { + cfg, err := l.svcCtx.AgentProductConfigModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, err + } + cfg.CostPrice = req.CostPrice + cfg.PriceRangeMin = req.PriceRangeMin + cfg.PriceRangeMax = req.PriceRangeMax + cfg.PricingStandard = req.PricingStandard + cfg.OverpricingRatio = req.OverpricingRatio + _, err = l.svcCtx.AgentProductConfigModel.Update(l.ctx, nil, cfg) + if err != nil { + return nil, err + } + resp = &types.AdminUpdateAgentProductionConfigResp{Success: true} + return +} diff --git a/app/main/api/internal/logic/admin_agent/adminupdateagentwalletbalancelogic.go b/app/main/api/internal/logic/admin_agent/adminupdateagentwalletbalancelogic.go new file mode 100644 index 0000000..583339b --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminupdateagentwalletbalancelogic.go @@ -0,0 +1,107 @@ +package admin_agent + +import ( + "context" + "errors" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateAgentWalletBalanceLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateAgentWalletBalanceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateAgentWalletBalanceLogic { + return &AdminUpdateAgentWalletBalanceLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateAgentWalletBalanceLogic) AdminUpdateAgentWalletBalance(req *types.AdminUpdateAgentWalletBalanceReq) (resp *types.AdminUpdateAgentWalletBalanceResp, err error) { + // 参数校验 + if req.AgentId <= 0 { + return nil, xerr.NewErrMsg("代理ID无效") + } + if req.Amount == 0 { + return nil, xerr.NewErrMsg("修改金额不能为0") + } + + // 查询代理钱包信息 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, req.AgentId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, xerr.NewErrMsg("代理钱包不存在") + } + return nil, err + } + + // 计算新余额 + newBalance := wallet.Balance + req.Amount + + // 校验余额不能为负数 + if newBalance < 0 { + return nil, xerr.NewErrMsg(fmt.Sprintf("操作后余额不能为负数,当前余额: %.2f,操作金额: %.2f", wallet.Balance, req.Amount)) + } + + // 更新余额 + updateErr := l.svcCtx.AgentWalletModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { + // 记录变动前的余额 + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + // 使用版本号更新 + wallet.Balance = newBalance + err := l.svcCtx.AgentWalletModel.UpdateWithVersion(transCtx, session, wallet) + if err != nil { + return err + } + + // 创建钱包交易流水记录(手动调整) + remark := fmt.Sprintf("管理员手动调整余额,金额: %.2f", req.Amount) + transErr := l.svcCtx.AgentService.CreateWalletTransaction( + transCtx, + session, + req.AgentId, + model.WalletTransactionTypeAdjust, + req.Amount, // 变动金额(正数表示增加,负数表示减少) + balanceBefore, // 变动前余额 + wallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额(保持不变) + "", // 关联交易ID(无关联) + 0, // 关联用户ID(无关联) + remark, // 备注 + ) + if transErr != nil { + l.Logger.Errorf("创建代理钱包流水记录失败: %+v", transErr) + return transErr + } + + l.Logger.Infof("代理钱包余额变更 - AgentId: %d, 原余额: %.2f, 变更金额: %.2f, 新余额: %.2f", + req.AgentId, balanceBefore, req.Amount, wallet.Balance) + + return nil + }) + + if updateErr != nil { + l.Logger.Errorf("更新代理钱包余额失败: %+v", updateErr) + return nil, xerr.NewErrMsg("更新余额失败") + } + + resp = &types.AdminUpdateAgentWalletBalanceResp{ + Success: true, + Balance: newBalance, + } + return +} diff --git a/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go b/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go new file mode 100644 index 0000000..d047728 --- /dev/null +++ b/app/main/api/internal/logic/admin_agent/adminupdatesystemconfiglogic.go @@ -0,0 +1,37 @@ +package admin_agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateSystemConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateSystemConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateSystemConfigLogic { + return &AdminUpdateSystemConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateSystemConfigLogic) AdminUpdateSystemConfig(req *types.AdminUpdateSystemConfigReq) (resp *types.AdminUpdateSystemConfigResp, err error) { + // 更新佣金安全防御模式配置 + if req.CommissionSafeMode != nil { + l.svcCtx.Config.SystemConfig.CommissionSafeMode = *req.CommissionSafeMode + logx.Infof("更新系统配置:佣金安全防御模式设置为 %v", *req.CommissionSafeMode) + } + + resp = &types.AdminUpdateSystemConfigResp{ + Success: true, + } + return +} diff --git a/app/main/api/internal/logic/admin_api/adminbatchupdateapistatuslogic.go b/app/main/api/internal/logic/admin_api/adminbatchupdateapistatuslogic.go new file mode 100644 index 0000000..59ebcc0 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/adminbatchupdateapistatuslogic.go @@ -0,0 +1,70 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminBatchUpdateApiStatusLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminBatchUpdateApiStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminBatchUpdateApiStatusLogic { + return &AdminBatchUpdateApiStatusLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminBatchUpdateApiStatusLogic) AdminBatchUpdateApiStatus(req *types.AdminBatchUpdateApiStatusReq) (resp *types.AdminBatchUpdateApiStatusResp, err error) { + // 1. 参数验证 + if len(req.Ids) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID列表不能为空") + } + if req.Status != 0 && req.Status != 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "状态值无效, status: %d", req.Status) + } + + // 2. 批量更新API状态 + successCount := 0 + for _, id := range req.Ids { + if id <= 0 { + continue + } + + // 查询API是否存在 + api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + continue // 跳过不存在的API + } + logx.Errorf("查询API失败, err: %v, id: %d", err, id) + continue + } + + // 更新状态 + api.Status = req.Status + _, err = l.svcCtx.AdminApiModel.Update(l.ctx, nil, api) + if err != nil { + logx.Errorf("更新API状态失败, err: %v, id: %d", err, id) + continue + } + + successCount++ + } + + // 3. 返回结果 + return &types.AdminBatchUpdateApiStatusResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_api/admincreateapilogic.go b/app/main/api/internal/logic/admin_api/admincreateapilogic.go new file mode 100644 index 0000000..2cedae9 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/admincreateapilogic.go @@ -0,0 +1,78 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminCreateApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateApiLogic { + return &AdminCreateApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateApiLogic) AdminCreateApi(req *types.AdminCreateApiReq) (resp *types.AdminCreateApiResp, err error) { + // 1. 参数验证 + if req.ApiName == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API名称不能为空") + } + if req.ApiCode == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API编码不能为空") + } + if req.Method == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "请求方法不能为空") + } + if req.Url == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API路径不能为空") + } + + // 2. 检查API编码是否已存在 + existing, err := l.svcCtx.AdminApiModel.FindOneByApiCode(l.ctx, req.ApiCode) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API失败, err: %v, apiCode: %s", err, req.ApiCode) + } + if existing != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API编码已存在: %s", req.ApiCode) + } + + // 3. 创建API记录 + apiData := &model.AdminApi{ + ApiName: req.ApiName, + ApiCode: req.ApiCode, + Method: req.Method, + Url: req.Url, + Status: req.Status, + Description: req.Description, + } + + result, err := l.svcCtx.AdminApiModel.Insert(l.ctx, nil, apiData) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "创建API失败, err: %v", err) + } + + // 4. 返回结果 + apiId, _ := result.LastInsertId() + return &types.AdminCreateApiResp{Id: apiId}, nil +} diff --git a/app/main/api/internal/logic/admin_api/admindeleteapilogic.go b/app/main/api/internal/logic/admin_api/admindeleteapilogic.go new file mode 100644 index 0000000..2655056 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/admindeleteapilogic.go @@ -0,0 +1,68 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminDeleteApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteApiLogic { + return &AdminDeleteApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteApiLogic) AdminDeleteApi(req *types.AdminDeleteApiReq) (resp *types.AdminDeleteApiResp, err error) { + // 1. 参数验证 + if req.Id <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID必须大于0, id: %d", req.Id) + } + + // 2. 查询API是否存在 + api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "API不存在, id: %d", req.Id) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API失败, err: %v, id: %d", err, req.Id) + } + + // 3. 检查是否有角色关联该API + roleApiBuilder := l.svcCtx.AdminRoleApiModel.SelectBuilder().Where("api_id = ?", req.Id) + roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, roleApiBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色API关联失败, err: %v, apiId: %d", err, req.Id) + } + if len(roleApis) > 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "该API已被角色使用,无法删除, apiId: %d", req.Id) + } + + // 4. 执行软删除 + err = l.svcCtx.AdminApiModel.DeleteSoft(l.ctx, nil, api) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "删除API失败, err: %v, id: %d", err, req.Id) + } + + // 5. 返回结果 + return &types.AdminDeleteApiResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_api/admingetapidetaillogic.go b/app/main/api/internal/logic/admin_api/admingetapidetaillogic.go new file mode 100644 index 0000000..e873ca2 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/admingetapidetaillogic.go @@ -0,0 +1,61 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetApiDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetApiDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetApiDetailLogic { + return &AdminGetApiDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetApiDetailLogic) AdminGetApiDetail(req *types.AdminGetApiDetailReq) (resp *types.AdminGetApiDetailResp, err error) { + // 1. 参数验证 + if req.Id <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID必须大于0, id: %d", req.Id) + } + + // 2. 查询API详情 + api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "API不存在, id: %d", req.Id) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API详情失败, err: %v, id: %d", err, req.Id) + } + + // 3. 返回结果 + return &types.AdminGetApiDetailResp{ + AdminApiInfo: types.AdminApiInfo{ + Id: api.Id, + ApiName: api.ApiName, + ApiCode: api.ApiCode, + Method: api.Method, + Url: api.Url, + Status: api.Status, + Description: api.Description, + CreateTime: api.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: api.UpdateTime.Format("2006-01-02 15:04:05"), + }, + }, nil +} diff --git a/app/main/api/internal/logic/admin_api/admingetapilistlogic.go b/app/main/api/internal/logic/admin_api/admingetapilistlogic.go new file mode 100644 index 0000000..e4dc8b4 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/admingetapilistlogic.go @@ -0,0 +1,89 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetApiListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetApiListLogic { + return &AdminGetApiListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetApiListLogic) AdminGetApiList(req *types.AdminGetApiListReq) (resp *types.AdminGetApiListResp, err error) { + // 1. 参数验证 + if req.Page <= 0 { + req.Page = 1 + } + if req.PageSize <= 0 { + req.PageSize = 20 + } + if req.PageSize > 100 { + req.PageSize = 100 + } + + // 2. 构建查询条件 + builder := l.svcCtx.AdminApiModel.SelectBuilder() + + // 添加搜索条件 + if req.ApiName != "" { + builder = builder.Where("api_name LIKE ?", "%"+req.ApiName+"%") + } + if req.Method != "" { + builder = builder.Where("method = ?", req.Method) + } + if req.Status > 0 { + builder = builder.Where("status = ?", req.Status) + } + + // 3. 查询总数 + total, err := l.svcCtx.AdminApiModel.FindCount(l.ctx, builder, "id") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API总数失败, err: %v", err) + } + + // 4. 查询列表 + apis, err := l.svcCtx.AdminApiModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API列表失败, err: %v", err) + } + + // 5. 转换数据格式 + var apiList []types.AdminApiInfo + for _, api := range apis { + apiList = append(apiList, types.AdminApiInfo{ + Id: api.Id, + ApiName: api.ApiName, + ApiCode: api.ApiCode, + Method: api.Method, + Url: api.Url, + Status: api.Status, + Description: api.Description, + CreateTime: api.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: api.UpdateTime.Format("2006-01-02 15:04:05"), + }) + } + + // 6. 返回结果 + return &types.AdminGetApiListResp{ + Items: apiList, + Total: total, + }, nil +} diff --git a/app/main/api/internal/logic/admin_api/adminupdateapilogic.go b/app/main/api/internal/logic/admin_api/adminupdateapilogic.go new file mode 100644 index 0000000..094e538 --- /dev/null +++ b/app/main/api/internal/logic/admin_api/adminupdateapilogic.go @@ -0,0 +1,92 @@ +package admin_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateApiLogic { + return &AdminUpdateApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateApiLogic) AdminUpdateApi(req *types.AdminUpdateApiReq) (resp *types.AdminUpdateApiResp, err error) { + // 1. 参数验证 + if req.Id <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID必须大于0, id: %d", req.Id) + } + if req.ApiName == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API名称不能为空") + } + if req.ApiCode == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API编码不能为空") + } + if req.Method == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "请求方法不能为空") + } + if req.Url == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API路径不能为空") + } + + // 2. 查询API是否存在 + api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "API不存在, id: %d", req.Id) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API失败, err: %v, id: %d", err, req.Id) + } + + // 3. 检查API编码是否被其他记录使用 + if api.ApiCode != req.ApiCode { + existing, err := l.svcCtx.AdminApiModel.FindOneByApiCode(l.ctx, req.ApiCode) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API失败, err: %v, apiCode: %s", err, req.ApiCode) + } + if existing != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API编码已存在: %s", req.ApiCode) + } + } + + // 4. 更新API信息 + api.ApiName = req.ApiName + api.ApiCode = req.ApiCode + api.Method = req.Method + api.Url = req.Url + api.Status = req.Status + api.Description = req.Description + + _, err = l.svcCtx.AdminApiModel.Update(l.ctx, nil, api) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "更新API失败, err: %v, id: %d", err, req.Id) + } + + // 5. 返回结果 + return &types.AdminUpdateApiResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_auth/adminloginlogic.go b/app/main/api/internal/logic/admin_auth/adminloginlogic.go new file mode 100644 index 0000000..4662930 --- /dev/null +++ b/app/main/api/internal/logic/admin_auth/adminloginlogic.go @@ -0,0 +1,93 @@ +package admin_auth + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + jwtx "bdrp-server/common/jwt" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminLoginLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminLoginLogic { + return &AdminLoginLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.AdminLoginResp, err error) { + // 1. 验证验证码 + if !req.Captcha { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha) + } + + // 2. 验证用户名和密码 + user, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, req.Username) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("用户名或密码错误"), "用户登录, 用户名或密码错误, 用户名: %s", req.Username) + } + + // 3. 验证密码 + if !crypto.PasswordVerify(req.Password, user.Password) { + return nil, errors.Wrapf(xerr.NewErrMsg("用户名或密码错误"), "用户登录, 用户名或密码错误, 用户名: %s", req.Username) + } + + // 4. 获取权限 + adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": user.Id}) + permissions, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, adminUserRoleBuilder, "role_id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("获取权限失败"), "用户登录, 获取权限失败, 用户名: %s", req.Username) + } + + // 获取角色ID数组 + roleIds := make([]int64, 0) + for _, permission := range permissions { + roleIds = append(roleIds, permission.RoleId) + } + + // 获取角色名称 + roles := make([]string, 0) + for _, roleId := range roleIds { + role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, roleId) + if err != nil { + continue + } + roles = append(roles, role.RoleCode) + } + + // 5. 生成token + refreshToken := l.svcCtx.Config.AdminConfig.RefreshAfter + expiresAt := l.svcCtx.Config.AdminConfig.AccessExpire + claims := jwtx.JwtClaims{ + UserId: user.Id, + AgentId: 0, + Platform: model.PlatformAdmin, + UserType: model.UserTypeAdmin, + IsAgent: model.AgentStatusNo, + } + token, err := jwtx.GenerateJwtToken(claims, l.svcCtx.Config.AdminConfig.AccessSecret, expiresAt) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("生成token失败"), "用户登录, 生成token失败, 用户名: %s", req.Username) + } + + return &types.AdminLoginResp{ + AccessToken: token, + AccessExpire: expiresAt, + RefreshAfter: refreshToken, + Roles: roles, + }, nil +} diff --git a/app/main/api/internal/logic/admin_feature/adminconfigfeatureexamplelogic.go b/app/main/api/internal/logic/admin_feature/adminconfigfeatureexamplelogic.go new file mode 100644 index 0000000..fbf31c3 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/adminconfigfeatureexamplelogic.go @@ -0,0 +1,92 @@ +package admin_feature + +import ( + "context" + "encoding/hex" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminConfigFeatureExampleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminConfigFeatureExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminConfigFeatureExampleLogic { + return &AdminConfigFeatureExampleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminConfigFeatureExampleLogic) AdminConfigFeatureExample(req *types.AdminConfigFeatureExampleReq) (resp *types.AdminConfigFeatureExampleResp, err error) { + // 1. 验证功能是否存在 + feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.FeatureId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询功能失败, featureId: %d, err: %v", req.FeatureId, err) + } + if feature == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), + "功能不存在, featureId: %d", req.FeatureId) + } + + // 2. 检查是否已存在示例数据 + existingExample, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, req.FeatureId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询示例数据失败, featureId: %d, err: %v", req.FeatureId, err) + } + + // 3. 加密示例数据 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "获取AES密钥失败: %v", decodeErr) + } + + encryptedData, aesEncryptErr := crypto.AesEncrypt([]byte(req.Data), key) + if aesEncryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "加密示例数据失败: %v", aesEncryptErr) + } + + // 4. 准备示例数据 + exampleData := &model.Example{ + ApiId: feature.ApiId, + FeatureId: req.FeatureId, + Content: encryptedData, + } + + // 4. 根据是否存在决定新增或更新 + if existingExample == nil { + // 新增示例数据 + _, err = l.svcCtx.ExampleModel.Insert(l.ctx, nil, exampleData) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "创建示例数据失败, featureId: %d, err: %v", req.FeatureId, err) + } + } else { + // 更新示例数据 + exampleData.Id = existingExample.Id + exampleData.Version = existingExample.Version + _, err = l.svcCtx.ExampleModel.Update(l.ctx, nil, exampleData) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "更新示例数据失败, featureId: %d, err: %v", req.FeatureId, err) + } + } + + // 5. 返回成功结果 + return &types.AdminConfigFeatureExampleResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go b/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go new file mode 100644 index 0000000..1abf556 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/admincreatefeaturelogic.go @@ -0,0 +1,47 @@ +package admin_feature + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminCreateFeatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateFeatureLogic { + return &AdminCreateFeatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateFeatureLogic) AdminCreateFeature(req *types.AdminCreateFeatureReq) (resp *types.AdminCreateFeatureResp, err error) { + // 1. 数据转换 + data := &model.Feature{ + ApiId: req.ApiId, + Name: req.Name, + CostPrice: req.CostPrice, + } + + // 2. 数据库操作 + result, err := l.svcCtx.FeatureModel.Insert(l.ctx, nil, data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "创建功能失败, err: %v, req: %+v", err, req) + } + + // 3. 返回结果 + id, _ := result.LastInsertId() + return &types.AdminCreateFeatureResp{Id: id}, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admindeletefeaturelogic.go b/app/main/api/internal/logic/admin_feature/admindeletefeaturelogic.go new file mode 100644 index 0000000..f7373a0 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/admindeletefeaturelogic.go @@ -0,0 +1,45 @@ +package admin_feature + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminDeleteFeatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteFeatureLogic { + return &AdminDeleteFeatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteFeatureLogic) AdminDeleteFeature(req *types.AdminDeleteFeatureReq) (resp *types.AdminDeleteFeatureResp, err error) { + // 1. 查询记录是否存在 + record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找功能失败, err: %v, id: %d", err, req.Id) + } + + // 2. 执行软删除 + err = l.svcCtx.FeatureModel.DeleteSoft(l.ctx, nil, record) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "删除功能失败, err: %v, id: %d", err, req.Id) + } + + // 3. 返回结果 + return &types.AdminDeleteFeatureResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go b/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go new file mode 100644 index 0000000..f2c8ce2 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/admingetfeaturedetaillogic.go @@ -0,0 +1,47 @@ +package admin_feature + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetFeatureDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetFeatureDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureDetailLogic { + return &AdminGetFeatureDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetFeatureDetailLogic) AdminGetFeatureDetail(req *types.AdminGetFeatureDetailReq) (resp *types.AdminGetFeatureDetailResp, err error) { + // 1. 查询记录 + record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找功能失败, err: %v, id: %d", err, req.Id) + } + + // 2. 构建响应 + resp = &types.AdminGetFeatureDetailResp{ + Id: record.Id, + ApiId: record.ApiId, + Name: record.Name, + CostPrice: record.CostPrice, + CreateTime: record.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: record.UpdateTime.Format("2006-01-02 15:04:05"), + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admingetfeatureexamplelogic.go b/app/main/api/internal/logic/admin_feature/admingetfeatureexamplelogic.go new file mode 100644 index 0000000..ce9ff1b --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/admingetfeatureexamplelogic.go @@ -0,0 +1,82 @@ +package admin_feature + +import ( + "context" + "encoding/hex" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetFeatureExampleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetFeatureExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureExampleLogic { + return &AdminGetFeatureExampleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetFeatureExampleLogic) AdminGetFeatureExample(req *types.AdminGetFeatureExampleReq) (resp *types.AdminGetFeatureExampleResp, err error) { + // 1. 查询示例数据 + example, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, req.FeatureId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 示例数据不存在,返回空数据 + return &types.AdminGetFeatureExampleResp{ + Id: 0, + FeatureId: req.FeatureId, + ApiId: "", + Data: "", + CreateTime: "", + UpdateTime: "", + }, nil + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询示例数据失败, featureId: %d, err: %v", req.FeatureId, err) + } + + // 2. 获取解密密钥 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "获取AES密钥失败: %v", decodeErr) + } + + // 3. 解密示例数据 + var decryptedData string + if example.Content == "000" { + // 特殊值,直接返回 + decryptedData = example.Content + } else { + // 解密数据 + decryptedBytes, decryptErr := crypto.AesDecrypt(example.Content, key) + if decryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "解密示例数据失败: %v", decryptErr) + } + decryptedData = string(decryptedBytes) + } + + // 4. 返回解密后的数据 + return &types.AdminGetFeatureExampleResp{ + Id: example.Id, + FeatureId: example.FeatureId, + ApiId: example.ApiId, + Data: decryptedData, + CreateTime: example.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: example.UpdateTime.Format("2006-01-02 15:04:05"), + }, nil +} diff --git a/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go b/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go new file mode 100644 index 0000000..9affe48 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/admingetfeaturelistlogic.go @@ -0,0 +1,67 @@ +package admin_feature + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetFeatureListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetFeatureListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetFeatureListLogic { + return &AdminGetFeatureListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetFeatureListLogic) AdminGetFeatureList(req *types.AdminGetFeatureListReq) (resp *types.AdminGetFeatureListResp, err error) { + // 1. 构建查询条件 + builder := l.svcCtx.FeatureModel.SelectBuilder() + + // 2. 添加查询条件 + if req.ApiId != nil && *req.ApiId != "" { + builder = builder.Where("api_id LIKE ?", "%"+*req.ApiId+"%") + } + if req.Name != nil && *req.Name != "" { + builder = builder.Where("name LIKE ?", "%"+*req.Name+"%") + } + + // 3. 执行分页查询 + list, total, err := l.svcCtx.FeatureModel.FindPageListByPageWithTotal( + l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询功能列表失败, err: %v, req: %+v", err, req) + } + + // 4. 构建响应列表 + 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"), + CostPrice: item.CostPrice, + } + items = append(items, listItem) + } + + // 5. 返回结果 + return &types.AdminGetFeatureListResp{ + Total: total, + Items: items, + }, nil +} diff --git a/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go b/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go new file mode 100644 index 0000000..f7d1325 --- /dev/null +++ b/app/main/api/internal/logic/admin_feature/adminupdatefeaturelogic.go @@ -0,0 +1,62 @@ +package admin_feature + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateFeatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateFeatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateFeatureLogic { + return &AdminUpdateFeatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateFeatureLogic) AdminUpdateFeature(req *types.AdminUpdateFeatureReq) (resp *types.AdminUpdateFeatureResp, err error) { + // 1. 参数验证 + if req.Id <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "功能ID必须大于0, id: %d", req.Id) + } + + // 2. 查询记录是否存在 + record, err := l.svcCtx.FeatureModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找功能失败, err: %v, id: %d", err, req.Id) + } + + // 3. 直接更新record的字段(只更新非空字段) + if req.ApiId != nil && *req.ApiId != "" { + record.ApiId = *req.ApiId + } + if req.Name != nil && *req.Name != "" { + record.Name = *req.Name + } + if req.CostPrice != nil { + record.CostPrice = *req.CostPrice + } + + // 4. 执行更新操作 + err = l.svcCtx.FeatureModel.UpdateWithVersion(l.ctx, nil, record) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "更新功能失败, err: %v, id: %d", err, req.Id) + } + + // 5. 返回成功结果 + return &types.AdminUpdateFeatureResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_menu/createmenulogic.go b/app/main/api/internal/logic/admin_menu/createmenulogic.go new file mode 100644 index 0000000..74ed473 --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/createmenulogic.go @@ -0,0 +1,97 @@ +package admin_menu + +import ( + "context" + "database/sql" + "encoding/json" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateMenuLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCreateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateMenuLogic { + return &CreateMenuLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateMenuLogic) CreateMenu(req *types.CreateMenuReq) (resp *types.CreateMenuResp, err error) { + // 1. 参数验证 + if req.Name == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称不能为空"), "菜单名称不能为空") + } + if req.Type == "menu" && req.Component == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("组件路径不能为空"), "组件路径不能为空") + } + + // 2. 检查名称和路径是否重复 + exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err) + } + if exists != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在") + } + + // 3. 检查父菜单是否存在(如果不是根菜单) + if req.Pid > 0 { + parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Pid) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %d, err: %v", req.Pid, err) + } + if parentMenu == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %d", req.Pid) + } + } + + // 4. 将类型标签转换为值 + typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err) + } + + // 5. 创建菜单记录 + menu := &model.AdminMenu{ + Pid: req.Pid, + Name: req.Name, + Path: req.Path, + Component: req.Component, + Redirect: sql.NullString{String: req.Redirect, Valid: req.Redirect != ""}, + Status: req.Status, + Type: typeValue, + Sort: req.Sort, + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + + // 将Meta转换为JSON字符串 + metaJson, err := json.Marshal(req.Meta) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err) + } + menu.Meta = string(metaJson) + + // 6. 保存到数据库 + _, err = l.svcCtx.AdminMenuModel.Insert(l.ctx, nil, menu) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建菜单失败, err: %v", err) + } + + return &types.CreateMenuResp{ + Id: menu.Id, + }, nil +} diff --git a/app/main/api/internal/logic/admin_menu/deletemenulogic.go b/app/main/api/internal/logic/admin_menu/deletemenulogic.go new file mode 100644 index 0000000..cd70d3b --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/deletemenulogic.go @@ -0,0 +1,75 @@ +package admin_menu + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteMenuLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDeleteMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteMenuLogic { + return &DeleteMenuLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeleteMenuLogic) DeleteMenu(req *types.DeleteMenuReq) (resp *types.DeleteMenuResp, err error) { + // 1. 参数验证 + if req.Id <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "菜单ID必须大于0, id: %d", req.Id) + } + + // 2. 查询菜单是否存在 + menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找菜单失败, err: %v, id: %d", err, req.Id) + } + + // 3. 检查是否有子菜单 + childMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where("pid = ?", req.Id) + childMenus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, childMenuBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询子菜单失败, err: %v, parent_id: %d", err, req.Id) + } + if len(childMenus) > 0 { + return nil, errors.Wrapf(xerr.NewErrMsg("该菜单下还有子菜单,无法删除"), + "该菜单下还有子菜单,无法删除, id: %d", req.Id) + } + + // 4. 检查是否有角色关联该菜单 + roleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where("menu_id = ?", req.Id) + roleMenus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, roleMenuBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色菜单关联失败, err: %v, menu_id: %d", err, req.Id) + } + if len(roleMenus) > 0 { + return nil, errors.Wrapf(xerr.NewErrMsg("该菜单已被角色使用,无法删除"), + "该菜单已被角色使用,无法删除, id: %d", req.Id) + } + + // 5. 执行软删除 + err = l.svcCtx.AdminMenuModel.DeleteSoft(l.ctx, nil, menu) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "删除菜单失败, err: %v, id: %d", err, req.Id) + } + + // 6. 返回成功结果 + return &types.DeleteMenuResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_menu/getmenualllogic.go b/app/main/api/internal/logic/admin_menu/getmenualllogic.go new file mode 100644 index 0000000..45990c4 --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/getmenualllogic.go @@ -0,0 +1,250 @@ +package admin_menu + +import ( + "context" + "sort" + "strconv" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/bytedance/sonic" + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetMenuAllLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetMenuAllLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuAllLogic { + return &GetMenuAllLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetMenuAllLogic) GetMenuAll(req *types.GetMenuAllReq) (resp *[]types.GetMenuAllResp, err error) { + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %+v", err) + } + + // 使用MapReduceVoid并发获取用户角色 + var roleIds []int64 + var permissions []*struct { + RoleId int64 + } + + type UserRoleResult struct { + RoleId int64 + } + + err = mr.MapReduceVoid( + func(source chan<- interface{}) { + adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": userId}) + source <- adminUserRoleBuilder + }, + func(item interface{}, writer mr.Writer[*UserRoleResult], cancel func(error)) { + builder := item.(squirrel.SelectBuilder) + result, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "role_id DESC") + if err != nil { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户角色信息失败, %+v", err)) + return + } + + for _, r := range result { + writer.Write(&UserRoleResult{RoleId: r.RoleId}) + } + }, + func(pipe <-chan *UserRoleResult, cancel func(error)) { + for item := range pipe { + permissions = append(permissions, &struct{ RoleId int64 }{RoleId: item.RoleId}) + } + }, + ) + if err != nil { + return nil, err + } + + for _, permission := range permissions { + roleIds = append(roleIds, permission.RoleId) + } + + // 使用MapReduceVoid并发获取角色菜单 + var menuIds []int64 + var roleMenus []*struct { + MenuId int64 + } + + type RoleMenuResult struct { + MenuId int64 + } + + err = mr.MapReduceVoid( + func(source chan<- interface{}) { + getRoleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where(squirrel.Eq{"role_id": roleIds}) + source <- getRoleMenuBuilder + }, + func(item interface{}, writer mr.Writer[*RoleMenuResult], cancel func(error)) { + builder := item.(squirrel.SelectBuilder) + result, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id DESC") + if err != nil { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取角色菜单信息失败, %+v", err)) + return + } + + for _, r := range result { + writer.Write(&RoleMenuResult{MenuId: r.MenuId}) + } + }, + func(pipe <-chan *RoleMenuResult, cancel func(error)) { + for item := range pipe { + roleMenus = append(roleMenus, &struct{ MenuId int64 }{MenuId: item.MenuId}) + } + }, + ) + if err != nil { + return nil, err + } + + for _, roleMenu := range roleMenus { + menuIds = append(menuIds, roleMenu.MenuId) + } + + // 使用MapReduceVoid并发获取菜单 + type AdminMenuStruct struct { + Id int64 + Pid int64 + Name string + Path string + Component string + Redirect struct { + String string + Valid bool + } + Meta string + Sort int64 + Type int64 + Status int64 + } + + var menus []*AdminMenuStruct + + err = mr.MapReduceVoid( + func(source chan<- interface{}) { + adminMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where(squirrel.Eq{"id": menuIds}) + source <- adminMenuBuilder + }, + func(item interface{}, writer mr.Writer[*AdminMenuStruct], cancel func(error)) { + builder := item.(squirrel.SelectBuilder) + result, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "sort ASC") + if err != nil { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取菜单信息失败, %+v", err)) + return + } + + for _, r := range result { + menu := &AdminMenuStruct{ + Id: r.Id, + Pid: r.Pid, + Name: r.Name, + Path: r.Path, + Component: r.Component, + Redirect: r.Redirect, + Meta: r.Meta, + Sort: r.Sort, + Type: r.Type, + Status: r.Status, + } + writer.Write(menu) + } + }, + func(pipe <-chan *AdminMenuStruct, cancel func(error)) { + for item := range pipe { + menus = append(menus, item) + } + }, + ) + if err != nil { + return nil, err + } + + // 转换为types.Menu结构并存储到映射表 + menuMap := make(map[string]types.GetMenuAllResp) + for _, menu := range menus { + // 只处理状态正常的菜单 + if menu.Status != 1 { + continue + } + + meta := make(map[string]interface{}) + err = sonic.Unmarshal([]byte(menu.Meta), &meta) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析菜单Meta信息失败, %+v", err) + } + + redirect := func() string { + if menu.Redirect.Valid { + return menu.Redirect.String + } + return "" + }() + + menuId := strconv.FormatInt(menu.Id, 10) + menuMap[menuId] = types.GetMenuAllResp{ + Name: menu.Name, + Path: menu.Path, + Redirect: redirect, + Component: menu.Component, + Sort: menu.Sort, + Meta: meta, + Children: make([]types.GetMenuAllResp, 0), + } + } + + // 按ParentId将菜单分组 + menuGroups := lo.GroupBy(menus, func(item *AdminMenuStruct) int64 { + return item.Pid + }) + + // 递归构建菜单树 + var buildMenuTree func(parentId int64) []types.GetMenuAllResp + buildMenuTree = func(parentId int64) []types.GetMenuAllResp { + children := make([]types.GetMenuAllResp, 0) + + childMenus, ok := menuGroups[parentId] + if !ok { + return children + } + + // 按Sort排序 + sort.Slice(childMenus, func(i, j int) bool { + return childMenus[i].Sort < childMenus[j].Sort + }) + + for _, childMenu := range childMenus { + menuId := strconv.FormatInt(childMenu.Id, 10) + if menu, exists := menuMap[menuId]; exists && childMenu.Status == 1 { + // 递归构建子菜单 + menu.Children = buildMenuTree(childMenu.Id) + children = append(children, menu) + } + } + + return children + } + + // 从根菜单开始构建(ParentId为0的是根菜单) + menuTree := buildMenuTree(0) + + return &menuTree, nil +} diff --git a/app/main/api/internal/logic/admin_menu/getmenudetaillogic.go b/app/main/api/internal/logic/admin_menu/getmenudetaillogic.go new file mode 100644 index 0000000..e9ff7d9 --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/getmenudetaillogic.go @@ -0,0 +1,30 @@ +package admin_menu + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetMenuDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetMenuDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuDetailLogic { + return &GetMenuDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetMenuDetailLogic) GetMenuDetail(req *types.GetMenuDetailReq) (resp *types.GetMenuDetailResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/main/api/internal/logic/admin_menu/getmenulistlogic.go b/app/main/api/internal/logic/admin_menu/getmenulistlogic.go new file mode 100644 index 0000000..60f334d --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/getmenulistlogic.go @@ -0,0 +1,109 @@ +package admin_menu + +import ( + "context" + "encoding/json" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetMenuListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetMenuListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuListLogic { + return &GetMenuListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetMenuListLogic) GetMenuList(req *types.GetMenuListReq) (resp []types.MenuListItem, err error) { + // 构建查询条件 + builder := l.svcCtx.AdminMenuModel.SelectBuilder() + + // 添加筛选条件 + if len(req.Name) > 0 { + builder = builder.Where("name LIKE ?", "%"+req.Name+"%") + } + if len(req.Path) > 0 { + builder = builder.Where("path LIKE ?", "%"+req.Path+"%") + } + if req.Status != -1 { + builder = builder.Where("status = ?", req.Status) + } + if req.Type != "" { + builder = builder.Where("type = ?", req.Type) + } + + // 排序但不分页,获取所有符合条件的菜单 + builder = builder.OrderBy("sort ASC") + + // 获取所有菜单 + menus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err) + } + + // 将菜单按ID存入map + menuMap := make(map[int64]types.MenuListItem) + for _, menu := range menus { + var meta map[string]interface{} + err := json.Unmarshal([]byte(menu.Meta), &meta) + if err != nil { + logx.Errorf("解析Meta字段失败: %v", err) + meta = make(map[string]interface{}) + } + menuType, err := l.svcCtx.DictService.GetDictLabel(l.ctx, "admin_menu_type", menu.Type) + if err != nil { + logx.Errorf("获取菜单类型失败: %v", err) + menuType = "" + } + item := types.MenuListItem{ + Id: menu.Id, + Pid: menu.Pid, + Name: menu.Name, + Path: menu.Path, + Component: menu.Component, + Redirect: menu.Redirect.String, + Meta: meta, + Status: menu.Status, + Type: menuType, + Sort: menu.Sort, + CreateTime: menu.CreateTime.Format("2006-01-02 15:04:05"), + Children: make([]types.MenuListItem, 0), + } + menuMap[menu.Id] = item + } + + // 构建父子关系 + for _, menu := range menus { + if menu.Pid > 0 { + // 找到父菜单 + if parent, exists := menuMap[menu.Pid]; exists { + // 添加当前菜单到父菜单的子菜单列表 + children := append(parent.Children, menuMap[menu.Id]) + parent.Children = children + menuMap[menu.Pid] = parent + } + } + } + + // 提取顶级菜单(ParentId为0)到响应列表 + result := make([]types.MenuListItem, 0) + for _, menu := range menus { + if menu.Pid == 0 { + result = append(result, menuMap[menu.Id]) + } + } + + return result, nil +} diff --git a/app/main/api/internal/logic/admin_menu/updatemenulogic.go b/app/main/api/internal/logic/admin_menu/updatemenulogic.go new file mode 100644 index 0000000..5c7097e --- /dev/null +++ b/app/main/api/internal/logic/admin_menu/updatemenulogic.go @@ -0,0 +1,96 @@ +package admin_menu + +import ( + "context" + "database/sql" + "encoding/json" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateMenuLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewUpdateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateMenuLogic { + return &UpdateMenuLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateMenuLogic) UpdateMenu(req *types.UpdateMenuReq) (resp *types.UpdateMenuResp, err error) { + // 1. 检查菜单是否存在 + menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, id: %d, err: %v", req.Id, err) + } + if menu == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单不存在, id: %d", req.Id) + } + + // 2. 将类型标签转换为值 + typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err) + } + + // 3. 检查父菜单是否存在(如果不是根菜单) + if req.Pid > 0 { + parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Pid) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %d, err: %v", req.Pid, err) + } + if parentMenu == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %d", req.Pid) + } + } + + // 4. 检查名称和路径是否重复 + if req.Name != menu.Name || req.Path != menu.Path { + exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err) + } + if exists != nil && exists.Id != req.Id { + return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在") + } + } + + // 5. 更新菜单信息 + + menu.Pid = req.Pid + menu.Name = req.Name + menu.Path = req.Path + menu.Component = req.Component + menu.Redirect = sql.NullString{String: req.Redirect, Valid: req.Redirect != ""} + menu.Status = req.Status + menu.Type = typeValue + menu.Sort = req.Sort + + // 将Meta转换为JSON字符串 + metaJson, err := json.Marshal(req.Meta) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err) + } + menu.Meta = string(metaJson) + + // 6. 保存更新 + _, err = l.svcCtx.AdminMenuModel.Update(l.ctx, nil, menu) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新菜单失败, err: %v", err) + } + + return &types.UpdateMenuResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_notification/admincreatenotificationlogic.go b/app/main/api/internal/logic/admin_notification/admincreatenotificationlogic.go new file mode 100644 index 0000000..5a32318 --- /dev/null +++ b/app/main/api/internal/logic/admin_notification/admincreatenotificationlogic.go @@ -0,0 +1,50 @@ +package admin_notification + +import ( + "context" + "database/sql" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminCreateNotificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateNotificationLogic { + return &AdminCreateNotificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateNotificationLogic) AdminCreateNotification(req *types.AdminCreateNotificationReq) (resp *types.AdminCreateNotificationResp, err error) { + startDate, _ := time.Parse("2006-01-02", req.StartDate) + endDate, _ := time.Parse("2006-01-02", req.EndDate) + data := &model.GlobalNotifications{ + Title: req.Title, + Content: req.Content, + NotificationPage: req.NotificationPage, + StartDate: sql.NullTime{Time: startDate, Valid: req.StartDate != ""}, + EndDate: sql.NullTime{Time: endDate, Valid: req.EndDate != ""}, + StartTime: req.StartTime, + EndTime: req.EndTime, + Status: req.Status, + } + result, err := l.svcCtx.GlobalNotificationsModel.Insert(l.ctx, nil, data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建通知失败, err: %v, req: %+v", err, req) + } + id, _ := result.LastInsertId() + return &types.AdminCreateNotificationResp{Id: id}, nil +} diff --git a/app/main/api/internal/logic/admin_notification/admindeletenotificationlogic.go b/app/main/api/internal/logic/admin_notification/admindeletenotificationlogic.go new file mode 100644 index 0000000..4f362bf --- /dev/null +++ b/app/main/api/internal/logic/admin_notification/admindeletenotificationlogic.go @@ -0,0 +1,38 @@ +package admin_notification + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminDeleteNotificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteNotificationLogic { + return &AdminDeleteNotificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteNotificationLogic) AdminDeleteNotification(req *types.AdminDeleteNotificationReq) (resp *types.AdminDeleteNotificationResp, err error) { + notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id) + } + err = l.svcCtx.GlobalNotificationsModel.DeleteSoft(l.ctx, nil, notification) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除通知失败, err: %v, id: %d", err, req.Id) + } + return &types.AdminDeleteNotificationResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_notification/admingetnotificationdetaillogic.go b/app/main/api/internal/logic/admin_notification/admingetnotificationdetaillogic.go new file mode 100644 index 0000000..73f8418 --- /dev/null +++ b/app/main/api/internal/logic/admin_notification/admingetnotificationdetaillogic.go @@ -0,0 +1,53 @@ +package admin_notification + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetNotificationDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetNotificationDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetNotificationDetailLogic { + return &AdminGetNotificationDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetNotificationDetailLogic) AdminGetNotificationDetail(req *types.AdminGetNotificationDetailReq) (resp *types.AdminGetNotificationDetailResp, err error) { + notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id) + } + resp = &types.AdminGetNotificationDetailResp{ + Id: notification.Id, + Title: notification.Title, + Content: notification.Content, + NotificationPage: notification.NotificationPage, + StartDate: "", + StartTime: notification.StartTime, + EndDate: "", + EndTime: notification.EndTime, + Status: notification.Status, + CreateTime: notification.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: notification.UpdateTime.Format("2006-01-02 15:04:05"), + } + if notification.StartDate.Valid { + resp.StartDate = notification.StartDate.Time.Format("2006-01-02") + } + if notification.EndDate.Valid { + resp.EndDate = notification.EndDate.Time.Format("2006-01-02") + } + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_notification/admingetnotificationlistlogic.go b/app/main/api/internal/logic/admin_notification/admingetnotificationlistlogic.go new file mode 100644 index 0000000..a7f4530 --- /dev/null +++ b/app/main/api/internal/logic/admin_notification/admingetnotificationlistlogic.go @@ -0,0 +1,82 @@ +package admin_notification + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetNotificationListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetNotificationListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetNotificationListLogic { + return &AdminGetNotificationListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetNotificationListLogic) AdminGetNotificationList(req *types.AdminGetNotificationListReq) (resp *types.AdminGetNotificationListResp, err error) { + builder := l.svcCtx.GlobalNotificationsModel.SelectBuilder() + if req.Title != nil { + builder = builder.Where("title LIKE ?", "%"+*req.Title+"%") + } + if req.NotificationPage != nil { + builder = builder.Where("notification_page = ?", *req.NotificationPage) + } + if req.Status != nil { + builder = builder.Where("status = ?", *req.Status) + } + if req.StartDate != nil { + if t, err := time.Parse("2006-01-02", *req.StartDate); err == nil { + builder = builder.Where("start_date >= ?", t) + } + } + if req.EndDate != nil { + if t, err := time.Parse("2006-01-02", *req.EndDate); err == nil { + builder = builder.Where("end_date <= ?", t) + } + } + list, total, err := l.svcCtx.GlobalNotificationsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询通知列表失败, err: %v, req: %+v", err, req) + } + items := make([]types.NotificationListItem, 0, len(list)) + for _, n := range list { + item := types.NotificationListItem{ + Id: n.Id, + Title: n.Title, + NotificationPage: n.NotificationPage, + Content: n.Content, + StartDate: "", + StartTime: n.StartTime, + EndDate: "", + EndTime: n.EndTime, + Status: n.Status, + CreateTime: n.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: n.UpdateTime.Format("2006-01-02 15:04:05"), + } + if n.StartDate.Valid { + item.StartDate = n.StartDate.Time.Format("2006-01-02") + } + if n.EndDate.Valid { + item.EndDate = n.EndDate.Time.Format("2006-01-02") + } + items = append(items, item) + } + resp = &types.AdminGetNotificationListResp{ + Total: total, + Items: items, + } + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_notification/adminupdatenotificationlogic.go b/app/main/api/internal/logic/admin_notification/adminupdatenotificationlogic.go new file mode 100644 index 0000000..0dd6e6a --- /dev/null +++ b/app/main/api/internal/logic/admin_notification/adminupdatenotificationlogic.go @@ -0,0 +1,66 @@ +package admin_notification + +import ( + "context" + "database/sql" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateNotificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateNotificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateNotificationLogic { + return &AdminUpdateNotificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateNotificationLogic) AdminUpdateNotification(req *types.AdminUpdateNotificationReq) (resp *types.AdminUpdateNotificationResp, err error) { + notification, err := l.svcCtx.GlobalNotificationsModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找通知失败, err: %v, id: %d", err, req.Id) + } + if req.StartDate != nil { + startDate, _ := time.Parse("2006-01-02", *req.StartDate) + notification.StartDate = sql.NullTime{Time: startDate, Valid: true} + } + if req.EndDate != nil { + endDate, _ := time.Parse("2006-01-02", *req.EndDate) + notification.EndDate = sql.NullTime{Time: endDate, Valid: true} + } + if req.Title != nil { + notification.Title = *req.Title + } + if req.Content != nil { + notification.Content = *req.Content + } + if req.NotificationPage != nil { + notification.NotificationPage = *req.NotificationPage + } + if req.StartTime != nil { + notification.StartTime = *req.StartTime + } + if req.EndTime != nil { + notification.EndTime = *req.EndTime + } + if req.Status != nil { + notification.Status = *req.Status + } + _, err = l.svcCtx.GlobalNotificationsModel.Update(l.ctx, nil, notification) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新通知失败, err: %v, req: %+v", err, req) + } + return &types.AdminUpdateNotificationResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_order/admincreateorderlogic.go b/app/main/api/internal/logic/admin_order/admincreateorderlogic.go new file mode 100644 index 0000000..fa816a6 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admincreateorderlogic.go @@ -0,0 +1,99 @@ +package admin_order + +import ( + "context" + "database/sql" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminCreateOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateOrderLogic { + return &AdminCreateOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateOrderLogic) AdminCreateOrder(req *types.AdminCreateOrderReq) (resp *types.AdminCreateOrderResp, err error) { + // 生成订单号 + orderNo := fmt.Sprintf("%dADMIN", time.Now().UnixNano()) + + // 根据产品名称查询产品ID + builder := l.svcCtx.ProductModel.SelectBuilder() + builder = builder.Where("product_name = ? AND del_state = ?", req.ProductName, 0) + products, err := l.svcCtx.ProductModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 查询产品失败 err: %v", err) + } + if len(products) == 0 { + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("产品不存在: %s", req.ProductName)), "AdminCreateOrder, 查询产品失败 err: %v", err) + } + product := products[0] + + // 创建订单对象 + order := &model.Order{ + OrderNo: orderNo, + PlatformOrderId: sql.NullString{String: req.PlatformOrderId, Valid: req.PlatformOrderId != ""}, + ProductId: product.Id, + PaymentPlatform: req.PaymentPlatform, + PaymentScene: req.PaymentScene, + Amount: req.Amount, + Status: req.Status, + } + + // 使用事务处理订单创建 + var orderId int64 + err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 插入订单 + result, err := l.svcCtx.OrderModel.Insert(ctx, session, order) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 创建订单失败 err: %v", err) + } + + // 获取订单ID + orderId, err = result.LastInsertId() + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 获取订单ID失败 err: %v", err) + } + + // 如果是推广订单,创建推广订单记录 + if req.IsPromotion == 1 { + promotionOrder := &model.AdminPromotionOrder{ + OrderId: orderId, + Version: 1, + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + _, err = l.svcCtx.AdminPromotionOrderModel.Insert(ctx, session, promotionOrder) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminCreateOrder, 创建推广订单失败 err: %v", err) + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &types.AdminCreateOrderResp{ + Id: orderId, + }, nil +} diff --git a/app/main/api/internal/logic/admin_order/admindeleteorderlogic.go b/app/main/api/internal/logic/admin_order/admindeleteorderlogic.go new file mode 100644 index 0000000..c47c494 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admindeleteorderlogic.go @@ -0,0 +1,63 @@ +package admin_order + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminDeleteOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteOrderLogic { + return &AdminDeleteOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteOrderLogic) AdminDeleteOrder(req *types.AdminDeleteOrderReq) (resp *types.AdminDeleteOrderResp, err error) { + // 获取订单信息 + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 查询订单失败 err: %v", err) + } + + // 使用事务删除订单 + err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 软删除订单 + err := l.svcCtx.OrderModel.DeleteSoft(ctx, session, order) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 删除订单失败 err: %v", err) + } + + // 删除关联的推广订单记录 + promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(ctx, order.Id) + if err == nil && promotionOrder != nil { + err = l.svcCtx.AdminPromotionOrderModel.DeleteSoft(ctx, session, promotionOrder) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminDeleteOrder, 删除推广订单失败 err: %v", err) + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &types.AdminDeleteOrderResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go b/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go new file mode 100644 index 0000000..081f9e4 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go @@ -0,0 +1,137 @@ +package admin_order + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetOrderDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetOrderDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderDetailLogic { + return &AdminGetOrderDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderDetailReq) (resp *types.AdminGetOrderDetailResp, err error) { + // 获取订单信息 + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询订单失败 err: %v", err) + } + + // 获取产品信息 + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, order.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询产品失败 err: %v", err) + } + + // 判断是否为推广订单 + var isPromotion int64 + promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(l.ctx, order.Id) + if err == nil && promotionOrder != nil { + isPromotion = 1 + } + + // 判断是否为代理订单并获取代理处理状态 + var isAgentOrder bool + var agentProcessStatus string + + agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, order.Id) + if err == nil && agentOrder != nil { + isAgentOrder = true + + // 查询代理佣金记录 + commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, + l.svcCtx.AgentCommissionModel.SelectBuilder().Where("order_id = ?", order.Id), "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询代理佣金失败 err: %v", err) + } + + if len(commissions) > 0 { + agentProcessStatus = "success" + } else { + // 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败 + if order.Status == "paid" { + agentProcessStatus = "pending" + } else { + agentProcessStatus = "failed" + } + } + } else { + isAgentOrder = false + agentProcessStatus = "not_agent" + } + + // 获取查询状态 + var queryState string + builder := l.svcCtx.QueryModel.SelectBuilder().Where("order_id = ?", order.Id).Columns("query_state") + queries, err := l.svcCtx.QueryModel.FindAll(l.ctx, builder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询查询状态失败 err: %v", err) + } + + if len(queries) > 0 { + queryState = queries[0].QueryState + } else { + // 查询清理日志 + 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, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询清理日志失败 err: %v", err) + } + + if len(cleanupDetails) > 0 { + queryState = model.QueryStateCleaned + } else { + queryState = "" + } + } + + // 构建响应 + resp = &types.AdminGetOrderDetailResp{ + Id: order.Id, + OrderNo: order.OrderNo, + PlatformOrderId: order.PlatformOrderId.String, + ProductName: product.ProductName, + PaymentPlatform: order.PaymentPlatform, + PaymentScene: order.PaymentScene, + Amount: order.Amount, + SalesCost: order.SalesCost, + Status: order.Status, + CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"), + IsPromotion: isPromotion, + QueryState: queryState, + IsAgentOrder: isAgentOrder, + AgentProcessStatus: agentProcessStatus, + } + + // 处理可选字段 + if order.PayTime.Valid { + resp.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05") + } + if order.RefundTime.Valid { + resp.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05") + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go new file mode 100644 index 0000000..1f7b2d6 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go @@ -0,0 +1,380 @@ +package admin_order + +import ( + "context" + "encoding/hex" + "strings" + "sync" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetOrderListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderListLogic { + return &AdminGetOrderListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListReq) (resp *types.AdminGetOrderListResp, err error) { + // 构建查询条件 + builder := l.svcCtx.OrderModel.SelectBuilder() + if req.OrderNo != "" { + builder = builder.Where("order_no = ?", req.OrderNo) + } + if req.PlatformOrderId != "" { + builder = builder.Where("platform_order_id = ?", req.PlatformOrderId) + } + if req.ProductName != "" { + builder = builder.Where("product_id IN (SELECT id FROM product WHERE product_name LIKE ?)", "%"+req.ProductName+"%") + } + if req.PaymentPlatform != "" { + builder = builder.Where("payment_platform = ?", req.PaymentPlatform) + } + if req.PaymentScene != "" { + builder = builder.Where("payment_scene = ?", req.PaymentScene) + } + if req.Amount > 0 { + builder = builder.Where("amount = ?", req.Amount) + } + if req.Status != "" { + builder = builder.Where("status = ?", req.Status) + } + if req.IsPromotion != -1 { + builder = builder.Where("id IN (SELECT order_id FROM admin_promotion_order WHERE del_state = 0)") + } + // 时间范围查询 + if req.CreateTimeStart != "" { + builder = builder.Where("create_time >= ?", req.CreateTimeStart) + } + if req.CreateTimeEnd != "" { + builder = builder.Where("create_time <= ?", req.CreateTimeEnd) + } + if req.PayTimeStart != "" { + builder = builder.Where("pay_time >= ?", req.PayTimeStart) + } + if req.PayTimeEnd != "" { + builder = builder.Where("pay_time <= ?", req.PayTimeEnd) + } + if req.RefundTimeStart != "" { + builder = builder.Where("refund_time >= ?", req.RefundTimeStart) + } + if req.RefundTimeEnd != "" { + builder = builder.Where("refund_time <= ?", req.RefundTimeEnd) + } + + // 按被查询人(query_user_record 表)过滤:姓名、身份证、手机号(库中为密文,需解密后匹配) + if req.QueryName != "" || req.QueryIdCard != "" || req.QueryMobile != "" { + orderIds, filterErr := l.filterOrderIdsByQueryUserRecord(req.QueryName, req.QueryIdCard, req.QueryMobile) + if filterErr != nil { + return nil, filterErr + } + if len(orderIds) == 0 { + builder = builder.Where("1 = 0") // 无匹配时返回空 + } else { + builder = builder.Where(squirrel.Eq{"id": orderIds}) + } + } + + // 并发获取总数和列表 + var total int64 + var orders []*model.Order + err = mr.Finish(func() error { + var err error + total, err = l.svcCtx.OrderModel.FindCount(l.ctx, builder, "id") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单总数失败 err: %v", err) + } + return nil + }, func() error { + var err error + orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单列表失败 err: %v", err) + } + return nil + }) + if err != nil { + return nil, err + } + + // 并发获取产品信息和查询状态 + productMap := make(map[int64]string) + queryStateMap := make(map[int64]string) + agentOrderMap := make(map[int64]bool) // 代理订单映射 + agentProcessStatusMap := make(map[int64]string) // 代理处理状态映射 + + var mu sync.Mutex + + // 批量获取查询状态 + if len(orders) > 0 { + orderIds := make([]int64, 0, len(orders)) + for _, order := range orders { + orderIds = append(orderIds, order.Id) + } + + // 1. 先查询当前查询状态 + builder := l.svcCtx.QueryModel.SelectBuilder(). + Where(squirrel.Eq{"order_id": orderIds}). + Columns("order_id", "query_state") + queries, err := l.svcCtx.QueryModel.FindAll(l.ctx, builder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询查询状态失败 err: %v", err) + } + + // 2. 记录已找到查询状态的订单ID + foundOrderIds := make(map[int64]bool) + for _, query := range queries { + queryStateMap[query.OrderId] = query.QueryState + foundOrderIds[query.OrderId] = true + } + + // 3. 查找未找到查询状态的订单是否在清理日志中 + notFoundOrderIds := make([]int64, 0) + for _, orderId := range orderIds { + if !foundOrderIds[orderId] { + notFoundOrderIds = append(notFoundOrderIds, orderId) + } + } + + if len(notFoundOrderIds) > 0 { + // 查询清理日志 + 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) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询清理日志失败 err: %v", err) + } + + // 记录已清理的订单状态 + for _, detail := range cleanupDetails { + if _, exists := queryStateMap[detail.OrderId]; !exists { + queryStateMap[detail.OrderId] = model.QueryStateCleaned // 使用常量标记为已清除状态 + } + } + + // 对于既没有查询状态也没有清理记录的订单,不设置状态(保持为空字符串) + for _, orderId := range notFoundOrderIds { + if _, exists := queryStateMap[orderId]; !exists { + queryStateMap[orderId] = "" // 未知状态保持为空字符串 + } + } + } + + // 批量获取代理订单状态 + agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, + l.svcCtx.AgentOrderModel.SelectBuilder().Where(squirrel.Eq{"order_id": orderIds}), "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理订单失败 err: %v", err) + } + + // 记录代理订单 + for _, agentOrder := range agentOrders { + agentOrderMap[agentOrder.OrderId] = true + } + + // 对于代理订单,查询代理处理状态 + if len(agentOrders) > 0 { + agentOrderIds := make([]int64, 0, len(agentOrders)) + for _, agentOrder := range agentOrders { + agentOrderIds = append(agentOrderIds, agentOrder.OrderId) + } + + // 查询代理佣金记录 + commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, + l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{"order_id": agentOrderIds}), "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理佣金失败 err: %v", err) + } + + // 记录有佣金记录的订单为处理成功 + processedOrderIds := make(map[int64]bool) + for _, commission := range commissions { + processedOrderIds[commission.OrderId] = true + } + + // 创建订单状态映射,避免重复查找 + orderStatusMap := make(map[int64]string) + for _, order := range orders { + orderStatusMap[order.Id] = order.Status + } + + // 设置代理处理状态 + for _, agentOrder := range agentOrders { + orderId := agentOrder.OrderId + if processedOrderIds[orderId] { + agentProcessStatusMap[orderId] = "success" + } else { + // 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败 + if orderStatusMap[orderId] == "paid" { + agentProcessStatusMap[orderId] = "pending" + } else { + agentProcessStatusMap[orderId] = "failed" + } + } + } + } + } + + // 并发获取产品信息 + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for _, order := range orders { + source <- order + } + }, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) { + order := item.(*model.Order) + + // 获取产品信息 + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, order.ProductId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询产品信息失败 err: %v", err)) + return + } + mu.Lock() + if product != nil { + productMap[product.Id] = product.ProductName + } else { + productMap[order.ProductId] = "" // 产品不存在时设置为空字符串 + } + mu.Unlock() + writer.Write(struct{}{}) + }, func(pipe <-chan struct{}, cancel func(error)) { + for range pipe { + } + }) + if err != nil { + return nil, err + } + + // 构建响应 + resp = &types.AdminGetOrderListResp{ + Total: total, + Items: make([]types.OrderListItem, 0, len(orders)), + } + + for _, order := range orders { + item := types.OrderListItem{ + Id: order.Id, + OrderNo: order.OrderNo, + PlatformOrderId: order.PlatformOrderId.String, + ProductName: productMap[order.ProductId], + PaymentPlatform: order.PaymentPlatform, + PaymentScene: order.PaymentScene, + Amount: order.Amount, + SalesCost: order.SalesCost, + Status: order.Status, + CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"), + QueryState: queryStateMap[order.Id], + } + if order.PayTime.Valid { + item.PayTime = order.PayTime.Time.Format("2006-01-02 15:04:05") + } + if order.RefundTime.Valid { + item.RefundTime = order.RefundTime.Time.Format("2006-01-02 15:04:05") + } + // 判断是否为推广订单 + promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(l.ctx, order.Id) + if err == nil && promotionOrder != nil { + item.IsPromotion = 1 + } + + // 设置代理订单相关字段 + if agentOrderMap[order.Id] { + item.IsAgentOrder = true + item.AgentProcessStatus = agentProcessStatusMap[order.Id] + } else { + item.IsAgentOrder = false + item.AgentProcessStatus = "not_agent" + } + resp.Items = append(resp.Items, item) + } + + return resp, nil +} + +// filterOrderIdsByQueryUserRecord 根据姓名、身份证、手机号(明文)从 query_user_record 解密后匹配,返回符合条件的 order_id 列表 +func (l *AdminGetOrderListLogic) filterOrderIdsByQueryUserRecord(queryName, queryIdCard, queryMobile string) ([]int64, error) { + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, keyErr := hex.DecodeString(secretKey) + if keyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "Encrypt.SecretKey 解析失败: %v", keyErr) + } + + qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where("order_id > ?", 0) + recs, err := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 query_user_record 失败: %v", err) + } + + orderIds := make([]int64, 0, len(recs)) + for _, rec := range recs { + match := true + + if queryName != "" { + var decName string + if rec.Name != "" { + bs, e := crypto.AesEcbDecrypt(rec.Name, key) + if e != nil { + match = false + } else { + decName = string(bs) + } + } + if match && !strings.Contains(decName, queryName) { + match = false + } + } + + if match && queryIdCard != "" { + var decIdCard string + if rec.IdCard != "" { + var e error + decIdCard, e = crypto.DecryptIDCard(rec.IdCard, key) + if e != nil { + match = false + } + } + if match && decIdCard != queryIdCard { + match = false + } + } + + if match && queryMobile != "" { + var decMobile string + if rec.Mobile != "" { + var e error + decMobile, e = crypto.DecryptMobile(rec.Mobile, secretKey) + if e != nil { + match = false + } + } + if match && decMobile != queryMobile { + match = false + } + } + + if match { + orderIds = append(orderIds, rec.OrderId) + } + } + return orderIds, nil +} diff --git a/app/main/api/internal/logic/admin_order/admingetordersourcestatisticslogic.go b/app/main/api/internal/logic/admin_order/admingetordersourcestatisticslogic.go new file mode 100644 index 0000000..d045542 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetordersourcestatisticslogic.go @@ -0,0 +1,87 @@ +package admin_order + +import ( + "context" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetOrderSourceStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetOrderSourceStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderSourceStatisticsLogic { + return &AdminGetOrderSourceStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetOrderSourceStatisticsLogic) AdminGetOrderSourceStatistics(req *types.AdminGetOrderSourceStatisticsReq) (resp *types.AdminGetOrderSourceStatisticsResp, err error) { + // 查询所有有产品ID的订单 + builder := l.svcCtx.OrderModel.SelectBuilder() + builder = builder.Where("product_id IS NOT NULL") + + // 获取所有符合条件的订单 + orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, builder, "id DESC") + if err != nil { + logx.Errorf("查询订单列表失败: %v", err) + return nil, fmt.Errorf("查询订单列表失败: %w", err) + } + + logx.Infof("获取到订单数量: %d", len(orders)) + + // 统计每个产品的订单数量 + productCountMap := make(map[int64]int64) + for _, order := range orders { + productCountMap[order.ProductId]++ + } + + // 构建返回结果 + items := make([]types.OrderSourceStatisticsItem, 0, len(productCountMap)) + for productId, count := range productCountMap { + // 获取产品信息 + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productId) + if err != nil { + logx.Errorf("查询产品信息失败 productId=%d, err=%v", productId, err) + // 如果查询失败,使用默认值 + items = append(items, types.OrderSourceStatisticsItem{ + ProductName: "未知产品", + OrderCount: count, + }) + continue + } + + items = append(items, types.OrderSourceStatisticsItem{ + ProductName: product.ProductName, + OrderCount: count, + }) + } + + // 按订单数量降序排序 + for i := 0; i < len(items)-1; i++ { + for j := i + 1; j < len(items); j++ { + if items[i].OrderCount < items[j].OrderCount { + items[i], items[j] = items[j], items[i] + } + } + } + + logx.Infof("查询到订单来源统计数据: %d 个产品,订单总数: %d", len(items), len(orders)) + return &types.AdminGetOrderSourceStatisticsResp{ + Items: items, + }, nil +} + +// 定义结果结构体 +type OrderSourceResult struct { + ProductName string `db:"product_name"` + OrderCount int64 `db:"order_count"` +} diff --git a/app/main/api/internal/logic/admin_order/admingetorderstatisticslogic.go b/app/main/api/internal/logic/admin_order/admingetorderstatisticslogic.go new file mode 100644 index 0000000..1d928a3 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetorderstatisticslogic.go @@ -0,0 +1,106 @@ +package admin_order + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetOrderStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetOrderStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetOrderStatisticsLogic { + return &AdminGetOrderStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetOrderStatisticsLogic) AdminGetOrderStatistics(req *types.AdminGetOrderStatisticsReq) (resp *types.AdminGetOrderStatisticsResp, err error) { + // 获取当前时间 + now := time.Now() + var startTime, endTime time.Time + + // 根据时间维度设置时间范围和格式 + switch req.Dimension { + case "day": + // 日:当月1号到今天 + startTime = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + endTime = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location()) + case "month": + // 月:今年1月到当月 + startTime = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()) + endTime = time.Date(now.Year(), now.Month(), 1, 23, 59, 59, 999999999, now.Location()).AddDate(0, 1, -1) + case "year": + // 年:过去5年 + startTime = time.Date(now.Year()-5, 1, 1, 0, 0, 0, 0, now.Location()) + endTime = time.Date(now.Year(), 12, 31, 23, 59, 59, 999999999, now.Location()) + case "all": + // 全部:所有时间,但按日统计 + startTime = time.Date(2020, 1, 1, 0, 0, 0, 0, now.Location()) // 假设从2020年开始 + endTime = now + default: + // 默认为日 + startTime = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + endTime = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location()) + } + + // 构建查询条件 + builder := l.svcCtx.OrderModel.SelectBuilder(). + Where("create_time >= ? AND create_time <= ?", startTime, endTime). + Where("status = ?", "paid") // 只统计已支付的订单 + + // 查询所有符合条件的订单 + orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, builder, "create_time ASC") + if err != nil { + logx.Errorf("查询订单统计数据失败: %v", err) + return nil, fmt.Errorf("查询订单统计数据失败: %w", err) + } + + // 按日期分组统计 + dateMap := make(map[string]*types.OrderStatisticsItem) + + for _, order := range orders { + var dateKey string + if req.Dimension == "year" { + dateKey = order.CreateTime.Format("2006") + } else if req.Dimension == "month" { + dateKey = order.CreateTime.Format("2006-01") + } else { + dateKey = order.CreateTime.Format("2006-01-02") + } + + if item, exists := dateMap[dateKey]; exists { + item.Count++ + item.Amount += order.Amount + } else { + dateMap[dateKey] = &types.OrderStatisticsItem{ + Date: dateKey, + Count: 1, + Amount: order.Amount, + } + } + } + + // 转换为切片 + items := make([]types.OrderStatisticsItem, 0, len(dateMap)) + for date := range dateMap { + items = append(items, *dateMap[date]) + } + + // 构建响应 + resp = &types.AdminGetOrderStatisticsResp{ + Items: items, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_order/admingetrefundstatisticslogic.go b/app/main/api/internal/logic/admin_order/admingetrefundstatisticslogic.go new file mode 100644 index 0000000..bf5cfff --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetrefundstatisticslogic.go @@ -0,0 +1,60 @@ +package admin_order + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetRefundStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetRefundStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetRefundStatisticsLogic { + return &AdminGetRefundStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetRefundStatisticsLogic) AdminGetRefundStatistics(req *types.AdminGetRefundStatisticsReq) (resp *types.AdminGetRefundStatisticsResp, err error) { + // 获取今日的开始和结束时间 + today := time.Now() + startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + + // 构建查询条件 + builder := l.svcCtx.OrderModel.SelectBuilder() + + // 查询总退款金额(status=refunded表示已退款) + totalBuilder := builder.Where("status = ?", "refunded") + totalRefundAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, totalBuilder, "amount") + if err != nil { + logx.Errorf("查询总退款金额失败: %v", err) + return nil, fmt.Errorf("查询总退款金额失败: %w", err) + } + + // 查询今日退款金额(status=refunded表示已退款,且退款时间为今日) + todayBuilder := builder.Where("status = ? AND refund_time >= ? AND refund_time < ?", "refunded", startOfDay, endOfDay) + todayRefundAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, todayBuilder, "amount") + if err != nil { + logx.Errorf("查询今日退款金额失败: %v", err) + return nil, fmt.Errorf("查询今日退款金额失败: %w", err) + } + + // 构建响应 + resp = &types.AdminGetRefundStatisticsResp{ + TotalRefundAmount: totalRefundAmount, + TodayRefundAmount: todayRefundAmount, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_order/admingetrevenuestatisticslogic.go b/app/main/api/internal/logic/admin_order/admingetrevenuestatisticslogic.go new file mode 100644 index 0000000..32e98ab --- /dev/null +++ b/app/main/api/internal/logic/admin_order/admingetrevenuestatisticslogic.go @@ -0,0 +1,143 @@ +package admin_order + +import ( + "context" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetRevenueStatisticsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetRevenueStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetRevenueStatisticsLogic { + return &AdminGetRevenueStatisticsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetRevenueStatisticsLogic) AdminGetRevenueStatistics(req *types.AdminGetRevenueStatisticsReq) (resp *types.AdminGetRevenueStatisticsResp, err error) { + // 获取今日的开始和结束时间 + today := time.Now() + startOfDay := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + + // 构建查询条件 + builder := l.svcCtx.OrderModel.SelectBuilder() + + // 查询总流水收入金额(status=paid表示已支付) + totalRevenueBuilder := builder.Where("status = ?", "paid") + totalRevenueAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, totalRevenueBuilder, "amount") + if err != nil { + logx.Errorf("查询总收入金额失败: %v", err) + return nil, fmt.Errorf("查询总收入金额失败: %w", err) + } + + // 查询今日流水收入金额(status=paid表示已支付,且支付时间为今日) + todayRevenueBuilder := builder.Where("status = ? AND pay_time >= ? AND pay_time < ?", "paid", startOfDay, endOfDay) + todayRevenueAmount, err := l.svcCtx.OrderModel.FindSum(l.ctx, todayRevenueBuilder, "amount") + if err != nil { + logx.Errorf("查询今日收入金额失败: %v", err) + return nil, fmt.Errorf("查询今日收入金额失败: %w", err) + } + + // 查询代理订单金额总和(只查询agent_platform_deduction表,不进行联表) + deductionAmount, err := l.svcCtx.AgentPlatformDeductionModel.FindSum( + l.ctx, + l.svcCtx.AgentPlatformDeductionModel.SelectBuilder(), + "amount") + if err != nil { + logx.Errorf("查询代理订单金额总和失败: %v", err) + return nil, fmt.Errorf("查询代理订单金额总和失败: %w", err) + } + + // 计算非代理订单金额,使用订单表作为主表,关联agent_platform_deduction表,查询没有代理记录的订单 + // 使用子查询方式避免JOIN,与order/list接口保持一致的查询风格 + nonDeductionBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND id NOT IN (SELECT order_id FROM agent_platform_deduction WHERE order_id IS NOT NULL)", "paid") + nonDeductionAmount, err := l.svcCtx.OrderModel.FindSum( + l.ctx, + nonDeductionBuilder, + "amount") + if err != nil { + logx.Errorf("查询非代理订单金额失败: %v", err) + return nil, fmt.Errorf("查询非代理订单金额失败: %w", err) + } + + // 查询订单成本总和(只查询order表,不进行联表) + orderCostBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND del_state = 0", "paid") + orderCostAmount, err := l.svcCtx.OrderModel.FindSum( + l.ctx, + orderCostBuilder, + "sales_cost") + if err != nil { + logx.Errorf("查询订单成本总和失败: %v", err) + return nil, fmt.Errorf("查询订单成本总和失败: %w", err) + } + // 计算总利润 = 代理订单金额总和 + 非代理订单金额总和 - 订单成本总和 + // 总收入 = 代理订单金额 + 非代理订单金额 + // 总利润 = 总收入 - 所有订单成本 + totalProfitAmount := deductionAmount + nonDeductionAmount - orderCostAmount + + // 计算今日利润 = 今日代理订单金额 + 今日非代理订单金额 - 今日订单成本总和 + + // 1. 查询今日代理订单金额 + todayDeductionBuilder := l.svcCtx.AgentPlatformDeductionModel.SelectBuilder(). + Where("create_time >= ? AND create_time < ?", startOfDay, endOfDay) + todayDeductionAmount, err := l.svcCtx.AgentPlatformDeductionModel.FindSum( + l.ctx, + todayDeductionBuilder, + "amount") + if err != nil { + logx.Errorf("查询今日代理订单金额失败: %v", err) + return nil, fmt.Errorf("查询今日代理订单金额失败: %w", err) + } + + // 2. 查询今日非代理订单金额 + todayNonDeductionBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND pay_time >= ? AND pay_time < ? AND id NOT IN (SELECT order_id FROM agent_platform_deduction WHERE order_id IS NOT NULL)", + "paid", startOfDay, endOfDay) + todayNonDeductionAmount, err := l.svcCtx.OrderModel.FindSum( + l.ctx, + todayNonDeductionBuilder, + "amount") + if err != nil { + logx.Errorf("查询今日非代理订单金额失败: %v", err) + return nil, fmt.Errorf("查询今日非代理订单金额失败: %w", err) + } + + // 3. 查询今日订单成本总和 + todayOrderCostBuilder := l.svcCtx.OrderModel.SelectBuilder(). + Where("status = ? AND pay_time >= ? AND pay_time < ?", "paid", startOfDay, endOfDay) + todayOrderCostAmount, err := l.svcCtx.OrderModel.FindSum( + l.ctx, + todayOrderCostBuilder, + "sales_cost") + if err != nil { + logx.Errorf("查询今日订单成本总和失败: %v", err) + return nil, fmt.Errorf("查询今日订单成本总和失败: %w", err) + } + + // 4. 计算今日利润 = 今日代理订单金额 + 今日非代理订单金额 - 今日订单成本总和 + todayProfitAmount := todayDeductionAmount + todayNonDeductionAmount - todayOrderCostAmount + + // 构建响应 + resp = &types.AdminGetRevenueStatisticsResp{ + TotalRevenueAmount: totalRevenueAmount, + TodayRevenueAmount: todayRevenueAmount, + TotalProfitAmount: totalProfitAmount, + TodayProfitAmount: todayProfitAmount, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go new file mode 100644 index 0000000..f0dfae2 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/adminrefundorderlogic.go @@ -0,0 +1,199 @@ +package admin_order + +import ( + "context" + "database/sql" + "fmt" + "time" + + paylogic "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +const ( + PaymentPlatformAlipay = "alipay" + PaymentPlatformWechat = "wechat" + OrderStatusPaid = "paid" + RefundNoPrefix = "refund-" +) + +type AdminRefundOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminRefundOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRefundOrderLogic { + return &AdminRefundOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} +func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) (resp *types.AdminRefundOrderResp, err error) { + // 获取并验证订单 + order, err := l.getAndValidateOrder(req.Id, req.RefundAmount) + if err != nil { + return nil, err + } + + // 根据支付平台处理退款 + switch order.PaymentPlatform { + case PaymentPlatformAlipay: + return l.handleAlipayRefund(order, req) + case PaymentPlatformWechat: + return l.handleWechatRefund(order, req) + default: + return nil, errors.Wrapf(xerr.NewErrMsg("不支持的支付平台"), "AdminRefundOrder, 不支持的支付平台: %s", order.PaymentPlatform) + } +} + +// getAndValidateOrder 获取并验证订单信息 +func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId int64, refundAmount float64) (*model.Order, error) { + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, orderId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err) + } + + // 检查订单状态 + if order.Status != OrderStatusPaid { + return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态: %s", order.Status) + } + + // 检查退款金额 + if refundAmount > order.Amount { + return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额: %f, 订单金额: %f", refundAmount, order.Amount) + } + + return order, nil +} + +// handleAlipayRefund 处理支付宝退款 +func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { + // 调用支付宝退款接口 + 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 + } + + // 退款成功后,按本次退款金额更新代理佣金状态并扣除钱包金额 + // 注意:refundAmount 为本次实际退款金额,可以是部分退款 + _ = paylogic.HandleCommissionAndWalletDeduction(l.ctx, l.svcCtx, nil, order, req.RefundAmount) + + return &types.AdminRefundOrderResp{ + Status: model.OrderStatusRefunded, + RefundNo: refundNo, + Amount: req.RefundAmount, + }, nil + } else { + // 支付宝退款失败,创建失败记录但不更新订单状态 + err = l.createRefundRecordOnly(order, req, refundNo, refundResp.TradeNo, model.OrderRefundStatusFailed) + if err != nil { + logx.Errorf("创建退款失败记录时出错: %v", err) + } + return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败: %v", refundResp.Msg)), "AdminRefundOrder, 支付宝退款失败") + } +} + +// handleWechatRefund 处理微信退款 +func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) { + // 调用微信退款接口 + err := l.svcCtx.WechatPayService.WeChatRefund(l.ctx, order.OrderNo, req.RefundAmount, order.Amount) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 微信退款失败 err: %v", err) + } + + // 微信退款是异步的,创建pending状态的退款记录 + // 注意:代理佣金扣除将在微信退款回调成功后再执行,不在此处提前扣除 + refundNo := l.generateRefundNo(order.OrderNo) + err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunding, model.OrderRefundStatusPending) + if err != nil { + return nil, err + } + + return &types.AdminRefundOrderResp{ + Status: model.OrderRefundStatusPending, + RefundNo: refundNo, + Amount: req.RefundAmount, + }, nil +} + +// 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 { + // 创建退款记录 + refund := &model.OrderRefund{ + 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) + } + + // 更新订单状态 + order.Status = orderStatus + if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil { + return fmt.Errorf("更新订单状态失败: %v", err) + } + + return nil + }) +} + +// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况) +func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error { + refund := &model.OrderRefund{ + 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 { + return fmt.Errorf("创建退款记录失败: %v", err) + } + return nil +} + +// generateRefundNo 生成退款单号 +func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string { + return fmt.Sprintf("%s%s", RefundNoPrefix, orderNo) +} + +// createNullString 创建 sql.NullString +func (l *AdminRefundOrderLogic) createNullString(value string) sql.NullString { + return sql.NullString{ + String: value, + Valid: value != "", + } +} diff --git a/app/main/api/internal/logic/admin_order/adminretryagentprocesslogic.go b/app/main/api/internal/logic/admin_order/adminretryagentprocesslogic.go new file mode 100644 index 0000000..48850a7 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/adminretryagentprocesslogic.go @@ -0,0 +1,55 @@ +package admin_order + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminRetryAgentProcessLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminRetryAgentProcessLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRetryAgentProcessLogic { + return &AdminRetryAgentProcessLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminRetryAgentProcessLogic) AdminRetryAgentProcess(req *types.AdminRetryAgentProcessReq) (resp *types.AdminRetryAgentProcessResp, err error) { + // 调用AgentService的重新执行代理处理方法 + err = l.svcCtx.AgentService.RetryAgentProcess(l.ctx, req.Id) + if err != nil { + // 检查是否是"已经处理"的错误 + if err.Error() == "代理处理已经成功,无需重新执行" { + return &types.AdminRetryAgentProcessResp{ + Status: "already_processed", + Message: "代理处理已经成功,无需重新执行", + ProcessedAt: time.Now().Format("2006-01-02 15:04:05"), + }, nil + } + + // 其他错误 + logx.Errorf("重新执行代理处理失败,订单ID: %d, 错误: %v", req.Id, err) + return &types.AdminRetryAgentProcessResp{ + Status: "failed", + Message: err.Error(), + ProcessedAt: time.Now().Format("2006-01-02 15:04:05"), + }, nil + } + + // 执行成功 + return &types.AdminRetryAgentProcessResp{ + Status: "success", + Message: "代理处理重新执行成功", + ProcessedAt: time.Now().Format("2006-01-02 15:04:05"), + }, nil +} diff --git a/app/main/api/internal/logic/admin_order/adminupdateorderlogic.go b/app/main/api/internal/logic/admin_order/adminupdateorderlogic.go new file mode 100644 index 0000000..4ea8ba0 --- /dev/null +++ b/app/main/api/internal/logic/admin_order/adminupdateorderlogic.go @@ -0,0 +1,113 @@ +package admin_order + +import ( + "context" + "database/sql" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateOrderLogic { + return &AdminUpdateOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateOrderLogic) AdminUpdateOrder(req *types.AdminUpdateOrderReq) (resp *types.AdminUpdateOrderResp, err error) { + // 获取原订单信息 + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 查询订单失败 err: %v", err) + } + + // 更新订单字段 + if req.OrderNo != nil { + order.OrderNo = *req.OrderNo + } + if req.PlatformOrderId != nil { + order.PlatformOrderId = sql.NullString{String: *req.PlatformOrderId, Valid: true} + } + if req.PaymentPlatform != nil { + order.PaymentPlatform = *req.PaymentPlatform + } + if req.PaymentScene != nil { + order.PaymentScene = *req.PaymentScene + } + if req.Amount != nil { + order.Amount = *req.Amount + } + if req.Status != nil { + order.Status = *req.Status + } + if req.PayTime != nil { + payTime, err := time.Parse("2006-01-02 15:04:05", *req.PayTime) + if err == nil { + order.PayTime = sql.NullTime{Time: payTime, Valid: true} + } + } + if req.RefundTime != nil { + refundTime, err := time.Parse("2006-01-02 15:04:05", *req.RefundTime) + if err == nil { + order.RefundTime = sql.NullTime{Time: refundTime, Valid: true} + } + } + + // 使用事务更新订单 + err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 更新订单 + _, err := l.svcCtx.OrderModel.Update(ctx, session, order) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 更新订单失败 err: %v", err) + } + + // 处理推广订单状态 + if req.IsPromotion != nil { + promotionOrder, err := l.svcCtx.AdminPromotionOrderModel.FindOneByOrderId(ctx, order.Id) + if err == nil && promotionOrder != nil { + // 如果存在推广订单记录但不需要推广,则删除 + if *req.IsPromotion == 0 { + err = l.svcCtx.AdminPromotionOrderModel.DeleteSoft(ctx, session, promotionOrder) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 删除推广订单失败 err: %v", err) + } + } + } else if *req.IsPromotion == 1 { + // 如果需要推广但不存在记录,则创建 + newPromotionOrder := &model.AdminPromotionOrder{ + OrderId: order.Id, + Version: 1, + } + _, err = l.svcCtx.AdminPromotionOrderModel.Insert(ctx, session, newPromotionOrder) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminUpdateOrder, 创建推广订单失败 err: %v", err) + } + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &types.AdminUpdateOrderResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_platform_user/admincreateplatformuserlogic.go b/app/main/api/internal/logic/admin_platform_user/admincreateplatformuserlogic.go new file mode 100644 index 0000000..ed16690 --- /dev/null +++ b/app/main/api/internal/logic/admin_platform_user/admincreateplatformuserlogic.go @@ -0,0 +1,57 @@ +package admin_platform_user + +import ( + "context" + "database/sql" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminCreatePlatformUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreatePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreatePlatformUserLogic { + return &AdminCreatePlatformUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreatePlatformUserLogic) AdminCreatePlatformUser(req *types.AdminCreatePlatformUserReq) (resp *types.AdminCreatePlatformUserResp, err error) { + // 校验手机号唯一性 + _, err = l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: req.Mobile, Valid: req.Mobile != ""}) + if err == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机号已存在: %s", req.Mobile) + } + if err != model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询手机号失败: %v", err) + } + + user := &model.User{ + Mobile: sql.NullString{String: req.Mobile, Valid: req.Mobile != ""}, + Password: sql.NullString{String: req.Password, Valid: req.Password != ""}, + Nickname: sql.NullString{String: req.Nickname, Valid: req.Nickname != ""}, + Info: req.Info, + Inside: req.Inside, + } + result, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err) + } + id, err := result.LastInsertId() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户ID失败: %v", err) + } + resp = &types.AdminCreatePlatformUserResp{Id: id} + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_platform_user/admindeleteplatformuserlogic.go b/app/main/api/internal/logic/admin_platform_user/admindeleteplatformuserlogic.go new file mode 100644 index 0000000..1bd9969 --- /dev/null +++ b/app/main/api/internal/logic/admin_platform_user/admindeleteplatformuserlogic.go @@ -0,0 +1,43 @@ +package admin_platform_user + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminDeletePlatformUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeletePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeletePlatformUserLogic { + return &AdminDeletePlatformUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeletePlatformUserLogic) AdminDeletePlatformUser(req *types.AdminDeletePlatformUserReq) (resp *types.AdminDeletePlatformUserResp, err error) { + user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err) + } + user.DelState = 1 + user.DeleteTime.Time = time.Now() + user.DeleteTime.Valid = true + err = l.svcCtx.UserModel.DeleteSoft(l.ctx, nil, user) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "软删除用户失败: %v", err) + } + resp = &types.AdminDeletePlatformUserResp{Success: true} + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_platform_user/admingetplatformuserdetaillogic.go b/app/main/api/internal/logic/admin_platform_user/admingetplatformuserdetaillogic.go new file mode 100644 index 0000000..57686ae --- /dev/null +++ b/app/main/api/internal/logic/admin_platform_user/admingetplatformuserdetaillogic.go @@ -0,0 +1,54 @@ +package admin_platform_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetPlatformUserDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetPlatformUserDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetPlatformUserDetailLogic { + return &AdminGetPlatformUserDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetPlatformUserDetailLogic) AdminGetPlatformUserDetail(req *types.AdminGetPlatformUserDetailReq) (resp *types.AdminGetPlatformUserDetailResp, err error) { + user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err) + } + key := l.svcCtx.Config.Encrypt.SecretKey + DecryptMobile, err := crypto.DecryptMobile(user.Mobile.String, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密手机号失败: %v", err) + } + // 查询平台类型(取第一个user_auth) + resp = &types.AdminGetPlatformUserDetailResp{ + Id: user.Id, + Mobile: DecryptMobile, + Nickname: "", + Info: user.Info, + Inside: user.Inside, + Disable: user.Disable, + CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"), + } + if user.Nickname.Valid { + resp.Nickname = user.Nickname.String + } + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_platform_user/admingetplatformuserlistlogic.go b/app/main/api/internal/logic/admin_platform_user/admingetplatformuserlistlogic.go new file mode 100644 index 0000000..75e0329 --- /dev/null +++ b/app/main/api/internal/logic/admin_platform_user/admingetplatformuserlistlogic.go @@ -0,0 +1,94 @@ +package admin_platform_user + +import ( + "context" + "database/sql" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetPlatformUserListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetPlatformUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetPlatformUserListLogic { + return &AdminGetPlatformUserListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetPlatformUserListLogic) AdminGetPlatformUserList(req *types.AdminGetPlatformUserListReq) (resp *types.AdminGetPlatformUserListResp, err error) { + builder := l.svcCtx.UserModel.SelectBuilder() + secretKey := l.svcCtx.Config.Encrypt.SecretKey + if req.Mobile != "" { + // 数据库存密文,搜索时把明文手机号加密后再查询 + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机号加密失败: %v", err) + } + builder = builder.Where("mobile = ?", encryptedMobile) + } + if req.Nickname != "" { + builder = builder.Where("nickname = ?", req.Nickname) + } + if req.Inside != 0 { + builder = builder.Where("inside = ?", req.Inside) + } + if req.CreateTimeStart != "" { + builder = builder.Where("create_time >= ?", req.CreateTimeStart) + } + if req.CreateTimeEnd != "" { + builder = builder.Where("create_time <= ?", req.CreateTimeEnd) + } + + orderBy := "id DESC" + if req.OrderBy != "" && req.OrderType != "" { + orderBy = fmt.Sprintf("%s %s", req.OrderBy, req.OrderType) + } + users, total, err := l.svcCtx.UserModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, orderBy) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户分页失败: %v", err) + } + var items []types.PlatformUserListItem + + for _, user := range users { + mobile := user.Mobile + if mobile.Valid { + encryptedMobile, err := crypto.DecryptMobile(mobile.String, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 解密手机号失败: %+v", err) + } + mobile = sql.NullString{String: encryptedMobile, Valid: true} + } + itemData := types.PlatformUserListItem{ + Id: user.Id, + Mobile: mobile.String, + Nickname: "", + Info: user.Info, + Inside: user.Inside, + Disable: user.Disable, + CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"), + } + if user.Nickname.Valid { + itemData.Nickname = user.Nickname.String + } + items = append(items, itemData) + } + resp = &types.AdminGetPlatformUserListResp{ + Total: total, + Items: items, + } + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_platform_user/adminupdateplatformuserlogic.go b/app/main/api/internal/logic/admin_platform_user/adminupdateplatformuserlogic.go new file mode 100644 index 0000000..2cad5d0 --- /dev/null +++ b/app/main/api/internal/logic/admin_platform_user/adminupdateplatformuserlogic.go @@ -0,0 +1,70 @@ +package admin_platform_user + +import ( + "context" + "database/sql" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdatePlatformUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdatePlatformUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdatePlatformUserLogic { + return &AdminUpdatePlatformUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdatePlatformUserLogic) AdminUpdatePlatformUser(req *types.AdminUpdatePlatformUserReq) (resp *types.AdminUpdatePlatformUserResp, err error) { + user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %d, err: %v", req.Id, err) + } + if req.Mobile != nil { + key := l.svcCtx.Config.Encrypt.SecretKey + EncryptMobile, err := crypto.EncryptMobile(*req.Mobile, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err) + } + user.Mobile = sql.NullString{String: EncryptMobile, Valid: true} + } + if req.Nickname != nil { + user.Nickname = sql.NullString{String: *req.Nickname, Valid: *req.Nickname != ""} + } + if req.Info != nil { + user.Info = *req.Info + } + if req.Inside != nil { + if *req.Inside != 1 && *req.Inside != 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "内部用户状态错误: %d", *req.Inside) + } + user.Inside = *req.Inside + } + if req.Disable != nil { + if *req.Disable != 1 && *req.Disable != 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "封禁状态错误: %d,0-可用 1-禁用", *req.Disable) + } + user.Disable = *req.Disable + } + if req.Password != nil { + user.Password = sql.NullString{String: *req.Password, Valid: *req.Password != ""} + } + _, err = l.svcCtx.UserModel.Update(l.ctx, nil, user) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err) + } + resp = &types.AdminUpdatePlatformUserResp{Success: true} + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_product/admincreateproductlogic.go b/app/main/api/internal/logic/admin_product/admincreateproductlogic.go new file mode 100644 index 0000000..fb1ab1a --- /dev/null +++ b/app/main/api/internal/logic/admin_product/admincreateproductlogic.go @@ -0,0 +1,50 @@ +package admin_product + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "database/sql" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminCreateProductLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateProductLogic { + return &AdminCreateProductLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) { + // 1. 数据转换 + data := &model.Product{ + ProductName: req.ProductName, + ProductEn: req.ProductEn, + Description: req.Description, + Notes: sql.NullString{String: req.Notes, Valid: req.Notes != ""}, + CostPrice: req.CostPrice, + SellPrice: req.SellPrice, + } + + // 2. 数据库操作 + result, err := l.svcCtx.ProductModel.Insert(l.ctx, nil, data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "创建产品失败, err: %v, req: %+v", err, req) + } + + // 3. 返回结果 + id, _ := result.LastInsertId() + return &types.AdminCreateProductResp{Id: id}, nil +} diff --git a/app/main/api/internal/logic/admin_product/admindeleteproductlogic.go b/app/main/api/internal/logic/admin_product/admindeleteproductlogic.go new file mode 100644 index 0000000..d02b9a7 --- /dev/null +++ b/app/main/api/internal/logic/admin_product/admindeleteproductlogic.go @@ -0,0 +1,44 @@ +package admin_product + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminDeleteProductLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteProductLogic { + return &AdminDeleteProductLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) { + // 1. 查询记录是否存在 + record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找产品失败, err: %v, id: %d", err, req.Id) + } + + // 2. 执行软删除 + err = l.svcCtx.ProductModel.DeleteSoft(l.ctx, nil, record) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "删除产品失败, err: %v, id: %d", err, req.Id) + } + + // 3. 返回结果 + return &types.AdminDeleteProductResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_product/admingetproductdetaillogic.go b/app/main/api/internal/logic/admin_product/admingetproductdetaillogic.go new file mode 100644 index 0000000..a43274d --- /dev/null +++ b/app/main/api/internal/logic/admin_product/admingetproductdetaillogic.go @@ -0,0 +1,49 @@ +package admin_product + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetProductDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetProductDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductDetailLogic { + return &AdminGetProductDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetProductDetailLogic) AdminGetProductDetail(req *types.AdminGetProductDetailReq) (resp *types.AdminGetProductDetailResp, err error) { + // 1. 查询记录 + record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找产品失败, err: %v, id: %d", err, req.Id) + } + + // 2. 构建响应 + resp = &types.AdminGetProductDetailResp{ + Id: record.Id, + ProductName: record.ProductName, + ProductEn: record.ProductEn, + Description: record.Description, + Notes: record.Notes.String, + CostPrice: record.CostPrice, + SellPrice: record.SellPrice, + 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_product/admingetproductfeaturelistlogic.go b/app/main/api/internal/logic/admin_product/admingetproductfeaturelistlogic.go new file mode 100644 index 0000000..aa58df0 --- /dev/null +++ b/app/main/api/internal/logic/admin_product/admingetproductfeaturelistlogic.go @@ -0,0 +1,119 @@ +package admin_product + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetProductFeatureListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetProductFeatureListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductFeatureListLogic { + return &AdminGetProductFeatureListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetProductFeatureListLogic) AdminGetProductFeatureList(req *types.AdminGetProductFeatureListReq) (resp *[]types.AdminGetProductFeatureListResp, err error) { + // 1. 构建查询条件 + builder := l.svcCtx.ProductFeatureModel.SelectBuilder(). + Where("product_id = ?", req.ProductId) + + // 2. 执行查询 + list, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "sort ASC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询产品功能列表失败, err: %v, product_id: %d", err, req.ProductId) + } + + // 3. 获取所有功能ID + featureIds := make([]int64, 0, len(list)) + for _, item := range list { + featureIds = append(featureIds, item.FeatureId) + } + + // 4. 并发查询功能详情 + type featureResult struct { + feature *model.Feature + err error + } + + results := make([]featureResult, len(featureIds)) + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for i, id := range featureIds { + source <- struct { + index int + id int64 + }{i, id} + } + }, func(item interface{}, writer mr.Writer[featureResult], cancel func(error)) { + data := item.(struct { + index int + id int64 + }) + feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, data.id) + writer.Write(featureResult{ + feature: feature, + err: err, + }) + }, func(pipe <-chan featureResult, cancel func(error)) { + for result := range pipe { + if result.err != nil { + l.Logger.Errorf("查询功能详情失败, feature_id: %d, err: %v", result.feature.Id, result.err) + continue + } + results = append(results, result) + } + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "并发查询功能详情失败, err: %v", err) + } + + // 5. 构建功能ID到详情的映射 + featureMap := make(map[int64]*model.Feature) + for _, result := range results { + if result.feature != nil { + featureMap[result.feature.Id] = result.feature + } + } + + // 6. 构建响应列表 + items := make([]types.AdminGetProductFeatureListResp, 0, len(list)) + for _, item := range list { + feature, exists := featureMap[item.FeatureId] + if !exists { + continue // 跳过不存在的功能 + } + + listItem := types.AdminGetProductFeatureListResp{ + Id: item.Id, + ProductId: item.ProductId, + FeatureId: item.FeatureId, + ApiId: feature.ApiId, + Name: feature.Name, + Sort: item.Sort, + Enable: item.Enable, + IsImportant: item.IsImportant, + CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"), + } + items = append(items, listItem) + } + + // 7. 返回结果 + return &items, nil +} diff --git a/app/main/api/internal/logic/admin_product/admingetproductlistlogic.go b/app/main/api/internal/logic/admin_product/admingetproductlistlogic.go new file mode 100644 index 0000000..77a5294 --- /dev/null +++ b/app/main/api/internal/logic/admin_product/admingetproductlistlogic.go @@ -0,0 +1,69 @@ +package admin_product + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetProductListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetProductListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetProductListLogic { + return &AdminGetProductListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetProductListLogic) AdminGetProductList(req *types.AdminGetProductListReq) (resp *types.AdminGetProductListResp, err error) { + // 1. 构建查询条件 + builder := l.svcCtx.ProductModel.SelectBuilder() + + // 2. 添加查询条件 + if req.ProductName != nil && *req.ProductName != "" { + builder = builder.Where("product_name LIKE ?", "%"+*req.ProductName+"%") + } + if req.ProductEn != nil && *req.ProductEn != "" { + builder = builder.Where("product_en LIKE ?", "%"+*req.ProductEn+"%") + } + + // 3. 执行分页查询 + list, total, err := l.svcCtx.ProductModel.FindPageListByPageWithTotal( + l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询产品列表失败, err: %v, req: %+v", err, req) + } + + // 4. 构建响应列表 + items := make([]types.ProductListItem, 0, len(list)) + for _, item := range list { + listItem := types.ProductListItem{ + Id: item.Id, + ProductName: item.ProductName, + ProductEn: item.ProductEn, + Description: item.Description, + Notes: item.Notes.String, + CostPrice: item.CostPrice, + SellPrice: item.SellPrice, + CreateTime: item.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: item.UpdateTime.Format("2006-01-02 15:04:05"), + } + items = append(items, listItem) + } + + // 5. 返回结果 + return &types.AdminGetProductListResp{ + Total: total, + Items: items, + }, nil +} diff --git a/app/main/api/internal/logic/admin_product/adminupdateproductfeatureslogic.go b/app/main/api/internal/logic/admin_product/adminupdateproductfeatureslogic.go new file mode 100644 index 0000000..05ae25f --- /dev/null +++ b/app/main/api/internal/logic/admin_product/adminupdateproductfeatureslogic.go @@ -0,0 +1,162 @@ +package admin_product + +import ( + "context" + "sync" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateProductFeaturesLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateProductFeaturesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateProductFeaturesLogic { + return &AdminUpdateProductFeaturesLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateProductFeaturesLogic) AdminUpdateProductFeatures(req *types.AdminUpdateProductFeaturesReq) (resp *types.AdminUpdateProductFeaturesResp, err error) { + // 1. 查询现有关联 + builder := l.svcCtx.ProductFeatureModel.SelectBuilder(). + Where("product_id = ?", req.ProductId) + existingList, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询现有产品功能关联失败, err: %v, product_id: %d", err, req.ProductId) + } + + // 2. 构建现有关联的映射 + existingMap := make(map[int64]*model.ProductFeature) + for _, item := range existingList { + existingMap[item.FeatureId] = item + } + + // 3. 构建新关联的映射 + newMap := make(map[int64]*types.ProductFeatureItem) + for _, item := range req.Features { + newMap[item.FeatureId] = &item + } + + // 4. 在事务中执行更新操作 + err = l.svcCtx.ProductFeatureModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 4.1 处理需要删除的关联 + var mu sync.Mutex + var deleteIds []int64 + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for featureId, existing := range existingMap { + if _, exists := newMap[featureId]; !exists { + source <- existing.Id + } + } + }, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) { + id := item.(int64) + mu.Lock() + deleteIds = append(deleteIds, id) + mu.Unlock() + }, func(pipe <-chan struct{}, cancel func(error)) { + // 等待所有ID收集完成 + }) + + if err != nil { + return errors.Wrapf(err, "收集待删除ID失败") + } + + // 批量删除 + if len(deleteIds) > 0 { + for _, id := range deleteIds { + err = l.svcCtx.ProductFeatureModel.Delete(ctx, session, id) + if err != nil { + return errors.Wrapf(err, "删除产品功能关联失败, product_id: %d, id: %d", + req.ProductId, id) + } + } + } + + // 4.2 并发处理需要新增或更新的关联 + var updateErr error + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for featureId, newItem := range newMap { + source <- struct { + featureId int64 + newItem *types.ProductFeatureItem + existing *model.ProductFeature + }{ + featureId: featureId, + newItem: newItem, + existing: existingMap[featureId], + } + } + }, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) { + data := item.(struct { + featureId int64 + newItem *types.ProductFeatureItem + existing *model.ProductFeature + }) + + if data.existing != nil { + // 更新现有关联 + data.existing.Sort = data.newItem.Sort + data.existing.Enable = data.newItem.Enable + data.existing.IsImportant = data.newItem.IsImportant + _, err = l.svcCtx.ProductFeatureModel.Update(ctx, session, data.existing) + if err != nil { + updateErr = errors.Wrapf(err, "更新产品功能关联失败, product_id: %d, feature_id: %d", + req.ProductId, data.featureId) + cancel(updateErr) + return + } + } else { + // 新增关联 + newFeature := &model.ProductFeature{ + ProductId: req.ProductId, + FeatureId: data.featureId, + Sort: data.newItem.Sort, + Enable: data.newItem.Enable, + IsImportant: data.newItem.IsImportant, + } + _, err = l.svcCtx.ProductFeatureModel.Insert(ctx, session, newFeature) + if err != nil { + updateErr = errors.Wrapf(err, "新增产品功能关联失败, product_id: %d, feature_id: %d", + req.ProductId, data.featureId) + cancel(updateErr) + return + } + } + }, func(pipe <-chan struct{}, cancel func(error)) { + // 等待所有更新完成 + }) + + if err != nil { + return errors.Wrapf(err, "并发更新产品功能关联失败") + } + if updateErr != nil { + return updateErr + } + + // 不自动更新产品成本价,保留用户手动设置的值 + return nil + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "更新产品功能关联失败, err: %v, req: %+v", err, req) + } + + // 5. 返回结果 + return &types.AdminUpdateProductFeaturesResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_product/adminupdateproductlogic.go b/app/main/api/internal/logic/admin_product/adminupdateproductlogic.go new file mode 100644 index 0000000..767f198 --- /dev/null +++ b/app/main/api/internal/logic/admin_product/adminupdateproductlogic.go @@ -0,0 +1,65 @@ +package admin_product + +import ( + "context" + "database/sql" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateProductLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateProductLogic { + return &AdminUpdateProductLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateProductLogic) AdminUpdateProduct(req *types.AdminUpdateProductReq) (resp *types.AdminUpdateProductResp, err error) { + // 1. 查询记录是否存在 + record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查找产品失败, err: %v, id: %d", err, req.Id) + } + + // 2. 更新字段 + if req.ProductName != nil { + record.ProductName = *req.ProductName + } + if req.ProductEn != nil { + record.ProductEn = *req.ProductEn + } + if req.Description != nil { + record.Description = *req.Description + } + if req.Notes != nil { + record.Notes = sql.NullString{String: *req.Notes, Valid: *req.Notes != ""} + } + if req.CostPrice != nil { + record.CostPrice = *req.CostPrice + } + if req.SellPrice != nil { + record.SellPrice = *req.SellPrice + } + + // 3. 执行更新操作 + _, err = l.svcCtx.ProductModel.Update(l.ctx, nil, record) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "更新产品失败, err: %v, req: %+v", err, req) + } + + // 4. 返回结果 + return &types.AdminUpdateProductResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/createpromotionlinklogic.go b/app/main/api/internal/logic/admin_promotion/createpromotionlinklogic.go new file mode 100644 index 0000000..5fd5231 --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/createpromotionlinklogic.go @@ -0,0 +1,137 @@ +package admin_promotion + +import ( + "context" + "crypto/rand" + "fmt" + "math/big" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type CreatePromotionLinkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCreatePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreatePromotionLinkLogic { + return &CreatePromotionLinkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 生成6位随机字符串(大小写字母和数字) +func generateRandomString() (string, error) { + const ( + chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + length = 6 + ) + + result := make([]byte, length) + for i := 0; i < length; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) + if err != nil { + return "", err + } + result[i] = chars[num.Int64()] + } + return string(result), nil +} + +func (l *CreatePromotionLinkLogic) CreatePromotionLink(req *types.CreatePromotionLinkReq) (resp *types.CreatePromotionLinkResp, err error) { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接, 获取用户信息失败, %+v", getUidErr) + } + + // 生成唯一URL + var url string + maxRetries := 5 // 最大重试次数 + for i := 0; i < maxRetries; i++ { + // 生成6位随机字符串 + randomStr, err := generateRandomString() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接, 生成随机字符串失败, %+v", err) + } + + // 检查URL是否已存在 + existLink, err := l.svcCtx.AdminPromotionLinkModel.FindOneByUrl(l.ctx, randomStr) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建推广链接, 检查URL是否存在失败, %+v", err) + } + + if existLink != nil { + continue // URL已存在,继续尝试 + } + + // URL可用 + url = randomStr + break + } + + if url == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建推广链接失败, 多次尝试生成唯一URL均失败") + } + // url = fmt.Sprintf("%s/%s", l.svcCtx.Config.AdminPromotion.URLDomain, url) + + // 创建推广链接 + link := &model.AdminPromotionLink{ + Name: req.Name, + Url: url, + AdminUserId: adminUserId, + } + + var linkId int64 + err = l.svcCtx.AdminPromotionLinkModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + result, err := l.svcCtx.AdminPromotionLinkModel.Insert(l.ctx, session, link) + if err != nil { + return fmt.Errorf("创建推广链接失败, %+v", err) + } + + linkId, err = result.LastInsertId() + if err != nil { + return fmt.Errorf("获取推广链接ID失败, %+v", err) + } + + // 创建总统计记录 + totalStats := &model.AdminPromotionLinkStatsTotal{ + LinkId: linkId, + } + _, err = l.svcCtx.AdminPromotionLinkStatsTotalModel.Insert(l.ctx, session, totalStats) + if err != nil { + return fmt.Errorf("创建推广链接总统计记录失败, %+v", err) + } + + // 创建统计历史记录 + historyStats := &model.AdminPromotionLinkStatsHistory{ + LinkId: linkId, + StatsDate: time.Now().Truncate(24 * time.Hour), + } + _, err = l.svcCtx.AdminPromotionLinkStatsHistoryModel.Insert(l.ctx, session, historyStats) + if err != nil { + return fmt.Errorf("创建推广链接统计历史记录失败, %+v", err) + } + return nil + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建推广链接失败, %+v", err) + } + + return &types.CreatePromotionLinkResp{ + Id: linkId, + Url: url, + }, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/deletepromotionlinklogic.go b/app/main/api/internal/logic/admin_promotion/deletepromotionlinklogic.go new file mode 100644 index 0000000..3bdd18e --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/deletepromotionlinklogic.go @@ -0,0 +1,91 @@ +package admin_promotion + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type DeletePromotionLinkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDeletePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeletePromotionLinkLogic { + return &DeletePromotionLinkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeletePromotionLinkLogic) DeletePromotionLink(req *types.DeletePromotionLinkReq) error { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "删除推广链接, 获取用户信息失败, %+v", getUidErr) + } + + // 获取链接信息 + link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id) + if err != nil { + return errors.Wrapf(err, "删除推广链接, 获取链接信息失败, %+v", err) + } + + // 验证用户权限 + if link.AdminUserId != adminUserId { + return errors.Wrapf(xerr.NewErrMsg("无权限删除此链接"), "删除推广链接, 无权限删除此链接, %+v", link) + } + + // 在事务中执行所有删除操作 + err = l.svcCtx.AdminPromotionLinkModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 软删除链接 + err = l.svcCtx.AdminPromotionLinkModel.DeleteSoft(l.ctx, session, link) + if err != nil { + return errors.Wrapf(err, "删除推广链接, 软删除链接失败, %+v", err) + } + + // 软删除总统计记录 + totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, link.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "删除推广链接, 获取总统计记录失败, %+v", err) + } + if totalStats != nil { + err = l.svcCtx.AdminPromotionLinkStatsTotalModel.DeleteSoft(l.ctx, session, totalStats) + if err != nil { + return errors.Wrapf(err, "删除推广链接, 软删除总统计记录失败, %+v", err) + } + } + + // 软删除历史统计记录 + builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder() + builder = builder.Where("link_id = ?", link.Id) + historyStats, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "") + if err != nil { + return errors.Wrapf(err, "删除推广链接, 获取历史统计记录失败, %+v", err) + } + for _, stat := range historyStats { + err = l.svcCtx.AdminPromotionLinkStatsHistoryModel.DeleteSoft(l.ctx, session, stat) + if err != nil { + return errors.Wrapf(err, "删除推广链接, 软删除历史统计记录失败, %+v", err) + } + } + + return nil + }) + + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除推广链接失败, %+v", err) + } + + return nil +} diff --git a/app/main/api/internal/logic/admin_promotion/getpromotionlinkdetaillogic.go b/app/main/api/internal/logic/admin_promotion/getpromotionlinkdetaillogic.go new file mode 100644 index 0000000..a8d465b --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/getpromotionlinkdetaillogic.go @@ -0,0 +1,65 @@ +package admin_promotion + +import ( + "context" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetPromotionLinkDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetPromotionLinkDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionLinkDetailLogic { + return &GetPromotionLinkDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetPromotionLinkDetailLogic) GetPromotionLinkDetail(req *types.GetPromotionLinkDetailReq) (resp *types.GetPromotionLinkDetailResp, err error) { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr) + } + + // 获取链接信息 + link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(err, "获取链接信息失败, %+v", err) + } + + // 验证用户权限 + if link.AdminUserId != adminUserId { + return nil, errors.Wrapf(xerr.NewErrMsg("无权限访问此链接"), "获取链接信息失败, 无权限访问此链接, %+v", link) + } + + // 获取总统计 + totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOne(l.ctx, link.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(err, "获取总统计失败, %+v", err) + } + return &types.GetPromotionLinkDetailResp{ + Name: link.Name, + Url: link.Url, + ClickCount: totalStats.ClickCount, + PayCount: totalStats.PayCount, + PayAmount: fmt.Sprintf("%.2f", totalStats.PayAmount), + CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: link.UpdateTime.Format("2006-01-02 15:04:05"), + LastClickTime: totalStats.LastClickTime.Time.Format("2006-01-02 15:04:05"), + LastPayTime: totalStats.LastPayTime.Time.Format("2006-01-02 15:04:05"), + }, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/getpromotionlinklistlogic.go b/app/main/api/internal/logic/admin_promotion/getpromotionlinklistlogic.go new file mode 100644 index 0000000..d4093f4 --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/getpromotionlinklistlogic.go @@ -0,0 +1,104 @@ +package admin_promotion + +import ( + "context" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetPromotionLinkListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetPromotionLinkListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionLinkListLogic { + return &GetPromotionLinkListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetPromotionLinkListLogic) GetPromotionLinkList(req *types.GetPromotionLinkListReq) (resp *types.GetPromotionLinkListResp, err error) { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr) + } + + // 构建查询条件 + builder := l.svcCtx.AdminPromotionLinkModel.SelectBuilder() + builder = builder.Where("admin_user_id = ?", adminUserId) + if req.Name != "" { + builder = builder.Where("name LIKE ?", "%"+req.Name+"%") + } + if req.Url != "" { + builder = builder.Where("url LIKE ?", "%"+req.Url+"%") + } + + // 获取列表和总数 + links, total, err := l.svcCtx.AdminPromotionLinkModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(err, "获取推广链接列表失败, %+v", err) + } + + // 使用MapReduce并发获取统计数据 + items := make([]types.PromotionLinkItem, len(links)) + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for _, link := range links { + source <- link + } + }, func(item interface{}, writer mr.Writer[types.PromotionLinkItem], cancel func(error)) { + link := item.(*model.AdminPromotionLink) + // 获取总统计 + totalStats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, link.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + cancel(errors.Wrapf(err, "获取总统计失败, linkId: %d, %+v", link.Id, err)) + return + } + writer.Write(types.PromotionLinkItem{ + Id: link.Id, + Name: link.Name, + Url: link.Url, + ClickCount: totalStats.ClickCount, + PayCount: totalStats.PayCount, + PayAmount: fmt.Sprintf("%.2f", totalStats.PayAmount), + CreateTime: link.CreateTime.Format("2006-01-02 15:04:05"), + LastClickTime: func() string { + if totalStats.LastClickTime.Valid { + return totalStats.LastClickTime.Time.Format("2006-01-02 15:04:05") + } + return "" + }(), + LastPayTime: func() string { + if totalStats.LastPayTime.Valid { + return totalStats.LastPayTime.Time.Format("2006-01-02 15:04:05") + } + return "" + }(), + }) + }, func(pipe <-chan types.PromotionLinkItem, cancel func(error)) { + for i := 0; i < len(links); i++ { + item := <-pipe + items[i] = item + } + }) + if err != nil { + return nil, errors.Wrapf(err, "获取推广链接统计数据失败, %+v", err) + } + + return &types.GetPromotionLinkListResp{ + Total: total, + Items: items, + }, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/getpromotionstatshistorylogic.go b/app/main/api/internal/logic/admin_promotion/getpromotionstatshistorylogic.go new file mode 100644 index 0000000..9f07824 --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/getpromotionstatshistorylogic.go @@ -0,0 +1,83 @@ +package admin_promotion + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetPromotionStatsHistoryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetPromotionStatsHistoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionStatsHistoryLogic { + return &GetPromotionStatsHistoryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetPromotionStatsHistoryLogic) GetPromotionStatsHistory(req *types.GetPromotionStatsHistoryReq) (resp []types.PromotionStatsHistoryItem, err error) { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr) + } + // 构建查询条件 + builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder() + + // 如果有日期范围,添加日期过滤 + if req.StartDate != "" && req.EndDate != "" { + startDate, err := time.Parse("2006-01-02", req.StartDate) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "开始日期格式错误") + } + endDate, err := time.Parse("2006-01-02", req.EndDate) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "结束日期格式错误") + } + // 将结束日期设置为当天的最后一刻 + endDate = endDate.Add(24*time.Hour - time.Second) + builder = builder.Where("stats_date BETWEEN ? AND ?", startDate, endDate) + } + + // 获取历史统计数据 + historyStats, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "stats_date DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取历史统计数据失败") + } + + // 转换为响应格式 + resp = make([]types.PromotionStatsHistoryItem, 0, len(historyStats)) + for _, stat := range historyStats { + // 验证链接是否属于当前用户 + link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, stat.LinkId) + if err != nil { + continue // 如果链接不存在,跳过该记录 + } + if link.AdminUserId != adminUserId { + continue // 如果链接不属于当前用户,跳过该记录 + } + + resp = append(resp, types.PromotionStatsHistoryItem{ + Id: stat.Id, + LinkId: stat.LinkId, + PayAmount: stat.PayAmount, + ClickCount: stat.ClickCount, + PayCount: stat.PayCount, + StatsDate: stat.StatsDate.Format("2006-01-02"), + }) + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/getpromotionstatstotallogic.go b/app/main/api/internal/logic/admin_promotion/getpromotionstatstotallogic.go new file mode 100644 index 0000000..5c7cdb4 --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/getpromotionstatstotallogic.go @@ -0,0 +1,166 @@ +package admin_promotion + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetPromotionStatsTotalLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetPromotionStatsTotalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPromotionStatsTotalLogic { + return &GetPromotionStatsTotalLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetPromotionStatsTotalLogic) GetPromotionStatsTotal(req *types.GetPromotionStatsTotalReq) (resp *types.GetPromotionStatsTotalResp, err error) { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取当前用户ID失败, %+v", getUidErr) + } + + // 获取用户的所有推广链接 + linkBuilder := l.svcCtx.AdminPromotionLinkModel.SelectBuilder() + linkBuilder = linkBuilder.Where("admin_user_id = ?", adminUserId) + links, err := l.svcCtx.AdminPromotionLinkModel.FindAll(l.ctx, linkBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广链接列表失败, %+v", err) + } + + // 如果没有推广链接,返回空统计 + if len(links) == 0 { + return &types.GetPromotionStatsTotalResp{}, nil + } + + // 构建链接ID列表 + linkIds := make([]int64, len(links)) + for i, link := range links { + linkIds[i] = link.Id + } + + // 获取并计算总统计数据 + var totalClickCount, totalPayCount int64 + var totalPayAmount float64 + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for _, linkId := range linkIds { + source <- linkId + } + }, func(item interface{}, writer mr.Writer[struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }], cancel func(error)) { + linkId := item.(int64) + stats, err := l.svcCtx.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(l.ctx, linkId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取总统计数据失败, linkId: %d, %+v", linkId, err)) + return + } + if stats != nil { + writer.Write(struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }{ + ClickCount: stats.ClickCount, + PayCount: stats.PayCount, + PayAmount: stats.PayAmount, + }) + } + }, func(pipe <-chan struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }, cancel func(error)) { + for stats := range pipe { + totalClickCount += stats.ClickCount + totalPayCount += stats.PayCount + totalPayAmount += stats.PayAmount + } + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取总统计数据失败, %+v", err) + } + + // 获取今日统计数据 + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + var todayClickCount, todayPayCount int64 + var todayPayAmount float64 + + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for _, linkId := range linkIds { + source <- linkId + } + }, func(item interface{}, writer mr.Writer[struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }], cancel func(error)) { + linkId := item.(int64) + builder := l.svcCtx.AdminPromotionLinkStatsHistoryModel.SelectBuilder() + builder = builder.Where("link_id = ? AND DATE(stats_date) = DATE(?)", linkId, today) + histories, err := l.svcCtx.AdminPromotionLinkStatsHistoryModel.FindAll(l.ctx, builder, "") + if err != nil { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取今日统计数据失败, linkId: %d, %+v", linkId, err)) + return + } + + var clickCount, payCount int64 + var payAmount float64 + for _, history := range histories { + clickCount += history.ClickCount + payCount += history.PayCount + payAmount += history.PayAmount + } + + writer.Write(struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }{ + ClickCount: clickCount, + PayCount: payCount, + PayAmount: payAmount, + }) + }, func(pipe <-chan struct { + ClickCount int64 + PayCount int64 + PayAmount float64 + }, cancel func(error)) { + for stats := range pipe { + todayClickCount += stats.ClickCount + todayPayCount += stats.PayCount + todayPayAmount += stats.PayAmount + } + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取今日统计数据失败, %+v", err) + } + + return &types.GetPromotionStatsTotalResp{ + TodayClickCount: int64(todayClickCount), + TodayPayCount: int64(todayPayCount), + TodayPayAmount: todayPayAmount, + TotalClickCount: int64(totalClickCount), + TotalPayCount: int64(totalPayCount), + TotalPayAmount: totalPayAmount, + }, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/recordlinkclicklogic.go b/app/main/api/internal/logic/admin_promotion/recordlinkclicklogic.go new file mode 100644 index 0000000..eaaeebe --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/recordlinkclicklogic.go @@ -0,0 +1,57 @@ +package admin_promotion + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type RecordLinkClickLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewRecordLinkClickLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RecordLinkClickLogic { + return &RecordLinkClickLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *RecordLinkClickLogic) RecordLinkClick(req *types.RecordLinkClickReq) (resp *types.RecordLinkClickResp, err error) { + // 校验路径格式 + if len(req.Path) != 6 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的推广链接路径") + } + + // 检查是否只包含大小写字母和数字 + for _, char := range req.Path { + if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的推广链接路径") + } + } + url := "" + // url := fmt.Sprintf("%s/%s", l.svcCtx.Config.AdminPromotion.URLDomain, req.Path) + + link, err := l.svcCtx.AdminPromotionLinkModel.FindOneByUrl(l.ctx, url) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "无效的推广链接路径") + } + + // 使用 statsService 更新点击统计 + err = l.svcCtx.AdminPromotionLinkStatsService.UpdateLinkStats(l.ctx, link.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新点击统计失败: %+v", err) + } + + return &types.RecordLinkClickResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_promotion/updatepromotionlinklogic.go b/app/main/api/internal/logic/admin_promotion/updatepromotionlinklogic.go new file mode 100644 index 0000000..bdb9ffe --- /dev/null +++ b/app/main/api/internal/logic/admin_promotion/updatepromotionlinklogic.go @@ -0,0 +1,57 @@ +package admin_promotion + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdatePromotionLinkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewUpdatePromotionLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePromotionLinkLogic { + return &UpdatePromotionLinkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdatePromotionLinkLogic) UpdatePromotionLink(req *types.UpdatePromotionLinkReq) error { + // 获取当前用户ID + adminUserId, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新推广链接, 获取用户信息失败, %+v", getUidErr) + } + + // 获取链接信息 + link, err := l.svcCtx.AdminPromotionLinkModel.FindOne(l.ctx, req.Id) + if err != nil { + return errors.Wrapf(err, "更新推广链接, 获取链接信息失败, %+v", err) + } + + // 验证用户权限 + if link.AdminUserId != adminUserId { + return errors.Wrapf(xerr.NewErrMsg("无权限修改此链接"), "更新推广链接, 无权限修改此链接, %+v", link) + } + + // 更新链接信息 + link.Name = *req.Name + link.UpdateTime = time.Now() + + _, err = l.svcCtx.AdminPromotionLinkModel.Update(l.ctx, nil, link) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新推广链接, 更新链接信息失败, %+v", err) + } + return nil +} diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go new file mode 100644 index 0000000..e1a7d2b --- /dev/null +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanupconfiglistlogic.go @@ -0,0 +1,62 @@ +package admin_query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/globalkey" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetQueryCleanupConfigListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetQueryCleanupConfigListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupConfigListLogic { + return &AdminGetQueryCleanupConfigListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetQueryCleanupConfigListLogic) AdminGetQueryCleanupConfigList(req *types.AdminGetQueryCleanupConfigListReq) (resp *types.AdminGetQueryCleanupConfigListResp, err error) { + // 构建查询条件 + builder := l.svcCtx.QueryCleanupConfigModel.SelectBuilder(). + Where("del_state = ?", globalkey.DelStateNo) + + if req.Status > 0 { + builder = builder.Where("status = ?", req.Status) + } + + // 查询配置列表 + configs, err := l.svcCtx.QueryCleanupConfigModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理配置列表失败 err: %v", err) + } + + // 构建响应 + resp = &types.AdminGetQueryCleanupConfigListResp{ + Items: make([]types.QueryCleanupConfigItem, 0, len(configs)), + } + + for _, config := range configs { + item := types.QueryCleanupConfigItem{ + Id: config.Id, + ConfigKey: config.ConfigKey, + ConfigValue: config.ConfigValue, + ConfigDesc: config.ConfigDesc, + Status: config.Status, + CreateTime: config.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: config.UpdateTime.Format("2006-01-02 15:04:05"), + } + resp.Items = append(resp.Items, item) + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go new file mode 100644 index 0000000..d2dc239 --- /dev/null +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanupdetaillistlogic.go @@ -0,0 +1,126 @@ +package admin_query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "bdrp-server/common/xerr" + "sync" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetQueryCleanupDetailListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetQueryCleanupDetailListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupDetailListLogic { + return &AdminGetQueryCleanupDetailListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetQueryCleanupDetailListLogic) AdminGetQueryCleanupDetailList(req *types.AdminGetQueryCleanupDetailListReq) (resp *types.AdminGetQueryCleanupDetailListResp, err error) { + // 1. 验证清理日志是否存在 + _, err = l.svcCtx.QueryCleanupLogModel.FindOne(l.ctx, req.LogId) + if err != nil { + if err == model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "清理日志不存在, log_id: %d", req.LogId) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志失败, log_id: %d, err: %v", req.LogId, err) + } + + // 2. 构建查询条件 + builder := l.svcCtx.QueryCleanupDetailModel.SelectBuilder(). + Where("cleanup_log_id = ?", req.LogId). + Where("del_state = ?", globalkey.DelStateNo) + + // 3. 并发获取总数和列表 + var total int64 + var details []*model.QueryCleanupDetail + err = mr.Finish(func() error { + var err error + total, err = l.svcCtx.QueryCleanupDetailModel.FindCount(l.ctx, builder, "id") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理详情总数失败 err: %v", err) + } + return nil + }, func() error { + var err error + details, err = l.svcCtx.QueryCleanupDetailModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理详情列表失败 err: %v", err) + } + return nil + }) + if err != nil { + return nil, err + } + + // 4. 获取所有产品ID + productIds := make([]int64, 0, len(details)) + for _, detail := range details { + productIds = append(productIds, detail.ProductId) + } + + // 5. 并发获取产品信息 + productMap := make(map[int64]string) + var mu sync.Mutex + err = mr.MapReduceVoid(func(source chan<- interface{}) { + for _, productId := range productIds { + source <- productId + } + }, func(item interface{}, writer mr.Writer[struct{}], cancel func(error)) { + productId := item.(int64) + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, productId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + cancel(errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询产品信息失败, product_id: %d, err: %v", productId, err)) + return + } + mu.Lock() + if product != nil { + productMap[productId] = product.ProductName + } else { + productMap[productId] = "" // 产品不存在时设置为空字符串 + } + mu.Unlock() + writer.Write(struct{}{}) + }, func(pipe <-chan struct{}, cancel func(error)) { + for range pipe { + } + }) + if err != nil { + return nil, err + } + + // 6. 构建响应 + resp = &types.AdminGetQueryCleanupDetailListResp{ + Total: total, + Items: make([]types.QueryCleanupDetailItem, 0, len(details)), + } + + for _, detail := range details { + item := types.QueryCleanupDetailItem{ + Id: detail.Id, + CleanupLogId: detail.CleanupLogId, + QueryId: detail.QueryId, + OrderId: detail.OrderId, + UserId: detail.UserId, + ProductName: productMap[detail.ProductId], + QueryState: detail.QueryState, + CreateTimeOld: detail.CreateTimeOld.Format("2006-01-02 15:04:05"), + CreateTime: detail.CreateTime.Format("2006-01-02 15:04:05"), + } + resp.Items = append(resp.Items, item) + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go b/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go new file mode 100644 index 0000000..7e5b5d3 --- /dev/null +++ b/app/main/api/internal/logic/admin_query/admingetquerycleanuploglistlogic.go @@ -0,0 +1,88 @@ +package admin_query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetQueryCleanupLogListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetQueryCleanupLogListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryCleanupLogListLogic { + return &AdminGetQueryCleanupLogListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetQueryCleanupLogListLogic) AdminGetQueryCleanupLogList(req *types.AdminGetQueryCleanupLogListReq) (resp *types.AdminGetQueryCleanupLogListResp, err error) { + // 构建查询条件 + builder := l.svcCtx.QueryCleanupLogModel.SelectBuilder(). + Where("del_state = ?", globalkey.DelStateNo) + + if req.Status > 0 { + builder = builder.Where("status = ?", req.Status) + } + if req.StartTime != "" { + builder = builder.Where("cleanup_time >= ?", req.StartTime) + } + if req.EndTime != "" { + builder = builder.Where("cleanup_time <= ?", req.EndTime) + } + + // 并发获取总数和列表 + var total int64 + var logs []*model.QueryCleanupLog + err = mr.Finish(func() error { + var err error + total, err = l.svcCtx.QueryCleanupLogModel.FindCount(l.ctx, builder, "id") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志总数失败 err: %v", err) + } + return nil + }, func() error { + var err error + logs, err = l.svcCtx.QueryCleanupLogModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询清理日志列表失败 err: %v", err) + } + return nil + }) + if err != nil { + return nil, err + } + + // 构建响应 + resp = &types.AdminGetQueryCleanupLogListResp{ + Total: total, + Items: make([]types.QueryCleanupLogItem, 0, len(logs)), + } + + for _, log := range logs { + item := types.QueryCleanupLogItem{ + Id: log.Id, + CleanupTime: log.CleanupTime.Format("2006-01-02 15:04:05"), + CleanupBefore: log.CleanupBefore.Format("2006-01-02 15:04:05"), + Status: log.Status, + AffectedRows: log.AffectedRows, + ErrorMsg: log.ErrorMsg.String, + Remark: log.Remark.String, + CreateTime: log.CreateTime.Format("2006-01-02 15:04:05"), + } + resp.Items = append(resp.Items, item) + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go new file mode 100644 index 0000000..79bc673 --- /dev/null +++ b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go @@ -0,0 +1,189 @@ +package admin_query + +import ( + "context" + "database/sql" + "encoding/hex" + "encoding/json" + "fmt" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetQueryDetailByOrderIdLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetQueryDetailByOrderIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetQueryDetailByOrderIdLogic { + return &AdminGetQueryDetailByOrderIdLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *types.AdminGetQueryDetailByOrderIdReq) (resp *types.AdminGetQueryDetailByOrderIdResp, err error) { + + // 获取报告信息 + queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err) + } + + var query types.AdminGetQueryDetailByOrderIdResp + query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") + query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05") + + // 解密查询数据 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %+v", err) + } + processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key) + if processParamsErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr) + } + processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key) + if processErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr) + } + updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData) + if updateFeatureAndProductFeatureErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr) + } + // 复制报告数据 + err = copier.Copy(&query, queryModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err) + } + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) + } + query.ProductName = product.ProductName + return &types.AdminGetQueryDetailByOrderIdResp{ + Id: query.Id, + OrderId: query.OrderId, + UserId: query.UserId, + ProductName: query.ProductName, + QueryParams: query.QueryParams, + QueryData: query.QueryData, + CreateTime: query.CreateTime, + UpdateTime: query.UpdateTime, + QueryState: query.QueryState, + }, nil +} + +// ProcessQueryData 解密和反序列化 QueryData +func ProcessQueryData(queryData sql.NullString, target *[]types.AdminQueryItem, key []byte) error { + queryDataStr := lzUtils.NullStringToString(queryData) + if queryDataStr == "" { + return nil + } + + // 解密数据 + decryptedData, decryptErr := crypto.AesDecrypt(queryDataStr, key) + if decryptErr != nil { + return decryptErr + } + + // 解析 JSON 数组 + var decryptedArray []map[string]interface{} + unmarshalErr := json.Unmarshal(decryptedData, &decryptedArray) + if unmarshalErr != nil { + return unmarshalErr + } + + // 确保 target 具有正确的长度 + if len(*target) == 0 { + *target = make([]types.AdminQueryItem, len(decryptedArray)) + } + + // 填充解密后的数据到 target + for i := 0; i < len(decryptedArray); i++ { + // 直接填充解密数据到 Data 字段 + (*target)[i].Data = decryptedArray[i] + } + return nil +} + +// ProcessQueryParams解密和反序列化 QueryParams +func ProcessQueryParams(QueryParams string, target *map[string]interface{}, key []byte) error { + // 解密 QueryParams + decryptedData, decryptErr := crypto.AesDecrypt(QueryParams, key) + if decryptErr != nil { + return decryptErr + } + + // 反序列化解密后的数据 + unmarshalErr := json.Unmarshal(decryptedData, target) + if unmarshalErr != nil { + return unmarshalErr + } + + return nil +} + +func (l *AdminGetQueryDetailByOrderIdLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.AdminQueryItem) error { + // 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引 + for i := len(*target) - 1; i >= 0; i-- { + queryItem := &(*target)[i] + + // 确保 Data 为 map 类型 + data, ok := queryItem.Data.(map[string]interface{}) + if !ok { + return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型") + } + + // 从 Data 中获取 apiID + apiID, ok := data["apiID"].(string) + if !ok { + return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型") + } + + // 查询 Feature + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID) + if err != nil { + // 如果 Feature 查不到,也要删除当前 QueryItem + *target = append((*target)[:i], (*target)[i+1:]...) + continue + } + + // 查询 ProductFeatureModel + builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID) + productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err) + } + + // 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项 + var featureData map[string]interface{} + sort := 0 + for _, pf := range productFeatures { + if pf.FeatureId == feature.Id { // 确保和 Feature 关联 + sort = int(pf.Sort) + break // 找到第一个符合条件的就退出循环 + } + } + featureData = map[string]interface{}{ + "featureName": feature.Name, + "sort": sort, + } + + // 更新 queryItem 的 Feature 字段(不是数组) + queryItem.Feature = featureData + } + + return nil +} diff --git a/app/main/api/internal/logic/admin_query/adminupdatequerycleanupconfiglogic.go b/app/main/api/internal/logic/admin_query/adminupdatequerycleanupconfiglogic.go new file mode 100644 index 0000000..f30fb44 --- /dev/null +++ b/app/main/api/internal/logic/admin_query/adminupdatequerycleanupconfiglogic.go @@ -0,0 +1,63 @@ +package admin_query + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateQueryCleanupConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateQueryCleanupConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateQueryCleanupConfigLogic { + return &AdminUpdateQueryCleanupConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateQueryCleanupConfigLogic) AdminUpdateQueryCleanupConfig(req *types.AdminUpdateQueryCleanupConfigReq) (resp *types.AdminUpdateQueryCleanupConfigResp, err error) { + // 使用事务处理更新操作 + err = l.svcCtx.QueryCleanupConfigModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 1. 查询配置是否存在 + config, err := l.svcCtx.QueryCleanupConfigModel.FindOne(ctx, req.Id) + if err != nil { + if err == model.ErrNotFound { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "配置不存在, id: %d", req.Id) + } + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询配置失败, id: %d, err: %v", req.Id, err) + } + + // 2. 更新配置 + config.ConfigValue = req.ConfigValue + config.Status = req.Status + config.UpdateTime = time.Now() + + _, err = l.svcCtx.QueryCleanupConfigModel.Update(ctx, session, config) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新配置失败, id: %d, err: %v", req.Id, err) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &types.AdminUpdateQueryCleanupConfigResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role/createrolelogic.go b/app/main/api/internal/logic/admin_role/createrolelogic.go new file mode 100644 index 0000000..d27b1a6 --- /dev/null +++ b/app/main/api/internal/logic/admin_role/createrolelogic.go @@ -0,0 +1,83 @@ +package admin_role + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type CreateRoleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCreateRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRoleLogic { + return &CreateRoleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateRoleLogic) CreateRole(req *types.CreateRoleReq) (resp *types.CreateRoleResp, err error) { + // 检查角色编码是否已存在 + roleModel, err := l.svcCtx.AdminRoleModel.FindOneByRoleCode(l.ctx, req.RoleCode) + if err != nil && err != model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建角色失败: %v", err) + } + if roleModel != nil && roleModel.RoleName == req.RoleName { + return nil, errors.Wrapf(xerr.NewErrMsg("角色名称已存在"), "创建角色失败, 角色名称已存在: %v", err) + } + // 创建角色 + role := &model.AdminRole{ + RoleName: req.RoleName, + RoleCode: req.RoleCode, + Description: req.Description, + Status: req.Status, + Sort: req.Sort, + } + var roleId int64 + // 使用事务创建角色和关联菜单 + err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建角色 + result, err := l.svcCtx.AdminRoleModel.Insert(ctx, session, role) + if err != nil { + return errors.New("插入新角色失败") + } + roleId, err = result.LastInsertId() + if err != nil { + return errors.New("获取新角色ID失败") + } + + // 创建角色菜单关联 + if len(req.MenuIds) > 0 { + for _, menuId := range req.MenuIds { + roleMenu := &model.AdminRoleMenu{ + RoleId: roleId, + MenuId: menuId, + } + _, err = l.svcCtx.AdminRoleMenuModel.Insert(ctx, session, roleMenu) + if err != nil { + return errors.New("插入角色菜单关联失败") + } + } + } + + return nil + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建角色失败: %v", err) + } + + return &types.CreateRoleResp{ + Id: roleId, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role/deleterolelogic.go b/app/main/api/internal/logic/admin_role/deleterolelogic.go new file mode 100644 index 0000000..ac1e92e --- /dev/null +++ b/app/main/api/internal/logic/admin_role/deleterolelogic.go @@ -0,0 +1,84 @@ +package admin_role + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type DeleteRoleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDeleteRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteRoleLogic { + return &DeleteRoleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeleteRoleLogic) DeleteRole(req *types.DeleteRoleReq) (resp *types.DeleteRoleResp, err error) { + // 检查角色是否存在 + _, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "删除角色失败, 角色不存在err: %v", err) + } + return nil, err + } + + // 使用事务删除角色和关联数据 + err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 删除角色菜单关联 + builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder(). + Where("role_id = ?", req.Id) + menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(ctx, builder, "id ASC") + if err != nil { + return err + } + for _, menu := range menus { + err = l.svcCtx.AdminRoleMenuModel.Delete(ctx, session, menu.Id) + if err != nil { + return err + } + } + + // 删除角色用户关联 + builder = l.svcCtx.AdminUserRoleModel.SelectBuilder(). + Where("role_id = ?", req.Id) + users, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC") + if err != nil { + return err + } + for _, user := range users { + err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, user.Id) + if err != nil { + return err + } + } + + // 删除角色 + err = l.svcCtx.AdminRoleModel.Delete(ctx, session, req.Id) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除角色失败err: %v", err) + } + + return &types.DeleteRoleResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role/getroledetaillogic.go b/app/main/api/internal/logic/admin_role/getroledetaillogic.go new file mode 100644 index 0000000..b0f4d6f --- /dev/null +++ b/app/main/api/internal/logic/admin_role/getroledetaillogic.go @@ -0,0 +1,91 @@ +package admin_role + +import ( + "context" + "sync" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetRoleDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetRoleDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRoleDetailLogic { + return &GetRoleDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetRoleDetailLogic) GetRoleDetail(req *types.GetRoleDetailReq) (resp *types.GetRoleDetailResp, err error) { + // 使用MapReduceVoid并发获取角色信息和菜单ID + var role *model.AdminRole + var menuIds []int64 + var mutex sync.Mutex + var wg sync.WaitGroup + + mr.MapReduceVoid(func(source chan<- interface{}) { + source <- 1 // 获取角色信息 + source <- 2 // 获取菜单ID + }, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) { + taskType := item.(int) + wg.Add(1) + defer wg.Done() + + if taskType == 1 { + result, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id) + if err != nil { + cancel(err) + return + } + mutex.Lock() + role = result + mutex.Unlock() + } else if taskType == 2 { + builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder(). + Where("role_id = ?", req.Id) + menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + cancel(err) + return + } + mutex.Lock() + menuIds = lo.Map(menus, func(item *model.AdminRoleMenu, _ int) int64 { + return item.MenuId + }) + mutex.Unlock() + } + }, func(pipe <-chan interface{}, cancel func(error)) { + // 不需要处理pipe中的数据 + }) + + wg.Wait() + + if role == nil { + return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "获取角色详情失败, 角色不存在err: %v", err) + } + + return &types.GetRoleDetailResp{ + Id: role.Id, + RoleName: role.RoleName, + RoleCode: role.RoleCode, + Description: role.Description, + Status: role.Status, + Sort: role.Sort, + CreateTime: role.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: role.UpdateTime.Format("2006-01-02 15:04:05"), + MenuIds: menuIds, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role/getrolelistlogic.go b/app/main/api/internal/logic/admin_role/getrolelistlogic.go new file mode 100644 index 0000000..b724ccc --- /dev/null +++ b/app/main/api/internal/logic/admin_role/getrolelistlogic.go @@ -0,0 +1,148 @@ +package admin_role + +import ( + "context" + "sync" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetRoleListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetRoleListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRoleListLogic { + return &GetRoleListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} +func (l *GetRoleListLogic) GetRoleList(req *types.GetRoleListReq) (resp *types.GetRoleListResp, err error) { + resp = &types.GetRoleListResp{ + Items: make([]types.RoleListItem, 0), + Total: 0, + } + + // 构建查询条件 + builder := l.svcCtx.AdminRoleModel.SelectBuilder() + if len(req.Name) > 0 { + builder = builder.Where("role_name LIKE ?", "%"+req.Name+"%") + } + if len(req.Code) > 0 { + builder = builder.Where("role_code LIKE ?", "%"+req.Code+"%") + } + if req.Status != -1 { + builder = builder.Where("status = ?", req.Status) + } + + // 设置分页 + offset := (req.Page - 1) * req.PageSize + builder = builder.OrderBy("sort ASC").Limit(uint64(req.PageSize)).Offset(uint64(offset)) + + // 使用MapReduceVoid并发获取总数和列表数据 + var roles []*model.AdminRole + var total int64 + var mutex sync.Mutex + var wg sync.WaitGroup + + mr.MapReduceVoid(func(source chan<- interface{}) { + source <- 1 // 获取角色列表 + source <- 2 // 获取总数 + }, func(item interface{}, writer mr.Writer[*model.AdminRole], cancel func(error)) { + taskType := item.(int) + wg.Add(1) + defer wg.Done() + + if taskType == 1 { + result, err := l.svcCtx.AdminRoleModel.FindAll(l.ctx, builder, "id DESC") + if err != nil { + cancel(err) + return + } + mutex.Lock() + roles = result + mutex.Unlock() + } else if taskType == 2 { + countBuilder := l.svcCtx.AdminRoleModel.SelectBuilder() + if len(req.Name) > 0 { + countBuilder = countBuilder.Where("role_name LIKE ?", "%"+req.Name+"%") + } + if len(req.Code) > 0 { + countBuilder = countBuilder.Where("role_code LIKE ?", "%"+req.Code+"%") + } + if req.Status != -1 { + countBuilder = countBuilder.Where("status = ?", req.Status) + } + + count, err := l.svcCtx.AdminRoleModel.FindCount(l.ctx, countBuilder, "id") + if err != nil { + cancel(err) + return + } + mutex.Lock() + total = count + mutex.Unlock() + } + }, func(pipe <-chan *model.AdminRole, cancel func(error)) { + // 不需要处理pipe中的数据 + }) + + wg.Wait() + + // 并发获取每个角色的菜单ID + var roleItems []types.RoleListItem + var roleItemsMutex sync.Mutex + + mr.MapReduceVoid(func(source chan<- interface{}) { + for _, role := range roles { + source <- role + } + }, func(item interface{}, writer mr.Writer[[]int64], cancel func(error)) { + role := item.(*model.AdminRole) + + // 获取角色关联的菜单ID + builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder(). + Where("role_id = ?", role.Id) + menus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + cancel(err) + return + } + menuIds := lo.Map(menus, func(item *model.AdminRoleMenu, _ int) int64 { + return item.MenuId + }) + + writer.Write(menuIds) + }, func(pipe <-chan []int64, cancel func(error)) { + for _, role := range roles { + menuIds := <-pipe + item := types.RoleListItem{ + Id: role.Id, + RoleName: role.RoleName, + RoleCode: role.RoleCode, + Description: role.Description, + Status: role.Status, + Sort: role.Sort, + CreateTime: role.CreateTime.Format("2006-01-02 15:04:05"), + MenuIds: menuIds, + } + roleItemsMutex.Lock() + roleItems = append(roleItems, item) + roleItemsMutex.Unlock() + } + }) + + resp.Items = roleItems + resp.Total = total + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_role/updaterolelogic.go b/app/main/api/internal/logic/admin_role/updaterolelogic.go new file mode 100644 index 0000000..c660e00 --- /dev/null +++ b/app/main/api/internal/logic/admin_role/updaterolelogic.go @@ -0,0 +1,148 @@ +package admin_role + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type UpdateRoleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewUpdateRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateRoleLogic { + return &UpdateRoleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateRoleLogic) UpdateRole(req *types.UpdateRoleReq) (resp *types.UpdateRoleResp, err error) { + // 检查角色是否存在 + role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("角色不存在"), "更新角色失败, 角色不存在err: %v", err) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err) + } + + // 检查角色编码是否重复 + if req.RoleCode != nil && *req.RoleCode != role.RoleCode { + roleModel, err := l.svcCtx.AdminRoleModel.FindOneByRoleCode(l.ctx, *req.RoleCode) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err) + } + if roleModel != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("角色编码已存在"), "更新角色失败, 角色编码已存在err: %v", err) + } + } + + // 更新角色信息 + if req.RoleName != nil { + role.RoleName = *req.RoleName + } + if req.RoleCode != nil { + role.RoleCode = *req.RoleCode + } + if req.Description != nil { + role.Description = *req.Description + } + if req.Status != nil { + role.Status = *req.Status + } + if req.Sort != nil { + role.Sort = *req.Sort + } + + // 使用事务更新角色和关联菜单 + err = l.svcCtx.AdminRoleModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 更新角色 + _, err = l.svcCtx.AdminRoleModel.Update(ctx, session, role) + if err != nil { + return err + } + if req.MenuIds != nil { + // 1. 获取当前关联的菜单ID + builder := l.svcCtx.AdminRoleMenuModel.SelectBuilder(). + Where("role_id = ?", req.Id) + currentMenus, err := l.svcCtx.AdminRoleMenuModel.FindAll(ctx, builder, "id ASC") + if err != nil { + return err + } + + // 2. 转换为map便于查找 + currentMenuMap := make(map[int64]*model.AdminRoleMenu) + for _, menu := range currentMenus { + currentMenuMap[menu.MenuId] = menu + } + + // 3. 检查新的菜单ID是否存在 + for _, menuId := range req.MenuIds { + exists, err := l.svcCtx.AdminMenuModel.FindOne(ctx, menuId) + if err != nil || exists == nil { + return errors.Wrapf(xerr.NewErrMsg("菜单不存在"), "菜单ID: %d", menuId) + } + } + + // 4. 找出需要删除和新增的关联 + var toDelete []*model.AdminRoleMenu + var toInsert []int64 + + // 需要删除的:当前存在但新列表中没有的 + for menuId, roleMenu := range currentMenuMap { + if !lo.Contains(req.MenuIds, menuId) { + toDelete = append(toDelete, roleMenu) + } + } + + // 需要新增的:新列表中有但当前不存在的 + for _, menuId := range req.MenuIds { + if _, exists := currentMenuMap[menuId]; !exists { + toInsert = append(toInsert, menuId) + } + } + + // 5. 删除需要移除的关联 + for _, roleMenu := range toDelete { + err = l.svcCtx.AdminRoleMenuModel.Delete(ctx, session, roleMenu.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除角色菜单关联失败: %v", err) + } + } + + // 6. 添加新的关联 + for _, menuId := range toInsert { + roleMenu := &model.AdminRoleMenu{ + RoleId: req.Id, + MenuId: menuId, + } + _, err = l.svcCtx.AdminRoleMenuModel.Insert(ctx, session, roleMenu) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "添加角色菜单关联失败: %v", err) + } + } + } + + return nil + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新角色失败err: %v", err) + } + + return &types.UpdateRoleResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role_api/adminassignroleapilogic.go b/app/main/api/internal/logic/admin_role_api/adminassignroleapilogic.go new file mode 100644 index 0000000..6114969 --- /dev/null +++ b/app/main/api/internal/logic/admin_role_api/adminassignroleapilogic.go @@ -0,0 +1,96 @@ +package admin_role_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminAssignRoleApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminAssignRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminAssignRoleApiLogic { + return &AdminAssignRoleApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminAssignRoleApiLogic) AdminAssignRoleApi(req *types.AdminAssignRoleApiReq) (resp *types.AdminAssignRoleApiResp, err error) { + // 1. 参数验证 + if req.RoleId <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "角色ID必须大于0, roleId: %d", req.RoleId) + } + if len(req.ApiIds) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID列表不能为空") + } + + // 2. 查询角色是否存在 + _, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "角色不存在, roleId: %d", req.RoleId) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 3. 批量分配API权限 + successCount := 0 + for _, apiId := range req.ApiIds { + if apiId <= 0 { + continue + } + + // 检查API是否存在 + _, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, apiId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + logx.Errorf("API不存在, apiId: %d", apiId) + continue + } + logx.Errorf("查询API失败, err: %v, apiId: %d", err, apiId) + continue + } + + // 检查是否已存在关联 + existing, err := l.svcCtx.AdminRoleApiModel.FindOneByRoleIdApiId(l.ctx, req.RoleId, apiId) + if err != nil && !errors.Is(err, model.ErrNotFound) { + logx.Errorf("查询角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId) + continue + } + if existing != nil { + continue // 已存在,跳过 + } + + // 创建关联 + roleApiData := &model.AdminRoleApi{ + RoleId: req.RoleId, + ApiId: apiId, + } + + _, err = l.svcCtx.AdminRoleApiModel.Insert(l.ctx, nil, roleApiData) + if err != nil { + logx.Errorf("创建角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId) + continue + } + + successCount++ + } + + // 4. 返回结果 + return &types.AdminAssignRoleApiResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_role_api/admingetallapilistlogic.go b/app/main/api/internal/logic/admin_role_api/admingetallapilistlogic.go new file mode 100644 index 0000000..364bd1e --- /dev/null +++ b/app/main/api/internal/logic/admin_role_api/admingetallapilistlogic.go @@ -0,0 +1,64 @@ +package admin_role_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetAllApiListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetAllApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetAllApiListLogic { + return &AdminGetAllApiListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetAllApiListLogic) AdminGetAllApiList(req *types.AdminGetAllApiListReq) (resp *types.AdminGetAllApiListResp, err error) { + // 1. 构建查询条件 + builder := l.svcCtx.AdminApiModel.SelectBuilder() + + // 添加状态过滤 + if req.Status > 0 { + builder = builder.Where("status = ?", req.Status) + } + + // 2. 查询所有API列表 + apis, err := l.svcCtx.AdminApiModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询API列表失败, err: %v", err) + } + + // 3. 转换数据格式 + var apiList []types.AdminRoleApiInfo + for _, api := range apis { + apiList = append(apiList, types.AdminRoleApiInfo{ + Id: 0, // 这里不是关联ID,而是API ID + RoleId: 0, // 这里不是角色ID + ApiId: api.Id, + ApiName: api.ApiName, + ApiCode: api.ApiCode, + Method: api.Method, + Url: api.Url, + Status: api.Status, + Description: api.Description, + }) + } + + // 4. 返回结果 + return &types.AdminGetAllApiListResp{ + Items: apiList, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role_api/admingetroleapilistlogic.go b/app/main/api/internal/logic/admin_role_api/admingetroleapilistlogic.go new file mode 100644 index 0000000..5033853 --- /dev/null +++ b/app/main/api/internal/logic/admin_role_api/admingetroleapilistlogic.go @@ -0,0 +1,84 @@ +package admin_role_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminGetRoleApiListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetRoleApiListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetRoleApiListLogic { + return &AdminGetRoleApiListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetRoleApiListLogic) AdminGetRoleApiList(req *types.AdminGetRoleApiListReq) (resp *types.AdminGetRoleApiListResp, err error) { + // 1. 参数验证 + if req.RoleId <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "角色ID必须大于0, roleId: %d", req.RoleId) + } + + // 2. 查询角色是否存在 + _, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "角色不存在, roleId: %d", req.RoleId) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 3. 查询角色API权限列表 + builder := l.svcCtx.AdminRoleApiModel.SelectBuilder(). + Where("role_id = ?", req.RoleId) + + roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色API权限失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 4. 转换数据格式 + var apiList []types.AdminRoleApiInfo + for _, roleApi := range roleApis { + // 查询API详情 + api, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, roleApi.ApiId) + if err != nil { + logx.Errorf("查询API详情失败, err: %v, apiId: %d", err, roleApi.ApiId) + continue + } + + apiList = append(apiList, types.AdminRoleApiInfo{ + Id: roleApi.Id, + RoleId: roleApi.RoleId, + ApiId: roleApi.ApiId, + ApiName: api.ApiName, + ApiCode: api.ApiCode, + Method: api.Method, + Url: api.Url, + Status: api.Status, + Description: api.Description, + }) + } + + // 5. 返回结果 + return &types.AdminGetRoleApiListResp{ + Items: apiList, + }, nil +} diff --git a/app/main/api/internal/logic/admin_role_api/adminremoveroleapilogic.go b/app/main/api/internal/logic/admin_role_api/adminremoveroleapilogic.go new file mode 100644 index 0000000..b8f241d --- /dev/null +++ b/app/main/api/internal/logic/admin_role_api/adminremoveroleapilogic.go @@ -0,0 +1,80 @@ +package admin_role_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminRemoveRoleApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminRemoveRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRemoveRoleApiLogic { + return &AdminRemoveRoleApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminRemoveRoleApiLogic) AdminRemoveRoleApi(req *types.AdminRemoveRoleApiReq) (resp *types.AdminRemoveRoleApiResp, err error) { + // 1. 参数验证 + if req.RoleId <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "角色ID必须大于0, roleId: %d", req.RoleId) + } + if len(req.ApiIds) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "API ID列表不能为空") + } + + // 2. 查询角色是否存在 + _, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "角色不存在, roleId: %d", req.RoleId) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 3. 批量移除API权限 + successCount := 0 + for _, apiId := range req.ApiIds { + if apiId <= 0 { + continue + } + + // 查询关联记录 + roleApi, err := l.svcCtx.AdminRoleApiModel.FindOneByRoleIdApiId(l.ctx, req.RoleId, apiId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + continue // 不存在,跳过 + } + logx.Errorf("查询角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId) + continue + } + + // 删除关联 + err = l.svcCtx.AdminRoleApiModel.DeleteSoft(l.ctx, nil, roleApi) + if err != nil { + logx.Errorf("删除角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId) + continue + } + + successCount++ + } + + // 4. 返回结果 + return &types.AdminRemoveRoleApiResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_role_api/adminupdateroleapilogic.go b/app/main/api/internal/logic/admin_role_api/adminupdateroleapilogic.go new file mode 100644 index 0000000..9894713 --- /dev/null +++ b/app/main/api/internal/logic/admin_role_api/adminupdateroleapilogic.go @@ -0,0 +1,96 @@ +package admin_role_api + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUpdateRoleApiLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateRoleApiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateRoleApiLogic { + return &AdminUpdateRoleApiLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateRoleApiLogic) AdminUpdateRoleApi(req *types.AdminUpdateRoleApiReq) (resp *types.AdminUpdateRoleApiResp, err error) { + // 1. 参数验证 + if req.RoleId <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR), + "角色ID必须大于0, roleId: %d", req.RoleId) + } + + // 2. 查询角色是否存在 + _, err = l.svcCtx.AdminRoleModel.FindOne(l.ctx, req.RoleId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "角色不存在, roleId: %d", req.RoleId) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 3. 删除该角色的所有API权限 + builder := l.svcCtx.AdminRoleApiModel.SelectBuilder().Where("role_id = ?", req.RoleId) + roleApis, err := l.svcCtx.AdminRoleApiModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), + "查询角色API权限失败, err: %v, roleId: %d", err, req.RoleId) + } + + // 删除现有权限 + for _, roleApi := range roleApis { + err = l.svcCtx.AdminRoleApiModel.DeleteSoft(l.ctx, nil, roleApi) + if err != nil { + logx.Errorf("删除角色API权限失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, roleApi.ApiId) + } + } + + // 4. 添加新的API权限 + if len(req.ApiIds) > 0 { + for _, apiId := range req.ApiIds { + if apiId <= 0 { + continue + } + + // 检查API是否存在 + _, err := l.svcCtx.AdminApiModel.FindOne(l.ctx, apiId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + logx.Errorf("API不存在, apiId: %d", apiId) + continue + } + logx.Errorf("查询API失败, err: %v, apiId: %d", err, apiId) + continue + } + + // 创建新的关联 + roleApiData := &model.AdminRoleApi{ + RoleId: req.RoleId, + ApiId: apiId, + } + + _, err = l.svcCtx.AdminRoleApiModel.Insert(l.ctx, nil, roleApiData) + if err != nil { + logx.Errorf("创建角色API关联失败, err: %v, roleId: %d, apiId: %d", err, req.RoleId, apiId) + } + } + } + + // 5. 返回结果 + return &types.AdminUpdateRoleApiResp{Success: true}, nil +} diff --git a/app/main/api/internal/logic/admin_user/admincreateuserlogic.go b/app/main/api/internal/logic/admin_user/admincreateuserlogic.go new file mode 100644 index 0000000..73b1c7a --- /dev/null +++ b/app/main/api/internal/logic/admin_user/admincreateuserlogic.go @@ -0,0 +1,88 @@ +package admin_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminCreateUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminCreateUserLogic { + return &AdminCreateUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminCreateUserLogic) AdminCreateUser(req *types.AdminCreateUserReq) (resp *types.AdminCreateUserResp, err error) { + // 检查用户名是否已存在 + exists, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, req.Username) + if err != nil && err != model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err) + } + if exists != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("用户名已存在"), "创建用户失败") + } + password, err := crypto.PasswordHash("123456") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建用户失败, 加密密码失败: %v", err) + } + // 创建用户 + user := &model.AdminUser{ + Username: req.Username, + Password: password, // 注意:实际应用中需要加密密码 + RealName: req.RealName, + Status: req.Status, + } + + // 使用事务创建用户和关联角色 + err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建用户 + result, err := l.svcCtx.AdminUserModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err := result.LastInsertId() + if err != nil { + return err + } + + // 创建用户角色关联 + if len(req.RoleIds) > 0 { + for _, roleId := range req.RoleIds { + userRole := &model.AdminUserRole{ + UserId: userId, + RoleId: roleId, + } + _, err = l.svcCtx.AdminUserRoleModel.Insert(ctx, session, userRole) + if err != nil { + return err + } + } + } + + return nil + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err) + } + + return &types.AdminCreateUserResp{ + Id: user.Id, + }, nil +} diff --git a/app/main/api/internal/logic/admin_user/admindeleteuserlogic.go b/app/main/api/internal/logic/admin_user/admindeleteuserlogic.go new file mode 100644 index 0000000..7c09bef --- /dev/null +++ b/app/main/api/internal/logic/admin_user/admindeleteuserlogic.go @@ -0,0 +1,68 @@ +package admin_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminDeleteUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminDeleteUserLogic { + return &AdminDeleteUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminDeleteUserLogic) AdminDeleteUser(req *types.AdminDeleteUserReq) (resp *types.AdminDeleteUserResp, err error) { + // 检查用户是否存在 + _, err = l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %v", err) + } + + // 使用事务删除用户和关联数据 + err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 删除用户角色关联 + builder := l.svcCtx.AdminUserRoleModel.SelectBuilder(). + Where("user_id = ?", req.Id) + roles, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC") + if err != nil { + return err + } + for _, role := range roles { + err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, role.Id) + if err != nil { + return err + } + } + + // 删除用户 + err = l.svcCtx.AdminUserModel.Delete(ctx, session, req.Id) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除用户失败: %v", err) + } + + return &types.AdminDeleteUserResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_user/admingetuserdetaillogic.go b/app/main/api/internal/logic/admin_user/admingetuserdetaillogic.go new file mode 100644 index 0000000..9816890 --- /dev/null +++ b/app/main/api/internal/logic/admin_user/admingetuserdetaillogic.go @@ -0,0 +1,88 @@ +package admin_user + +import ( + "context" + "errors" + "sync" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetUserDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetUserDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetUserDetailLogic { + return &AdminGetUserDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetUserDetailLogic) AdminGetUserDetail(req *types.AdminGetUserDetailReq) (resp *types.AdminGetUserDetailResp, err error) { + // 使用MapReduceVoid并发获取用户信息和角色ID + var user *model.AdminUser + var roleIds []int64 + var mutex sync.Mutex + var wg sync.WaitGroup + + mr.MapReduceVoid(func(source chan<- interface{}) { + source <- 1 // 获取用户信息 + source <- 2 // 获取角色ID + }, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) { + taskType := item.(int) + wg.Add(1) + defer wg.Done() + + if taskType == 1 { + result, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id) + if err != nil { + cancel(err) + return + } + mutex.Lock() + user = result + mutex.Unlock() + } else if taskType == 2 { + builder := l.svcCtx.AdminUserRoleModel.SelectBuilder(). + Where("user_id = ?", req.Id) + roles, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + cancel(err) + return + } + mutex.Lock() + roleIds = lo.Map(roles, func(item *model.AdminUserRole, _ int) int64 { + return item.RoleId + }) + mutex.Unlock() + } + }, func(pipe <-chan interface{}, cancel func(error)) { + // 不需要处理pipe中的数据 + }) + + wg.Wait() + + if user == nil { + return nil, errors.New("用户不存在") + } + + return &types.AdminGetUserDetailResp{ + Id: user.Id, + Username: user.Username, + RealName: user.RealName, + Status: user.Status, + CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: user.UpdateTime.Format("2006-01-02 15:04:05"), + RoleIds: roleIds, + }, nil +} diff --git a/app/main/api/internal/logic/admin_user/admingetuserlistlogic.go b/app/main/api/internal/logic/admin_user/admingetuserlistlogic.go new file mode 100644 index 0000000..d013f08 --- /dev/null +++ b/app/main/api/internal/logic/admin_user/admingetuserlistlogic.go @@ -0,0 +1,149 @@ +package admin_user + +import ( + "context" + "sync" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type AdminGetUserListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminGetUserListLogic { + return &AdminGetUserListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminGetUserListLogic) AdminGetUserList(req *types.AdminGetUserListReq) (resp *types.AdminGetUserListResp, err error) { + resp = &types.AdminGetUserListResp{ + Items: make([]types.AdminUserListItem, 0), + Total: 0, + } + + // 构建查询条件 + builder := l.svcCtx.AdminUserModel.SelectBuilder(). + Where("del_state = ?", 0) + if len(req.Username) > 0 { + builder = builder.Where("username LIKE ?", "%"+req.Username+"%") + } + if len(req.RealName) > 0 { + builder = builder.Where("real_name LIKE ?", "%"+req.RealName+"%") + } + if req.Status != -1 { + builder = builder.Where("status = ?", req.Status) + } + + // 设置分页 + offset := (req.Page - 1) * req.PageSize + builder = builder.OrderBy("id DESC").Limit(uint64(req.PageSize)).Offset(uint64(offset)) + + // 使用MapReduceVoid并发获取总数和列表数据 + var users []*model.AdminUser + var total int64 + var mutex sync.Mutex + var wg sync.WaitGroup + + mr.MapReduceVoid(func(source chan<- interface{}) { + source <- 1 // 获取用户列表 + source <- 2 // 获取总数 + }, func(item interface{}, writer mr.Writer[*model.AdminUser], cancel func(error)) { + taskType := item.(int) + wg.Add(1) + defer wg.Done() + + if taskType == 1 { + result, err := l.svcCtx.AdminUserModel.FindAll(l.ctx, builder, "id DESC") + if err != nil { + cancel(err) + return + } + mutex.Lock() + users = result + mutex.Unlock() + } else if taskType == 2 { + countBuilder := l.svcCtx.AdminUserModel.SelectBuilder(). + Where("del_state = ?", 0) + if len(req.Username) > 0 { + countBuilder = countBuilder.Where("username LIKE ?", "%"+req.Username+"%") + } + if len(req.RealName) > 0 { + countBuilder = countBuilder.Where("real_name LIKE ?", "%"+req.RealName+"%") + } + if req.Status != -1 { + countBuilder = countBuilder.Where("status = ?", req.Status) + } + + count, err := l.svcCtx.AdminUserModel.FindCount(l.ctx, countBuilder, "id") + if err != nil { + cancel(err) + return + } + mutex.Lock() + total = count + mutex.Unlock() + } + }, func(pipe <-chan *model.AdminUser, cancel func(error)) { + // 不需要处理pipe中的数据 + }) + + wg.Wait() + + // 并发获取每个用户的角色ID + var userItems []types.AdminUserListItem + var userItemsMutex sync.Mutex + + mr.MapReduceVoid(func(source chan<- interface{}) { + for _, user := range users { + source <- user + } + }, func(item interface{}, writer mr.Writer[[]int64], cancel func(error)) { + user := item.(*model.AdminUser) + + // 获取用户关联的角色ID + builder := l.svcCtx.AdminUserRoleModel.SelectBuilder(). + Where("user_id = ?", user.Id) + roles, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "id ASC") + if err != nil { + cancel(err) + return + } + roleIds := lo.Map(roles, func(item *model.AdminUserRole, _ int) int64 { + return item.RoleId + }) + + writer.Write(roleIds) + }, func(pipe <-chan []int64, cancel func(error)) { + for _, user := range users { + roleIds := <-pipe + item := types.AdminUserListItem{ + Id: user.Id, + Username: user.Username, + RealName: user.RealName, + Status: user.Status, + CreateTime: user.CreateTime.Format("2006-01-02 15:04:05"), + RoleIds: roleIds, + } + userItemsMutex.Lock() + userItems = append(userItems, item) + userItemsMutex.Unlock() + } + }) + + resp.Items = userItems + resp.Total = total + + return resp, nil +} diff --git a/app/main/api/internal/logic/admin_user/adminresetpasswordlogic.go b/app/main/api/internal/logic/admin_user/adminresetpasswordlogic.go new file mode 100644 index 0000000..cea601c --- /dev/null +++ b/app/main/api/internal/logic/admin_user/adminresetpasswordlogic.go @@ -0,0 +1,63 @@ +package admin_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminResetPasswordLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminResetPasswordLogic { + return &AdminResetPasswordLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminResetPasswordLogic) AdminResetPassword(req *types.AdminResetPasswordReq) (resp *types.AdminResetPasswordResp, err error) { + // 检查用户是否存在 + user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id) + if err != nil { + if err == model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrMsg("用户不存在"), "用户ID: %d", req.Id) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err) + } + + // 检查用户状态 + if user.Status != 1 { + return nil, errors.Wrapf(xerr.NewErrMsg("用户已被禁用,无法重置密码"), "用户ID: %d", req.Id) + } + + // 对密码进行加密 + hashedPassword, err := crypto.PasswordHash(req.Password) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "密码加密失败: %v", err) + } + + // 更新用户密码 + user.Password = hashedPassword + _, err = l.svcCtx.AdminUserModel.Update(l.ctx, nil, user) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新密码失败: %v", err) + } + + l.Infof("管理员密码重置成功,用户ID: %d, 用户名: %s", req.Id, user.Username) + + return &types.AdminResetPasswordResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_user/adminupdateuserlogic.go b/app/main/api/internal/logic/admin_user/adminupdateuserlogic.go new file mode 100644 index 0000000..b607314 --- /dev/null +++ b/app/main/api/internal/logic/admin_user/adminupdateuserlogic.go @@ -0,0 +1,141 @@ +package admin_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminUpdateUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUpdateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUpdateUserLogic { + return &AdminUpdateUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUpdateUserLogic) AdminUpdateUser(req *types.AdminUpdateUserReq) (resp *types.AdminUpdateUserResp, err error) { + // 检查用户是否存在 + user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户不存在: %v", err) + } + + // 检查用户名是否重复 + if req.Username != nil && *req.Username != user.Username { + exists, err := l.svcCtx.AdminUserModel.FindOneByUsername(l.ctx, *req.Username) + if err != nil && err != model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err) + } + if exists != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("用户名已存在"), "更新用户失败") + } + } + + // 更新用户信息 + if req.Username != nil { + user.Username = *req.Username + } + if req.RealName != nil { + user.RealName = *req.RealName + } + if req.Status != nil { + user.Status = *req.Status + } + + // 使用事务更新用户和关联角色 + err = l.svcCtx.AdminUserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 更新用户 + _, err = l.svcCtx.AdminUserModel.Update(ctx, session, user) + if err != nil { + return err + } + + // 只有当RoleIds不为nil时才更新角色关联 + if req.RoleIds != nil { + // 1. 获取当前关联的角色ID + builder := l.svcCtx.AdminUserRoleModel.SelectBuilder(). + Where("user_id = ?", req.Id) + currentRoles, err := l.svcCtx.AdminUserRoleModel.FindAll(ctx, builder, "id ASC") + if err != nil { + return err + } + + // 2. 转换为map便于查找 + currentRoleMap := make(map[int64]*model.AdminUserRole) + for _, role := range currentRoles { + currentRoleMap[role.RoleId] = role + } + + // 3. 检查新的角色ID是否存在 + for _, roleId := range req.RoleIds { + exists, err := l.svcCtx.AdminRoleModel.FindOne(ctx, roleId) + if err != nil || exists == nil { + return errors.Wrapf(xerr.NewErrMsg("角色不存在"), "角色ID: %d", roleId) + } + } + + // 4. 找出需要删除和新增的关联 + var toDelete []*model.AdminUserRole + var toInsert []int64 + + // 需要删除的:当前存在但新列表中没有的 + for roleId, userRole := range currentRoleMap { + if !lo.Contains(req.RoleIds, roleId) { + toDelete = append(toDelete, userRole) + } + } + + // 需要新增的:新列表中有但当前不存在的 + for _, roleId := range req.RoleIds { + if _, exists := currentRoleMap[roleId]; !exists { + toInsert = append(toInsert, roleId) + } + } + + // 5. 删除需要移除的关联 + for _, userRole := range toDelete { + err = l.svcCtx.AdminUserRoleModel.Delete(ctx, session, userRole.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除用户角色关联失败: %v", err) + } + } + + // 6. 添加新的关联 + for _, roleId := range toInsert { + userRole := &model.AdminUserRole{ + UserId: req.Id, + RoleId: roleId, + } + _, err = l.svcCtx.AdminUserRoleModel.Insert(ctx, session, userRole) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "添加用户角色关联失败: %v", err) + } + } + } + + return nil + }) + + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新用户失败: %v", err) + } + + return &types.AdminUpdateUserResp{ + Success: true, + }, nil +} diff --git a/app/main/api/internal/logic/admin_user/adminuserinfologic.go b/app/main/api/internal/logic/admin_user/adminuserinfologic.go new file mode 100644 index 0000000..a8f4adb --- /dev/null +++ b/app/main/api/internal/logic/admin_user/adminuserinfologic.go @@ -0,0 +1,67 @@ +package admin_user + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AdminUserInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAdminUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminUserInfoLogic { + return &AdminUserInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AdminUserInfoLogic) AdminUserInfo(req *types.AdminUserInfoReq) (resp *types.AdminUserInfoResp, err error) { + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %+v", err) + } + + user, err := l.svcCtx.AdminUserModel.FindOne(l.ctx, userId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID信息失败, %+v", err) + } + // 获取权限 + adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": user.Id}) + permissions, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, adminUserRoleBuilder, "role_id DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("获取权限失败"), "用户登录, 获取权限失败, 用户名: %s", user.Username) + } + + // 获取角色ID数组 + roleIds := make([]int64, 0) + for _, permission := range permissions { + roleIds = append(roleIds, permission.RoleId) + } + + // 获取角色名称 + roles := make([]string, 0) + for _, roleId := range roleIds { + role, err := l.svcCtx.AdminRoleModel.FindOne(l.ctx, roleId) + if err != nil { + continue + } + roles = append(roles, role.RoleCode) + } + return &types.AdminUserInfoResp{ + Username: user.Username, + RealName: user.RealName, + Roles: roles, + }, nil +} diff --git a/app/main/api/internal/logic/agent/activateagentmembershiplogic.go b/app/main/api/internal/logic/agent/activateagentmembershiplogic.go new file mode 100644 index 0000000..ec6bb53 --- /dev/null +++ b/app/main/api/internal/logic/agent/activateagentmembershiplogic.go @@ -0,0 +1,85 @@ +package agent + +import ( + "context" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "encoding/json" + "fmt" + "time" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ActivateAgentMembershipLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewActivateAgentMembershipLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ActivateAgentMembershipLogic { + return &ActivateAgentMembershipLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} +func (l *ActivateAgentMembershipLogic) ActivateAgentMembership(req *types.AgentActivateMembershipReq) (resp *types.AgentActivateMembershipResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + // 查询用户代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) + } + // 定义等级顺序映射 + levelOrder := map[string]int{ + "": 1, + model.AgentLeveNameNormal: 1, + model.AgentLeveNameVIP: 2, + model.AgentLeveNameSVIP: 3, + } + + // 验证请求等级合法性 + if _, valid := levelOrder[req.Type]; !valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "无效的代理等级: %s", req.Type) + } + + // 如果存在代理记录,进行等级验证 + if agentModel != nil { + currentLevel, exists := levelOrder[agentModel.LevelName] + if !exists { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "非法的当前代理等级: %s", agentModel.LevelName) + } + + requestedLevel := levelOrder[req.Type] + if requestedLevel < currentLevel { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "禁止降级操作(当前等级:%s,请求等级:%s)", agentModel.LevelName, req.Type) + } + // 同等级视为续费,允许操作 + } + outTradeNo := "A_" + l.svcCtx.AlipayService.GenerateOutTradeNo() + redisKey := fmt.Sprintf(types.AgentVipCacheKey, userID, outTradeNo) + agentVipCache := types.AgentVipCache{Type: req.Type} + jsonData, err := json.Marshal(agentVipCache) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化代理VIP缓存失败: %v", err) + } + cacheErr := l.svcCtx.Redis.SetexCtx(l.ctx, redisKey, string(jsonData), int(2*time.Hour)) + if cacheErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "设置缓存失败: %v", cacheErr) + } + return &types.AgentActivateMembershipResp{ + Id: outTradeNo, + }, nil +} diff --git a/app/main/api/internal/logic/agent/agentrealnamelogic.go b/app/main/api/internal/logic/agent/agentrealnamelogic.go new file mode 100644 index 0000000..e6b124c --- /dev/null +++ b/app/main/api/internal/logic/agent/agentrealnamelogic.go @@ -0,0 +1,103 @@ +package agent + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/redis" +) + +type AgentRealNameLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAgentRealNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentRealNameLogic { + return &AgentRealNameLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AgentRealNameLogic) AgentRealName(req *types.AgentRealNameReq) (resp *types.AgentRealNameResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %v", err) + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err) + } + // 开发环境下跳过验证码验证 + if os.Getenv("ENV") != "development" { + // 检查手机号是否在一分钟内已发送过验证码 + redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理实名, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理实名, 验证码不正确: %s", encryptedMobile) + } + } + agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息失败, %v", err) + } + + agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err) + } + + if agentRealName != nil && agentRealName.Status == model.AgentRealNameStatusApproved { + return nil, errors.Wrapf(xerr.NewErrMsg("代理实名信息已审核通过"), "代理实名信息已审核通过") + } + // 三要素验证 + threeVerification := service.ThreeFactorVerificationRequest{ + Name: req.Name, + IDCard: req.IDCard, + Mobile: req.Mobile, + } + verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err) + } + if !verification.Passed { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err) + } + agentRealName = &model.AgentRealName{ + AgentId: agent.Id, + Status: model.AgentRealNameStatusApproved, + Name: req.Name, + IdCard: req.IDCard, + ApproveTime: sql.NullTime{Time: time.Now(), Valid: true}, + } + _, err = l.svcCtx.AgentRealNameModel.Insert(l.ctx, nil, agentRealName) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "添加代理实名信息失败, %v", err) + } + + return &types.AgentRealNameResp{ + Status: agentRealName.Status, + }, nil +} diff --git a/app/main/api/internal/logic/agent/agentwithdrawallogic.go b/app/main/api/internal/logic/agent/agentwithdrawallogic.go new file mode 100644 index 0000000..e832927 --- /dev/null +++ b/app/main/api/internal/logic/agent/agentwithdrawallogic.go @@ -0,0 +1,401 @@ +package agent + +import ( + "context" + "database/sql" + "fmt" + "time" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/cenkalti/backoff/v4" + "github.com/pkg/errors" + "github.com/smartwalle/alipay/v3" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +// 状态常量 +const ( + StatusProcessing = 1 // 处理中 + StatusSuccess = 2 // 成功 + StatusFailed = 3 // 失败 +) + +// 前端响应状态 +const ( + WithdrawStatusProcessing = 1 + WithdrawStatusSuccess = 2 + WithdrawStatusFailed = 3 +) + +type AgentWithdrawalLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentWithdrawalLogic { + return &AgentWithdrawalLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types.WithdrawalResp, error) { + var ( + outBizNo string + withdrawRes = &types.WithdrawalResp{} + agentID int64 + ) + var finalWithdrawAmount float64 // 实际到账金额 + // 使用事务处理核心操作 + err := l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 查询代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) + } + agentID = agentModel.Id // 保存agentId用于日志 + agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证") + } + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err) + } + if agentRealName.Status != model.AgentRealNameStatusApproved { + return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过") + } + if agentRealName.Name != req.PayeeName { + return errors.Wrapf(xerr.NewErrMsg("您的实名认证信息不匹配, 无法提现"), "您的实名认证信息不匹配") + } + // 查询钱包 + agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err) + } + + // 校验可提现金额 + if req.Amount > agentWallet.Balance { + return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "获取用户ID失败") + } + + // 生成交易号 + outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo() + + // 冻结资金(事务内操作) + if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err) + } + yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month())) + // 统一按6%收取税收,不再有免税额度 + taxRate := l.svcCtx.Config.TaxConfig.TaxRate + var ( + taxAmount float64 // 应缴税费 + taxDeductionPart float64 // 应税金额 + TaxStatus int64 // 扣税状态 + exemptionAmount float64 // 免税金额(固定为0) + ) + + // 统一扣税逻辑:所有提现都按6%收取税收 + exemptionAmount = 0 // 免税金额 = 0 + TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税 + taxDeductionPart = req.Amount // 应税金额 = 提现金额 + taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率 + finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费 + + // 创建提现记录(初始状态为处理中) + withdrawalID, err := l.createWithdrawalRecord(session, agentModel.Id, req.PayeeAccount, req.PayeeName, req.Amount, finalWithdrawAmount, taxAmount, outBizNo) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err) + } + // 扣税记录 + taxModel := &model.AgentWithdrawalTax{ + AgentId: agentModel.Id, + YearMonth: yearMonth, + WithdrawalId: withdrawalID, + WithdrawalAmount: req.Amount, + ExemptionAmount: exemptionAmount, + TaxableAmount: taxDeductionPart, + TaxRate: taxRate, + TaxAmount: taxAmount, + ActualAmount: finalWithdrawAmount, + TaxStatus: TaxStatus, + Remark: sql.NullString{String: "提现成功自动扣税", Valid: true}, + ExemptionRecordId: 0, // 不再使用免税额度记录 + } + _, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err) + } + return nil + }) + + if err != nil { + return nil, err + } + + // 支付宝提现不再直接调用转账接口,改为先申请后审核 + // 直接返回申请中状态,等待管理员审核 + withdrawRes.Status = WithdrawStatusProcessing + withdrawRes.FailMsg = "" + + l.Logger.Infof("支付宝提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, req.Amount) + return withdrawRes, nil +} + +// 错误类型映射 +func (l *AgentWithdrawalLogic) mapAlipayError(code string) string { + errorMapping := map[string]string{ + // 账户存在性错误 + "PAYEE_ACCOUNT_NOT_EXSIT": "收款账户不存在,请检查账号是否正确", + "PAYEE_NOT_EXIST": "收款账户不存在或姓名有误,请核实信息", + "PAYEE_ACC_OCUPIED": "收款账号存在多个账户,无法确认唯一性", + "PAYEE_MID_CANNOT_SAME": "收款方和中间方不能是同一个人,请修改收款方或者中间方信息", + + // 实名认证问题 + "PAYEE_CERTIFY_LEVEL_LIMIT": "收款方未完成实名认证", + "PAYEE_NOT_RELNAME_CERTIFY": "收款方未完成实名认证", + "PAYEE_CERT_INFO_ERROR": "收款方证件信息不匹配", + + // 账户状态异常 + "PAYEE_ACCOUNT_STATUS_ERROR": "收款账户状态异常,请更换账号", + "PAYEE_USERINFO_STATUS_ERROR": "收款账户状态异常,无法收款", + "PERMIT_LIMIT_PAYEE": "收款账户异常,请更换账号", + "BLOCK_USER_FORBBIDEN_RECIEVE": "账户冻结无法收款", + "PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT": "收款方托管子户累计收款金额超限", + + // 账户信息错误 + "PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配", + "PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致", + "PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配", + "PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能,请更换收款账号", + "PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现,请更换收款账号", + + // 权限与限制 + "PAYEE_RECEIVE_COUNT_EXCEED_LIMIT": "收款次数超限,请明日再试", + "PAYEE_OUT_PERMLIMIT_CHECK_FAILURE": "收款方权限校验不通过", + "PERMIT_NON_BANK_LIMIT_PAYEE": "收款方未完善身份信息,无法收款", + } + if msg, ok := errorMapping[code]; ok { + return msg + } + return "系统错误,请联系客服" +} + +// 创建提现记录(事务内操作) +func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, payeeAccount string, payeeName string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) { + record := &model.AgentWithdrawal{ + AgentId: agentID, + WithdrawType: 1, // 支付宝提现 + WithdrawNo: outBizNo, + PayeeAccount: payeeAccount, + PayeeName: sql.NullString{String: payeeName, Valid: true}, // 设置收款人姓名 + Amount: amount, + ActualAmount: finalWithdrawAmount, + TaxAmount: taxAmount, + Status: StatusProcessing, + } + + result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record) + if err != nil { + return 0, err + } + return result.LastInsertId() +} + +// 冻结资金(事务内操作) +func (l *AgentWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error { + wallet.Balance -= amount + wallet.FrozenBalance += amount + err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet) + if err != nil { + return err + } + + return nil +} + +// 处理异步轮询 +func (l *AgentWithdrawalLogic) startAsyncPolling(outBizNo string) { + go func() { + detachedCtx := context.WithoutCancel(l.ctx) + retryConfig := &backoff.ExponentialBackOff{ + InitialInterval: 10 * time.Second, + RandomizationFactor: 0.5, // 增加随机因子防止惊群 + Multiplier: 2, + MaxInterval: 30 * time.Second, + MaxElapsedTime: 5 * time.Minute, // 缩短总超时 + Clock: backoff.SystemClock, + } + retryConfig.Reset() + operation := func() error { + statusRsp, err := l.svcCtx.AlipayService.QueryTransferStatus(detachedCtx, outBizNo) + if err != nil { + return err // 触发重试 + } + + switch statusRsp.Status { + case "SUCCESS": + l.handleTransferSuccess(outBizNo, statusRsp) + return nil + case "FAIL": + l.handleTransferFailure(outBizNo, statusRsp) + return nil + default: + return fmt.Errorf("转账处理中") + } + } + + err := backoff.RetryNotify(operation, + backoff.WithContext(retryConfig, detachedCtx), + func(err error, duration time.Duration) { + l.Logger.Infof("轮询延迟 outBizNo:%s 等待:%v", outBizNo, duration) + }) + + if err != nil { + l.handleTransferTimeout(outBizNo) + } + }() +} + +// 统一状态更新 +func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status int64, errorMsg string) { + detachedCtx := context.WithoutCancel(l.ctx) + + err := l.svcCtx.AgentModel.Trans(detachedCtx, func(ctx context.Context, session sqlx.Session) error { + // 获取提现记录 + record, err := l.svcCtx.AgentWithdrawalModel.FindOneByWithdrawNo(l.ctx, outBizNo) + if err != nil { + return err + } + + // 更新状态 + record.Status = status + record.Remark = lzUtils.StringToNullString(errorMsg) + if _, err = l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil { + return err + } + // 失败时解冻资金 + if status == StatusFailed { + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return err + } + + wallet.Balance += record.Amount + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return err + } + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return err + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + // 不再需要恢复免税额度,因为统一按6%收取税收 + } + if status == StatusSuccess { + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId) + if err != nil { + return err + } + + wallet.FrozenBalance -= record.Amount + if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil { + return err + } + taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id) + if err != nil { + return err + } + if taxModel.TaxStatus == model.TaxStatusPending { + taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功 + taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err) + } + } + + // 提现成功后,给上级代理发放提现奖励 + withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session) + if withdrawRewardErr != nil { + l.Logger.Errorf("发放提现奖励失败,代理ID:%d,提现金额:%f,错误:%+v", record.AgentId, record.Amount, withdrawRewardErr) + // 提现奖励失败不影响主流程,只记录日志 + } else { + l.Logger.Infof("发放提现奖励成功,代理ID:%d,提现金额:%f", record.AgentId, record.Amount) + } + } + return nil + }) + + if err != nil { + l.Logger.Errorf("状态更新失败 outBizNo:%s error:%v", outBizNo, err) + } +} + +// 成功处理 +func (l *AgentWithdrawalLogic) handleTransferSuccess(outBizNo string, rsp interface{}) { + l.updateWithdrawalStatus(outBizNo, StatusSuccess, "") + l.Logger.Infof("提现成功 outBizNo:%s", outBizNo) +} + +// 失败处理 +func (l *AgentWithdrawalLogic) handleTransferFailure(outBizNo string, rsp interface{}) { + var errorMsg string + if resp, ok := rsp.(*alipay.FundTransUniTransferRsp); ok { + errorMsg = l.mapAlipayError(resp.SubCode) + } + l.updateWithdrawalStatus(outBizNo, StatusFailed, errorMsg) + l.Logger.Errorf("提现失败 outBizNo:%s reason:%s", outBizNo, errorMsg) +} + +// 超时处理 +func (l *AgentWithdrawalLogic) handleTransferTimeout(outBizNo string) { + l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理超时") + l.Logger.Errorf("轮询超时 outBizNo:%s", outBizNo) +} + +// 错误处理 +func (l *AgentWithdrawalLogic) handleTransferError(outBizNo string, err error, contextMsg string) { + l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理异常") + l.Logger.Errorf("%s outBizNo:%s error:%v", contextMsg, outBizNo, err) +} + +func (l *AgentWithdrawalLogic) createMonthlyExemption(session sqlx.Session, agentId int64, yearMonth int64) (*model.AgentWithdrawalTaxExemption, error) { + exemption := &model.AgentWithdrawalTaxExemption{ + AgentId: agentId, + YearMonth: yearMonth, + TotalExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount, + UsedExemptionAmount: 0.00, + RemainingExemptionAmount: l.svcCtx.Config.TaxConfig.TaxExemptionAmount, + } + + result, err := l.svcCtx.AgentWithdrawalTaxExemptionModel.Insert(l.ctx, session, exemption) + if err != nil { + return nil, err + } + + id, _ := result.LastInsertId() + exemption.Id = id + return exemption, nil +} diff --git a/app/main/api/internal/logic/agent/applyforagentlogic.go b/app/main/api/internal/logic/agent/applyforagentlogic.go new file mode 100644 index 0000000..7638b67 --- /dev/null +++ b/app/main/api/internal/logic/agent/applyforagentlogic.go @@ -0,0 +1,193 @@ +package agent + +import ( + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + "context" + "database/sql" + "fmt" + "os" + "time" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ApplyForAgentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyForAgentLogic { + return &ApplyForAgentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, %v", err) + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err) + } + // 开发环境下跳过验证码验证 + if os.Getenv("ENV") != "development" { + // 校验验证码 + redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理申请, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理申请, 验证码不正确: %s", encryptedMobile) + } + } + if req.Ancestor == req.Mobile { + return nil, errors.Wrapf(xerr.NewErrMsg("不能成为自己的代理"), "") + } + var userID int64 + transErr := l.svcCtx.AgentAuditModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { + // 两种情况,1. 已注册账号然后申请代理 2. 未注册账号申请代理 + user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) + if findUserErr != nil && !errors.Is(findUserErr, model.ErrNotFound) { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findUserErr) + } + if user == nil { + if claims != nil && claims.UserType == model.UserTypeNormal { + return errors.Wrapf(xerr.NewErrMsg("当前用户已注册,请输入注册的手机号"), "代理申请, 当前用户已注册") + } + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err) + } + } else { + // 被封禁用户禁止登录/申请 + if user.Disable == 1 { + return errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + if claims != nil && claims.UserType == model.UserTypeTemp { + // 临时用户,转为正式用户 + err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err) + } + } + userID = user.Id + } + + // 使用SelectBuilder构建查询,查找符合user_id的记录并按创建时间降序排序获取最新一条 + builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1) + agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(transCtx, builder, "") + if findAgentAuditErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 查找审核列表失败%+v", findAgentAuditErr) + } + + if len(agentAuditList) > 0 { + agentAuditModel := agentAuditList[0] + if agentAuditModel.Status == 0 { + return errors.Wrapf(xerr.NewErrMsg("您的代理申请中"), "代理申请, 代理申请中") + } else { + return errors.Wrapf(xerr.NewErrMsg("您已申请过代理"), "代理申请, 代理已申请过") + } + } + + var agentAudit model.AgentAudit + agentAudit.UserId = userID + agentAudit.Mobile = encryptedMobile + agentAudit.Region = req.Region + agentAudit.Status = 1 + _, insetAgentAuditErr := l.svcCtx.AgentAuditModel.Insert(transCtx, session, &agentAudit) + if insetAgentAuditErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 保存代理审核信息失败: %v", insetAgentAuditErr) + } + + // 新增代理 + var agentModel model.Agent + agentModel.Mobile = agentAudit.Mobile + agentModel.Region = agentAudit.Region + agentModel.UserId = agentAudit.UserId + agentModel.LevelName = model.AgentLeveNameNormal + agentModelInsert, insertAgentModelErr := l.svcCtx.AgentModel.Insert(transCtx, session, &agentModel) + if insertAgentModelErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理失败: %+v", insertAgentModelErr) + } + agentID, _ := agentModelInsert.LastInsertId() + + // 关联上级 + if req.Ancestor != "" { + ancestorEncryptedMobile, err := crypto.EncryptMobile(req.Ancestor, secretKey) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err) + } + ancestorAgentModel, findAgentModelErr := l.svcCtx.AgentModel.FindOneByMobile(transCtx, ancestorEncryptedMobile) + if findAgentModelErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 查找上级代理失败: %+v", findAgentModelErr) + } + agentClosureModel := model.AgentClosure{ + AncestorId: ancestorAgentModel.Id, + DescendantId: agentID, + Depth: 1, + } + _, insertAgentClosureModelErr := l.svcCtx.AgentClosureModel.Insert(transCtx, session, &agentClosureModel) + if insertAgentClosureModelErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 添加代理上下级关联失败: %+v", insertAgentClosureModelErr) + } + } + + // 新增代理钱包 + var agentWallet model.AgentWallet + agentWallet.AgentId = agentID + _, insertAgentWalletModelErr := l.svcCtx.AgentWalletModel.Insert(transCtx, session, &agentWallet) + if insertAgentWalletModelErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理钱包失败: %+v", insertAgentWalletModelErr) + } + + // 新增税务额度 + agentWithdrawalTaxExemption := model.AgentWithdrawalTaxExemption{ + AgentId: agentID, + YearMonth: int64(time.Now().Year()*100 + int(time.Now().Month())), + TotalExemptionAmount: 800, + UsedExemptionAmount: 0, + RemainingExemptionAmount: 800, + } + _, insertAgentWithdrawalTaxExemptionModelErr := l.svcCtx.AgentWithdrawalTaxExemptionModel.Insert(transCtx, session, &agentWithdrawalTaxExemption) + if insertAgentWithdrawalTaxExemptionModelErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理税务额度失败: %+v", insertAgentWithdrawalTaxExemptionModelErr) + } + + return nil + }) + if transErr != nil { + return nil, transErr + } + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.AgentApplyResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} diff --git a/app/main/api/internal/logic/agent/bankcardwithdrawallogic.go b/app/main/api/internal/logic/agent/bankcardwithdrawallogic.go new file mode 100644 index 0000000..0ff327b --- /dev/null +++ b/app/main/api/internal/logic/agent/bankcardwithdrawallogic.go @@ -0,0 +1,224 @@ +package agent + +import ( + "context" + "database/sql" + "regexp" + "time" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +// 提现类型常量 +const ( + WithdrawTypeAlipay = 1 // 支付宝提现 + WithdrawTypeBankCard = 2 // 银行卡提现 +) + +type BankCardWithdrawalLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BankCardWithdrawalLogic { + return &BankCardWithdrawalLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdrawalReq) (resp *types.WithdrawalResp, err error) { + var ( + outBizNo string + withdrawRes = &types.WithdrawalResp{} + agentID int64 + ) + var finalWithdrawAmount float64 // 实际到账金额 + + // 验证银行卡号格式(16-19位数字) + bankCardNoRegex := regexp.MustCompile(`^\d{16,19}$`) + if !bankCardNoRegex.MatchString(req.BankCardNo) { + return nil, errors.Wrapf(xerr.NewErrMsg("银行卡号格式不正确,请输入16-19位数字"), "银行卡号格式验证失败") + } + + // 验证开户支行不能为空 + if req.BankName == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("开户支行不能为空"), "开户支行验证失败") + } + + // 使用事务处理核心操作 + err = l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 查询代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) + } + agentID = agentModel.Id // 保存agentId用于日志 + + // 查询实名认证信息 + agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证") + } + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err) + } + if agentRealName.Status != model.AgentRealNameStatusApproved { + return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过") + } + + // 查询钱包 + agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err) + } + + // 校验可提现金额 + if req.Amount > agentWallet.Balance { + return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "余额不足") + } + + // 最低提现金额验证 + if req.Amount < 50 { + return errors.Wrapf(xerr.NewErrMsg("提现金额不能低于50元"), "金额验证失败") + } + + // 生成交易号 + outBizNo = "BC_" + l.svcCtx.AlipayService.GenerateOutTradeNo() + + // 冻结资金(事务内操作) + if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err) + } + + yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month())) + // 统一按6%收取税收 + taxRate := l.svcCtx.Config.TaxConfig.TaxRate + var ( + taxAmount float64 // 应缴税费 + taxDeductionPart float64 // 应税金额 + TaxStatus int64 // 扣税状态 + exemptionAmount float64 // 免税金额(固定为0) + ) + + // 统一扣税逻辑:所有提现都按6%收取税收 + exemptionAmount = 0 // 免税金额 = 0 + TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税 + taxDeductionPart = req.Amount // 应税金额 = 提现金额 + taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率 + finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费 + + // 创建提现记录(初始状态为申请中,提现类型为银行卡) + withdrawalID, err := l.createBankCardWithdrawalRecord(session, agentModel.Id, req.BankCardNo, req.BankName, agentRealName.Name, req.Amount, finalWithdrawAmount, taxAmount, outBizNo) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err) + } + + // 扣税记录 + taxModel := &model.AgentWithdrawalTax{ + AgentId: agentModel.Id, + YearMonth: yearMonth, + WithdrawalId: withdrawalID, + WithdrawalAmount: req.Amount, + ExemptionAmount: exemptionAmount, + TaxableAmount: taxDeductionPart, + TaxRate: taxRate, + TaxAmount: taxAmount, + ActualAmount: finalWithdrawAmount, + TaxStatus: TaxStatus, + Remark: sql.NullString{String: "银行卡提现申请,待审核扣税", Valid: true}, + ExemptionRecordId: 0, // 不再使用免税额度记录 + } + _, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err) + } + return nil + }) + + if err != nil { + return nil, err + } + + // 银行卡提现不需要调用支付接口,直接返回申请中状态 + withdrawRes.Status = WithdrawStatusProcessing + withdrawRes.FailMsg = "" + + l.Logger.Infof("银行卡提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, req.Amount) + return withdrawRes, nil +} + +// 创建银行卡提现记录(事务内操作) +func (l *BankCardWithdrawalLogic) createBankCardWithdrawalRecord(session sqlx.Session, agentID int64, bankCardNo string, bankName string, payeeName string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) { + record := &model.AgentWithdrawal{ + AgentId: agentID, + WithdrawType: WithdrawTypeBankCard, // 银行卡提现 + WithdrawNo: outBizNo, + PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段 + Amount: amount, + ActualAmount: finalWithdrawAmount, + TaxAmount: taxAmount, + Status: StatusProcessing, // 申请中状态 + BankCardNo: sql.NullString{String: bankCardNo, Valid: true}, + BankName: sql.NullString{String: bankName, Valid: true}, + PayeeName: sql.NullString{String: payeeName, Valid: true}, + } + + result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record) + if err != nil { + return 0, err + } + return result.LastInsertId() +} + +// 冻结资金(事务内操作) +func (l *BankCardWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error { + // 记录变动前的余额 + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + // 更新钱包余额 + wallet.Balance -= amount + wallet.FrozenBalance += amount + err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet) + if err != nil { + return err + } + + // 记录交易流水(冻结操作) + err = l.svcCtx.AgentService.CreateWalletTransaction( + l.ctx, + session, + wallet.AgentId, + model.WalletTransactionTypeFreeze, + amount, // 变动金额 + balanceBefore, // 变动前余额 + wallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + wallet.FrozenBalance, // 变动后冻结余额 + "", // 关联交易ID(暂时为空,创建提现记录后可以更新) + 0, // 关联用户ID + "提现申请冻结资金", // 备注 + ) + if err != nil { + return err + } + + return nil +} diff --git a/app/main/api/internal/logic/agent/generatinglinklogic.go b/app/main/api/internal/logic/agent/generatinglinklogic.go new file mode 100644 index 0000000..3da4403 --- /dev/null +++ b/app/main/api/internal/logic/agent/generatinglinklogic.go @@ -0,0 +1,111 @@ +package agent + +import ( + "context" + "encoding/hex" + "encoding/json" + "strconv" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GeneratingLinkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GeneratingLinkLogic { + return &GeneratingLinkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err) + } + productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.Product) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err) + } + + agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, productModel.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err) + } + price, err := strconv.ParseFloat(req.Price, 64) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err) + } + if price < agentProductConfig.PriceRangeMin || price > agentProductConfig.PriceRangeMax { + return nil, errors.Wrapf(xerr.NewErrMsg("请设定范围区间内的价格"), "") + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, err + } + + build := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{ + squirrel.Eq{"user_id": userID}, + squirrel.Eq{"product_id": productModel.Id}, // 添加 product_id 的匹配条件 + squirrel.Eq{"price": price}, // 添加 price 的匹配条件 + squirrel.Eq{"agent_id": agentModel.Id}, // 添加 agent_id 的匹配条件 + }) + + agentLinkModel, err := l.svcCtx.AgentLinkModel.FindAll(l.ctx, build, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err) + } + if len(agentLinkModel) > 0 { + return &types.AgentGeneratingLinkResp{ + LinkIdentifier: agentLinkModel[0].LinkIdentifier, + }, nil + } + + var agentIdentifier types.AgentIdentifier + agentIdentifier.AgentID = agentModel.Id + agentIdentifier.Product = req.Product + agentIdentifier.Price = req.Price + agentIdentifierByte, err := json.Marshal(agentIdentifier) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单,序列化标识失败, %v", err) + } + key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10") + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取AES密钥失败: %+v", decodeErr) + } + + // Encrypt the params + encrypted, err := crypto.AesEncryptURL(agentIdentifierByte, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err) + } + + var agentLink model.AgentLink + agentLink.AgentId = agentModel.Id + agentLink.UserId = userID + agentLink.LinkIdentifier = encrypted + agentLink.ProductId = productModel.Id + agentLink.Price = price + _, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, &agentLink) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err) + } + return &types.AgentGeneratingLinkResp{ + LinkIdentifier: encrypted, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentauditstatuslogic.go b/app/main/api/internal/logic/agent/getagentauditstatuslogic.go new file mode 100644 index 0000000..7582c53 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentauditstatuslogic.go @@ -0,0 +1,52 @@ +package agent + +import ( + "context" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentAuditStatusLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentAuditStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentAuditStatusLogic { + return &GetAgentAuditStatusLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentAuditStatusLogic) GetAgentAuditStatus() (resp *types.AgentAuditStatusResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理审核信息, %v", err) + } + + // 使用SelectBuilder构建查询,查找符合user_id的记录并按创建时间降序排序获取最新一条 + builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1) + agentAuditList, err := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理审核信息, %v", err) + } + + if len(agentAuditList) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "未找到代理审核信息") + } + + agentAuditModel := agentAuditList[0] + var agentAuditStautsResp types.AgentAuditStatusResp + copier.Copy(&agentAuditStautsResp, agentAuditModel) + return &agentAuditStautsResp, nil +} diff --git a/app/main/api/internal/logic/agent/getagentcommissionlogic.go b/app/main/api/internal/logic/agent/getagentcommissionlogic.go new file mode 100644 index 0000000..f0d75c2 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentcommissionlogic.go @@ -0,0 +1,114 @@ +package agent + +import ( + "context" + "encoding/hex" + "encoding/json" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentCommissionLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentCommissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentCommissionLogic { + return &GetAgentCommissionLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentCommissionLogic) GetAgentCommission(req *types.GetCommissionReq) (resp *types.GetCommissionResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err) + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err) + } + builder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": agentModel.Id, + }) + agentCommissionModelList, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, 查找列表错误, %v", err) + } + + var list = make([]types.Commission, 0) + + if len(agentCommissionModelList) > 0 { + for _, agentCommissionModel := range agentCommissionModelList { + var commission types.Commission + copyErr := copier.Copy(&commission, agentCommissionModel) + if copyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err) + } + // 显式设置 status 字段和已退款金额 + commission.Status = agentCommissionModel.Status + commission.RefundedAmount = agentCommissionModel.RefundedAmount + // 计算净佣金金额(防御性处理,避免出现负数) + netAmount := agentCommissionModel.Amount - agentCommissionModel.RefundedAmount + if netAmount < 0 { + netAmount = 0 + } + commission.NetAmount = netAmount + product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, agentCommissionModel.ProductId) + if findProductErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err) + } + commission.CreateTime = agentCommissionModel.CreateTime.Format("2006-01-02 15:04:05") + commission.ProductName = product.ProductName + + // 从 order 表获取 platform_order_id + orderModel, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, agentCommissionModel.OrderId) + if findOrderErr == nil && orderModel != nil && orderModel.PlatformOrderId.Valid { + commission.OrderId = orderModel.PlatformOrderId.String + } else { + commission.OrderId = "" + } + + queryModel, queryErr := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, agentCommissionModel.OrderId) + if queryErr == nil && queryModel != nil { + + key := l.svcCtx.Config.Encrypt.SecretKey + keyBytes, decodeErr := hex.DecodeString(key) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err) + } + // 根据订单号查询query表获取query_params + decryptedData, decryptErr := crypto.AesDecrypt(queryModel.QueryParams, keyBytes) + if decryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err) + } + // 解析query_params,从JSON字符串转换为map + if queryModel.QueryParams != "" { + + var queryParamsMap map[string]interface{} + if unmarshalErr := json.Unmarshal(decryptedData, &queryParamsMap); unmarshalErr == nil { + commission.QueryParams = queryParamsMap + } + } + } + list = append(list, commission) + } + } + return &types.GetCommissionResp{ + Total: total, + List: list, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentinfologic.go b/app/main/api/internal/logic/agent/getagentinfologic.go new file mode 100644 index 0000000..3e5163b --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentinfologic.go @@ -0,0 +1,94 @@ +package agent + +import ( + "context" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + "database/sql" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentInfoLogic { + return &GetAgentInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentInfoLogic) GetAgentInfo() (resp *types.AgentInfoResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err) + } + userID := claims.UserId + userType := claims.UserType + if userType == model.UserTypeTemp { + return &types.AgentInfoResp{ + IsAgent: false, + Status: 3, + }, nil + } + agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1) + agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "") + + if findAgentAuditErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", findAgentAuditErr) + } + + if len(agentAuditList) == 0 { + return &types.AgentInfoResp{ + IsAgent: false, + Status: 3, + }, nil + } + + agentAuditModel := agentAuditList[0] + return &types.AgentInfoResp{ + IsAgent: false, + Status: agentAuditModel.Status, + }, nil + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", err) + } + agent.Mobile, err = crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err) + } + + IsRealName := false + agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err) + } + if agentRealName != nil { + IsRealName = true + } + return &types.AgentInfoResp{ + AgentID: agent.Id, + Level: agent.LevelName, + IsAgent: true, + Status: 1, + Region: agent.Region, + Mobile: agent.Mobile, + ExpiryTime: agent.MembershipExpiryTime.Time.Format("2006-01-02 15:04:05"), + IsRealName: IsRealName, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go b/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go new file mode 100644 index 0000000..cf205ce --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentmembershipproductconfiglogic.go @@ -0,0 +1,80 @@ +package agent + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentMembershipProductConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentMembershipProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentMembershipProductConfigLogic { + return &GetAgentMembershipProductConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentMembershipProductConfigLogic) GetAgentMembershipProductConfig(req *types.AgentMembershipProductConfigReq) (resp *types.AgentMembershipProductConfigResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取用户ID失败: %v", err) + } + + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取代理信息失败: %v", err) + } + if agentModel.LevelName == "" { + agentModel.LevelName = model.AgentLeveNameNormal + } + agentMembershipConfigModel, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, agentModel.LevelName) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,获取平台配置会员信息失败: %v", err) + } + agentMembershipUserConfigModel, err := l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentModel.Id, req.ProductID) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, err + } + var agentMembershipUserConfig types.AgentMembershipUserConfig + if agentMembershipUserConfigModel != nil { + err = copier.Copy(&agentMembershipUserConfig, agentMembershipUserConfigModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,复制平台配置会员信息失败: %v", err) + } + } else { + agentMembershipUserConfig.ProductID = req.ProductID + } + agentProductConfigModelAll, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, req.ProductID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取会员用户报告配置, 获取产品配置%v", err) + } + + var productConfig types.ProductConfig + err = copier.Copy(&productConfig, agentProductConfigModelAll) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取会员用户报告配置,复制平台产品配置失败: %v", err) + } + return &types.AgentMembershipProductConfigResp{ + AgentMembershipUserConfig: agentMembershipUserConfig, + ProductConfig: productConfig, + PriceIncreaseAmount: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceIncreaseAmount), + PriceIncreaseMax: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceIncreaseMax), + PriceRatio: lzUtils.NullFloat64ToFloat64(agentMembershipConfigModel.PriceRatio), + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentproductconfiglogic.go b/app/main/api/internal/logic/agent/getagentproductconfiglogic.go new file mode 100644 index 0000000..11dd056 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentproductconfiglogic.go @@ -0,0 +1,140 @@ +package agent + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/mr" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentProductConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentProductConfigLogic { + return &GetAgentProductConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +type AgentProductConfigResp struct { +} + +func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentProductConfigResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err) + } + agentModel, 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.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err) + } + // 1. 查询推广项目配置数据 + builder := l.svcCtx.AgentProductConfigModel.SelectBuilder() + agentProductConfigModelAll, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %v", err) + } + + // 用于存放最终组装好的响应数据 + var respList []types.AgentProductConfig + + // 2. 使用 mr.MapReduceVoid 并行处理每个推广项目配置项 + mrMapErr := mr.MapReduceVoid( + // source 函数:遍历所有推广项目配置,将每个配置项发送到 channel 中 + func(source chan<- interface{}) { + for _, config := range agentProductConfigModelAll { + source <- config + } + }, + // map 函数:处理每个推广项目配置项,根据 ProductId 查询会员用户配置,并组装响应数据 + func(item interface{}, writer mr.Writer[*types.AgentProductConfig], cancel func(error)) { + // 将 item 转换为推广项目配置模型 + config := item.(*model.AgentProductConfig) + var agentProductConfig types.AgentProductConfig + // 配置平台成本价和定价成本 + agentProductConfigModel, findAgentProductConfigErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, config.ProductId) + if findAgentProductConfigErr != nil { + cancel(findAgentProductConfigErr) + return + } + agentProductConfig.ProductID = config.ProductId + agentProductConfig.CostPrice = agentProductConfigModel.CostPrice + agentProductConfig.PriceRangeMin = agentProductConfigModel.PriceRangeMin + agentProductConfig.PriceRangeMax = agentProductConfigModel.PriceRangeMax + agentProductConfig.PPricingStandard = agentProductConfigModel.PricingStandard + agentProductConfig.POverpricingRatio = agentProductConfigModel.OverpricingRatio + + // 看推广人是否有上级,上级是否有这个配置权限,上级是否有相关配置 + agentClosureModel, findAgentClosureErr := l.svcCtx.AgentClosureModel.FindOneByDescendantIdDepth(l.ctx, agentModel.Id, 1) + if findAgentClosureErr != nil && !errors.Is(findAgentClosureErr, model.ErrNotFound) { + cancel(findAgentClosureErr) + return + } + if agentClosureModel != nil { + ancestorAgentModel, findAncestorAgentErr := l.svcCtx.AgentModel.FindOne(l.ctx, agentClosureModel.AncestorId) + if findAncestorAgentErr != nil { + cancel(findAncestorAgentErr) + return + } + if ancestorAgentModel.LevelName == "" { + ancestorAgentModel.LevelName = model.AgentLeveNameNormal + } + agentMembershipConfigModel, findAgentMembershipErr := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, ancestorAgentModel.LevelName) + if findAgentMembershipErr != nil { + cancel(findAgentMembershipErr) + return + } + // 是否有提成本价 + if agentMembershipConfigModel.PriceIncreaseAmount.Valid { + // 根据产品ID查询会员用户配置数据 + membershipUserConfigModel, membershipConfigErr := l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentClosureModel.AncestorId, config.ProductId) + if membershipConfigErr != nil { + if errors.Is(membershipConfigErr, model.ErrNotFound) { + writer.Write(&agentProductConfig) + return + } + cancel(membershipConfigErr) + return + } + agentProductConfig.CostPrice += membershipUserConfigModel.PriceIncreaseAmount + agentProductConfig.PriceRangeMin += membershipUserConfigModel.PriceIncreaseAmount + agentProductConfig.APricingStandard = membershipUserConfigModel.PriceRangeFrom + agentProductConfig.APricingEnd = membershipUserConfigModel.PriceRangeTo + agentProductConfig.AOverpricingRatio = membershipUserConfigModel.PriceRatio + } + } + writer.Write(&agentProductConfig) + + }, + // reduce 函数:收集 map 阶段写入的响应数据,并汇总到 respList 中 + func(pipe <-chan *types.AgentProductConfig, cancel func(error)) { + for item := range pipe { + respList = append(respList, *item) + } + }, + ) + if mrMapErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %+v", mrMapErr) + } + + // 3. 组装最终响应返回 + return &types.AgentProductConfigResp{ + AgentProductConfig: respList, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go b/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go new file mode 100644 index 0000000..eed0d33 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentpromotionqrcodelogic.go @@ -0,0 +1,72 @@ +package agent + +import ( + "context" + "fmt" + "net/http" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentPromotionQrcodeLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext + writer http.ResponseWriter +} + +func NewGetAgentPromotionQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *GetAgentPromotionQrcodeLogic { + return &GetAgentPromotionQrcodeLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + writer: writer, + } +} + +func (l *GetAgentPromotionQrcodeLogic) GetAgentPromotionQrcode(req *types.GetAgentPromotionQrcodeReq) error { + // 1. 参数验证 + if req.QrcodeUrl == "" { + return errors.Wrapf(xerr.NewErrMsg("二维码URL不能为空"), "二维码URL为空") + } + + if req.QrcodeType == "" { + req.QrcodeType = "promote" // 设置默认类型 + } + + // 3. 检查指定类型的背景图是否存在 + if !l.svcCtx.ImageService.CheckImageExists(req.QrcodeType) { + l.Errorf("指定的二维码类型对应的背景图不存在: %s", req.QrcodeType) + return errors.Wrapf(xerr.NewErrMsg("指定的二维码类型不支持"), "二维码类型: %s", req.QrcodeType) + } + + // 4. 处理图片,添加二维码 + imageData, contentType, err := l.svcCtx.ImageService.ProcessImageWithQRCode(req.QrcodeType, req.QrcodeUrl) + if err != nil { + l.Errorf("处理图片失败: %v, 类型: %s, URL: %s", err, req.QrcodeType, req.QrcodeUrl) + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广二维码图片失败: %v", err) + } + + // 5. 设置响应头 + l.writer.Header().Set("Content-Type", contentType) + l.writer.Header().Set("Content-Length", fmt.Sprintf("%d", len(imageData))) + l.writer.Header().Set("Cache-Control", "public, max-age=3600") // 缓存1小时 + l.writer.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"qrcode_%s.png\"", req.QrcodeType)) + + // 6. 写入图片数据 + _, err = l.writer.Write(imageData) + if err != nil { + l.Errorf("写入图片数据失败: %v", err) + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "输出图片数据失败: %v", err) + } + + l.Infof("成功生成代理推广二维码图片,类型: %s, URL: %s, 图片大小: %d bytes", + req.QrcodeType, req.QrcodeUrl, len(imageData)) + + return nil +} diff --git a/app/main/api/internal/logic/agent/getagentrevenueinfologic.go b/app/main/api/internal/logic/agent/getagentrevenueinfologic.go new file mode 100644 index 0000000..4968e59 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentrevenueinfologic.go @@ -0,0 +1,232 @@ +package agent + +import ( + "context" + "time" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentRevenueInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentRevenueInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentRevenueInfoLogic { + return &GetAgentRevenueInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentRevenueInfoLogic) GetAgentRevenueInfo(req *types.GetAgentRevenueInfoReq) (resp *types.GetAgentRevenueInfoResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err) + } + userID := claims.UserId + userType := claims.UserType + if userType == model.UserTypeTemp { + return &types.GetAgentRevenueInfoResp{ + Balance: 0, + TotalEarnings: 0, + FrozenBalance: 0, + DirectPush: types.DirectPushReport{ + TotalCommission: 0, + TotalReport: 0, + Today: types.TimeRangeReport{}, + Last7D: types.TimeRangeReport{}, + Last30D: types.TimeRangeReport{}, + }, + ActiveReward: types.ActiveReward{ + TotalReward: 0, + Today: types.ActiveRewardData{}, + Last7D: types.ActiveRewardData{}, + Last30D: types.ActiveRewardData{}, + }, + }, nil + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err) + } + agentWalletModel, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err) + } + resp = &types.GetAgentRevenueInfoResp{} + resp.Balance = agentWalletModel.Balance + resp.TotalEarnings = agentWalletModel.TotalEarnings + resp.FrozenBalance = agentWalletModel.FrozenBalance + + // 直推报告统计 + //now := time.Now() + //startTime := now.AddDate(0, 0, -30).Format("2006-01-02 15:04:05") + //endTime := now.Format("2006-01-02 15:04:05") + + // 直推报告佣金 + agentCommissionModelBuild := l.svcCtx.AgentCommissionModel.SelectBuilder(). + Where(squirrel.Eq{"agent_id": agentModel.Id}) + //.Where(squirrel.Expr("create_time BETWEEN ? AND ?", startTime, endTime)) + + agentCommissionsModel, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, agentCommissionModelBuild, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err) + } + // 筛选分类 + directPush, err := calculateDirectPushReport(agentCommissionsModel, nil) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err) + } + // 绑定到响应体 + resp.DirectPush = directPush + + // 活跃下级统计 + agentRewardsModelBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where("agent_id = ?", agentModel.Id) + agentRewardsModel, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, agentRewardsModelBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励, %v", err) + } + activeReward := calculateActiveReward(agentRewardsModel) + resp.ActiveReward = activeReward + + return resp, nil +} + +// 统计直推报告的独立函数 +func calculateDirectPushReport(commissions []*model.AgentCommission, loc *time.Location) (types.DirectPushReport, error) { + // 初始化报告结构 + report := types.DirectPushReport{ + Today: types.TimeRangeReport{}, + Last7D: types.TimeRangeReport{}, + Last30D: types.TimeRangeReport{}, + } + + // 获取当前中国时间 + now := time.Now() + + // 计算时间分界点 + todayStart := now.Add(-24 * time.Hour) + last7dStart := now.AddDate(0, 0, -7) + last30dStart := now.AddDate(0, 0, -30) + + // 遍历所有佣金记录 + for _, c := range commissions { + // 转换时区 + createTime := c.CreateTime + + // 只统计未被全部退款的佣金 + if c.Status == 2 { + continue + } + + // 统计净佣金:原始金额减去已退款金额 + netAmount := c.Amount - c.RefundedAmount + if netAmount <= 0 { + continue + } + + // 统计总量 + report.TotalCommission += netAmount + report.TotalReport++ + + // 近24小时(滚动周期) + if createTime.After(todayStart) { + report.Today.Commission += netAmount + report.Today.Report++ + } + + // 近7天(滚动周期) + if createTime.After(last7dStart) { + report.Last7D.Commission += netAmount + report.Last7D.Report++ + } + + // 近30天(滚动周期) + if createTime.After(last30dStart) { + report.Last30D.Commission += netAmount + report.Last30D.Report++ + } + } + + return report, nil +} +func calculateActiveReward(rewards []*model.AgentRewards) types.ActiveReward { + result := types.ActiveReward{ + Today: types.ActiveRewardData{}, + Last7D: types.ActiveRewardData{}, + Last30D: types.ActiveRewardData{}, + } + + now := time.Now() + todayStart := now.Add(-24 * time.Hour) // 近24小时 + last7dStart := now.AddDate(0, 0, -7) // 近7天 + last30dStart := now.AddDate(0, 0, -30) // 近30天 + + for _, r := range rewards { + createTime := r.CreateTime + amount := r.Amount + + // 总奖励累加 + result.TotalReward += amount + + // 时间范围判断 + isToday := createTime.After(todayStart) + isLast7d := createTime.After(last7dStart) + isLast30d := createTime.After(last30dStart) + + // 类型分类统计 + switch r.Type { + case model.AgentRewardsTypeDescendantWithdraw: + addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "withdraw") + + case model.AgentRewardsTypeDescendantNewActive: + addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "new_active") + + case model.AgentRewardsTypeDescendantUpgradeSvip, model.AgentRewardsTypeDescendantUpgradeVip: + addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "upgrade") + + case model.AgentRewardsTypeDescendantPromotion: + addToPeriods(&result, amount, isToday, isLast7d, isLast30d, "promotion") + } + } + return result +} + +// 统一处理时间段累加 +func addToPeriods(res *types.ActiveReward, amount float64, today, last7d, last30d bool, t string) { + if today { + addToData(&res.Today, amount, t) + } + if last7d { + addToData(&res.Last7D, amount, t) + } + if last30d { + addToData(&res.Last30D, amount, t) + } +} + +// 分类添加具体字段 +func addToData(data *types.ActiveRewardData, amount float64, t string) { + switch t { + case "withdraw": + data.SubWithdrawReward += amount + case "new_active": + data.NewActiveReward += amount + case "upgrade": + data.SubUpgradeReward += amount + case "promotion": + data.SubPromoteReward += amount + } +} diff --git a/app/main/api/internal/logic/agent/getagentrewardslogic.go b/app/main/api/internal/logic/agent/getagentrewardslogic.go new file mode 100644 index 0000000..b3115f2 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentrewardslogic.go @@ -0,0 +1,68 @@ +package agent + +import ( + "context" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentRewardsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentRewardsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentRewardsLogic { + return &GetAgentRewardsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentRewardsLogic) GetAgentRewards(req *types.GetRewardsReq) (resp *types.GetRewardsResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理奖励列表, %v", err) + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励列表, %v", err) + } + builder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": agentModel.Id, + }) + agentRewardsModelList, total, err := l.svcCtx.AgentRewardsModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理奖励列表, 查找列表错误, %v", err) + } + + var list = make([]types.Rewards, 0) + if len(agentRewardsModelList) > 0 { + for _, agentRewardsModel := range agentRewardsModelList { + var rewards types.Rewards + copyErr := copier.Copy(&rewards, agentRewardsModel) + if copyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理奖励列表, %v", err) + } + + rewards.CreateTime = agentRewardsModel.CreateTime.Format("2006-01-02 15:04:05") + list = append(list, rewards) + } + } + return &types.GetRewardsResp{ + Total: total, + List: list, + }, nil + + return +} diff --git a/app/main/api/internal/logic/agent/getagentsubordinatecontributiondetaillogic.go b/app/main/api/internal/logic/agent/getagentsubordinatecontributiondetaillogic.go new file mode 100644 index 0000000..03525e6 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentsubordinatecontributiondetaillogic.go @@ -0,0 +1,200 @@ +package agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentSubordinateContributionDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentSubordinateContributionDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentSubordinateContributionDetailLogic { + return &GetAgentSubordinateContributionDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentSubordinateContributionDetailLogic) GetAgentSubordinateContributionDetail(req *types.GetAgentSubordinateContributionDetailReq) (resp *types.GetAgentSubordinateContributionDetailResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 获取用户ID%v", err) + } + + // 获取当前代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取代理信息%v", err) + } + + // 获取下级代理信息 + subordinateAgent, err := l.svcCtx.AgentModel.FindOne(l.ctx, req.SubordinateID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取下级代理信息%v", err) + } + + // 验证是否是当前代理的下级 + closureBuilder := l.svcCtx.AgentClosureModel.SelectBuilder().Where(squirrel.Eq{ + "ancestor_id": agentModel.Id, + "descendant_id": req.SubordinateID, + }) + closureList, err := l.svcCtx.AgentClosureModel.FindAll(l.ctx, closureBuilder, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 验证代理关系%v", err) + } + if len(closureList) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 非法的代理关系") + } + closure := closureList[0] + + // 获取佣金扣除记录 + deductionBuilder := l.svcCtx.AgentCommissionDeductionModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": agentModel.Id, + "deducted_agent_id": req.SubordinateID, + }) + deductionList, err := l.svcCtx.AgentCommissionDeductionModel.FindAll(l.ctx, deductionBuilder, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取佣金扣除记录%v", err) + } + + // 获取奖励记录 + rewardsBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": agentModel.Id, + "relation_agent_id": req.SubordinateID, + }) + rewards, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, rewardsBuilder, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取奖励记录%v", err) + } + + // 计算总贡献 + var totalContribution float64 + for _, v := range deductionList { + totalContribution += v.Amount + } + // 加上奖励金额 + for _, v := range rewards { + totalContribution += v.Amount + } + + // 获取佣金记录 + commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": req.SubordinateID, + }) + commissionList, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, commissionBuilder, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取佣金记录%v", err) + } + + // 计算总收益和总单量 + var totalEarnings float64 + for _, v := range commissionList { + totalEarnings += v.Amount + } + + // 初始化统计数据 + stats := types.AgentSubordinateContributionStats{ + CostCount: 0, + CostAmount: 0, + PricingCount: 0, + PricingAmount: 0, + DescendantPromotionCount: 0, + DescendantPromotionAmount: 0, + DescendantUpgradeVipCount: 0, + DescendantUpgradeVipAmount: 0, + DescendantUpgradeSvipCount: 0, + DescendantUpgradeSvipAmount: 0, + DescendantStayActiveCount: 0, + DescendantStayActiveAmount: 0, + DescendantNewActiveCount: 0, + DescendantNewActiveAmount: 0, + DescendantWithdrawCount: 0, + DescendantWithdrawAmount: 0, + } + + // 统计佣金扣除记录 + for _, v := range deductionList { + switch v.Type { + case "cost": + stats.CostCount++ + stats.CostAmount += v.Amount + case "pricing": + stats.PricingCount++ + stats.PricingAmount += v.Amount + } + } + + // 统计奖励记录 + for _, v := range rewards { + switch v.Type { + case "descendant_promotion": + stats.DescendantPromotionCount++ + stats.DescendantPromotionAmount += v.Amount + case "descendant_upgrade_vip": + stats.DescendantUpgradeVipCount++ + stats.DescendantUpgradeVipAmount += v.Amount + case "descendant_upgrade_svip": + stats.DescendantUpgradeSvipCount++ + stats.DescendantUpgradeSvipAmount += v.Amount + case "descendant_stay_active": + stats.DescendantStayActiveCount++ + stats.DescendantStayActiveAmount += v.Amount + case "descendant_new_active": + stats.DescendantNewActiveCount++ + stats.DescendantNewActiveAmount += v.Amount + case "descendant_withdraw": + stats.DescendantWithdrawCount++ + stats.DescendantWithdrawAmount += v.Amount + } + } + + // 解密手机号 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + mobile, err := crypto.DecryptMobile(subordinateAgent.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 解密手机号失败: %v", err) + } + + // 获取合并后的分页列表 + unionDetails, total, err := l.svcCtx.AgentClosureModel.FindUnionPageListByPageWithTotal(l.ctx, agentModel.Id, req.SubordinateID, req.Page, req.PageSize) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 获取分页列表%v", err) + } + + // 转换为响应类型 + detailList := make([]types.AgentSubordinateContributionDetail, 0, len(unionDetails)) + for _, v := range unionDetails { + detail := types.AgentSubordinateContributionDetail{ + ID: v.Id, + CreateTime: v.CreateTime, + Amount: v.Amount, + Type: v.Type, + } + detailList = append(detailList, detail) + } + + return &types.GetAgentSubordinateContributionDetailResp{ + Mobile: maskPhone(mobile), + Total: total, + CreateTime: closure.CreateTime.Format("2006-01-02 15:04:05"), + TotalEarnings: totalEarnings, + TotalContribution: totalContribution, + TotalOrders: int64(len(commissionList)), + LevelName: subordinateAgent.LevelName, + List: detailList, + Stats: stats, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentsubordinatelistlogic.go b/app/main/api/internal/logic/agent/getagentsubordinatelistlogic.go new file mode 100644 index 0000000..ae3c4e9 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentsubordinatelistlogic.go @@ -0,0 +1,173 @@ +package agent + +import ( + "context" + "strings" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetAgentSubordinateListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentSubordinateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentSubordinateListLogic { + return &GetAgentSubordinateListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentSubordinateListLogic) GetAgentSubordinateList(req *types.GetAgentSubordinateListReq) (resp *types.GetAgentSubordinateListResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级列表, 获取用户ID%v", err) + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理信息%v", err) + } + agentID := agentModel.Id + + builder := l.svcCtx.AgentClosureModel.SelectBuilder().Where(squirrel.Eq{ + "ancestor_id": agentID, + }) + agentClosureModelList, total, err := l.svcCtx.AgentClosureModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理关系%v", err) + } + + // 构建ID到CreateTime的映射 + createTimeMap := make(map[int64]time.Time) + descendantIDs := make([]int64, 0) + for _, v := range agentClosureModelList { + descendantIDs = append(descendantIDs, v.DescendantId) + createTimeMap[v.DescendantId] = v.CreateTime + } + + // 并发查询代理信息 + agentMap := make(map[int64]*model.Agent) + var descendantList []types.AgentSubordinateList + err = mr.Finish(func() error { + return mr.MapReduceVoid(func(source chan<- interface{}) { + for _, id := range descendantIDs { + source <- id + } + }, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) { + id := item.(int64) + agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, id) + if err != nil { + cancel(err) + return + } + writer.Write(agent) + }, func(pipe <-chan interface{}, cancel func(error)) { + for item := range pipe { + agent := item.(*model.Agent) + agentMap[agent.Id] = agent + } + }) + }, func() error { + // 并发查询佣金扣除信息 + deductionBuilder := l.svcCtx.AgentCommissionDeductionModel.SelectBuilder(). + Where(squirrel.Eq{"agent_id": agentID}). + Where(squirrel.Eq{"deducted_agent_id": descendantIDs}) + deductionList, err := l.svcCtx.AgentCommissionDeductionModel.FindAll(l.ctx, deductionBuilder, "") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理佣金扣除信息%v", err) + } + deductionMap := make(map[int64]float64) + for _, v := range deductionList { + deductionMap[v.DeductedAgentId] += v.Amount + } + + // 并发查询奖励信息 + rewardsBuilder := l.svcCtx.AgentRewardsModel.SelectBuilder(). + Where(squirrel.Eq{"agent_id": agentID}). + Where(squirrel.Eq{"relation_agent_id": descendantIDs}) + rewardsList, err := l.svcCtx.AgentRewardsModel.FindAll(l.ctx, rewardsBuilder, "") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理奖励信息%v", err) + } + rewardsMap := make(map[int64]float64) + for _, v := range rewardsList { + if v.RelationAgentId.Valid { + rewardsMap[v.RelationAgentId.Int64] += v.Amount + } + } + + // 并发查询佣金信息 + commissionBuilder := l.svcCtx.AgentCommissionModel.SelectBuilder(). + Where(squirrel.Eq{"agent_id": descendantIDs}) + commissionList, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, commissionBuilder, "") + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级列表, 获取代理佣金信息%v", err) + } + commissionMap := make(map[int64]float64) + orderCountMap := make(map[int64]int64) + for _, v := range commissionList { + commissionMap[v.AgentId] += v.Amount + orderCountMap[v.AgentId]++ + } + + // 构建返回结果 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + descendantList = make([]types.AgentSubordinateList, 0, len(descendantIDs)) + for _, id := range descendantIDs { + agent, exists := agentMap[id] + if !exists { + continue + } + + mobile, err := crypto.DecryptMobile(agent.Mobile, secretKey) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err) + } + + subordinate := types.AgentSubordinateList{ + ID: id, + Mobile: maskPhone(mobile), + LevelName: agent.LevelName, + CreateTime: createTimeMap[id].Format("2006-01-02 15:04:05"), + TotalContribution: deductionMap[id] + rewardsMap[id], + TotalEarnings: commissionMap[id], + TotalOrders: orderCountMap[id], + } + descendantList = append(descendantList, subordinate) + } + return nil + }) + + if err != nil { + return nil, err + } + + return &types.GetAgentSubordinateListResp{ + Total: total, + List: descendantList, + }, nil +} + +// 手机号脱敏 +func maskPhone(phone string) string { + length := len(phone) + if length < 8 { + return phone // 如果长度太短,可能不是手机号,不处理 + } + // 保留前3位和后4位 + return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:] +} diff --git a/app/main/api/internal/logic/agent/getagentwithdrawallogic.go b/app/main/api/internal/logic/agent/getagentwithdrawallogic.go new file mode 100644 index 0000000..0ebce24 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentwithdrawallogic.go @@ -0,0 +1,66 @@ +package agent + +import ( + "context" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentWithdrawalLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalLogic { + return &GetAgentWithdrawalLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentWithdrawalLogic) GetAgentWithdrawal(req *types.GetWithdrawalReq) (resp *types.GetWithdrawalResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err) + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, %v", err) + } + builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().Where(squirrel.Eq{ + "agent_id": agentModel.Id, + }) + agentWithdrawalModelList, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, 查找列表错误, %v", err) + } + + var list = make([]types.Withdrawal, 0) + + if len(agentWithdrawalModelList) > 0 { + for _, agentWithdrawalModel := range agentWithdrawalModelList { + var withdrawal types.Withdrawal + copyErr := copier.Copy(&withdrawal, agentWithdrawalModel) + if copyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err) + } + withdrawal.CreateTime = agentWithdrawalModel.CreateTime.Format("2006-01-02 15:04:05") + list = append(list, withdrawal) + } + } + return &types.GetWithdrawalResp{ + Total: total, + List: list, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getagentwithdrawaltaxexemptionlogic.go b/app/main/api/internal/logic/agent/getagentwithdrawaltaxexemptionlogic.go new file mode 100644 index 0000000..17e23f2 --- /dev/null +++ b/app/main/api/internal/logic/agent/getagentwithdrawaltaxexemptionlogic.go @@ -0,0 +1,42 @@ +package agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAgentWithdrawalTaxExemptionLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAgentWithdrawalTaxExemptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalTaxExemptionLogic { + return &GetAgentWithdrawalTaxExemptionLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAgentWithdrawalTaxExemptionLogic) GetAgentWithdrawalTaxExemption(req *types.GetWithdrawalTaxExemptionReq) (resp *types.GetWithdrawalTaxExemptionResp, err error) { + _, err = ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %+v", err) + } + + // 统一按6%收取税收,不再有免税额度 + return &types.GetWithdrawalTaxExemptionResp{ + TotalExemptionAmount: 0.00, // 免税总额度为0 + UsedExemptionAmount: 0.00, // 已使用免税额度为0 + RemainingExemptionAmount: 0.00, // 剩余免税额度为0 + TaxRate: l.svcCtx.Config.TaxConfig.TaxRate, // 6%税收 + }, nil +} diff --git a/app/main/api/internal/logic/agent/getbankcardinfologic.go b/app/main/api/internal/logic/agent/getbankcardinfologic.go new file mode 100644 index 0000000..3494e27 --- /dev/null +++ b/app/main/api/internal/logic/agent/getbankcardinfologic.go @@ -0,0 +1,81 @@ +package agent + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetBankCardInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetBankCardInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBankCardInfoLogic { + return &GetBankCardInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetBankCardInfoLogic) GetBankCardInfo(req *types.GetBankCardInfoReq) (resp *types.GetBankCardInfoResp, err error) { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 查询代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) + } + + // 查询实名认证信息 + agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("您未进行实名认证"), "您未进行实名认证") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err) + } + + // 初始化响应,包含实名认证信息 + resp = &types.GetBankCardInfoResp{ + PayeeName: agentRealName.Name, + IdCard: agentRealName.IdCard, + BankCardNo: "", + BankName: "", + } + + // 查询最近一次成功的银行卡提现记录,用于自动填充 + builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder() + builder = builder.Where(squirrel.Eq{"agent_id": agentModel.Id}) + builder = builder.Where(squirrel.Eq{"withdraw_type": 2}) // 银行卡提现 + builder = builder.Where(squirrel.Eq{"status": 2}) // 成功状态 + builder = builder.OrderBy("create_time DESC") + builder = builder.Limit(1) + + list, err := l.svcCtx.AgentWithdrawalModel.FindAll(l.ctx, builder, "create_time DESC") + if err == nil && len(list) > 0 { + lastRecord := list[0] + if lastRecord.BankCardNo.Valid { + resp.BankCardNo = lastRecord.BankCardNo.String + } + if lastRecord.BankName.Valid { + resp.BankName = lastRecord.BankName.String + } + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/agent/getlinkdatalogic.go b/app/main/api/internal/logic/agent/getlinkdatalogic.go new file mode 100644 index 0000000..5f8db61 --- /dev/null +++ b/app/main/api/internal/logic/agent/getlinkdatalogic.go @@ -0,0 +1,86 @@ +package agent + +import ( + "context" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/mr" +) + +type GetLinkDataLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetLinkDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLinkDataLogic { + return &GetLinkDataLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.GetLinkDataResp, err error) { + agentLinkModel, err := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, req.LinkIdentifier) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err) + } + + productModel, err := l.svcCtx.ProductModel.FindOne(l.ctx, agentLinkModel.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err) + } + + // 查询产品关联的 feature + build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{ + "product_id": productModel.Id, + }) + productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, 查找产品关联错误: %v", err) + } + + var product types.Product + err = copier.Copy(&product, productModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理链接数据, 产品信息结构体复制失败, %v", err) + } + product.SellPrice = agentLinkModel.Price + + // 并发查询所有 feature 详情 + mr.MapReduceVoid(func(source chan<- interface{}) { + for _, productFeature := range productFeatureAll { + source <- productFeature.FeatureId + } + }, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) { + id := item.(int64) + + feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id) + if findFeatureErr != nil { + logx.WithContext(l.ctx).Errorf("获取代理链接数据, 查找关联feature错误: %d, err:%v", id, findFeatureErr) + return + } + if feature != nil && feature.Id > 0 { + writer.Write(feature) + } + }, func(pipe <-chan *model.Feature, cancel func(error)) { + for item := range pipe { + var feature types.Feature + _ = copier.Copy(&feature, item) + product.Features = append(product.Features, feature) + } + }) + return &types.GetLinkDataResp{ + Product: product, + }, nil +} diff --git a/app/main/api/internal/logic/agent/getmembershipinfologic.go b/app/main/api/internal/logic/agent/getmembershipinfologic.go new file mode 100644 index 0000000..2835f3f --- /dev/null +++ b/app/main/api/internal/logic/agent/getmembershipinfologic.go @@ -0,0 +1,100 @@ +package agent + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetMembershipInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetMembershipInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMembershipInfoLogic { + return &GetMembershipInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetMembershipInfoLogic) GetMembershipInfo() (resp *types.GetMembershipInfoResp, err error) { + // 获取普通代理配置 + normalConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取普通代理配置失败: %v", err) + } + + // 获取VIP会员配置 + vipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameVIP) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取VIP会员配置失败: %v", err) + } + + // 获取SVIP会员配置 + svipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameSVIP) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取SVIP会员配置失败: %v", err) + } + + // 转换配置的辅助函数 + convertConfig := func(config *model.AgentMembershipConfig) (types.MembershipConfigInfo, error) { + var configInfo types.MembershipConfigInfo + err := copier.Copy(&configInfo, config) + if err != nil { + return configInfo, err + } + // 转换Null类型字段 + configInfo.Price = lzUtils.NullFloat64ToFloat64(config.Price) + configInfo.ReportCommission = lzUtils.NullFloat64ToFloat64(config.ReportCommission) + configInfo.LowerActivityReward = lzUtils.NullFloat64ToFloat64(config.LowerActivityReward) + configInfo.NewActivityReward = lzUtils.NullFloat64ToFloat64(config.NewActivityReward) + configInfo.LowerStandardCount = lzUtils.NullInt64ToInt64(config.LowerStandardCount) + configInfo.NewLowerStandardCount = lzUtils.NullInt64ToInt64(config.NewLowerStandardCount) + configInfo.LowerWithdrawRewardRatio = lzUtils.NullFloat64ToFloat64(config.LowerWithdrawRewardRatio) + configInfo.LowerConvertVipReward = lzUtils.NullFloat64ToFloat64(config.LowerConvertVipReward) + configInfo.LowerConvertSvipReward = lzUtils.NullFloat64ToFloat64(config.LowerConvertSvipReward) + configInfo.ExemptionAmount = lzUtils.NullFloat64ToFloat64(config.ExemptionAmount) + configInfo.PriceIncreaseMax = lzUtils.NullFloat64ToFloat64(config.PriceIncreaseMax) + configInfo.PriceRatio = lzUtils.NullFloat64ToFloat64(config.PriceRatio) + configInfo.PriceIncreaseAmount = lzUtils.NullFloat64ToFloat64(config.PriceIncreaseAmount) + return configInfo, nil + } + + // 转换普通代理配置 + normalConfigInfo, err := convertConfig(normalConfig) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换普通代理配置失败: %v", err) + } + + // 转换VIP配置 + vipConfigInfo, err := convertConfig(vipConfig) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换VIP配置失败: %v", err) + } + + // 转换SVIP配置 + svipConfigInfo, err := convertConfig(svipConfig) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换SVIP配置失败: %v", err) + } + + // 构建响应数据 + resp = &types.GetMembershipInfoResp{ + NormalConfig: normalConfigInfo, + VipConfig: vipConfigInfo, + SvipConfig: svipConfigInfo, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go new file mode 100644 index 0000000..cdb86c8 --- /dev/null +++ b/app/main/api/internal/logic/agent/saveagentmembershipuserconfiglogic.go @@ -0,0 +1,82 @@ +package agent + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type SaveAgentMembershipUserConfigLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewSaveAgentMembershipUserConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveAgentMembershipUserConfigLogic { + return &SaveAgentMembershipUserConfigLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SaveAgentMembershipUserConfigLogic) SaveAgentMembershipUserConfig(req *types.SaveAgentMembershipUserConfigReq) error { + userID, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,获取用户ID失败: %v", err) + } + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置: %v", err) + } + + var agentMembershipUserConfigModel *model.AgentMembershipUserConfig + agentMembershipUserConfigModel, err = l.svcCtx.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(l.ctx, agentModel.Id, req.ProductID) + + // 检查记录是否存在 + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 记录不存在,创建新的配置对象 + agentMembershipUserConfigModel = &model.AgentMembershipUserConfig{ + UserId: userID, + AgentId: agentModel.Id, + ProductId: req.ProductID, + PriceRatio: req.PriceRatio, + PriceIncreaseAmount: req.PriceIncreaseAmount, + PriceRangeFrom: req.PriceRangeFrom, + PriceRangeTo: req.PriceRangeTo, + } + + // 插入新记录 + _, err = l.svcCtx.AgentMembershipUserConfigModel.Insert(l.ctx, nil, agentMembershipUserConfigModel) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,插入新记录失败: %v", err) + } + return nil + } + + // 其他错误 + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,查询记录失败: %v", err) + } + + // 记录存在,更新现有配置 + agentMembershipUserConfigModel.PriceRatio = req.PriceRatio + agentMembershipUserConfigModel.PriceIncreaseAmount = req.PriceIncreaseAmount + agentMembershipUserConfigModel.PriceRangeFrom = req.PriceRangeFrom + agentMembershipUserConfigModel.PriceRangeTo = req.PriceRangeTo + + _, err = l.svcCtx.AgentMembershipUserConfigModel.Update(l.ctx, nil, agentMembershipUserConfigModel) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存会员代理报告配置,更新记录失败: %v", err) + } + + return nil +} diff --git a/app/main/api/internal/logic/app/getappversionlogic.go b/app/main/api/internal/logic/app/getappversionlogic.go new file mode 100644 index 0000000..d5f843f --- /dev/null +++ b/app/main/api/internal/logic/app/getappversionlogic.go @@ -0,0 +1,31 @@ +package app + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAppVersionLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAppVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAppVersionLogic { + return &GetAppVersionLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAppVersionLogic) GetAppVersion() (resp *types.GetAppVersionResp, err error) { + return &types.GetAppVersionResp{ + Version: "1.0.0", + WgtUrl: "", + }, nil +} diff --git a/app/main/api/internal/logic/app/healthchecklogic.go b/app/main/api/internal/logic/app/healthchecklogic.go new file mode 100644 index 0000000..56e1ff1 --- /dev/null +++ b/app/main/api/internal/logic/app/healthchecklogic.go @@ -0,0 +1,31 @@ +package app + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type HealthCheckLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewHealthCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HealthCheckLogic { + return &HealthCheckLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *HealthCheckLogic) HealthCheck() (resp *types.HealthCheckResp, err error) { + return &types.HealthCheckResp{ + Status: "UP", + Message: "Service is healthy HahaHa", + }, nil +} diff --git a/app/main/api/internal/logic/auth/sendsmslogic.go b/app/main/api/internal/logic/auth/sendsmslogic.go new file mode 100644 index 0000000..660cb81 --- /dev/null +++ b/app/main/api/internal/logic/auth/sendsmslogic.go @@ -0,0 +1,124 @@ +package auth + +import ( + "context" + "fmt" + "math/rand" + "time" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/pkg/captcha" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v3/client" + "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/zeromicro/go-zero/core/logx" +) + +type SendSmsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLogic { + return &SendSmsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error { + cfg := l.svcCtx.Config.Captcha + // captcha 开关:关闭后直接跳过图形验证码校验 + if !cfg.Enable { + return nil + } + if err := captcha.VerifyWithRequest(captcha.Config{ + AccessKeyID: cfg.AccessKeyID, + AccessKeySecret: cfg.AccessKeySecret, + EndpointURL: cfg.EndpointURL, + SceneID: cfg.SceneID, + }, req.CaptchaVerifyParam, l.ctx); err != nil { + return err + } + // 默认action类型:当未传入时,默认为login,便于小程序环境兼容 + action := req.ActionType + if action == "" { + action = "login" + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err) + } + // 检查手机号是否在一分钟内已发送过验证码 + limitCodeKey := fmt.Sprintf("limit:%s:%s", action, encryptedMobile) + exists, err := l.svcCtx.Redis.Exists(limitCodeKey) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取redis缓存失败: %s", encryptedMobile) + } + + if exists { + // 如果 Redis 中已经存在标记,说明在 1 分钟内请求过,返回错误 + return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "短信发送, 手机号1分钟内重复请求发送验证码: %s", encryptedMobile) + } + + code := fmt.Sprintf("%06d", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000)) + + // 发送短信 + smsResp, err := l.sendSmsRequest(req.Mobile, code) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 调用阿里客户端失败: %v", err) + } + if *smsResp.Body.Code != "OK" { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 阿里客户端响应失败: %s", *smsResp.Body.Message) + } + codeKey := fmt.Sprintf("%s:%s", action, encryptedMobile) + // 将验证码保存到 Redis,设置过期时间 + err = l.svcCtx.Redis.Setex(codeKey, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟 + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置过期时间失败: %v", err) + } + // 在 Redis 中设置 1 分钟的标记,限制重复请求 + err = l.svcCtx.Redis.Setex(limitCodeKey, code, 60) // 标记 1 分钟内不能重复请求 + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置限制重复请求失败: %v", err) + } + return nil +} + +// CreateClient 创建阿里云短信客户端 +func (l *SendSmsLogic) CreateClient() (*dysmsapi.Client, error) { + config := &openapi.Config{ + AccessKeyId: &l.svcCtx.Config.VerifyCode.AccessKeyID, + AccessKeySecret: &l.svcCtx.Config.VerifyCode.AccessKeySecret, + } + config.Endpoint = tea.String(l.svcCtx.Config.VerifyCode.EndpointURL) + return dysmsapi.NewClient(config) +} + +// sendSmsRequest 发送短信请求 +func (l *SendSmsLogic) sendSmsRequest(mobile, code string) (*dysmsapi.SendSmsResponse, error) { + // 初始化阿里云短信客户端 + cli, err := l.CreateClient() + if err != nil { + return nil, err + } + + request := &dysmsapi.SendSmsRequest{ + SignName: tea.String(l.svcCtx.Config.VerifyCode.SignName), + TemplateCode: tea.String(l.svcCtx.Config.VerifyCode.TemplateCode), + PhoneNumbers: tea.String(mobile), + TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", code)), + } + runtime := &service.RuntimeOptions{} + return cli.SendSmsWithOptions(request, runtime) +} diff --git a/app/main/api/internal/logic/authorization/downloadauthorizationdocumentbynamelogic.go b/app/main/api/internal/logic/authorization/downloadauthorizationdocumentbynamelogic.go new file mode 100644 index 0000000..3305523 --- /dev/null +++ b/app/main/api/internal/logic/authorization/downloadauthorizationdocumentbynamelogic.go @@ -0,0 +1,59 @@ +package authorization + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DownloadAuthorizationDocumentByNameLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDownloadAuthorizationDocumentByNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DownloadAuthorizationDocumentByNameLogic { + return &DownloadAuthorizationDocumentByNameLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DownloadAuthorizationDocumentByNameLogic) DownloadAuthorizationDocumentByName(req *types.DownloadAuthorizationDocumentByNameReq) (resp *types.DownloadAuthorizationDocumentResp, err error) { + builder := l.svcCtx.AuthorizationDocumentModel.SelectBuilder(). + Where("file_name = ?", req.FileName). + Where("del_state = ?", globalkey.DelStateNo). + Limit(1) + + authDocs, err := l.svcCtx.AuthorizationDocumentModel.FindAll(l.ctx, builder, "") + if err != nil { + logx.Errorf("根据文件名查询授权书失败: fileName=%s, error=%v", req.FileName, err) + return nil, err + } + + if len(authDocs) == 0 { + logx.Errorf("根据文件名未找到授权书: fileName=%s", req.FileName) + return nil, model.ErrNotFound + } + authDoc := authDocs[0] + + if authDoc.Status != "active" { + logx.Errorf("授权书状态异常: fileName=%s, status=%s", req.FileName, authDoc.Status) + return nil, errors.New("授权书不可用") + } + + filePath := l.svcCtx.AuthorizationService.ResolveFilePath(authDoc.FilePath, authDoc.FileUrl) + + resp = &types.DownloadAuthorizationDocumentResp{ + FileName: authDoc.FileName, + FilePath: filePath, + } + return resp, nil +} diff --git a/app/main/api/internal/logic/authorization/downloadauthorizationdocumentlogic.go b/app/main/api/internal/logic/authorization/downloadauthorizationdocumentlogic.go new file mode 100644 index 0000000..e229917 --- /dev/null +++ b/app/main/api/internal/logic/authorization/downloadauthorizationdocumentlogic.go @@ -0,0 +1,50 @@ +package authorization + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DownloadAuthorizationDocumentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDownloadAuthorizationDocumentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DownloadAuthorizationDocumentLogic { + return &DownloadAuthorizationDocumentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DownloadAuthorizationDocumentLogic) DownloadAuthorizationDocument(req *types.DownloadAuthorizationDocumentReq) (resp *types.DownloadAuthorizationDocumentResp, err error) { + // 1. 从数据库获取授权书信息 + authDoc, err := l.svcCtx.AuthorizationDocumentModel.FindOne(l.ctx, req.DocumentId) + if err != nil { + logx.Errorf("获取授权书失败: documentId=%d, error=%v", req.DocumentId, err) + return nil, err + } + + // 2. 检查授权书状态 + if authDoc.Status != "active" { + logx.Errorf("授权书状态异常: documentId=%d, status=%s", req.DocumentId, authDoc.Status) + return nil, errors.New("授权书不可用") + } + + // 3. 构建响应 + filePath := l.svcCtx.AuthorizationService.ResolveFilePath(authDoc.FilePath, authDoc.FileUrl) + + resp = &types.DownloadAuthorizationDocumentResp{ + FileName: authDoc.FileName, + FilePath: filePath, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/authorization/getauthorizationdocumentbyorderlogic.go b/app/main/api/internal/logic/authorization/getauthorizationdocumentbyorderlogic.go new file mode 100644 index 0000000..3da6ba2 --- /dev/null +++ b/app/main/api/internal/logic/authorization/getauthorizationdocumentbyorderlogic.go @@ -0,0 +1,62 @@ +package authorization + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAuthorizationDocumentByOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAuthorizationDocumentByOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAuthorizationDocumentByOrderLogic { + return &GetAuthorizationDocumentByOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAuthorizationDocumentByOrderLogic) GetAuthorizationDocumentByOrder(req *types.GetAuthorizationDocumentByOrderReq) (resp *types.GetAuthorizationDocumentByOrderResp, err error) { + // 1. 根据订单ID查询授权书列表 + authDocs, err := l.svcCtx.AuthorizationDocumentModel.FindByOrderId(l.ctx, req.OrderId) + if err != nil { + logx.Errorf("根据订单ID获取授权书失败: orderId=%d, error=%v", req.OrderId, err) + return nil, err + } + + // 2. 构建响应列表 + var documents []types.AuthorizationDocumentInfo + for _, authDoc := range authDocs { + // 只返回状态为active的授权书 + if authDoc.Status == "active" { + fullFileURL := l.svcCtx.AuthorizationService.GetFullFileURL(authDoc.FileUrl) + + documents = append(documents, types.AuthorizationDocumentInfo{ + DocumentId: authDoc.Id, + UserId: authDoc.UserId, + OrderId: authDoc.OrderId, + QueryId: authDoc.QueryId, + FileName: authDoc.FileName, + FileUrl: fullFileURL, + FileSize: authDoc.FileSize, + FileType: authDoc.FileType, + Status: authDoc.Status, + CreateTime: authDoc.CreateTime.Format("2006-01-02 15:04:05"), + }) + } + } + + // 3. 构建响应 + resp = &types.GetAuthorizationDocumentByOrderResp{ + Documents: documents, + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/authorization/getauthorizationdocumentlogic.go b/app/main/api/internal/logic/authorization/getauthorizationdocumentlogic.go new file mode 100644 index 0000000..5a0d757 --- /dev/null +++ b/app/main/api/internal/logic/authorization/getauthorizationdocumentlogic.go @@ -0,0 +1,59 @@ +package authorization + +import ( + "context" + "errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetAuthorizationDocumentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetAuthorizationDocumentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAuthorizationDocumentLogic { + return &GetAuthorizationDocumentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetAuthorizationDocumentLogic) GetAuthorizationDocument(req *types.GetAuthorizationDocumentReq) (resp *types.GetAuthorizationDocumentResp, err error) { + // 1. 从数据库获取授权书信息 + authDoc, err := l.svcCtx.AuthorizationDocumentModel.FindOne(l.ctx, req.DocumentId) + if err != nil { + logx.Errorf("获取授权书失败: documentId=%d, error=%v", req.DocumentId, err) + return nil, err + } + + // 2. 检查授权书状态 + if authDoc.Status != "active" { + logx.Errorf("授权书状态异常: documentId=%d, status=%s", req.DocumentId, authDoc.Status) + return nil, errors.New("授权书不可用") + } + + // 3. 构建完整文件URL + fullFileURL := l.svcCtx.AuthorizationService.GetFullFileURL(authDoc.FileUrl) + + // 4. 构建响应 + resp = &types.GetAuthorizationDocumentResp{ + DocumentId: authDoc.Id, + UserId: authDoc.UserId, + OrderId: authDoc.OrderId, + QueryId: authDoc.QueryId, + FileName: authDoc.FileName, + FileUrl: fullFileURL, + FileSize: authDoc.FileSize, + FileType: authDoc.FileType, + Status: authDoc.Status, + CreateTime: authDoc.CreateTime.Format("2006-01-02 15:04:05"), + } + + return resp, nil +} diff --git a/app/main/api/internal/logic/notification/getnotificationslogic.go b/app/main/api/internal/logic/notification/getnotificationslogic.go new file mode 100644 index 0000000..bfe8bb7 --- /dev/null +++ b/app/main/api/internal/logic/notification/getnotificationslogic.go @@ -0,0 +1,57 @@ +package notification + +import ( + "context" + "bdrp-server/common/xerr" + "time" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetNotificationsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetNotificationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNotificationsLogic { + return &GetNotificationsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetNotificationsLogic) GetNotifications() (resp *types.GetNotificationsResp, err error) { + // 获取今天的日期 + now := time.Now() + + // 获取开始和结束日期的时间戳 + todayStart := now.Format("2006-01-02") + " 00:00:00" + todayEnd := now.Format("2006-01-02") + " 23:59:59" + + // 构建查询条件 + builder := l.svcCtx.GlobalNotificationsModel.SelectBuilder(). + Where("status = ?", "active"). + Where("(start_date IS NULL OR start_date <= ?)", todayEnd). // start_date 是 NULL 或者小于等于今天结束时间 + Where("(end_date IS NULL OR end_date >= ?)", todayStart) // end_date 是 NULL 或者大于等于今天开始时间 + + notificationsModelList, findErr := l.svcCtx.GlobalNotificationsModel.FindAll(l.ctx, builder, "") + if findErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "全局通知, 查找通知失败, err:%+v", findErr) + } + + var notifications []types.Notification + copyErr := copier.Copy(¬ifications, ¬ificationsModelList) + if copyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "全局通知, 复制结构体失败, err:%+v", copyErr) + } + + return &types.GetNotificationsResp{Notifications: notifications}, nil +} diff --git a/app/main/api/internal/logic/pay/alipaycallbacklogic.go b/app/main/api/internal/logic/pay/alipaycallbacklogic.go new file mode 100644 index 0000000..86b8296 --- /dev/null +++ b/app/main/api/internal/logic/pay/alipaycallbacklogic.go @@ -0,0 +1,233 @@ +package pay + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/smartwalle/alipay/v3" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AlipayCallbackLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAlipayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayCallbackLogic { + return &AlipayCallbackLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error { + notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r) + if err != nil { + logx.Errorf("支付宝支付回调,%v", err) + return nil + } + + // 根据订单号前缀判断订单类型 + orderNo := notification.OutTradeNo + if strings.HasPrefix(orderNo, "Q_") { + // 查询订单处理 + return l.handleQueryOrderPayment(w, notification) + } else if strings.HasPrefix(orderNo, "A_") { + // 代理会员订单处理 + return l.handleAgentVipOrderPayment(w, notification) + } else { + // 兼容旧订单,假设没有前缀的是查询订单 + return l.handleQueryOrderPayment(w, notification) + } +} + +// 处理查询订单支付 +func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error { + order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo) + if findOrderErr != nil { + logx.Errorf("支付宝支付回调,查找订单失败: %+v", findOrderErr) + return nil + } + + if order.Status != "pending" { + alipay.ACKNotification(w) + return nil + } + + user, err := l.svcCtx.UserModel.FindOne(l.ctx, order.UserId) + if err != nil { + logx.Errorf("支付宝支付回调,查找用户失败: %+v", err) + return nil + } + + amount := lzUtils.ToAlipayAmount(order.Amount) + if user.Inside != 1 { + // 确保订单金额和状态正确,防止重复更新 + if amount != notification.TotalAmount { + logx.Errorf("支付宝支付回调,金额不一致") + return nil + } + } + + switch notification.TradeStatus { + case alipay.TradeStatusSuccess: + order.Status = "paid" + order.PayTime = lzUtils.TimeToNullTime(time.Now()) + default: + return nil + } + + order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo) + if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil { + logx.Errorf("支付宝支付回调,修改订单信息失败: %+v", updateErr) + return nil + } + if order.Status == "paid" { + if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil { + logx.Errorf("异步任务调度失败: %v", asyncErr) + return asyncErr + } + } + + alipay.ACKNotification(w) + return nil +} + +// 处理代理会员订单支付 +func (l *AlipayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWriter, notification *alipay.Notification) error { + agentOrder, findAgentOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo) + if findAgentOrderErr != nil { + logx.Errorf("支付宝支付回调,查找代理会员订单失败: %+v", findAgentOrderErr) + return nil + } + + if agentOrder.Status != "pending" { + alipay.ACKNotification(w) + return nil + } + + user, err := l.svcCtx.UserModel.FindOne(l.ctx, agentOrder.UserId) + if err != nil { + logx.Errorf("支付宝支付回调,查找用户失败: %+v", err) + return nil + } + + amount := lzUtils.ToAlipayAmount(agentOrder.Amount) + if user.Inside != 1 { + // 确保订单金额和状态正确,防止重复更新 + if amount != notification.TotalAmount { + logx.Errorf("支付宝支付回调,金额不一致") + return nil + } + } + + switch notification.TradeStatus { + case alipay.TradeStatusSuccess: + agentOrder.Status = "paid" + default: + return nil + } + + if agentOrder.Status == "paid" { + err = l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { + agentModel, err := l.svcCtx.AgentModel.FindOne(transCtx, agentOrder.AgentId) + if err != nil { + return fmt.Errorf("查找代理信息失败: %+v", err) + } + agentOrder.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo) + if updateErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, agentOrder); updateErr != nil { + return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr) + } + + // 记录旧等级,用于判断是否为升级 + oldLevel := agentModel.LevelName + + // 设置会员等级 + agentModel.LevelName = agentOrder.LevelName + + // 延长会员时间 + // 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励) + isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now()) + if isValidRenewal { + logx.Infof("代理会员有效期内续费成功,会员ID:%d,等级:%s", agentModel.Id, agentModel.LevelName) + } else { + logx.Infof("代理会员新购、升级或重新激活成功,会员ID:%d,等级:%s", agentModel.Id, agentModel.LevelName) + } + agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime) + + if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil { + return fmt.Errorf("修改代理信息失败: %+v", updateErr) + } + + // 如果不是有效期内续费,给上级代理发放升级奖励 + if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) { + // 验证升级路径的有效性 + if oldLevel != agentOrder.LevelName { + upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session) + if upgradeRewardErr != nil { + logx.Errorf("发放升级奖励失败,代理ID:%d,旧等级:%s,新等级:%s,错误:%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr) + // 升级奖励失败不影响主流程,只记录日志 + } else { + logx.Infof("发放升级奖励成功,代理ID:%d,旧等级:%s,新等级:%s", agentModel.Id, oldLevel, agentOrder.LevelName) + } + } + } + + return nil + }) + if err != nil { + logx.Errorf("支付宝支付回调,处理代理会员订单失败: %+v", err) + refundErr := l.handleRefund(agentOrder) + if refundErr != nil { + logx.Errorf("支付宝支付回调,退款失败: %+v", refundErr) + } + return nil + } + } + + alipay.ACKNotification(w) + return nil +} + +func (l *AlipayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeOrder) error { + ctx := context.Background() + // 退款 + if order.PaymentMethod == "wechat" { + refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount) + if refundErr != nil { + return refundErr + } + } else { + refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) + if refundErr != nil { + return refundErr + } + if refund.IsSuccess() { + logx.Errorf("支付宝退款成功, orderID: %d", order.Id) + // 更新订单状态为退款 + order.Status = "refunded" + updateOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(ctx, nil, order) + if updateOrderErr != nil { + logx.Errorf("更新订单状态失败,订单ID: %d, 错误: %v", order.Id, updateOrderErr) + return fmt.Errorf("更新订单状态失败: %v", updateOrderErr) + } + return nil + } else { + logx.Errorf("支付宝退款失败:%v", refundErr) + return refundErr + } + // 直接成功 + } + return nil +} diff --git a/app/main/api/internal/logic/pay/iapcallbacklogic.go b/app/main/api/internal/logic/pay/iapcallbacklogic.go new file mode 100644 index 0000000..ed8a422 --- /dev/null +++ b/app/main/api/internal/logic/pay/iapcallbacklogic.go @@ -0,0 +1,82 @@ +package pay + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/lzUtils" + "time" + + "github.com/pkg/errors" + + "github.com/zeromicro/go-zero/core/logx" +) + +type IapCallbackLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewIapCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IapCallbackLogic { + return &IapCallbackLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *IapCallbackLogic) IapCallback(req *types.IapCallbackReq) error { + // Step 1: 查找订单 + order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderID) + if findOrderErr != nil { + logx.Errorf("苹果内购支付回调,查找订单失败: %+v", findOrderErr) + return nil + } + + // Step 2: 验证订单状态 + if order.Status != "pending" { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 订单状态异常: %+v", order) + } + + // Step 3: 调用 VerifyReceipt 验证苹果支付凭证 + //receipt := req.TransactionReceipt // 从请求中获取支付凭证 + //verifyResponse, verifyErr := l.svcCtx.ApplePayService.VerifyReceipt(l.ctx, receipt) + //if verifyErr != nil { + // return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 验证订单异常: %+v", verifyErr) + //} + + // Step 4: 验证订单 + //product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, order.Id) + //if findProductErr != nil { + // return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "苹果内购支付回调, 获取订单相关商品失败: %+v", findProductErr) + //} + //isProductMatched := false + //appleProductID := l.svcCtx.ApplePayService.GetIappayAppID(product.ProductEn) + //for _, item := range verifyResponse.Receipt.InApp { + // if item.ProductID == appleProductID { + // isProductMatched = true + // order.PlatformOrderId = lzUtils.StringToNullString(item.TransactionID) // 记录交易 ID + // break + // } + //} + //if !isProductMatched { + // return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 商品 ID 不匹配,订单 ID: %d, 回调苹果商品 ID: %s", order.Id, verifyResponse.Receipt.InApp[0].ProductID) + //} + + // Step 5: 更新订单状态 mm + order.Status = "paid" + order.PayTime = lzUtils.TimeToNullTime(time.Now()) + + // 更新订单到数据库 + if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 修改订单信息失败: %+v", updateErr) + } + + // Step 6: 处理订单完成后的逻辑 + if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调,异步任务调度失败: %v", asyncErr) + } + return nil +} diff --git a/app/main/api/internal/logic/pay/paymentchecklogic.go b/app/main/api/internal/logic/pay/paymentchecklogic.go new file mode 100644 index 0000000..f19df5a --- /dev/null +++ b/app/main/api/internal/logic/pay/paymentchecklogic.go @@ -0,0 +1,49 @@ +package pay + +import ( + "context" + "strings" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type PaymentCheckLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewPaymentCheckLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentCheckLogic { + return &PaymentCheckLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *PaymentCheckLogic) PaymentCheck(req *types.PaymentCheckReq) (resp *types.PaymentCheckResp, err error) { + if strings.HasPrefix(req.OrderNo, "A_") { + order, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", err) + } + return &types.PaymentCheckResp{ + Type: "agent_vip", + Status: order.Status, + }, nil + } else { + 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: "query", + Status: order.Status, + }, nil + } +} diff --git a/app/main/api/internal/logic/pay/paymentlogic.go b/app/main/api/internal/logic/pay/paymentlogic.go new file mode 100644 index 0000000..a6ba2ac --- /dev/null +++ b/app/main/api/internal/logic/pay/paymentlogic.go @@ -0,0 +1,347 @@ +package pay + +import ( + "context" + "database/sql" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/redis/go-redis/v9" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type PaymentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} +type PaymentTypeResp struct { + amount float64 + outTradeNo string + description string + orderID int64 // 仅 query 类型有值;agent_vip 为 0 +} + +func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic { + return &PaymentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) { + var paymentTypeResp *PaymentTypeResp + var prepayData interface{} + + l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + switch req.PayType { + case "agent_vip", "agent_upgrade": + paymentTypeResp, err = l.AgentVipOrderPayment(req, session) + if err != nil { + return err + } + + case "query": + paymentTypeResp, err = l.QueryOrderPayment(req, session) + if err != nil { + return err + } + default: + err = errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "不支持的支付类型: %s", req.PayType) + return err + } + + // 开发环境测试支付模式:仅当 pay_method=test 时跳过实际支付,直接返回 test_payment_success + // 支付宝/微信在开发环境下仍走真实支付流程(跳转沙箱),支付成功后由回调更新订单 + // 注意:订单状态更新在事务外进行,避免在事务中查询不到订单的问题 + isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test" + if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 { + prepayData = "test_payment_success" + logx.Infof("开发环境测试支付模式:订单 %s (ID: %d) 将在事务提交后更新状态", paymentTypeResp.outTradeNo, paymentTypeResp.orderID) + return nil + } + + // 仅 wechat/alipay/appleiap 调起真实支付;test 仅在上面 isDevTestPayment 分支处理 + var createOrderErr error + if req.PayMethod == "wechat" { + prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo) + } else if req.PayMethod == "alipay" { + prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo) + } else if req.PayMethod == "appleiap" { + prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo) + } else if req.PayMethod == "test" { + if os.Getenv("ENV") != "development" { + return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "开发环境测试支付仅在开发环境可用") + } + return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "开发环境测试支付仅支持 query 类型订单") + } else { + return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "不支持的支付方式: %s", req.PayMethod) + } + if createOrderErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr) + } + return nil + }) + if err != nil { + return nil, err + } + // 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程(仅 pay_method=test 且 query 类型 orderID>0) + isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test" + if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 { + go func() { + time.Sleep(200 * time.Millisecond) + + finalOrderID := paymentTypeResp.orderID + order, findOrderErr := l.svcCtx.OrderModel.FindOne(context.Background(), finalOrderID) + if findOrderErr != nil { + logx.Errorf("开发测试模式,查找订单失败,订单ID: %d, 错误: %v", finalOrderID, findOrderErr) + return + } + order.Status = "paid" + now := time.Now() + order.PayTime = sql.NullTime{Time: now, Valid: true} + + // 空报告模式:在 PaymentPlatform 标记为 "test",在 paySuccessNotify.ProcessTask 中通过 + // order.PaymentPlatform == "test" 识别(isEmptyReportMode),并生成空报告、跳过 API 调用 + isEmptyReportMode := req.PayMethod == "test" + if isEmptyReportMode { + order.PaymentPlatform = "test" + logx.Infof("开发环境空报告模式:订单 %s (ID: %d) 已标记为空报告模式", paymentTypeResp.outTradeNo, finalOrderID) + } + updateErr := l.svcCtx.OrderModel.UpdateWithVersion(context.Background(), nil, order) + if updateErr != nil { + logx.Errorf("开发测试模式,更新订单状态失败,订单ID: %d, 错误: %v", finalOrderID, updateErr) + return + } + + if enqErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); enqErr != nil { + logx.Errorf("开发测试模式,入队生成报告失败,订单ID: %d, 错误: %v", finalOrderID, enqErr) + } + + logx.Infof("开发测试模式,订单状态已更新为已支付并已入队生成报告,订单ID: %d", finalOrderID) + + // 再次短暂延迟,确保订单状态更新已提交 + time.Sleep(100 * time.Millisecond) + }() + } + + switch v := prepayData.(type) { + case string: + // 如果 prepayData 是字符串类型,直接返回 + return &types.PaymentResp{PrepayId: v, OrderNo: paymentTypeResp.outTradeNo}, nil + default: + return &types.PaymentResp{PrepayData: prepayData, OrderNo: paymentTypeResp.outTradeNo}, nil + } +} + +func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { + userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr) + } + outTradeNo := req.Id + redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo) + cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey) + if cacheErr != nil { + if cacheErr == redis.Nil { + return nil, errors.Wrapf(xerr.NewErrMsg("订单已过期"), "生成订单, 缓存不存在, %+v", cacheErr) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取缓存失败, %+v", cacheErr) + } + var data types.QueryCacheLoad + err = json.Unmarshal([]byte(cache), &data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %v", err) + } + + product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %v", err) + } + + var amount float64 + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %v", err) + } + + if data.AgentIdentifier != "" { + agentLinkModel, findAgentLinkErr := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, data.AgentIdentifier) + if findAgentLinkErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取代理订单失败: %+v", findAgentLinkErr) + } + amount = agentLinkModel.Price + } else { + amount = product.SellPrice + } + + if user.Inside == 1 { + amount = 0.01 + } + + // 检查72小时内身份证查询次数限制 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取AES密钥失败: %+v", decodeErr) + } + // 解密缓存中的参数 + decryptedParams, decryptErr := crypto.AesDecrypt(data.Params, key) + if decryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解密缓存参数失败: %v", decryptErr) + } + var params map[string]interface{} + if unmarshalErr := json.Unmarshal(decryptedParams, ¶ms); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析解密参数失败: %v", unmarshalErr) + } + // 获取身份证号 + idCard, ok := params["id_card"].(string) + if !ok || idCard == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取身份证号失败") + } + // 加密身份证号用于查询 + encryptedIdCard, encryptErr := crypto.EncryptIDCard(idCard, key) + if encryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 加密身份证号失败: %v", encryptErr) + } + // 查询72小时内的查询次数 + queryCount, countErr := l.svcCtx.QueryUserRecordModel.CountByEncryptedIdCardIn72Hours(l.ctx, encryptedIdCard) + if countErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询记录失败: %v", countErr) + } + // 如果72小时内查询次数大于等于2次,禁止支付(当前这次是第3次) + if queryCount >= 2 { + return nil, errors.Wrapf(xerr.NewErrMsg("查询受限通知:检测到您72小时内已完成2次报告查询,系统已自动暂停服务。如需紧急查询,请联系客服申请临时额度。"), "生成订单, 查询次数超限: %d", queryCount) + } + + var orderID int64 + order := model.Order{ + OrderNo: outTradeNo, + UserId: userID, + ProductId: product.Id, + PaymentPlatform: req.PayMethod, + PaymentScene: "app", + Amount: amount, + Status: "pending", + } + orderInsertResult, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order) + if insertOrderErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr) + } + insertedOrderID, lastInsertIdErr := orderInsertResult.LastInsertId() + if lastInsertIdErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取保存订单ID失败: %+v", lastInsertIdErr) + } + orderID = insertedOrderID + + // 更新查询用户记录表的 order_id,便于通过查询信息追溯订单 + if rec, findErr := l.svcCtx.QueryUserRecordModel.FindOneByQueryNo(l.ctx, outTradeNo); findErr == nil { + rec.OrderId = orderID + _, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, session, rec) + } + + if data.AgentIdentifier != "" { + agent, parsingErr := l.agentParsing(data.AgentIdentifier) + if parsingErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 解析代理标识符失败: %+v", parsingErr) + } + var agentOrder model.AgentOrder + agentOrder.OrderId = orderID + agentOrder.AgentId = agent.AgentID + _, agentOrderInsert := l.svcCtx.AgentOrderModel.Insert(l.ctx, session, &agentOrder) + if agentOrderInsert != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert) + } + } + return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil +} +func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { + userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr) + } + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %v", err) + } + // 查询用户代理信息 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) + } + redisKey := fmt.Sprintf(types.AgentVipCacheKey, userID, req.Id) + cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey) + if cacheErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取缓存失败, %+v", cacheErr) + } + var agentVipCache types.AgentVipCache + err = json.Unmarshal([]byte(cache), &agentVipCache) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %+v", err) + } + agentMembershipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, agentVipCache.Type) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取代理会员配置失败, %+v", err) + } + + // 验证会员配置价格是否有效 + if !agentMembershipConfig.Price.Valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效", agentVipCache.Type) + } + + amount := agentMembershipConfig.Price.Float64 + // 验证价格是否合理 + if amount <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效: %f", agentVipCache.Type, amount) + } + + // 内部用户测试金额 + if user.Inside == 1 { + amount = 0.01 + } + agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{ + OrderNo: req.Id, + UserId: userID, + AgentId: agentModel.Id, + Amount: amount, + PaymentMethod: req.PayMethod, + LevelName: agentVipCache.Type, + Status: "pending", + } + _, err = l.svcCtx.AgentMembershipRechargeOrderModel.Insert(l.ctx, session, &agentMembershipRechargeOrder) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理会员充值订单失败: %+v", err) + } + return &PaymentTypeResp{amount: amount, outTradeNo: req.Id, description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName)}, nil +} +func (l *PaymentLogic) agentParsing(agentIdentifier string) (*types.AgentIdentifier, error) { + key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10") + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 获取AES密钥失败: %+v", decodeErr) + } + // Encrypt the params + + encrypted, err := crypto.AesDecryptURL(agentIdentifier, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, %v", err) + } + var agentIdentifierStruct types.AgentIdentifier + err = json.Unmarshal(encrypted, &agentIdentifierStruct) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务,反序列化失败 %v", err) + } + return &agentIdentifierStruct, nil +} diff --git a/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go new file mode 100644 index 0000000..63b66b4 --- /dev/null +++ b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go @@ -0,0 +1,239 @@ +package pay + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/model" + "bdrp-server/pkg/lzkit/lzUtils" + + "bdrp-server/app/main/api/internal/svc" + + "github.com/wechatpay-apiv3/wechatpay-go/services/payments" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type WechatPayCallbackLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewWechatPayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayCallbackLogic { + return &WechatPayCallbackLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *WechatPayCallbackLogic) WechatPayCallback(w http.ResponseWriter, r *http.Request) error { + notification, err := l.svcCtx.WechatPayService.HandleWechatPayNotification(l.ctx, r) + if err != nil { + logx.Errorf("微信支付回调,%v", err) + return nil + } + + // 根据订单号前缀判断订单类型 + orderNo := *notification.OutTradeNo + if strings.HasPrefix(orderNo, "Q_") { + // 查询订单处理 + return l.handleQueryOrderPayment(w, notification) + } else if strings.HasPrefix(orderNo, "A_") { + // 代理会员订单处理 + return l.handleAgentVipOrderPayment(w, notification) + } else { + // 兼容旧订单,假设没有前缀的是查询订单 + return l.handleQueryOrderPayment(w, notification) + } +} + +// 处理查询订单支付 +func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error { + order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo) + if findOrderErr != nil { + logx.Errorf("微信支付回调,查找订单信息失败: %+v", findOrderErr) + return nil + } + + amount := lzUtils.ToWechatAmount(order.Amount) + if amount != *notification.Amount.Total { + logx.Errorf("微信支付回调,金额不一致") + return nil + } + + if order.Status != "pending" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil + } + + switch *notification.TradeState { + case service.TradeStateSuccess: + order.Status = "paid" + order.PayTime = lzUtils.TimeToNullTime(time.Now()) + case service.TradeStateClosed: + order.Status = "closed" + order.CloseTime = lzUtils.TimeToNullTime(time.Now()) + case service.TradeStateRevoked: + order.Status = "failed" + default: + return nil + } + + order.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId) + if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil { + logx.Errorf("微信支付回调,更新订单失败%+v", updateErr) + return nil + } + // 更新查询用户记录表的 platform_order_id + if rec, findErr := l.svcCtx.QueryUserRecordModel.FindOneByQueryNo(l.ctx, *notification.OutTradeNo); findErr == nil { + rec.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId) + _, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, nil, rec) + } + + if order.Status == "paid" { + if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil { + logx.Errorf("异步任务调度失败: %v", asyncErr) + return asyncErr + } + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil +} + +// 处理代理会员订单支付 +func (l *WechatPayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWriter, notification *payments.Transaction) error { + agentOrder, findAgentOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo) + if findAgentOrderErr != nil { + logx.Errorf("微信支付回调,查找代理会员订单失败: %+v", findAgentOrderErr) + return nil + } + + if agentOrder.Status != "pending" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil + } + + user, err := l.svcCtx.UserModel.FindOne(l.ctx, agentOrder.UserId) + if err != nil { + logx.Errorf("微信支付回调,查找用户失败: %+v", err) + return nil + } + + amount := lzUtils.ToWechatAmount(agentOrder.Amount) + if user.Inside != 1 { + if amount != *notification.Amount.Total { + logx.Errorf("微信支付回调,金额不一致") + return nil + } + } + + switch *notification.TradeState { + case service.TradeStateSuccess: + agentOrder.Status = "paid" + default: + return nil + } + + if agentOrder.Status == "paid" { + err = l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { + agentModel, err := l.svcCtx.AgentModel.FindOne(transCtx, agentOrder.AgentId) + if err != nil { + return fmt.Errorf("查找代理信息失败: %+v", err) + } + + agentOrder.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId) + if updateErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, agentOrder); updateErr != nil { + return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr) + } + + // 记录旧等级,用于判断是否为升级 + oldLevel := agentModel.LevelName + + // 设置会员等级 + agentModel.LevelName = agentOrder.LevelName + + // 延长会员时间 + // 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励) + isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now()) + if isValidRenewal { + logx.Infof("代理会员有效期内续费成功,会员ID:%d,等级:%s", agentModel.Id, agentModel.LevelName) + } else { + logx.Infof("代理会员新购、升级或重新激活成功,会员ID:%d,等级:%s", agentModel.Id, agentModel.LevelName) + } + agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime) + + if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil { + return fmt.Errorf("修改代理信息失败: %+v", updateErr) + } + + // 如果不是有效期内续费,给上级代理发放升级奖励 + if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) { + // 验证升级路径的有效性 + if oldLevel != agentOrder.LevelName { + upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session) + if upgradeRewardErr != nil { + logx.Errorf("发放升级奖励失败,代理ID:%d,旧等级:%s,新等级:%s,错误:%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr) + // 升级奖励失败不影响主流程,只记录日志 + } else { + logx.Infof("发放升级奖励成功,代理ID:%d,旧等级:%s,新等级:%s", agentModel.Id, oldLevel, agentOrder.LevelName) + } + } + } + + return nil + }) + + if err != nil { + logx.Errorf("微信支付回调,处理代理会员订单失败: %+v", err) + refundErr := l.handleRefund(agentOrder) + if refundErr != nil { + logx.Errorf("微信支付回调,退款失败: %+v", refundErr) + } + return nil + } + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) + return nil +} + +func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechargeOrder) error { + ctx := context.Background() + // 退款 + if order.PaymentMethod == "wechat" { + refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount) + if refundErr != nil { + return refundErr + } + } else { + refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) + if refundErr != nil { + return refundErr + } + if refund.IsSuccess() { + logx.Errorf("支付宝退款成功, orderID: %d", order.Id) + // 更新订单状态为退款 + order.Status = "refunded" + updateOrderErr := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(ctx, nil, order) + if updateOrderErr != nil { + logx.Errorf("更新订单状态失败,订单ID: %d, 错误: %v", order.Id, updateOrderErr) + return fmt.Errorf("更新订单状态失败: %v", updateOrderErr) + } + return nil + } else { + logx.Errorf("支付宝退款失败:%v", refundErr) + return refundErr + } + } + return nil +} diff --git a/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go new file mode 100644 index 0000000..e77e417 --- /dev/null +++ b/app/main/api/internal/logic/pay/wechatpayrefundcallbacklogic.go @@ -0,0 +1,393 @@ +package pay + +import ( + "context" + "database/sql" + "net/http" + "strings" + "time" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// HandleCommissionAndWalletDeduction 处理退款后的佣金状态更新和钱包金额扣除 +// refundAmount 为本次实际退款金额(单位:元),从代理侧总共需要承担的金额 +// 该函数会优先冲减当前订单相关的佣金(基于 RefundedAmount),不足部分再从钱包余额/冻结余额中扣除 +func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.ServiceContext, session sqlx.Session, order *model.Order, refundAmount float64) error { + if refundAmount <= 0 { + return nil + } + + // 查询当前订单关联的所有佣金记录(包括已结算和冻结),剔除已经完全退款的 + commissionBuilder := svcCtx.AgentCommissionModel.SelectBuilder() + commissions, commissionsErr := svcCtx.AgentCommissionModel.FindAll(ctx, commissionBuilder.Where(squirrel.And{ + squirrel.Eq{"order_id": order.Id}, + squirrel.NotEq{"status": 2}, // 排除已全部退款的佣金 + }), "") + if commissionsErr != nil { + logx.Errorf("查询代理佣金失败,订单ID: %d, 错误: %v", order.Id, commissionsErr) + return nil // 返回 nil,因为佣金更新失败不应影响退款流程 + } + + if len(commissions) == 0 { + return nil + } + + // 剩余需要由佣金 + 钱包共同承担的退款金额 + remainRefundAmount := refundAmount + + // 记录每个代理本次需要从钱包扣除的金额,避免同一代理多条佣金时重复查钱包并产生多条流水 + type walletAdjust struct { + agentId int64 + amount float64 // 需要从该代理钱包扣除的金额(正数) + } + walletAdjustMap := make(map[int64]*walletAdjust) + + // 1. 先在佣金记录上做冲减:增加 RefundedAmount,必要时将状态置为已退款 + for _, commission := range commissions { + available := commission.Amount - commission.RefundedAmount + if available <= 0 { + continue + } + + if remainRefundAmount <= 0 { + break + } + + // 当前这条佣金最多可冲减 available,本次实际冲减 currentRefund + currentRefund := available + if currentRefund > remainRefundAmount { + currentRefund = remainRefundAmount + } + + // 更新佣金的已退款金额 + commission.RefundedAmount += currentRefund + // 如果这条佣金已经被完全冲减,则标记为已退款 + if commission.RefundedAmount >= commission.Amount { + commission.Status = 2 + } + + // 更新佣金状态到数据库 + var updateCommissionErr error + if session != nil { + updateCommissionErr = svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, commission) + } else { + updateCommissionErr = svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, nil, commission) + } + if updateCommissionErr != nil { + logx.Errorf("更新代理佣金状态失败,佣金ID: %d, 订单ID: %d, 错误: %v", commission.Id, order.Id, updateCommissionErr) + continue // 如果佣金状态更新失败,就不继续计入本次冲减 + } + + // 记录该代理需要从钱包扣除的金额(可能后续还有其他佣金叠加) + wa, ok := walletAdjustMap[commission.AgentId] + if !ok { + wa = &walletAdjust{agentId: commission.AgentId} + walletAdjustMap[commission.AgentId] = wa + } + wa.amount += currentRefund + + remainRefundAmount -= currentRefund + } + + // 2. 再按代理维度,从钱包(冻结余额/可用余额)中扣除对应金额 + for _, wa := range walletAdjustMap { + if wa.amount <= 0 { + continue + } + + // 处理用户钱包的金额扣除 + wallet, err := svcCtx.AgentWalletModel.FindOneByAgentId(ctx, wa.agentId) + if err != nil { + logx.Errorf("查询代理钱包失败,代理ID: %d, 错误: %v", wa.agentId, err) + continue + } + + // 记录变动前的余额 + balanceBefore := wallet.Balance + frozenBalanceBefore := wallet.FrozenBalance + + // 优先从冻结余额中扣除(与原先“冻结佣金优先使用冻结余额”的设计一致) + deduct := wa.amount + if wallet.FrozenBalance >= deduct { + wallet.FrozenBalance -= deduct + } else { + remaining := deduct - wallet.FrozenBalance + wallet.FrozenBalance = 0 + // 可用余额可以为负数,由业务承担风险 + wallet.Balance -= remaining + } + + // 变动后余额和冻结余额 + balanceAfter := wallet.Balance + frozenBalanceAfter := wallet.FrozenBalance + // 更新钱包 + var updateWalletErr error + if session != nil { + updateWalletErr = svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet) + } else { + updateWalletErr = svcCtx.AgentWalletModel.UpdateWithVersion(ctx, nil, wallet) + } + if updateWalletErr != nil { + logx.Errorf("更新代理钱包失败,代理ID: %d, 错误: %v", wa.agentId, updateWalletErr) + continue + } + // 创建钱包交易流水记录(退款) + transErr := svcCtx.AgentService.CreateWalletTransaction( + ctx, + session, + wa.agentId, + model.WalletTransactionTypeRefund, + -wa.amount*-1, // 钱包流水金额为负数 + balanceBefore, + balanceAfter, + frozenBalanceBefore, + frozenBalanceAfter, + order.OrderNo, + 0, // 这里不强绑到某一条具体佣金记录,按订单维度记录 + "订单退款,佣金已扣除", + ) + if transErr != nil { + logx.Errorf("创建代理钱包流水记录失败,代理ID: %d, 错误: %v", wa.agentId, transErr) + continue + } + } + return nil +} + +type WechatPayRefundCallbackLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewWechatPayRefundCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayRefundCallbackLogic { + return &WechatPayRefundCallbackLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// handleQueryOrderRefund 处理查询订单退款 +// refundAmountYuan 表示微信本次实际退款金额(单位:元) +func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, status refunddomestic.Status, refundAmountYuan float64) error { + order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo) + if err != nil { + return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo) + } + + // 检查订单是否已经处理过退款 + if order.Status == model.OrderStatusRefunded { + logx.Infof("订单已经是退款状态,无需重复处理: orderNo=%s", orderNo) + return nil + } + + // 只处理成功和失败状态 + var orderStatus, refundStatus string + switch status { + case refunddomestic.STATUS_SUCCESS: + orderStatus = model.OrderStatusRefunded + refundStatus = model.OrderRefundStatusSuccess + case refunddomestic.STATUS_CLOSED: + // 退款关闭,保持订单原状态,更新退款记录为失败 + refundStatus = model.OrderRefundStatusFailed + case refunddomestic.STATUS_ABNORMAL: + // 退款异常,保持订单原状态,更新退款记录为失败 + refundStatus = model.OrderRefundStatusFailed + default: + // 其他状态暂不处理 + return nil + } + + // 使用事务同时更新订单和退款记录 + err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + // 更新订单状态(仅在退款成功时更新) + if status == refunddomestic.STATUS_SUCCESS { + order.Status = orderStatus + order.RefundTime = sql.NullTime{ + Time: time.Now(), + Valid: true, + } + if err := l.svcCtx.OrderModel.UpdateWithVersion(ctx, session, order); err != nil { + return errors.Wrapf(err, "更新查询订单状态失败: %s", orderNo) + } + + // 退款成功时,按本次实际退款金额更新代理佣金状态并扣除钱包金额 + _ = HandleCommissionAndWalletDeduction(ctx, l.svcCtx, session, order, refundAmountYuan) + } + + // 查找最新的pending状态的退款记录 + refund, err := l.findLatestPendingRefund(ctx, order.Id) + if err != nil { + if err == model.ErrNotFound { + logx.Errorf("未找到订单对应的待处理退款记录: orderNo=%s, orderId=%d", orderNo, order.Id) + return nil // 没有退款记录时不报错,只记录警告 + } + return errors.Wrapf(err, "查找退款记录失败: orderNo=%s", orderNo) + } + + // 检查退款记录是否已经处理过 + if refund.Status == model.OrderRefundStatusSuccess { + logx.Infof("退款记录已经是成功状态,无需重复处理: orderNo=%s, refundId=%d", orderNo, refund.Id) + return nil + } + + refund.Status = refundStatus + if status == refunddomestic.STATUS_SUCCESS { + refund.RefundTime = sql.NullTime{ + Time: time.Now(), + Valid: true, + } + } else if status == refunddomestic.STATUS_CLOSED { + refund.CloseTime = sql.NullTime{ + Time: time.Now(), + Valid: true, + } + } + + if _, err := l.svcCtx.OrderRefundModel.Update(ctx, session, refund); err != nil { + return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo) + } + + return nil + }) + + if err != nil { + return errors.Wrapf(err, "更新订单和退款记录失败: %s", orderNo) + } + + return nil +} + +// handleAgentOrderRefund 处理代理会员订单退款 +func (l *WechatPayRefundCallbackLogic) handleAgentOrderRefund(orderNo string, status refunddomestic.Status) error { + order, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, orderNo) + if err != nil { + return errors.Wrapf(err, "查找代理会员订单信息失败: %s", orderNo) + } + + // 检查订单是否已经处理过退款 + if order.Status == "refunded" { + logx.Infof("代理会员订单已经是退款状态,无需重复处理: orderNo=%s", orderNo) + return nil + } + + if status == refunddomestic.STATUS_SUCCESS { + order.Status = "refunded" + } else if status == refunddomestic.STATUS_ABNORMAL { + return nil // 异常状态直接返回 + } else { + return nil // 其他状态直接返回 + } + + if err := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, order); err != nil { + return errors.Wrapf(err, "更新代理会员订单状态失败: %s", orderNo) + } + + return nil +} + +// sendSuccessResponse 发送成功响应 +func (l *WechatPayRefundCallbackLogic) sendSuccessResponse(w http.ResponseWriter) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("success")) +} + +func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWriter, r *http.Request) error { + // 1. 处理微信退款通知 + notification, err := l.svcCtx.WechatPayService.HandleRefundNotification(l.ctx, r) + if err != nil { + logx.Errorf("微信退款回调处理失败: %v", err) + l.sendSuccessResponse(w) + return nil + } + + // 2. 检查关键字段是否为空 + if notification.OutTradeNo == nil { + logx.Errorf("微信退款回调OutTradeNo字段为空") + l.sendSuccessResponse(w) + return nil + } + + orderNo := *notification.OutTradeNo + + // 3. 判断退款状态,优先使用Status,如果Status为nil则使用SuccessTime判断 + var status refunddomestic.Status + var statusDetermined bool = false + + if notification.Status != nil { + status = *notification.Status + statusDetermined = true + } else if notification.SuccessTime != nil && !notification.SuccessTime.IsZero() { + // 如果Status为空但SuccessTime有值,说明退款成功 + status = refunddomestic.STATUS_SUCCESS + statusDetermined = true + } else { + logx.Errorf("微信退款回调Status和SuccessTime都为空,无法确定退款状态: orderNo=%s", orderNo) + l.sendSuccessResponse(w) + return nil + } + + if !statusDetermined { + logx.Errorf("微信退款回调无法确定退款状态: orderNo=%s", orderNo) + l.sendSuccessResponse(w) + return nil + } + + var processErr error + + // 计算本次实际退款金额(单位:元),用于后续佣金和钱包扣减 + var refundAmountYuan float64 + if notification.Amount != nil && notification.Amount.Refund != nil { + // 微信退款金额单位为分,这里转换为元 + refundAmountYuan = float64(*notification.Amount.Refund) / 100.0 + } + + // 4. 根据订单号前缀处理不同类型的订单 + switch { + case strings.HasPrefix(orderNo, "Q_"): + processErr = l.handleQueryOrderRefund(orderNo, status, refundAmountYuan) + case strings.HasPrefix(orderNo, "A_"): + processErr = l.handleAgentOrderRefund(orderNo, status) + default: + // 兼容旧订单,假设没有前缀的是查询订单 + processErr = l.handleQueryOrderRefund(orderNo, status, refundAmountYuan) + } + + // 5. 处理错误并响应 + if processErr != nil { + logx.Errorf("处理退款订单失败: orderNo=%s, err=%v", orderNo, processErr) + } + + // 无论处理是否成功,都返回成功响应给微信 + l.sendSuccessResponse(w) + return nil +} + +// findLatestPendingRefund 查找订单最新的pending状态退款记录 +func (l *WechatPayRefundCallbackLogic) findLatestPendingRefund(ctx context.Context, orderId int64) (*model.OrderRefund, error) { + // 使用SelectBuilder查询最新的pending状态退款记录 + builder := l.svcCtx.OrderRefundModel.SelectBuilder(). + Where("order_id = ? AND status = ? AND del_state = ?", orderId, model.OrderRefundStatusPending, globalkey.DelStateNo). + OrderBy("id DESC"). + Limit(1) + + refunds, err := l.svcCtx.OrderRefundModel.FindAll(ctx, builder, "") + if err != nil { + return nil, err + } + + if len(refunds) == 0 { + return nil, model.ErrNotFound + } + + return refunds[0], nil +} diff --git a/app/main/api/internal/logic/product/getproductappbyenlogic.go b/app/main/api/internal/logic/product/getproductappbyenlogic.go new file mode 100644 index 0000000..3a8f74e --- /dev/null +++ b/app/main/api/internal/logic/product/getproductappbyenlogic.go @@ -0,0 +1,75 @@ +package product + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/mr" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetProductAppByEnLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetProductAppByEnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductAppByEnLogic { + return &GetProductAppByEnLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetProductAppByEnLogic) GetProductAppByEn(req *types.GetProductByEnRequest) (resp *types.ProductResponse, err error) { + productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.ProductEn) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品错误: %v", err) + } + + build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{ + "product_id": productModel.Id, + }) + productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品关联错误: %v", err) + } + var product types.Product + err = copier.Copy(&product, productModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %v", err) + } + mr.MapReduceVoid(func(source chan<- interface{}) { + for _, productFeature := range productFeatureAll { + source <- productFeature.FeatureId + } + }, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) { + id := item.(int64) + + feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id) + if findFeatureErr != nil { + logx.WithContext(l.ctx).Errorf("产品查询, 查找关联feature错误: %d, err:%v", id, findFeatureErr) + return + } + if feature != nil && feature.Id > 0 { + writer.Write(feature) + } + }, func(pipe <-chan *model.Feature, cancel func(error)) { + for item := range pipe { + var feature types.Feature + _ = copier.Copy(&feature, item) + product.Features = append(product.Features, feature) + } + }) + + return &types.ProductResponse{Product: product}, nil +} diff --git a/app/main/api/internal/logic/product/getproductbyenlogic.go b/app/main/api/internal/logic/product/getproductbyenlogic.go new file mode 100644 index 0000000..f69133a --- /dev/null +++ b/app/main/api/internal/logic/product/getproductbyenlogic.go @@ -0,0 +1,75 @@ +package product + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/mr" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetProductByEnLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetProductByEnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductByEnLogic { + return &GetProductByEnLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetProductByEnLogic) GetProductByEn(req *types.GetProductByEnRequest) (resp *types.ProductResponse, err error) { + productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.ProductEn) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品错误: %v", err) + } + + build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{ + "product_id": productModel.Id, + }) + productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品关联错误: %v", err) + } + var product types.Product + err = copier.Copy(&product, productModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %v", err) + } + mr.MapReduceVoid(func(source chan<- interface{}) { + for _, productFeature := range productFeatureAll { + source <- productFeature.FeatureId + } + }, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) { + id := item.(int64) + + feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id) + if findFeatureErr != nil { + logx.WithContext(l.ctx).Errorf("产品查询, 查找关联feature错误: %d, err:%v", id, findFeatureErr) + return + } + if feature != nil && feature.Id > 0 { + writer.Write(feature) + } + }, func(pipe <-chan *model.Feature, cancel func(error)) { + for item := range pipe { + var feature types.Feature + _ = copier.Copy(&feature, item) + product.Features = append(product.Features, feature) + } + }) + + return &types.ProductResponse{Product: product}, nil +} diff --git a/app/main/api/internal/logic/product/getproductbyidlogic.go b/app/main/api/internal/logic/product/getproductbyidlogic.go new file mode 100644 index 0000000..879de06 --- /dev/null +++ b/app/main/api/internal/logic/product/getproductbyidlogic.go @@ -0,0 +1,30 @@ +package product + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetProductByIDLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetProductByIDLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductByIDLogic { + return &GetProductByIDLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetProductByIDLogic) GetProductByID(req *types.GetProductByIDRequest) (resp *types.ProductResponse, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/main/api/internal/logic/query/querydetailbyorderidlogic.go b/app/main/api/internal/logic/query/querydetailbyorderidlogic.go new file mode 100644 index 0000000..872df7d --- /dev/null +++ b/app/main/api/internal/logic/query/querydetailbyorderidlogic.go @@ -0,0 +1,214 @@ +package query + +import ( + "context" + "database/sql" + "encoding/hex" + "encoding/json" + "fmt" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryDetailByOrderIdLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryDetailByOrderIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDetailByOrderIdLogic { + return &QueryDetailByOrderIdLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryDetailByOrderIdLogic) QueryDetailByOrderId(req *types.QueryDetailByOrderIdReq) (resp *types.QueryDetailByOrderIdResp, err error) { + // 获取当前用户ID + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 获取订单信息 + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "报告查询, 订单不存在: %v", err) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + 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 { + // 安全验证:确保订单属于当前用户 + if order.UserId != userId { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "无权查看此订单报告") + } + } + + // 检查订单状态 + if order.Status != "paid" { + return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "") + } + + // 获取报告信息 + queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + + var query types.Query + query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") + query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05") + + // 解密查询数据 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %v", err) + } + processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key) + if processParamsErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr) + } + processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key) + if processErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr) + } + updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData) + if updateFeatureAndProductFeatureErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr) + } + // 复制报告数据 + err = copier.Copy(&query, queryModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err) + } + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) + } + query.ProductName = product.ProductName + query.Product = product.ProductEn + return &types.QueryDetailByOrderIdResp{ + Query: query, + }, nil +} + +// ProcessQueryData 解密和反序列化 QueryData +func ProcessQueryData(queryData sql.NullString, target *[]types.QueryItem, key []byte) error { + queryDataStr := lzUtils.NullStringToString(queryData) + if queryDataStr == "" { + return nil + } + + // 解密数据 + decryptedData, decryptErr := crypto.AesDecrypt(queryDataStr, key) + if decryptErr != nil { + return decryptErr + } + + // 解析 JSON 数组 + var decryptedArray []map[string]interface{} + unmarshalErr := json.Unmarshal(decryptedData, &decryptedArray) + if unmarshalErr != nil { + return unmarshalErr + } + + // 确保 target 具有正确的长度 + if len(*target) == 0 { + *target = make([]types.QueryItem, len(decryptedArray)) + } + + // 填充解密后的数据到 target + for i := 0; i < len(decryptedArray); i++ { + // 直接填充解密数据到 Data 字段 + (*target)[i].Data = decryptedArray[i] + } + return nil +} +func (l *QueryDetailByOrderIdLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.QueryItem) error { + // 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引 + for i := len(*target) - 1; i >= 0; i-- { + queryItem := &(*target)[i] + + // 确保 Data 为 map 类型 + data, ok := queryItem.Data.(map[string]interface{}) + if !ok { + return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型") + } + + // 从 Data 中获取 apiID + apiID, ok := data["apiID"].(string) + if !ok { + return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型") + } + + // 查询 Feature + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID) + if err != nil { + // 如果 Feature 查不到,也要删除当前 QueryItem + *target = append((*target)[:i], (*target)[i+1:]...) + continue + } + + // 查询 ProductFeatureModel + builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID) + productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err) + } + + // 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项 + var featureData map[string]interface{} + // foundFeature := false + sort := 0 + for _, pf := range productFeatures { + if pf.FeatureId == feature.Id { // 确保和 Feature 关联 + sort = int(pf.Sort) + break // 找到第一个符合条件的就退出循环 + } + } + featureData = map[string]interface{}{ + "featureName": feature.Name, + "sort": sort, + } + + // 更新 queryItem 的 Feature 字段(不是数组) + queryItem.Feature = featureData + } + + return nil +} + +// ProcessQueryParams解密和反序列化 QueryParams +func ProcessQueryParams(QueryParams string, target *map[string]interface{}, key []byte) error { + // 解密 QueryParams + decryptedData, decryptErr := crypto.AesDecrypt(QueryParams, key) + if decryptErr != nil { + return decryptErr + } + + // 反序列化解密后的数据 + unmarshalErr := json.Unmarshal(decryptedData, target) + if unmarshalErr != nil { + return unmarshalErr + } + + return nil +} diff --git a/app/main/api/internal/logic/query/querydetailbyordernologic.go b/app/main/api/internal/logic/query/querydetailbyordernologic.go new file mode 100644 index 0000000..d28fd3f --- /dev/null +++ b/app/main/api/internal/logic/query/querydetailbyordernologic.go @@ -0,0 +1,156 @@ +package query + +import ( + "context" + "encoding/hex" + "fmt" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryDetailByOrderNoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryDetailByOrderNoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDetailByOrderNoLogic { + return &QueryDetailByOrderNoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryDetailByOrderNoLogic) QueryDetailByOrderNo(req *types.QueryDetailByOrderNoReq) (resp *types.QueryDetailByOrderNoResp, err error) { + // 获取当前用户ID + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err) + } + + // 获取订单信息 + order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "报告查询, 订单不存在: %v", err) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + + // 安全验证:确保订单属于当前用户 + if order.UserId != userId { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "无权查看此订单报告") + } + + // 检查订单状态 + if order.Status != "paid" { + return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "") + } + + // 获取报告信息 + queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + + var query types.Query + query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") + query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05") + + // 解密查询数据 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %v", err) + } + processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key) + if processParamsErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr) + } + processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key) + if processErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr) + } + updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData) + if updateFeatureAndProductFeatureErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr) + } + // 复制报告数据 + err = copier.Copy(&query, queryModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err) + } + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) + } + query.Product = product.ProductEn + query.ProductName = product.ProductName + return &types.QueryDetailByOrderNoResp{ + Query: query, + }, nil +} + +func (l *QueryDetailByOrderNoLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.QueryItem) error { + // 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引 + for i := len(*target) - 1; i >= 0; i-- { + queryItem := &(*target)[i] + + // 确保 Data 为 map 类型 + data, ok := queryItem.Data.(map[string]interface{}) + if !ok { + return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型") + } + + // 从 Data 中获取 apiID + apiID, ok := data["apiID"].(string) + if !ok { + return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型") + } + + // 查询 Feature + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID) + if err != nil { + // 如果 Feature 查不到,也要删除当前 QueryItem + *target = append((*target)[:i], (*target)[i+1:]...) + continue + } + + // 查询 ProductFeatureModel + builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID) + productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err) + } + + // 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项 + var featureData map[string]interface{} + // foundFeature := false + sort := 0 + for _, pf := range productFeatures { + if pf.FeatureId == feature.Id { // 确保和 Feature 关联 + sort = int(pf.Sort) + break // 找到第一个符合条件的就退出循环 + } + } + featureData = map[string]interface{}{ + "featureName": feature.Name, + "sort": sort, + } + + // 更新 queryItem 的 Feature 字段(不是数组) + queryItem.Feature = featureData + } + + return nil +} diff --git a/app/main/api/internal/logic/query/queryexamplelogic.go b/app/main/api/internal/logic/query/queryexamplelogic.go new file mode 100644 index 0000000..071fa03 --- /dev/null +++ b/app/main/api/internal/logic/query/queryexamplelogic.go @@ -0,0 +1,111 @@ +package query + +import ( + "context" + "encoding/hex" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/bytedance/sonic" + "github.com/pkg/errors" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryExampleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryExampleLogic { + return &QueryExampleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryExampleLogic) QueryExample(req *types.QueryExampleReq) (resp *types.QueryExampleResp, err error) { + // 根据产品特性标识获取产品信息 + product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.Feature) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取商品信息失败, %v", err) + } + + // 创建一个空的Query结构体来存储结果 + query := types.Query{ + Product: product.ProductEn, + ProductName: product.ProductName, + QueryData: make([]types.QueryItem, 0), + QueryParams: make(map[string]interface{}), + } + query.QueryParams = map[string]interface{}{ + "id_card": "45000000000000000", + "mobile": "13700000000", + "name": "张老三", + } + // 查询ProductFeatureModel获取产品相关的功能列表 + builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", product.Id) + productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 查询 ProductFeatureModel 错误: %v", err) + } + // 从每个启用的特性获取示例数据并合并 + for _, pf := range productFeatures { + if pf.Enable != 1 { + continue // 跳过未启用的特性 + } + + // 根据特性ID查找示例数据 + example, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, pf.FeatureId) + if err != nil { + logx.Infof("示例报告, 特性ID %d 无示例数据: %v", pf.FeatureId, err) + continue // 如果没有示例数据就跳过 + } + + // 获取对应的Feature信息 + feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, pf.FeatureId) + if err != nil { + logx.Infof("示例报告, 无法获取特性ID %d 的信息: %v", pf.FeatureId, err) + continue + } + + var queryItem types.QueryItem + + // 解密查询数据 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取AES解密解药失败, %v", err) + } + // 解析示例内容 + if example.Content == "000" { + queryItem.Data = example.Content + } else { + // 解密数据 + decryptedData, decryptErr := crypto.AesDecrypt(example.Content, key) + if decryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 解密数据失败: %v", decryptErr) + } + err = sonic.Unmarshal([]byte(decryptedData), &queryItem.Data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 解析示例内容失败: %v", err) + } + } + + // 添加特性信息 + queryItem.Feature = map[string]interface{}{ + "featureName": feature.Name, + "sort": pf.Sort, + } + // 添加到查询数据中 + query.QueryData = append(query.QueryData, queryItem) + } + + return &types.QueryExampleResp{ + Query: query, + }, nil +} diff --git a/app/main/api/internal/logic/query/querygeneratesharelinklogic.go b/app/main/api/internal/logic/query/querygeneratesharelinklogic.go new file mode 100644 index 0000000..4f2962a --- /dev/null +++ b/app/main/api/internal/logic/query/querygeneratesharelinklogic.go @@ -0,0 +1,111 @@ +package query + +import ( + "context" + "encoding/hex" + "encoding/json" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryGenerateShareLinkLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryGenerateShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryGenerateShareLinkLogic { + return &QueryGenerateShareLinkLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryGenerateShareLinkLogic) QueryGenerateShareLink(req *types.QueryGenerateShareLinkReq) (resp *types.QueryGenerateShareLinkResp, err error) { + userId, err := ctxdata.GetUidFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取用户ID失败: %v", err) + } + + // 检查参数 + if (req.OrderId == nil || *req.OrderId == 0) && (req.OrderNo == nil || *req.OrderNo == "") { + return nil, errors.Wrapf(xerr.NewErrMsg("订单ID和订单号不能同时为空"), "") + } + + var order *model.Order + // 优先使用OrderId查询 + if req.OrderId != nil && *req.OrderId != 0 { + order, err = l.svcCtx.OrderModel.FindOne(l.ctx, *req.OrderId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取订单失败: %v", err) + } + } else if req.OrderNo != nil && *req.OrderNo != "" { + // 使用OrderNo查询 + order, err = l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *req.OrderNo) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrMsg("订单不存在"), "") + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取订单失败: %v", err) + } + } else { + return nil, errors.Wrapf(xerr.NewErrMsg("订单ID和订单号不能同时为空"), "") + } + + if order.Status != model.OrderStatusPaid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 订单未支付") + } + + query, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取查询失败: %v", err) + } + + if query.QueryState != model.QueryStateSuccess { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 查询未成功") + } + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 获取用户失败: %v", err) + } + if user.Inside != 1 { + if order.UserId != userId { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 无权操作此订单") + } + } + + expireAt := time.Now().Add(time.Duration(l.svcCtx.Config.Query.ShareLinkExpire) * time.Second) + payload := types.QueryShareLinkPayload{ + OrderId: order.Id, // 使用查询到的订单ID + ExpireAt: expireAt.Unix(), + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, err := hex.DecodeString(secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 解密失败: %v", err) + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 序列化失败: %v", err) + } + encryptedPayload, err := crypto.AesEncryptURL(payloadBytes, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成分享链接, 加密失败: %v", err) + } + return &types.QueryGenerateShareLinkResp{ + ShareLink: encryptedPayload, + }, nil +} diff --git a/app/main/api/internal/logic/query/querylistlogic.go b/app/main/api/internal/logic/query/querylistlogic.go new file mode 100644 index 0000000..e31e1f9 --- /dev/null +++ b/app/main/api/internal/logic/query/querylistlogic.go @@ -0,0 +1,78 @@ +package query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/Masterminds/squirrel" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryListLogic { + return &QueryListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryListResp, err error) { + userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 获取用户信息失败, %+v", getUidErr) + } + + // 直接构建查询query表的条件 + build := l.svcCtx.QueryModel.SelectBuilder().Where(squirrel.Eq{ + "user_id": userID, + }) + + // 直接从query表分页查询 + queryList, total, err := l.svcCtx.QueryModel.FindPageListByPageWithTotal(l.ctx, build, req.Page, req.PageSize, "create_time DESC") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 查找报告列表错误, %+v", err) + } + + var list []types.Query + if len(queryList) > 0 { + for _, queryModel := range queryList { + var query types.Query + query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") + query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05") + copyErr := copier.Copy(&query, queryModel) + if copyErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 报告结构体复制失败, %+v", err) + } + product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId) + if findProductErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 获取商品信息失败, %+v", err) + } + + // 检查订单状态,如果订单已退款,则设置查询状态为已退款 + order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, queryModel.OrderId) + if findOrderErr == nil && order.Status == model.OrderStatusRefunded { + query.QueryState = model.QueryStateRefunded + } + query.ProductName = product.ProductName + query.Product = product.ProductEn + list = append(list, query) + } + } + + return &types.QueryListResp{ + Total: total, + List: list, + }, nil +} diff --git a/app/main/api/internal/logic/query/queryprovisionalorderlogic.go b/app/main/api/internal/logic/query/queryprovisionalorderlogic.go new file mode 100644 index 0000000..9845605 --- /dev/null +++ b/app/main/api/internal/logic/query/queryprovisionalorderlogic.go @@ -0,0 +1,63 @@ +package query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "encoding/json" + "fmt" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryProvisionalOrderLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryProvisionalOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryProvisionalOrderLogic { + return &QueryProvisionalOrderLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryProvisionalOrderLogic) QueryProvisionalOrder(req *types.QueryProvisionalOrderReq) (resp *types.QueryProvisionalOrderResp, err error) { + userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) + if getUidErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 获取用户信息失败, %+v", getUidErr) + } + redisKey := fmt.Sprintf("%d:%s", userID, req.Id) + cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey) + if cacheErr != nil { + return nil, cacheErr + } + var data types.QueryCache + err = json.Unmarshal([]byte(cache), &data) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 解析缓存内容失败, %v", err) + } + + productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 查找产品错误: %v", err) + } + var product types.Product + err = copier.Copy(&product, productModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 用户信息结构体复制失败: %v", err) + } + return &types.QueryProvisionalOrderResp{ + Name: data.Name, + IdCard: data.Name, + Mobile: data.Mobile, + Product: product, + }, nil +} diff --git a/app/main/api/internal/logic/query/queryretrylogic.go b/app/main/api/internal/logic/query/queryretrylogic.go new file mode 100644 index 0000000..bde2786 --- /dev/null +++ b/app/main/api/internal/logic/query/queryretrylogic.go @@ -0,0 +1,44 @@ +package query + +import ( + "context" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryRetryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryRetryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryRetryLogic { + return &QueryRetryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryRetryLogic) QueryRetry(req *types.QueryRetryReq) (resp *types.QueryRetryResp, err error) { + + query, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询重试, 查找报告失败, %v", err) + } + if query.QueryState == "success" { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIN_FAILED, "该报告不能重试"), "报告查询重试, 该报告不能重试, %d", query.Id) + } + + if asyncErr := l.svcCtx.AsynqService.SendQueryTask(query.OrderId); asyncErr != nil { + logx.Errorf("异步任务调度失败: %v", asyncErr) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询重试, 异步任务调度失败, %+v", asyncErr) + } + return +} diff --git a/app/main/api/internal/logic/query/queryserviceagentlogic.go b/app/main/api/internal/logic/query/queryserviceagentlogic.go new file mode 100644 index 0000000..7386d64 --- /dev/null +++ b/app/main/api/internal/logic/query/queryserviceagentlogic.go @@ -0,0 +1,27 @@ +package query + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryServiceAgentLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryServiceAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryServiceAgentLogic { + return &QueryServiceAgentLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryServiceAgentLogic) QueryServiceAgent(req *types.QueryServiceReq) (resp *types.QueryServiceResp, err error) { + return &types.QueryServiceResp{}, nil +} diff --git a/app/main/api/internal/logic/query/queryserviceapplogic.go b/app/main/api/internal/logic/query/queryserviceapplogic.go new file mode 100644 index 0000000..00adb5d --- /dev/null +++ b/app/main/api/internal/logic/query/queryserviceapplogic.go @@ -0,0 +1,30 @@ +package query + +import ( + "context" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryServiceAppLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryServiceAppLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryServiceAppLogic { + return &QueryServiceAppLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryServiceAppLogic) QueryServiceApp(req *types.QueryServiceReq) (resp *types.QueryServiceResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/main/api/internal/logic/query/queryservicelogic.go b/app/main/api/internal/logic/query/queryservicelogic.go new file mode 100644 index 0000000..db9ad8f --- /dev/null +++ b/app/main/api/internal/logic/query/queryservicelogic.go @@ -0,0 +1,701 @@ +package query + +import ( + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + "bdrp-server/pkg/lzkit/validator" + "context" + "database/sql" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/redis" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryServiceLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryServiceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryServiceLogic { + return &QueryServiceLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryServiceLogic) QueryService(req *types.QueryServiceReq) (resp *types.QueryServiceResp, err error) { + if req.AgentIdentifier != "" { + + l.ctx = context.WithValue(l.ctx, "agentIdentifier", req.AgentIdentifier) + } else if req.App { + l.ctx = context.WithValue(l.ctx, "app", req.App) + } + return l.PreprocessLogic(req, req.Product) +} + +var productProcessors = map[string]func(*QueryServiceLogic, *types.QueryServiceReq) (*types.QueryServiceResp, error){ + "marriage": (*QueryServiceLogic).ProcessMarriageLogic, + "homeservice": (*QueryServiceLogic).ProcessHomeServiceLogic, + "companyinfo": (*QueryServiceLogic).ProcessCompanyInfoLogic, + "preloanbackgroundcheck": (*QueryServiceLogic).ProcessPreLoanBackgroundCheckLogic, + "rentalinfo": (*QueryServiceLogic).ProcessRentalInfoLogic, + "backgroundcheck": (*QueryServiceLogic).ProcessBackgroundCheckLogic, + "personalData": (*QueryServiceLogic).ProcessPersonalDataLogic, +} + +func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) { + if processor, exists := productProcessors[product]; exists { + return processor(l, req) // 调用对应的处理函数 + } + return nil, errors.New("未找到相应的处理程序") +} +func (l *QueryServiceLogic) ProcessMarriageLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.MarriageReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "marriage", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "marriage", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// 处理家政服务相关逻辑 +func (l *QueryServiceLogic) ProcessHomeServiceLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.HomeServiceReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "homeservice", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "homeservice", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// 处理公司信息查询相关逻辑 +func (l *QueryServiceLogic) ProcessCompanyInfoLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.CompanyInfoReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码(CompanyInfo 暂不校验) + // verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + // if verifyCodeErr != nil { + // return nil, verifyCodeErr + // } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "companyinfo", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "companyinfo", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// 处理贷前背景检查相关逻辑 +func (l *QueryServiceLogic) ProcessPreLoanBackgroundCheckLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.PreLoanBackgroundCheckReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "preloanbackgroundcheck", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "preloanbackgroundcheck", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// 处理诚信租赁查询相关逻辑 +func (l *QueryServiceLogic) ProcessRentalInfoLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.RentalInfoReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "rentalinfo", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "rentalinfo", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// 处理人事背调相关逻辑 +func (l *QueryServiceLogic) ProcessBackgroundCheckLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.BackgroundCheckReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "backgroundcheck", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "backgroundcheck", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} +func (l *QueryServiceLogic) ProcessPersonalDataLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) { + // AES解密 + decryptData, DecryptDataErr := l.DecryptData(req.Data) + if DecryptDataErr != nil { + return nil, DecryptDataErr + } + + // 校验参数 + var data types.PersonalDataReq + if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr) + } + + if validatorErr := validator.Validate(data); validatorErr != nil { + return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr) + } + + // 校验验证码 + verifyCodeErr := l.VerifyCode(data.Mobile, data.Code) + if verifyCodeErr != nil { + return nil, verifyCodeErr + } + + // 校验三要素 + verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile) + if verifyErr != nil { + return nil, verifyErr + } + + // 缓存 + params := map[string]interface{}{ + "name": data.Name, + "id_card": data.IDCard, + "mobile": data.Mobile, + } + userID, err := l.GetOrCreateUser() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err) + } + cacheNo, cacheDataErr := l.CacheData(params, "personalData", userID) + if cacheDataErr != nil { + return nil, cacheDataErr + } + l.recordQueryUserRecord(params, "personalData", userID, cacheNo) + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.QueryServiceResp{ + Id: cacheNo, + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +func (l *QueryServiceLogic) DecryptData(data string) ([]byte, error) { + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "密钥获取失败: %+v", decodeErr) + } + decryptData, aesDecryptErr := crypto.AesDecrypt(data, key) + if aesDecryptErr != nil || len(decryptData) == 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密失败: %+v", aesDecryptErr) + } + return decryptData, nil +} + +// 校验验证码 +func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error { + // 开发环境下跳过验证码验证 + if os.Getenv("ENV") == "development" { + return nil + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %+v", err) + } + codeRedisKey := fmt.Sprintf("%s:%s", "query", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(codeRedisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "验证码过期: %s", mobile) + } + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码redis缓存失败, mobile: %s, err: %+v", mobile, err) + } + if cacheCode != code { + return errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "验证码不正确: %s", mobile) + } + return nil +} + +// 二、三要素验证 +func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error { + // 开发环境下跳过二/三要素验证(避免未授权IP调用天元API失败) + isDevelopment := os.Getenv("ENV") == "development" + if isDevelopment { + return nil + } + + if !l.svcCtx.Config.SystemConfig.ThreeVerify { + twoVerification := service.TwoFactorVerificationRequest{ + Name: Name, + IDCard: IDCard, + } + verification, err := l.svcCtx.VerificationService.TwoFactorVerification(twoVerification) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "二要素验证失败: %v", err) + } + if !verification.Passed { + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "二要素验证不通过: %v", err) + } + } else { + // 三要素验证 + threeVerification := service.ThreeFactorVerificationRequest{ + Name: Name, + IDCard: IDCard, + Mobile: Mobile, + } + verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err) + } + if !verification.Passed { + return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err) + } + } + return nil +} + +// 缓存 +func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product string, userID int64) (string, error) { + agentIdentifier, _ := l.ctx.Value("agentIdentifier").(string) + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 获取AES密钥失败: %+v", decodeErr) + } + paramsMarshal, marshalErr := json.Marshal(params) + if marshalErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr) + } + encryptParams, aesEncryptErr := crypto.AesEncrypt(paramsMarshal, key) + if aesEncryptErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 加密参数失败: %+v", aesEncryptErr) + } + queryCache := types.QueryCacheLoad{ + Params: encryptParams, + Product: Product, + AgentIdentifier: agentIdentifier, + } + jsonData, marshalErr := json.Marshal(queryCache) + if marshalErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr) + } + outTradeNo := "Q_" + l.svcCtx.AlipayService.GenerateOutTradeNo() + redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo) + cacheErr := l.svcCtx.Redis.SetexCtx(l.ctx, redisKey, string(jsonData), int(2*time.Hour)) + if cacheErr != nil { + return "", cacheErr + } + return outTradeNo, nil +} + +// recordQueryUserRecord 写入查询用户记录表,用于通过姓名/身份证/手机号追溯订单 +// 重要:name、id_card、mobile 必须以 AES-ECB+Base64 密文入库,禁止写入明文 +func (l *QueryServiceLogic) recordQueryUserRecord(params map[string]interface{}, product string, userID int64, queryNo string) { + getStr := func(k string) string { + if v, ok := params[k]; ok { + if s, ok := v.(string); ok { + return s + } + } + return "" + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + if secretKey == "" { + l.Errorf("查询用户记录表加密失败, Encrypt.SecretKey 未配置,拒绝写入明文 queryNo=%s", queryNo) + return + } + key, keyErr := hex.DecodeString(secretKey) + if keyErr != nil { + l.Errorf("查询用户记录表加密失败, 密钥解析错误 queryNo=%s err=%v", queryNo, keyErr) + return + } + // 以下三字段仅使用加密后的值赋值,不得使用 getStr 的明文 + encName := "" + if name := getStr("name"); name != "" { + if s, err := crypto.AesEcbEncrypt([]byte(name), key); err != nil { + l.Errorf("查询用户记录表姓名加密失败 queryNo=%s err=%v", queryNo, err) + return + } else { + encName = s + } + } + encIdCard := "" + if idCard := getStr("id_card"); idCard != "" { + if s, err := crypto.EncryptIDCard(idCard, key); err != nil { + l.Errorf("查询用户记录表身份证加密失败 queryNo=%s err=%v", queryNo, err) + return + } else { + encIdCard = s + } + } + encMobile := "" + if mobile := getStr("mobile"); mobile != "" { + if s, err := crypto.EncryptMobile(mobile, secretKey); err != nil { + l.Errorf("查询用户记录表手机号加密失败 queryNo=%s err=%v", queryNo, err) + return + } else { + encMobile = s + } + } + agentIdentifier := sql.NullString{} + if v, ok := l.ctx.Value("agentIdentifier").(string); ok && v != "" { + agentIdentifier = sql.NullString{String: v, Valid: true} + } + // rec 的 Name、IdCard、Mobile 仅使用密文 encName、encIdCard、encMobile + rec := &model.QueryUserRecord{ + UserId: userID, + Name: encName, + IdCard: encIdCard, + Mobile: encMobile, + Product: product, + QueryNo: queryNo, + OrderId: 0, + PlatformOrderId: sql.NullString{}, + AgentIdentifier: agentIdentifier, + } + if _, err := l.svcCtx.QueryUserRecordModel.Insert(l.ctx, nil, rec); err != nil { + l.Errorf("查询用户记录表写入失败 queryNo=%s err=%v", queryNo, err) + } +} + +// GetOrCreateUser 获取或创建用户 +// 1. 如果上下文中已有用户ID,直接返回 +// 2. 如果是代理查询或APP请求,创建新用户 +// 3. 其他情况返回未登录错误 +func (l *QueryServiceLogic) GetOrCreateUser() (int64, error) { + // 尝试获取用户ID + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { + return 0, err + } + userID := claims.UserId + return userID, nil + + // // 如果不是未登录错误,说明是其他错误,直接返回 + // if !ctxdata.IsNoUserIdError(err) { + // return 0, err + // } + + // // 检查是否是代理查询或APP请求 + // isAgentQuery := false + // if agentID, ok := l.ctx.Value("agentIdentifier").(string); ok && agentID != "" { + // isAgentQuery = true + // } + // if app, ok := l.ctx.Value("app").(bool); ok && app { + // isAgentQuery = true + // } + + // // 如果不是代理查询或APP请求,返回未登录错误 + // if !isAgentQuery { + // return 0, ctxdata.ErrNoUserIdInCtx + // } + + // // 创建新用户 + // return l.svcCtx.UserService.RegisterUUIDUser(l.ctx) +} diff --git a/app/main/api/internal/logic/query/querysharedetaillogic.go b/app/main/api/internal/logic/query/querysharedetaillogic.go new file mode 100644 index 0000000..f608511 --- /dev/null +++ b/app/main/api/internal/logic/query/querysharedetaillogic.go @@ -0,0 +1,165 @@ +package query + +import ( + "context" + "encoding/hex" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/bytedance/sonic" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type QueryShareDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQueryShareDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryShareDetailLogic { + return &QueryShareDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QueryShareDetailLogic) QueryShareDetail(req *types.QueryShareDetailReq) (resp *types.QueryShareDetailResp, err error) { + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %v", err) + } + decryptedID, decryptErr := crypto.AesDecryptURL(req.Id, key) + if decryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 解密数据失败: %v", decryptErr) + } + + var payload types.QueryShareLinkPayload + err = sonic.Unmarshal(decryptedID, &payload) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 解密数据失败: %v", err) + } + + // 检查分享链接是否过期 + now := time.Now().Unix() + if now > payload.ExpireAt { + return &types.QueryShareDetailResp{ + Status: "expired", + }, nil + } + + // 获取订单信息 + order, err := l.svcCtx.OrderModel.FindOne(l.ctx, payload.OrderId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "报告查询, 订单不存在: %v", err) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + + // 检查订单状态 + if order.Status != "paid" { + return nil, errors.Wrapf(xerr.NewErrMsg("订单未支付,无法查看报告"), "") + } + + // 获取报告信息 + queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %v", err) + } + + var query types.Query + query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05") + query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05") + + processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key) + if processParamsErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr) + } + processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key) + if processErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr) + } + updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData) + if updateFeatureAndProductFeatureErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr) + } + // 复制报告数据 + err = copier.Copy(&query, queryModel) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err) + } + product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) + } + query.ProductName = product.ProductName + query.Product = product.ProductEn + return &types.QueryShareDetailResp{ + Status: "success", + Query: query, + }, nil +} + +func (l *QueryShareDetailLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.QueryItem) error { + // 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引 + for i := len(*target) - 1; i >= 0; i-- { + queryItem := &(*target)[i] + + // 确保 Data 为 map 类型 + data, ok := queryItem.Data.(map[string]interface{}) + if !ok { + return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型") + } + + // 从 Data 中获取 apiID + apiID, ok := data["apiID"].(string) + if !ok { + return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型") + } + + // 查询 Feature + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID) + if err != nil { + // 如果 Feature 查不到,也要删除当前 QueryItem + *target = append((*target)[:i], (*target)[i+1:]...) + continue + } + + // 查询 ProductFeatureModel + builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID) + productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "") + if err != nil { + return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err) + } + + // 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项 + var featureData map[string]interface{} + // foundFeature := false + sort := 0 + for _, pf := range productFeatures { + if pf.FeatureId == feature.Id { // 确保和 Feature 关联 + sort = int(pf.Sort) + break // 找到第一个符合条件的就退出循环 + } + } + featureData = map[string]interface{}{ + "featureName": feature.Name, + "sort": sort, + } + + // 更新 queryItem 的 Feature 字段(不是数组) + queryItem.Feature = featureData + } + + return nil +} diff --git a/app/main/api/internal/logic/query/querysingletestlogic.go b/app/main/api/internal/logic/query/querysingletestlogic.go new file mode 100644 index 0000000..68fb2b7 --- /dev/null +++ b/app/main/api/internal/logic/query/querysingletestlogic.go @@ -0,0 +1,52 @@ +package query + +import ( + "context" + "encoding/json" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type QuerySingleTestLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewQuerySingleTestLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QuerySingleTestLogic { + return &QuerySingleTestLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *QuerySingleTestLogic) QuerySingleTest(req *types.QuerySingleTestReq) (resp *types.QuerySingleTestResp, err error) { + //featrueModel, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, req.Api) + //if err != nil { + // return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 获取接口失败 : %d", err) + //} + marshalParams, err := json.Marshal(req.Params) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 序列化参数失败 : %d", err) + } + apiResp, err := l.svcCtx.ApiRequestService.PreprocessRequestApi(marshalParams, req.Api) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 获取接口失败 : %d", err) + } + var respData interface{} + err = json.Unmarshal(apiResp, &respData) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 反序列化接口失败 : %d", err) + } + return &types.QuerySingleTestResp{ + Data: respData, + Api: req.Api, + }, nil +} diff --git a/app/main/api/internal/logic/query/updatequerydatalogic.go b/app/main/api/internal/logic/query/updatequerydatalogic.go new file mode 100644 index 0000000..2b0f7bd --- /dev/null +++ b/app/main/api/internal/logic/query/updatequerydatalogic.go @@ -0,0 +1,71 @@ +package query + +import ( + "context" + "database/sql" + "encoding/hex" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateQueryDataLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 更新查询数据 +func NewUpdateQueryDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateQueryDataLogic { + return &UpdateQueryDataLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateQueryDataLogic) UpdateQueryData(req *types.UpdateQueryDataReq) (resp *types.UpdateQueryDataResp, err error) { + // 1. 从数据库中获取查询记录 + query, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.Id) + if err != nil { + if err == model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询记录不存在, 查询ID: %d", req.Id) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询数据库失败, 查询ID: %d, err: %v", req.Id, err) + } + + // 2. 获取加密密钥 + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取AES密钥失败: %v", decodeErr) + } + + // 3. 加密数据 - 传入的是JSON,需要加密处理 + encryptData, aesEncryptErr := crypto.AesEncrypt([]byte(req.QueryData), key) + if aesEncryptErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密查询数据失败: %v", aesEncryptErr) + } + + // 4. 更新数据库记录 + query.QueryData = sql.NullString{ + String: encryptData, + Valid: true, + } + updateErr := l.svcCtx.QueryModel.UpdateWithVersion(l.ctx, nil, query) + if updateErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新查询数据失败: %v", updateErr) + } + + // 5. 返回结果 + return &types.UpdateQueryDataResp{ + Id: query.Id, + UpdatedAt: query.UpdateTime.Format("2006-01-02 15:04:05"), + }, nil +} diff --git a/app/main/api/internal/logic/user/bindmobilelogic.go b/app/main/api/internal/logic/user/bindmobilelogic.go new file mode 100644 index 0000000..d575f69 --- /dev/null +++ b/app/main/api/internal/logic/user/bindmobilelogic.go @@ -0,0 +1,110 @@ +package user + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/redis" +) + +type BindMobileLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewBindMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindMobileLogic { + return &BindMobileLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.BindMobileResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, %v", err) + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err) + } + // 开发环境下跳过验证码验证 + if os.Getenv("ENV") != "development" { + // 检查手机号是否在一分钟内已发送过验证码 + redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) + } + } + var userID int64 + user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) + } + if user != nil { + // 被封禁用户禁止绑定/登录 + if user.Disable == 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + // 进行平台绑定 + if claims != nil { + if claims.UserType == model.UserTypeTemp { + userTemp, err := l.svcCtx.UserTempModel.FindOne(l.ctx, claims.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, 读取临时用户失败: %v", err) + } + userAuth, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, userTemp.AuthType) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, 读取用户认证失败: %v", err) + } + if userAuth != nil && userAuth.AuthKey != userTemp.AuthKey { + return nil, errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他微信号"), "绑定手机号, 临时用户已注册: %s", encryptedMobile) + } + err = l.svcCtx.UserService.TempUserBindUser(l.ctx, nil, user.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 临时用户绑定用户失败: %+v", err) + } + } + } + userID = user.Id + } else { + // 创建账号,并绑定手机号 + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 注册用户失败: %+v", err) + } + } + + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 生成token失败: %+v", err) + } + now := time.Now().Unix() + return &types.BindMobileResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} diff --git a/app/main/api/internal/logic/user/canceloutlogic.go b/app/main/api/internal/logic/user/canceloutlogic.go new file mode 100644 index 0000000..a31631c --- /dev/null +++ b/app/main/api/internal/logic/user/canceloutlogic.go @@ -0,0 +1,252 @@ +package user + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/zeromicro/go-zero/core/mr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" + + "bdrp-server/app/main/api/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CancelOutLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCancelOutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelOutLogic { + return &CancelOutLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CancelOutLogic) CancelOut() error { + userID, getUserIdErr := ctxdata.GetUidFromCtx(l.ctx) + if getUserIdErr != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", getUserIdErr) + } + + // 1. 先检查用户是否是代理 + agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理信息失败, userId: %d", userID) + } + + // 如果用户是代理,进行额外检查 + if agentModel != nil { + // 1.1 检查代理等级是否为VIP或SVIP + if agentModel.LevelName == model.AgentLeveNameVIP || agentModel.LevelName == model.AgentLeveNameSVIP { + return errors.Wrapf(xerr.NewErrMsg("您是"+agentModel.LevelName+"会员,请联系客服进行注销"), "用户是代理会员,不能注销") + } + + // 1.2 检查代理钱包是否有余额或冻结金额 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败, agentId: %d", agentModel.Id) + } + + if wallet != nil && (wallet.Balance > 0 || wallet.FrozenBalance > 0) { + if wallet.Balance > 0 { + return errors.Wrapf(xerr.NewErrMsg("您的钱包还有余额%.2f元,请先提现后再注销账号"), "用户钱包有余额,不能注销", wallet.Balance) + } + if wallet.FrozenBalance > 0 { + return errors.Wrapf(xerr.NewErrMsg("您的钱包还有冻结金额%.2f元,请等待解冻后再注销账号"), "用户钱包有冻结金额,不能注销", wallet.FrozenBalance) + } + } + } + + // 在事务中处理用户注销相关操作 + err = l.svcCtx.UserModel.Trans(l.ctx, func(tranCtx context.Context, session sqlx.Session) error { + // 1. 删除用户基本信息 + if err := l.svcCtx.UserModel.Delete(tranCtx, session, userID); err != nil { + return errors.Wrapf(err, "删除用户基本信息失败, userId: %d", userID) + } + + // 2. 查询并删除用户授权信息 + UserAuthModelBuilder := l.svcCtx.UserAuthModel.SelectBuilder().Where("user_id = ?", userID) + userAuths, err := l.svcCtx.UserAuthModel.FindAll(tranCtx, UserAuthModelBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询用户授权信息失败, userId: %d", userID) + } + + // 并发删除用户授权信息 + if len(userAuths) > 0 { + funcs := make([]func() error, len(userAuths)) + for i, userAuth := range userAuths { + authID := userAuth.Id + funcs[i] = func() error { + return l.svcCtx.UserAuthModel.Delete(tranCtx, session, authID) + } + } + + if err := mr.Finish(funcs...); err != nil { + return errors.Wrapf(err, "删除用户授权信息失败") + } + } + + // 3. 处理代理相关信息 + if agentModel != nil { + // 3.1 删除代理信息 + if err := l.svcCtx.AgentModel.Delete(tranCtx, session, agentModel.Id); err != nil { + return errors.Wrapf(err, "删除代理信息失败, agentId: %d", agentModel.Id) + } + + // 3.2 查询并删除代理会员配置 + configBuilder := l.svcCtx.AgentMembershipUserConfigModel.SelectBuilder().Where("agent_id = ?", agentModel.Id) + configs, err := l.svcCtx.AgentMembershipUserConfigModel.FindAll(tranCtx, configBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理会员配置失败, agentId: %d", agentModel.Id) + } + + // 并发删除代理会员配置 + if len(configs) > 0 { + configFuncs := make([]func() error, len(configs)) + for i, config := range configs { + configId := config.Id + configFuncs[i] = func() error { + return l.svcCtx.AgentMembershipUserConfigModel.Delete(tranCtx, session, configId) + } + } + + if err := mr.Finish(configFuncs...); err != nil { + return errors.Wrapf(err, "删除代理会员配置失败") + } + } + + // 3.3 删除代理钱包信息 + wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(tranCtx, agentModel.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理钱包信息失败, agentId: %d", agentModel.Id) + } + + if wallet != nil { + if err := l.svcCtx.AgentWalletModel.Delete(tranCtx, session, wallet.Id); err != nil { + return errors.Wrapf(err, "删除代理钱包信息失败, walletId: %d", wallet.Id) + } + } + + // 3.4 删除代理关系信息 + closureBuilder := l.svcCtx.AgentClosureModel.SelectBuilder().Where("ancestor_id = ? AND depth = ?", agentModel.Id, 1) + closures, err := l.svcCtx.AgentClosureModel.FindAll(tranCtx, closureBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理关系信息失败, agentId: %d", agentModel.Id) + } + + if len(closures) > 0 { + closureFuncs := make([]func() error, len(closures)) + for i, closure := range closures { + closureId := closure.Id + closureFuncs[i] = func() error { + return l.svcCtx.AgentClosureModel.Delete(tranCtx, session, closureId) + } + } + + if err := mr.Finish(closureFuncs...); err != nil { + return errors.Wrapf(err, "删除代理关系信息失败") + } + } + } + + // 4. 查询并删除代理审核信息 + auditBuilder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID) + audits, err := l.svcCtx.AgentAuditModel.FindAll(tranCtx, auditBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理审核信息失败, userId: %d", userID) + } + + // 并发删除代理审核信息 + if len(audits) > 0 { + auditFuncs := make([]func() error, len(audits)) + for i, audit := range audits { + auditId := audit.Id + auditFuncs[i] = func() error { + return l.svcCtx.AgentAuditModel.Delete(tranCtx, session, auditId) + } + } + + if err := mr.Finish(auditFuncs...); err != nil { + return errors.Wrapf(err, "删除代理审核信息失败") + } + } + + // 5. 删除用户查询记录 + queryBuilder := l.svcCtx.QueryModel.SelectBuilder().Where("user_id = ?", userID) + queries, err := l.svcCtx.QueryModel.FindAll(tranCtx, queryBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询用户查询记录失败, userId: %d", userID) + } + + if len(queries) > 0 { + queryFuncs := make([]func() error, len(queries)) + for i, query := range queries { + queryId := query.Id + queryFuncs[i] = func() error { + return l.svcCtx.QueryModel.Delete(tranCtx, session, queryId) + } + } + + if err := mr.Finish(queryFuncs...); err != nil { + return errors.Wrapf(err, "删除用户查询记录失败") + } + } + + // 6. 删除用户订单记录 + orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where("user_id = ?", userID) + orders, err := l.svcCtx.OrderModel.FindAll(tranCtx, orderBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询用户订单记录失败, userId: %d", userID) + } + + if len(orders) > 0 { + orderFuncs := make([]func() error, len(orders)) + for i, order := range orders { + orderId := order.Id + orderFuncs[i] = func() error { + return l.svcCtx.OrderModel.Delete(tranCtx, session, orderId) + } + } + + if err := mr.Finish(orderFuncs...); err != nil { + return errors.Wrapf(err, "删除用户订单记录失败") + } + } + + // 7. 删除代理订单信息 + agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().Where("agent_id = ?", agentModel.Id) + agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(tranCtx, agentOrderBuilder, "") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return errors.Wrapf(err, "查询代理订单信息失败, agentId: %d, err: %v", agentModel.Id, err) + } + + if len(agentOrders) > 0 { + agentOrderFuncs := make([]func() error, len(agentOrders)) + for i, agentOrder := range agentOrders { + agentOrderId := agentOrder.Id + agentOrderFuncs[i] = func() error { + return l.svcCtx.AgentOrderModel.Delete(tranCtx, session, agentOrderId) + } + } + + if err := mr.Finish(agentOrderFuncs...); err != nil { + return errors.Wrapf(err, "删除代理订单信息失败") + } + } + return nil + }) + + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户注销失败%v", err) + } + + return nil +} diff --git a/app/main/api/internal/logic/user/detaillogic.go b/app/main/api/internal/logic/user/detaillogic.go new file mode 100644 index 0000000..1ca9d80 --- /dev/null +++ b/app/main/api/internal/logic/user/detaillogic.go @@ -0,0 +1,74 @@ +package user + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/jinzhu/copier" + "github.com/pkg/errors" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { + return &DetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) + } + + userID := claims.UserId + userType := claims.UserType + if userType != model.UserTypeNormal { + return &types.UserInfoResp{ + UserInfo: types.User{ + Id: userID, + UserType: userType, + Mobile: "", + NickName: "", + }, + }, nil + } + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_NOT_FOUND), "用户信息, 用户不存在, %v", err) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户信息, 数据库查询用户信息失败, %v", err) + } + var userInfo types.User + err = copier.Copy(&userInfo, user) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %v", err) + } + + if user.Mobile.Valid { + userInfo.Mobile, err = crypto.DecryptMobile(user.Mobile.String, l.svcCtx.Config.Encrypt.SecretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 解密手机号失败, %v", err) + } + } + userInfo.UserType = claims.UserType + + return &types.UserInfoResp{ + UserInfo: userInfo, + }, nil +} diff --git a/app/main/api/internal/logic/user/getsignaturelogic.go b/app/main/api/internal/logic/user/getsignaturelogic.go new file mode 100644 index 0000000..79789ae --- /dev/null +++ b/app/main/api/internal/logic/user/getsignaturelogic.go @@ -0,0 +1,185 @@ +package user + +import ( + "context" + "crypto/sha1" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetSignatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetSignatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSignatureLogic { + return &GetSignatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetSignatureLogic) GetSignature(req *types.GetSignatureReq) (resp *types.GetSignatureResp, err error) { + // 1. 获取access_token + accessToken, err := l.getAccessToken() + if err != nil { + l.Errorf("获取access_token失败: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err) + } + + // 2. 获取jsapi_ticket + jsapiTicket, err := l.getJsapiTicket(accessToken) + if err != nil { + l.Errorf("获取jsapi_ticket失败: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取jsapi_ticket失败: %v", err) + } + + // 3. 生成签名 + timestamp := time.Now().Unix() + nonceStr := l.generateNonceStr(16) + signature := l.generateSignature(jsapiTicket, nonceStr, timestamp, req.Url) + + // 4. 返回完整的JS-SDK配置信息 + return &types.GetSignatureResp{ + AppId: l.svcCtx.Config.WechatH5.AppID, + Timestamp: timestamp, + NonceStr: nonceStr, + Signature: signature, + }, nil +} + +// getAccessToken 获取微信公众号access_token +func (l *GetSignatureLogic) getAccessToken() (string, error) { + appID := l.svcCtx.Config.WechatH5.AppID + appSecret := l.svcCtx.Config.WechatH5.AppSecret + + url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var result struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + + if err = json.Unmarshal(body, &result); err != nil { + return "", err + } + + if result.ErrCode != 0 { + return "", fmt.Errorf("获取access_token失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg) + } + + return result.AccessToken, nil +} + +// getJsapiTicket 获取jsapi_ticket +func (l *GetSignatureLogic) getJsapiTicket(accessToken string) (string, error) { + url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var result struct { + Ticket string `json:"ticket"` + ExpiresIn int `json:"expires_in"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + + if err = json.Unmarshal(body, &result); err != nil { + return "", err + } + + if result.ErrCode != 0 { + return "", fmt.Errorf("获取jsapi_ticket失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg) + } + + return result.Ticket, nil +} + +// generateNonceStr 生成随机字符串 +func (l *GetSignatureLogic) generateNonceStr(length int) string { + chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + result := make([]byte, length) + for i := 0; i < length; i++ { + result[i] = chars[i%len(chars)] + } + return string(result) +} + +// generateSignature 生成签名 +func (l *GetSignatureLogic) generateSignature(jsapiTicket, nonceStr string, timestamp int64, urlStr string) string { + // 对URL进行解码,避免重复编码 + decodedURL, err := url.QueryUnescape(urlStr) + if err != nil { + decodedURL = urlStr + } + + // 构建签名字符串 + params := map[string]string{ + "jsapi_ticket": jsapiTicket, + "noncestr": nonceStr, + "timestamp": fmt.Sprintf("%d", timestamp), + "url": decodedURL, + } + + // 对参数进行字典序排序 + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + // 拼接字符串 + var signStr strings.Builder + for i, k := range keys { + if i > 0 { + signStr.WriteString("&") + } + signStr.WriteString(k) + signStr.WriteString("=") + signStr.WriteString(params[k]) + } + + // SHA1加密 + h := sha1.New() + h.Write([]byte(signStr.String())) + signature := fmt.Sprintf("%x", h.Sum(nil)) + + return signature +} diff --git a/app/main/api/internal/logic/user/gettokenlogic.go b/app/main/api/internal/logic/user/gettokenlogic.go new file mode 100644 index 0000000..9c99834 --- /dev/null +++ b/app/main/api/internal/logic/user/gettokenlogic.go @@ -0,0 +1,62 @@ +package user + +import ( + "context" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetTokenLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetTokenLogic { + return &GetTokenLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetTokenLogic) GetToken() (resp *types.MobileCodeLoginResp, err error) { + claims, err := ctxdata.GetClaimsFromCtx(l.ctx) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) + } + // 被封禁用户禁止刷新 token(临时用户 ID 在 user_temp 表) + if claims.UserType == model.UserTypeTemp { + _, err := l.svcCtx.UserTempModel.FindOne(l.ctx, claims.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) + } + } else { + user, err := l.svcCtx.UserModel.FindOne(l.ctx, claims.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) + } + if user.Disable == 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + } + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, claims.UserId, claims.UserType) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) + } + // 获取当前时间戳 + now := time.Now().Unix() + return &types.MobileCodeLoginResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} diff --git a/app/main/api/internal/logic/user/mobilecodeloginlogic.go b/app/main/api/internal/logic/user/mobilecodeloginlogic.go new file mode 100644 index 0000000..9ca9918 --- /dev/null +++ b/app/main/api/internal/logic/user/mobilecodeloginlogic.go @@ -0,0 +1,85 @@ +package user + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "bdrp-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/redis" + + "github.com/zeromicro/go-zero/core/logx" +) + +type MobileCodeLoginLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewMobileCodeLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MobileCodeLoginLogic { + return &MobileCodeLoginLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (resp *types.MobileCodeLoginResp, err error) { + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err) + } + // 开发环境下跳过验证码验证 + if os.Getenv("ENV") != "development" { + // 检查手机号是否在一分钟内已发送过验证码 + redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) + } + } + var userID int64 + user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) + if findUserErr != nil && findUserErr != model.ErrNotFound { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if user == nil { + userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 注册用户失败: %+v", err) + } + } else { + // 被封禁用户禁止登录 + if user.Disable == 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + userID = user.Id + } + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID) + } + + // 获取当前时间戳 + now := time.Now().Unix() + return &types.MobileCodeLoginResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} diff --git a/app/main/api/internal/logic/user/wxh5authlogic.go b/app/main/api/internal/logic/user/wxh5authlogic.go new file mode 100644 index 0000000..9ba0118 --- /dev/null +++ b/app/main/api/internal/logic/user/wxh5authlogic.go @@ -0,0 +1,137 @@ +package user + +import ( + "context" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/pkg/errors" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type WxH5AuthLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewWxH5AuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxH5AuthLogic { + return &WxH5AuthLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthResp, err error) { + // Step 1: 使用code获取access_token + accessTokenResp, err := l.GetAccessToken(req.Code) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err) + } + + // Step 2: 查找用户授权信息 + userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid) + if findErr != nil && !errors.Is(findErr, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", findErr) + } + + // Step 3: 处理用户信息 + var userID int64 + var userType int64 + if userAuth != nil { + // 已存在用户,直接登录(被封禁用户禁止登录) + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err) + } + if user.Disable == 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + userID = userAuth.UserId + userType = model.UserTypeNormal + } else { + // 检查临时用户表 + userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err) + } + + if userTemp == nil { + // 创建临时用户记录 + userTemp = &model.UserTemp{ + AuthType: model.UserAuthTypeWxh5OpenID, + AuthKey: accessTokenResp.Openid, + } + result, err := l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err) + } + userID, err = result.LastInsertId() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的临时用户ID失败: %v", err) + } + } else { + userID = userTemp.Id + } + userType = model.UserTypeTemp + } + + // Step 4: 生成JWT Token + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err) + } + + // Step 5: 返回登录结果 + now := time.Now().Unix() + return &types.WXH5AuthResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +type AccessTokenResp struct { + AccessToken string `json:"access_token"` + Openid string `json:"openid"` +} + +// GetAccessToken 通过code获取access_token +func (l *WxH5AuthLogic) GetAccessToken(code string) (*AccessTokenResp, error) { + appID := l.svcCtx.Config.WechatH5.AppID + appSecret := l.svcCtx.Config.WechatH5.AppSecret + + url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code) + + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var accessTokenResp AccessTokenResp + if err = json.Unmarshal(body, &accessTokenResp); err != nil { + return nil, err + } + + if accessTokenResp.AccessToken == "" || accessTokenResp.Openid == "" { + return nil, errors.New("accessTokenResp.AccessToken为空") + } + + return &accessTokenResp, nil +} diff --git a/app/main/api/internal/logic/user/wxminiauthlogic.go b/app/main/api/internal/logic/user/wxminiauthlogic.go new file mode 100644 index 0000000..2c3d85d --- /dev/null +++ b/app/main/api/internal/logic/user/wxminiauthlogic.go @@ -0,0 +1,154 @@ +package user + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type WxMiniAuthLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMiniAuthLogic { + return &WxMiniAuthLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) { + // 1. 获取session_key和openid + sessionKeyResp, err := l.GetSessionKey(req.Code) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err) + } + + // 2. 查找用户授权信息 + userAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", err) + } + + // 3. 处理用户信息 + var userID int64 + var userType int64 + if userAuth != nil { + // 已存在用户,直接登录(被封禁用户禁止登录) + user, err := l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err) + } + if user.Disable == 1 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") + } + userID = userAuth.UserId + userType = model.UserTypeNormal + } else { + // 注册临时用户 + userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err) + } + if userTemp == nil { + // 创建新的临时用户 + userTemp = &model.UserTemp{} + userTemp.AuthType = model.UserAuthTypeWxMiniOpenID + userTemp.AuthKey = sessionKeyResp.Openid + result, err := l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err) + } + // 获取新创建的临时用户ID + userID, err = result.LastInsertId() + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的临时用户ID失败: %v", err) + } + } else { + // 使用已存在的临时用户ID + userID = userTemp.Id + } + userType = model.UserTypeTemp + } + + // 4. 生成JWT Token + token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err) + } + + // 5. 返回登录结果 + now := time.Now().Unix() + return &types.WXMiniAuthResp{ + AccessToken: token, + AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, + RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter, + }, nil +} + +// SessionKeyResp 小程序登录返回结构 +type SessionKeyResp struct { + Openid string `json:"openid"` + SessionKey string `json:"session_key"` + Unionid string `json:"unionid,omitempty"` + ErrCode int `json:"errcode,omitempty"` + ErrMsg string `json:"errmsg,omitempty"` +} + +// GetSessionKey 通过code获取小程序的session_key和openid +func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) { + var appID string + var appSecret string + + appID = l.svcCtx.Config.WechatMini.AppID + appSecret = l.svcCtx.Config.WechatMini.AppSecret + + url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", + appID, appSecret, code) + + resp, err := http.Get(url) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取响应失败: %v", err) + } + + var sessionKeyResp SessionKeyResp + if err = json.Unmarshal(body, &sessionKeyResp); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析响应失败: %v", err) + } + + // 检查微信返回的错误码 + if sessionKeyResp.ErrCode != 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "微信接口返回错误: errcode=%d, errmsg=%s", + sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg) + } + + // 验证必要字段 + if sessionKeyResp.Openid == "" || sessionKeyResp.SessionKey == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "微信接口返回数据不完整: openid=%s, session_key=%s", + sessionKeyResp.Openid, sessionKeyResp.SessionKey) + } + + return &sessionKeyResp, nil +} diff --git a/app/main/api/internal/middleware/adminauthinterceptormiddleware.go b/app/main/api/internal/middleware/adminauthinterceptormiddleware.go new file mode 100644 index 0000000..9c12eb4 --- /dev/null +++ b/app/main/api/internal/middleware/adminauthinterceptormiddleware.go @@ -0,0 +1,234 @@ +package middleware + +import ( + "context" + "net/http" + "strings" + + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + jwtx "bdrp-server/common/jwt" + "bdrp-server/common/result" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/rest/httpx" +) + +const ( + // 定义错误码 + AdminErrCodeUnauthorized = 401 +) + +type AdminAuthInterceptorMiddleware struct { + Config config.Config + // 注入model依赖 + AdminUserModel model.AdminUserModel + AdminUserRoleModel model.AdminUserRoleModel + AdminRoleModel model.AdminRoleModel + AdminApiModel model.AdminApiModel + AdminRoleApiModel model.AdminRoleApiModel +} + +func NewAdminAuthInterceptorMiddleware(c config.Config, + adminUserModel model.AdminUserModel, + adminUserRoleModel model.AdminUserRoleModel, + adminRoleModel model.AdminRoleModel, + adminApiModel model.AdminApiModel, + adminRoleApiModel model.AdminRoleApiModel) *AdminAuthInterceptorMiddleware { + return &AdminAuthInterceptorMiddleware{ + Config: c, + AdminUserModel: adminUserModel, + AdminUserRoleModel: adminUserRoleModel, + AdminRoleModel: adminRoleModel, + AdminApiModel: adminApiModel, + AdminRoleApiModel: adminRoleApiModel, + } +} + +func (m *AdminAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 1. JWT 校验 + claims, err := m.validateJWT(r) + if err != nil { + m.writeUnauthorizedResponse(w, err) + return + } + + // 2. 检查用户类型是否为管理员 + if claims.UserType != model.UserTypeAdmin { + authErr := errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "用户类型错误,需要管理员权限") + m.writeUnauthorizedResponse(w, authErr) + return + } + + // 3. 检查平台标识 + if claims.Platform != model.PlatformAdmin { + authErr := errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "平台标识错误,需要管理员平台") + m.writeUnauthorizedResponse(w, authErr) + return + } + + // 4. 将用户信息放入上下文 + ctx := context.WithValue(r.Context(), jwtx.ExtraKey, claims) + r = r.WithContext(ctx) + + // 5. API 权限校验 + if err := m.validateApiPermission(r.Context(), claims.UserId, r.Method, r.URL.Path); err != nil { + m.writeUnauthorizedResponse(w, err) + return + } + + // 6. 通过所有校验,继续处理请求 + next(w, r) + } +} + +// writeUnauthorizedResponse 写入401未授权响应 +func (m *AdminAuthInterceptorMiddleware) writeUnauthorizedResponse(w http.ResponseWriter, err error) { + errcode := xerr.TOKEN_EXPIRE_ERROR + errmsg := xerr.MapErrMsg(errcode) + + // 从错误中提取错误码和错误消息 + causeErr := errors.Cause(err) + if e, ok := causeErr.(*xerr.CodeError); ok { + errcode = e.GetErrCode() + errmsg = e.GetErrMsg() + } + + // 返回401 HTTP状态码 + httpx.WriteJson(w, http.StatusUnauthorized, result.Error(errcode, errmsg)) +} + +// validateJWT 验证JWT token +func (m *AdminAuthInterceptorMiddleware) validateJWT(r *http.Request) (*jwtx.JwtClaims, error) { + // 从请求头中获取Authorization字段 + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "缺少Authorization头") + } + + // 去掉Bearer前缀 + token := strings.TrimPrefix(authHeader, "Bearer ") + if token == authHeader { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "Authorization头格式错误,缺少Bearer前缀") + } + + claims, err := jwtx.ParseJwtToken(token, m.Config.AdminConfig.AccessSecret) + if err != nil { + // 根据错误类型返回不同的错误码 + errMsg := err.Error() + if strings.Contains(errMsg, "token已过期") || strings.Contains(errMsg, "expired") { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "token解析失败: %v", err) + } + // 其他JWT解析错误使用统一的token过期错误码(更符合用户理解) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "token解析失败: %v", err) + } + + return claims, nil +} + +// validateApiPermission 验证API权限 +func (m *AdminAuthInterceptorMiddleware) validateApiPermission(ctx context.Context, userId int64, method, path string) error { + // 1. 获取用户角色 + userRoles, err := m.getUserRoles(ctx, userId) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户角色失败: %v", err) + } + + if len(userRoles) == 0 { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户没有分配角色") + } + + // 2. 检查是否为超级管理员 + if m.isSuperAdmin(ctx, userRoles) { + // 超级管理员拥有所有权限,直接放行 + return nil + } + + // 3. 获取当前请求的API信息 + api, err := m.getApiByMethodAndPath(ctx, method, path) + if err != nil { + // 如果API不存在,可能是公开接口,放行 + return nil + } + + // 4. 检查用户角色是否有该API权限 + hasPermission, err := m.checkRoleApiPermission(ctx, userRoles, api.Id) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查API权限失败: %v", err) + } + + if !hasPermission { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "权限不足,无法访问该接口") + } + + return nil +} + +// getUserRoles 获取用户角色 +func (m *AdminAuthInterceptorMiddleware) getUserRoles(ctx context.Context, userId int64) ([]int64, error) { + builder := m.AdminUserRoleModel.SelectBuilder().Where("user_id = ?", userId) + userRoles, err := m.AdminUserRoleModel.FindAll(ctx, builder, "") + if err != nil { + return nil, err + } + + var roleIds []int64 + for _, userRole := range userRoles { + roleIds = append(roleIds, userRole.RoleId) + } + + return roleIds, nil +} + +// isSuperAdmin 检查是否为超级管理员 +func (m *AdminAuthInterceptorMiddleware) isSuperAdmin(ctx context.Context, roleIds []int64) bool { + // 检查是否有超级管理员角色 + for _, roleId := range roleIds { + role, err := m.AdminRoleModel.FindOne(ctx, roleId) + if err != nil { + continue + } + // 检查是否为超级管理员角色 + if role.RoleCode == model.AdminRoleCodeSuper { + return true + } + } + return false +} + +// getApiByMethodAndPath 根据方法和路径获取API信息 +func (m *AdminAuthInterceptorMiddleware) getApiByMethodAndPath(ctx context.Context, method, path string) (*model.AdminApi, error) { + builder := m.AdminApiModel.SelectBuilder(). + Where("method = ? AND url = ? AND status = ?", method, path, 1) + + apis, err := m.AdminApiModel.FindAll(ctx, builder, "") + if err != nil { + return nil, err + } + + if len(apis) == 0 { + return nil, errors.New("API不存在") + } + + return apis[0], nil +} + +// checkRoleApiPermission 检查角色是否有API权限 +func (m *AdminAuthInterceptorMiddleware) checkRoleApiPermission(ctx context.Context, roleIds []int64, apiId int64) (bool, error) { + for _, roleId := range roleIds { + // 检查角色是否有该API权限 + _, err := m.AdminRoleApiModel.FindOneByRoleIdApiId(ctx, roleId, apiId) + if err == nil { + // 找到权限记录,说明有权限 + return true, nil + } + // 如果错误不是NotFound,说明是其他错误 + if !errors.Is(err, model.ErrNotFound) { + return false, err + } + } + + return false, nil +} diff --git a/app/main/api/internal/middleware/authinterceptormiddleware.go b/app/main/api/internal/middleware/authinterceptormiddleware.go new file mode 100644 index 0000000..cd604ff --- /dev/null +++ b/app/main/api/internal/middleware/authinterceptormiddleware.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "context" + "net/http" + + "bdrp-server/app/main/api/internal/config" + jwtx "bdrp-server/common/jwt" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/rest/httpx" +) + +const ( + // 定义错误码 + ErrCodeUnauthorized = 401 +) + +type AuthInterceptorMiddleware struct { + Config config.Config +} + +func NewAuthInterceptorMiddleware(c config.Config) *AuthInterceptorMiddleware { + return &AuthInterceptorMiddleware{ + Config: c, + } +} + +func (m *AuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 从请求头中获取Authorization字段 + authHeader := r.Header.Get("Authorization") + + // 如果没有Authorization头,直接放行 + if authHeader == "" { + next(w, r) + return + } + + // 解析JWT令牌 + claims, err := jwtx.ParseJwtToken(authHeader, m.Config.JwtAuth.AccessSecret) + if err != nil { + // JWT解析失败,返回401错误 + httpx.Error(w, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err)) + return + } + + ctx := context.WithValue(r.Context(), jwtx.ExtraKey, claims) + + // 使用新的上下文继续处理请求 + next(w, r.WithContext(ctx)) + } +} diff --git a/app/main/api/internal/middleware/global_sourceinterceptor_middleware.go b/app/main/api/internal/middleware/global_sourceinterceptor_middleware.go new file mode 100644 index 0000000..c7197b3 --- /dev/null +++ b/app/main/api/internal/middleware/global_sourceinterceptor_middleware.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "context" + "net/http" +) + +const ( + PlatformKey = "X-Platform" +) + +func GlobalSourceInterceptor(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // 获取请求头 X-Platform 的值 + platform := r.Header.Get(PlatformKey) + + // 将值放入新的 context 中 + ctx := r.Context() + if platform != "" { + ctx = context.WithValue(ctx, "platform", platform) + } + + // 通过 r.WithContext 将更新后的 ctx 传递给后续的处理函数 + r = r.WithContext(ctx) + + // 传递给下一个处理器 + next(w, r) + } +} diff --git a/app/main/api/internal/middleware/userauthinterceptormiddleware.go b/app/main/api/internal/middleware/userauthinterceptormiddleware.go new file mode 100644 index 0000000..b6d20c1 --- /dev/null +++ b/app/main/api/internal/middleware/userauthinterceptormiddleware.go @@ -0,0 +1,60 @@ +package middleware + +import ( + "net/http" + + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/common/result" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/rest/httpx" +) + +// 用户封禁状态:0 可用,1 禁用 +const userDisableStatus = 1 + +type UserAuthInterceptorMiddleware struct { + UserModel model.UserModel +} + +func NewUserAuthInterceptorMiddleware(userModel model.UserModel) *UserAuthInterceptorMiddleware { + return &UserAuthInterceptorMiddleware{UserModel: userModel} +} + +func (m *UserAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + claims, err := ctxdata.GetClaimsFromCtx(r.Context()) + if err != nil { + m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err)) + return + } + if claims.UserType == model.UserTypeTemp { + m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(xerr.USER_NEED_BIND_MOBILE), "请先绑定手机号")) + return + } + // 封禁校验:用户已被禁用则直接拒绝 + user, err := m.UserModel.FindOne(r.Context(), claims.UserId) + if err != nil { + m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)) + return + } + if user.Disable == userDisableStatus { + m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_DISABLED)) + return + } + next(w, r) + } +} + +// writeErrorResponse 统一返回 code + msg,便于前端展示提示信息 +func (m *UserAuthInterceptorMiddleware) writeErrorResponse(w http.ResponseWriter, statusCode int, err error) { + errcode := xerr.SERVER_COMMON_ERROR + errmsg := xerr.MapErrMsg(errcode) + if e, ok := errors.Cause(err).(*xerr.CodeError); ok { + errcode = e.GetErrCode() + errmsg = e.GetErrMsg() + } + httpx.WriteJson(w, statusCode, result.Error(errcode, errmsg)) +} diff --git a/app/main/api/internal/queue/cleanQueryData.go b/app/main/api/internal/queue/cleanQueryData.go new file mode 100644 index 0000000..da876d5 --- /dev/null +++ b/app/main/api/internal/queue/cleanQueryData.go @@ -0,0 +1,167 @@ +package queue + +import ( + "context" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "database/sql" + "fmt" + "strconv" + "time" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// TASKTIME 定义为每天凌晨3点执行 +const TASKTIME = "0 3 * * *" + +type CleanQueryDataHandler struct { + svcCtx *svc.ServiceContext +} + +func NewCleanQueryDataHandler(svcCtx *svc.ServiceContext) *CleanQueryDataHandler { + return &CleanQueryDataHandler{ + svcCtx: svcCtx, + } +} + +// 获取配置值 +func (l *CleanQueryDataHandler) getConfigValue(ctx context.Context, key string) (string, error) { + // 通过缓存获取配置 + config, err := l.svcCtx.QueryCleanupConfigModel.FindOneByConfigKey(ctx, key) + if err != nil { + if err == model.ErrNotFound { + return "", fmt.Errorf("配置项 %s 不存在", key) + } + return "", err + } + + // 检查配置状态 + if config.Status != 1 { + return "", fmt.Errorf("配置项 %s 已禁用或已删除", key) + } + + return config.ConfigValue, nil +} + +func (l *CleanQueryDataHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + now := time.Now() + logx.Infof("%s - 开始执行查询数据清理任务", now.Format("2006-01-02 15:04:05")) + + // 1. 检查是否启用清理 + enableCleanup, err := l.getConfigValue(ctx, "enable_cleanup") + if err != nil { + return err + } + if enableCleanup != "1" { + logx.Infof("查询数据清理任务已禁用") + return nil + } + + // 2. 获取保留天数 + retentionDaysStr, err := l.getConfigValue(ctx, "retention_days") + if err != nil { + return err + } + retentionDays, err := strconv.Atoi(retentionDaysStr) + if err != nil { + return err + } + + // 3. 获取批次大小 + batchSizeStr, err := l.getConfigValue(ctx, "batch_size") + if err != nil { + return err + } + batchSize, err := strconv.Atoi(batchSizeStr) + if err != nil { + return err + } + + // 计算清理截止时间 + cleanupBefore := now.AddDate(0, 0, -retentionDays) + + // 创建清理日志记录 + cleanupLog := &model.QueryCleanupLog{ + CleanupTime: now, + CleanupBefore: cleanupBefore, + Status: 1, + Remark: sql.NullString{String: "定时清理数据", Valid: true}, + } + + // 使用事务处理清理操作和日志记录 + err = l.svcCtx.QueryModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 分批处理 + for { + // 1. 查询一批要删除的记录 + builder := l.svcCtx.QueryModel.SelectBuilder(). + Where("create_time < ?", cleanupBefore). + Where("del_state = ?", globalkey.DelStateNo). + Limit(uint64(batchSize)) + + queries, err := l.svcCtx.QueryModel.FindAll(ctx, builder, "") + if err != nil { + cleanupLog.Status = 2 + cleanupLog.ErrorMsg = sql.NullString{String: err.Error(), Valid: true} + return err + } + + if len(queries) == 0 { + break // 没有更多数据需要清理 + } + + // 2. 执行清理 + for _, query := range queries { + err = l.svcCtx.QueryModel.DeleteSoft(ctx, session, query) + if err != nil { + cleanupLog.Status = 2 + cleanupLog.ErrorMsg = sql.NullString{String: err.Error(), Valid: true} + return err + } + } + + // 3. 更新影响行数 + cleanupLog.AffectedRows += int64(len(queries)) + + // 4. 保存清理日志(每批次都记录) + cleanupLogInsertResult, err := l.svcCtx.QueryCleanupLogModel.Insert(ctx, session, cleanupLog) + if err != nil { + return err + } + cleanupLogId, err := cleanupLogInsertResult.LastInsertId() + if err != nil { + return err + } + + // 5. 保存清理明细 + for _, query := range queries { + detail := &model.QueryCleanupDetail{ + CleanupLogId: cleanupLogId, + QueryId: query.Id, + OrderId: query.OrderId, + UserId: query.UserId, + ProductId: query.ProductId, + QueryState: query.QueryState, + CreateTimeOld: query.CreateTime, + } + _, err = l.svcCtx.QueryCleanupDetailModel.Insert(ctx, session, detail) + if err != nil { + return err + } + } + } + + return nil + }) + + if err != nil { + logx.Errorf("%s - 清理查询数据失败: %v", now.Format("2006-01-02 15:04:05"), err) + return err + } + + logx.Infof("%s - 查询数据清理完成,共删除 %d 条记录", now.Format("2006-01-02 15:04:05"), cleanupLog.AffectedRows) + return nil +} diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go new file mode 100644 index 0000000..5b33087 --- /dev/null +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -0,0 +1,474 @@ +package queue + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + "os" + "path" + "regexp" + "strings" + paylogic "bdrp-server/app/main/api/internal/logic/pay" + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + "bdrp-server/pkg/lzkit/crypto" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" +) + +type PaySuccessNotifyUserHandler struct { + svcCtx *svc.ServiceContext +} + +func NewPaySuccessNotifyUserHandler(svcCtx *svc.ServiceContext) *PaySuccessNotifyUserHandler { + return &PaySuccessNotifyUserHandler{ + svcCtx: svcCtx, + } +} + +var payload struct { + OrderID int64 `json:"order_id"` +} + +func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + // 从任务的负载中解码数据 + if err := json.Unmarshal(t.Payload(), &payload); err != nil { + return fmt.Errorf("解析任务负载失败: %w", err) + } + + order, err := l.svcCtx.OrderModel.FindOne(ctx, payload.OrderID) + if err != nil { + return fmt.Errorf("无效的订单ID: %d, %v", payload.OrderID, err) + } + // 必须已支付才处理:仅支付宝/微信/苹果回调或 pay_method=test 的异步流程会将订单标为 paid,此处不再按 ENV 放宽 + if order.Status != "paid" { + err = fmt.Errorf("无效的订单状态(非已支付): orderID=%d, status=%s", payload.OrderID, order.Status) + logx.Errorf("处理任务失败,原因: %v", err) + return asynq.SkipRetry + } + env := os.Getenv("ENV") + product, err := l.svcCtx.ProductModel.FindOne(ctx, order.ProductId) + if err != nil { + return fmt.Errorf("找不到相关产品: orderID: %d, productID: %d", payload.OrderID, order.ProductId) + } + redisKey := fmt.Sprintf(types.QueryCacheKey, order.UserId, order.OrderNo) + cache, cacheErr := l.svcCtx.Redis.GetCtx(ctx, redisKey) + if cacheErr != nil { + return fmt.Errorf("获取缓存内容失败: %+v", cacheErr) + } + var data types.QueryCacheLoad + err = json.Unmarshal([]byte(cache), &data) + if err != nil { + return fmt.Errorf("解析缓存内容失败: %+v", err) + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return fmt.Errorf("获取AES密钥失败: %+v", decodeErr) + } + decryptData, aesdecryptErr := crypto.AesDecrypt(data.Params, key) + if aesdecryptErr != nil { + return fmt.Errorf("解密参数失败: %+v", aesdecryptErr) + } + + query := &model.Query{ + OrderId: order.Id, + UserId: order.UserId, + ProductId: product.Id, + QueryParams: data.Params, + QueryState: "pending", + } + result, insertQueryErr := l.svcCtx.QueryModel.Insert(ctx, nil, query) + if insertQueryErr != nil { + return fmt.Errorf("保存查询失败: %+v", insertQueryErr) + } + + // 获取插入后的ID + queryId, err := result.LastInsertId() + if err != nil { + return fmt.Errorf("获取插入的查询ID失败: %+v", err) + } + + // 从数据库中查询完整的查询记录 + query, err = l.svcCtx.QueryModel.FindOne(ctx, queryId) + if err != nil { + return fmt.Errorf("获取插入后的查询记录失败: %+v", err) + } + + // 解析解密后的参数获取用户信息 + var userInfo map[string]interface{} + if err := json.Unmarshal(decryptData, &userInfo); err != nil { + return fmt.Errorf("解析用户信息失败: %+v", err) + } + + // 生成授权书 + authDoc, err := l.svcCtx.AuthorizationService.GenerateAuthorizationDocument( + ctx, order.UserId, order.Id, queryId, userInfo, + ) + if err != nil { + logx.Errorf("生成授权书失败: %v", err) + } + + // 将授权书URL添加到解密数据中 + if authDoc != nil { + // 生成授权书下载接口URL + downloadURL := l.buildAuthorizationDownloadURL(authDoc.FileName) + userInfo["authorization_url"] = downloadURL + + // 重新序列化用户信息 + updatedDecryptData, marshalErr := json.Marshal(userInfo) + if marshalErr != nil { + logx.Errorf("序列化用户信息失败: %v", marshalErr) + } else { + // 重新加密更新后的数据 + encryptedUpdatedData, encryptErr := crypto.AesEncrypt(updatedDecryptData, key) + if encryptErr != nil { + logx.Errorf("重新加密用户信息失败: %v", encryptErr) + } else { + // 更新查询记录中的参数 + query.QueryParams = string(encryptedUpdatedData) + updateParamsErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) + if updateParamsErr != nil { + logx.Errorf("更新查询参数失败: %v", updateParamsErr) + } else { + logx.Infof("成功更新查询参数,包含授权书URL: %s", downloadURL) + } + } + decryptData = updatedDecryptData + } + } + // 调用API请求服务(开发环境下不调用其它产品,使用默认空报告) + var responseData []service.APIResponseData + if env == "development" { + // 开发环境:生成仅包含基本信息的默认空报告,不调用外部 API + // 空报告模式:生成空的报告数据,跳过API调用 + logx.Infof("空报告模式:订单 %s (ID: %s) 跳过API调用,生成空报告", order.OrderNo, order.Id) + // 空数组,表示没有数据;与 json.Marshal 配合得到 [] + responseData = []service.APIResponseData{} + } else { + var processErr error + responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) + if processErr != nil { + return l.handleError(ctx, processErr, order, query) + } + } + + // 计算成功模块的总成本价 + totalCostPrice := 0.0 + if responseData != nil { + for _, item := range responseData { + if item.Success { + // 根据API ID查找功能模块 + feature, err := l.svcCtx.FeatureModel.FindOneByApiId(ctx, item.ApiID) + if err != nil { + logx.Errorf("查找功能模块失败, API ID: %s, 错误: %v", item.ApiID, err) + continue + } + // 累加成本价 + totalCostPrice += feature.CostPrice + } + } + } + + // 更新订单的销售成本 + order.SalesCost = totalCostPrice + updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order) + if updateOrderErr != nil { + logx.Errorf("更新订单销售成本失败, 订单ID: %d, 错误: %v", order.Id, updateOrderErr) + // 注意:这里不返回错误,因为订单销售成本更新失败不应影响整个查询流程 + } else { + logx.Infof("成功更新订单销售成本, 订单ID: %d, 总成本价: %f", order.Id, totalCostPrice) + } + + // 对返回的类型进行二进制转换 + combinedResponse, marshalErr := json.Marshal(responseData) + if marshalErr != nil { + err = fmt.Errorf("响应数据转 JSON 失败: %v", marshalErr) + return l.handleError(ctx, err, order, query) + } + // 加密返回响应 + encryptData, aesEncryptErr := crypto.AesEncrypt(combinedResponse, key) + if aesEncryptErr != nil { + err = fmt.Errorf("加密响应信息失败: %v", aesEncryptErr) + return l.handleError(ctx, err, order, query) + } + query.QueryData = lzUtils.StringToNullString(encryptData) + updateErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) + if updateErr != nil { + err = fmt.Errorf("保存响应数据失败: %v", updateErr) + return l.handleError(ctx, err, order, query) + } + + query.QueryState = "success" + updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) + if updateQueryErr != nil { + updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr) + return l.handleError(ctx, updateQueryErr, order, query) + } + + err = l.svcCtx.AgentService.AgentProcess(ctx, order) + if err != nil { + return l.handleError(ctx, err, order, query) + } + + _, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey) + if delErr != nil { + logx.Errorf("删除Redis缓存失败,但任务已成功处理,订单ID: %d, 错误: %v", order.Id, delErr) + } + + return nil +} + +func (l *PaySuccessNotifyUserHandler) buildAuthorizationDownloadURL(fileName string) string { + escapedFileName := url.PathEscape(fileName) + base := l.svcCtx.Config.Authorization.FileBaseURL + if parsed, err := url.Parse(base); err == nil && parsed.Scheme != "" && parsed.Host != "" { + parsed.Path = path.Join("/api/v1/authorization/download/file", escapedFileName) + parsed.RawQuery = "" + parsed.Fragment = "" + return parsed.String() + } + return fmt.Sprintf("/api/v1/authorization/download/file/%s", escapedFileName) +} + +// 定义一个中间件函数 +func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error, order *model.Order, query *model.Query) error { + logx.Errorf("处理任务失败,原因: %v", err) + + redisKey := fmt.Sprintf(types.QueryCacheKey, order.UserId, order.OrderNo) + _, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey) + if delErr != nil { + logx.Errorf("删除Redis缓存失败,订单ID: %d, 错误: %v", order.Id, delErr) + } + + if order.Status == "paid" && query.QueryState == "pending" { + // 更新查询状态为失败 + query.QueryState = "failed" + updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query) + if updateQueryErr != nil { + logx.Errorf("更新查询状态失败,订单ID: %d, 错误: %v", order.Id, updateQueryErr) + return asynq.SkipRetry + } + + // 退款 + if order.PaymentPlatform == "wechat" { + // 微信退款为异步结果,这里只发起退款申请,订单状态与佣金/钱包扣减交由退款回调统一处理 + refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount) + if refundErr != nil { + logx.Error(refundErr) + return asynq.SkipRetry + } + logx.Infof("已发起微信退款申请, orderID: %d, amount: %f", order.Id, order.Amount) + return asynq.SkipRetry + } else { + // 支付宝退款为同步结果,这里直接根据返回结果更新订单和佣金/钱包 + refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) + if refundErr != nil { + logx.Error(refundErr) + return asynq.SkipRetry + } + if refund.IsSuccess() { + logx.Infof("支付宝退款成功, orderID: %d", order.Id) + // 更新订单状态为退款 + order.Status = "refunded" + updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order) + if updateOrderErr != nil { + logx.Errorf("更新订单状态失败,订单ID: %d, 错误: %v", order.Id, updateOrderErr) + return fmt.Errorf("更新订单状态失败: %v", updateOrderErr) + } + + // 使用公共函数按本次退款金额处理佣金和钱包扣除 + _ = paylogic.HandleCommissionAndWalletDeduction(ctx, l.svcCtx, nil, order, order.Amount) + + return asynq.SkipRetry + } else { + logx.Errorf("支付宝退款失败:%v", refundErr) + return asynq.SkipRetry + } + // 直接成功 + } + + } + + return asynq.SkipRetry +} + +// desensitizeParams 对敏感数据进行脱敏处理 +func (l *PaySuccessNotifyUserHandler) desensitizeParams(data []byte) ([]byte, error) { + // 解析JSON数据到map + var paramsMap map[string]interface{} + if err := json.Unmarshal(data, ¶msMap); err != nil { + return nil, fmt.Errorf("解析JSON数据失败: %v", err) + } + + // 处理可能包含敏感信息的字段 + for key, value := range paramsMap { + if strValue, ok := value.(string); ok { + // 根据字段名和内容判断并脱敏 + if isNameField(key) && len(strValue) > 0 { + // 姓名脱敏 + paramsMap[key] = maskName(strValue) + } else if isIDCardField(key) && len(strValue) > 10 { + // 身份证号脱敏 + paramsMap[key] = maskIDCard(strValue) + } else if isPhoneField(key) && len(strValue) >= 8 { + // 手机号脱敏 + paramsMap[key] = maskPhone(strValue) + } else if len(strValue) > 3 { + // 其他所有未匹配的字段都进行通用脱敏 + paramsMap[key] = maskGeneral(strValue) + } + } else if mapValue, ok := value.(map[string]interface{}); ok { + // 递归处理嵌套的map + for subKey, subValue := range mapValue { + if subStrValue, ok := subValue.(string); ok { + if isNameField(subKey) && len(subStrValue) > 0 { + mapValue[subKey] = maskName(subStrValue) + } else if isIDCardField(subKey) && len(subStrValue) > 10 { + mapValue[subKey] = maskIDCard(subStrValue) + } else if isPhoneField(subKey) && len(subStrValue) >= 8 { + mapValue[subKey] = maskPhone(subStrValue) + } else if len(subStrValue) > 3 { + // 其他所有未匹配的字段都进行通用脱敏 + mapValue[subKey] = maskGeneral(subStrValue) + } + } + } + } + } + + // 将处理后的map重新序列化为JSON + return json.Marshal(paramsMap) +} + +// 判断是否为姓名字段 +func isNameField(key string) bool { + key = strings.ToLower(key) + return strings.Contains(key, "name") || strings.Contains(key, "姓名") || + strings.Contains(key, "owner") || strings.Contains(key, "main") +} + +// 判断是否为身份证字段 +func isIDCardField(key string) bool { + key = strings.ToLower(key) + return strings.Contains(key, "idcard") || strings.Contains(key, "id_card") || + strings.Contains(key, "身份证") || strings.Contains(key, "证件号") +} + +// 判断是否为手机号字段 +func isPhoneField(key string) bool { + key = strings.ToLower(key) + return strings.Contains(key, "phone") || strings.Contains(key, "mobile") || + strings.Contains(key, "手机") || strings.Contains(key, "电话") +} + +// 判断是否包含敏感数据模式 +func containsSensitivePattern(value string) bool { + // 检查是否包含连续的数字或字母模式 + numPattern := regexp.MustCompile(`\d{6,}`) + return numPattern.MatchString(value) +} + +// 姓名脱敏 +func maskName(name string) string { + // 将字符串转换为rune切片以正确处理中文字符 + runes := []rune(name) + length := len(runes) + + if length <= 1 { + return name + } + + if length == 2 { + // 两个字:保留第一个字,第二个字用*替代 + return string(runes[0]) + "*" + } + + // 三个字及以上:保留首尾字,中间用*替代 + first := string(runes[0]) + last := string(runes[length-1]) + mask := strings.Repeat("*", length-2) + + return first + mask + last +} + +// 身份证号脱敏 +func maskIDCard(idCard string) string { + length := len(idCard) + if length <= 10 { + return idCard // 如果长度太短,可能不是身份证,不处理 + } + // 保留前3位和后4位 + return idCard[:3] + strings.Repeat("*", length-7) + idCard[length-4:] +} + +// 手机号脱敏 +func maskPhone(phone string) string { + length := len(phone) + if length < 8 { + return phone // 如果长度太短,可能不是手机号,不处理 + } + // 保留前3位和后4位 + return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:] +} + +// 通用敏感信息脱敏 - 根据字符串长度比例进行脱敏 +func maskGeneral(value string) string { + length := len(value) + + // 小于3个字符的不脱敏 + if length <= 3 { + return value + } + + // 根据字符串长度计算保留字符数 + var prefixLen, suffixLen int + + switch { + case length <= 6: // 短字符串 + // 保留首尾各1个字符 + prefixLen, suffixLen = 1, 1 + case length <= 10: // 中等长度字符串 + // 保留首部30%和尾部20%的字符 + prefixLen = int(float64(length) * 0.3) + suffixLen = int(float64(length) * 0.2) + case length <= 20: // 较长字符串 + // 保留首部25%和尾部15%的字符 + prefixLen = int(float64(length) * 0.25) + suffixLen = int(float64(length) * 0.15) + default: // 非常长的字符串 + // 保留首部20%和尾部10%的字符 + prefixLen = int(float64(length) * 0.2) + suffixLen = int(float64(length) * 0.1) + } + + // 确保至少有一个字符被保留 + if prefixLen < 1 { + prefixLen = 1 + } + if suffixLen < 1 { + suffixLen = 1 + } + + // 确保前缀和后缀总长不超过总长度的80% + if prefixLen+suffixLen > int(float64(length)*0.8) { + // 调整为总长度的80% + totalVisible := int(float64(length) * 0.8) + // 前缀占60%,后缀占40% + prefixLen = int(float64(totalVisible) * 0.6) + suffixLen = totalVisible - prefixLen + } + + // 创建脱敏后的字符串 + prefix := value[:prefixLen] + suffix := value[length-suffixLen:] + masked := strings.Repeat("*", length-prefixLen-suffixLen) + + return prefix + masked + suffix +} diff --git a/app/main/api/internal/queue/routes.go b/app/main/api/internal/queue/routes.go new file mode 100644 index 0000000..5c18130 --- /dev/null +++ b/app/main/api/internal/queue/routes.go @@ -0,0 +1,43 @@ +package queue + +import ( + "context" + "fmt" + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + + "github.com/hibiken/asynq" +) + +type CronJob struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob { + return &CronJob{ + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CronJob) Register() *asynq.ServeMux { + redisClientOpt := asynq.RedisClientOpt{Addr: l.svcCtx.Config.CacheRedis[0].Host, Password: l.svcCtx.Config.CacheRedis[0].Pass} + scheduler := asynq.NewScheduler(redisClientOpt, nil) + + // 注册清理查询数据任务 + task := asynq.NewTask(types.MsgCleanQueryData, nil, nil) + _, err := scheduler.Register(TASKTIME, task) + if err != nil { + panic(fmt.Sprintf("定时任务注册失败:%v", err)) + } + scheduler.Start() + fmt.Println("定时任务启动!!!") + + mux := asynq.NewServeMux() + mux.Handle(types.MsgPaySuccessQuery, NewPaySuccessNotifyUserHandler(l.svcCtx)) + mux.Handle(types.MsgCleanQueryData, NewCleanQueryDataHandler(l.svcCtx)) + mux.Handle(types.MsgUnfreezeCommission, NewUnfreezeCommissionHandler(l.svcCtx)) + + return mux +} diff --git a/app/main/api/internal/queue/unfreezeCommission.go b/app/main/api/internal/queue/unfreezeCommission.go new file mode 100644 index 0000000..8eb43bf --- /dev/null +++ b/app/main/api/internal/queue/unfreezeCommission.go @@ -0,0 +1,159 @@ +package queue + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "bdrp-server/app/main/api/internal/svc" + "bdrp-server/app/main/api/internal/types" + "bdrp-server/app/main/model" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// 定义佣金状态常量 +const ( + CommissionStatusReleased = 0 // 已发放 + CommissionStatusFrozen = 1 // 冻结佣金 +) + +type UnfreezeCommissionHandler struct { + svcCtx *svc.ServiceContext +} + +func NewUnfreezeCommissionHandler(svcCtx *svc.ServiceContext) *UnfreezeCommissionHandler { + return &UnfreezeCommissionHandler{ + svcCtx: svcCtx, + } +} + +func (l *UnfreezeCommissionHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { + now := time.Now() + logx.Infof("%s - 开始执行佣金解冻任务", now.Format("2006-01-02 15:04:05")) + // 解析任务payload,获取佣金ID + var payload types.MsgUnfreezeCommissionPayload + if err := json.Unmarshal(t.Payload(), &payload); err != nil { + logx.Errorf("解析佣金解冻任务payload失败: %v", err) + return err + } + + commissionID := payload.CommissionID + if commissionID <= 0 { + logx.Errorf("无效的佣金ID: %d", commissionID) + return fmt.Errorf("无效的佣金ID: %d", commissionID) + } + + // 根据佣金ID查询特定佣金记录 + commission, err := l.svcCtx.AgentCommissionModel.FindOne(ctx, commissionID) + if err != nil { + logx.Errorf("查询佣金记录ID %d 失败: %v", commissionID, err) + return err + } + + // 检查佣金状态是否为冻结状态 + if commission.Status != CommissionStatusFrozen { + logx.Infof("佣金记录ID %d 状态不是冻结状态,当前状态: %d,无需处理", commissionID, commission.Status) + return nil + } + + // 使用事务处理解冻操作 + err = l.svcCtx.AgentCommissionModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 获取代理钱包记录 + agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId) + if err != nil { + logx.Errorf("查询代理ID %d 的钱包记录失败: %v", commission.AgentId, err) + return err + } + + // 计算当前佣金在发生退款后的“净佣金金额” + commissionAmount := commission.Amount - commission.RefundedAmount + if commissionAmount <= 0 { + logx.Infof("佣金记录ID %d 已被全部退款或无可解冻金额,跳过解冻", commissionID) + return nil + } + + // 更新钱包余额:增加净佣金金额到 balance,减少相应的 frozen_balance + agentWallet.Balance += commissionAmount + agentWallet.FrozenBalance -= commissionAmount + agentWallet.UpdateTime = now + + // 更新钱包数据库(使用 UpdateWithVersion 保持乐观锁) + updateWalletErr := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, agentWallet) + if updateWalletErr != nil { + // 如果是版本冲突错误,重新查询最新的数据后重试 + if errors.Is(updateWalletErr, model.ErrNoRowsUpdate) { + logx.Infof("代理ID %d 的钱包版本冲突,重新查询最新数据重试", commission.AgentId) + latestWallet, findErr := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId) + if findErr != nil { + logx.Errorf("重新查询代理ID %d 的钱包记录失败: %v", commission.AgentId, findErr) + return findErr + } + // 重新累加金额 + latestWallet.Balance += commissionAmount + latestWallet.FrozenBalance -= commissionAmount + latestWallet.UpdateTime = now + retryUpdateErr := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, latestWallet) + if retryUpdateErr != nil { + logx.Errorf("重试更新代理ID %d 的钱包记录失败: %v", commission.AgentId, retryUpdateErr) + return retryUpdateErr + } + logx.Infof("重试成功,已更新代理ID %d 的钱包记录", commission.AgentId) + } else { + logx.Errorf("更新代理ID %d 的钱包记录失败: %v", commission.AgentId, updateWalletErr) + return updateWalletErr + } + } + + // 钱包更新成功后,再更新佣金状态为已发放 + commission.Status = CommissionStatusReleased + commission.UpdateTime = now + + // 更新佣金数据库(使用 UpdateWithVersion 保持乐观锁) + err = l.svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, commission) + if err != nil { + // 如果是版本冲突错误,重新查询最新的数据后重试 + if errors.Is(err, model.ErrNoRowsUpdate) { + logx.Infof("佣金记录ID %d 版本冲突,重新查询最新数据重试", commissionID) + latestCommission, findErr := l.svcCtx.AgentCommissionModel.FindOne(ctx, commissionID) + if findErr != nil { + logx.Errorf("重新查询佣金记录ID %d 失败: %v", commissionID, findErr) + return findErr + } + // 检查状态是否已被其他操作修改 + if latestCommission.Status != CommissionStatusFrozen { + logx.Errorf("佣金记录ID %d 的状态已被其他操作修改,当前状态: %d", commissionID, latestCommission.Status) + return fmt.Errorf("佣金记录状态已被修改") + } + // 重新更新状态 + latestCommission.Status = CommissionStatusReleased + latestCommission.UpdateTime = now + retryUpdateErr := l.svcCtx.AgentCommissionModel.UpdateWithVersion(ctx, session, latestCommission) + if retryUpdateErr != nil { + logx.Errorf("重试更新佣金记录ID %d 失败: %v", commissionID, retryUpdateErr) + return retryUpdateErr + } + logx.Infof("重试成功,已更新佣金记录ID %d", commissionID) + } else { + logx.Errorf("更新佣金记录ID %d 失败: %v", commissionID, err) + return err + } + } + + logx.Infof("成功解冻佣金记录ID %d,代理ID %d,佣金金额 %.2f,已将佣金金额从冻结余额转移到可用余额", + commissionID, commission.AgentId, commissionAmount) + return nil + }) + + if err != nil { + logx.Errorf("%s - 佣金解冻任务失败: %v", now.Format("2006-01-02 15:04:05"), err) + return err + } + + logx.Infof("%s - 佣金解冻任务完成,佣金ID: %d", now.Format("2006-01-02 15:04:05"), commissionID) + return nil +} diff --git a/app/main/api/internal/service/adminPromotionLinkStatsService.go b/app/main/api/internal/service/adminPromotionLinkStatsService.go new file mode 100644 index 0000000..13360ec --- /dev/null +++ b/app/main/api/internal/service/adminPromotionLinkStatsService.go @@ -0,0 +1,211 @@ +package service + +import ( + "context" + "database/sql" + "time" + + "bdrp-server/app/main/model" + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AdminPromotionLinkStatsService struct { + logx.Logger + AdminPromotionLinkModel model.AdminPromotionLinkModel + AdminPromotionLinkStatsTotalModel model.AdminPromotionLinkStatsTotalModel + AdminPromotionLinkStatsHistoryModel model.AdminPromotionLinkStatsHistoryModel +} + +func NewAdminPromotionLinkStatsService( + AdminPromotionLinkModel model.AdminPromotionLinkModel, + AdminPromotionLinkStatsTotalModel model.AdminPromotionLinkStatsTotalModel, + AdminPromotionLinkStatsHistoryModel model.AdminPromotionLinkStatsHistoryModel, +) *AdminPromotionLinkStatsService { + return &AdminPromotionLinkStatsService{ + Logger: logx.WithContext(context.Background()), + AdminPromotionLinkModel: AdminPromotionLinkModel, + AdminPromotionLinkStatsTotalModel: AdminPromotionLinkStatsTotalModel, + AdminPromotionLinkStatsHistoryModel: AdminPromotionLinkStatsHistoryModel, + } +} + +// ensureTotalStats 确保总统计记录存在,如果不存在则创建 +func (s *AdminPromotionLinkStatsService) ensureTotalStats(ctx context.Context, session sqlx.Session, linkId int64) (*model.AdminPromotionLinkStatsTotal, error) { + totalStats, err := s.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(ctx, linkId) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 如果记录不存在,创建新记录 + totalStats = &model.AdminPromotionLinkStatsTotal{ + LinkId: linkId, + ClickCount: 0, + PayCount: 0, + PayAmount: 0, + } + _, err = s.AdminPromotionLinkStatsTotalModel.Insert(ctx, session, totalStats) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建总统计记录失败: %+v", err) + } + // 重新获取创建后的记录 + totalStats, err = s.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(ctx, linkId) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的总统计记录失败: %+v", err) + } + } else { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询总统计失败: %+v", err) + } + } + return totalStats, nil +} + +// ensureHistoryStats 确保历史统计记录存在,如果不存在则创建 +func (s *AdminPromotionLinkStatsService) ensureHistoryStats(ctx context.Context, session sqlx.Session, linkId int64, today time.Time) (*model.AdminPromotionLinkStatsHistory, error) { + historyStats, err := s.AdminPromotionLinkStatsHistoryModel.FindOneByLinkIdStatsDate(ctx, linkId, today) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 如果记录不存在,创建新记录 + historyStats = &model.AdminPromotionLinkStatsHistory{ + LinkId: linkId, + StatsDate: today, + ClickCount: 0, + PayCount: 0, + PayAmount: 0, + } + _, err = s.AdminPromotionLinkStatsHistoryModel.Insert(ctx, session, historyStats) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建今日统计记录失败: %+v", err) + } + // 重新获取创建后的记录 + historyStats, err = s.AdminPromotionLinkStatsHistoryModel.FindOneByLinkIdStatsDate(ctx, linkId, today) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的今日统计记录失败: %+v", err) + } + } else { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询今日统计记录失败: %+v", err) + } + } + return historyStats, nil +} + +// UpdateLinkStats 更新推广链接统计 +func (s *AdminPromotionLinkStatsService) UpdateLinkStats(ctx context.Context, linkId int64) error { + return s.AdminPromotionLinkStatsTotalModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 确保总统计记录存在 + totalStats, err := s.ensureTotalStats(ctx, session, linkId) + if err != nil { + return err + } + + // 更新总统计 + totalStats.ClickCount++ + totalStats.LastClickTime = sql.NullTime{Time: time.Now(), Valid: true} + err = s.AdminPromotionLinkStatsTotalModel.UpdateWithVersion(ctx, session, totalStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新总统计失败: %+v", err) + } + + // 确保历史统计记录存在 + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + historyStats, err := s.ensureHistoryStats(ctx, session, linkId, today) + if err != nil { + return err + } + + // 更新历史统计 + historyStats.ClickCount++ + historyStats.LastClickTime = sql.NullTime{Time: time.Now(), Valid: true} + err = s.AdminPromotionLinkStatsHistoryModel.UpdateWithVersion(ctx, session, historyStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新历史统计失败: %+v", err) + } + + return nil + }) +} + +// UpdatePaymentStats 更新付费统计 +func (s *AdminPromotionLinkStatsService) UpdatePaymentStats(ctx context.Context, linkId int64, amount float64) error { + return s.AdminPromotionLinkStatsTotalModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 确保总统计记录存在 + totalStats, err := s.ensureTotalStats(ctx, session, linkId) + if err != nil { + return err + } + + // 更新总统计 + totalStats.PayCount++ + totalStats.PayAmount += amount + totalStats.LastPayTime = sql.NullTime{Time: time.Now(), Valid: true} + err = s.AdminPromotionLinkStatsTotalModel.UpdateWithVersion(ctx, session, totalStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新总统计失败: %+v", err) + } + + // 确保历史统计记录存在 + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + historyStats, err := s.ensureHistoryStats(ctx, session, linkId, today) + if err != nil { + return err + } + + // 更新历史统计 + historyStats.PayCount++ + historyStats.PayAmount += amount + historyStats.LastPayTime = sql.NullTime{Time: time.Now(), Valid: true} + err = s.AdminPromotionLinkStatsHistoryModel.UpdateWithVersion(ctx, session, historyStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新历史统计失败: %+v", err) + } + + return nil + }) +} + +// CreateLinkStats 创建新的推广链接统计记录 +func (s *AdminPromotionLinkStatsService) CreateLinkStats(ctx context.Context, linkId int64) error { + return s.AdminPromotionLinkStatsTotalModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 检查总统计记录是否已存在 + _, err := s.AdminPromotionLinkStatsTotalModel.FindOneByLinkId(ctx, linkId) + if err == nil { + // 记录已存在,不需要创建 + return nil + } + if err != model.ErrNotFound { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询总统计记录失败: %+v", err) + } + + // 创建总统计记录 + totalStats := &model.AdminPromotionLinkStatsTotal{ + LinkId: linkId, + ClickCount: 0, + PayCount: 0, + PayAmount: 0, + } + _, err = s.AdminPromotionLinkStatsTotalModel.Insert(ctx, session, totalStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建总统计记录失败: %+v", err) + } + + // 创建今日历史统计记录 + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + historyStats := &model.AdminPromotionLinkStatsHistory{ + LinkId: linkId, + StatsDate: today, + ClickCount: 0, + PayCount: 0, + PayAmount: 0, + } + _, err = s.AdminPromotionLinkStatsHistoryModel.Insert(ctx, session, historyStats) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建历史统计记录失败: %+v", err) + } + + return nil + }) +} diff --git a/app/main/api/internal/service/agentService.go b/app/main/api/internal/service/agentService.go new file mode 100644 index 0000000..c0625eb --- /dev/null +++ b/app/main/api/internal/service/agentService.go @@ -0,0 +1,850 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + "bdrp-server/common/globalkey" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type AgentService struct { + config config.Config + OrderModel model.OrderModel + AgentModel model.AgentModel + AgentAuditModel model.AgentAuditModel + AgentClosureModel model.AgentClosureModel + AgentCommissionModel model.AgentCommissionModel + AgentCommissionDeductionModel model.AgentCommissionDeductionModel + AgentWalletModel model.AgentWalletModel + AgentLinkModel model.AgentLinkModel + AgentOrderModel model.AgentOrderModel + AgentRewardsModel model.AgentRewardsModel + AgentMembershipConfigModel model.AgentMembershipConfigModel + AgentMembershipRechargeOrderModel model.AgentMembershipRechargeOrderModel + AgentMembershipUserConfigModel model.AgentMembershipUserConfigModel + AgentProductConfigModel model.AgentProductConfigModel + AgentPlatformDeductionModel model.AgentPlatformDeductionModel + AgentActiveStatModel model.AgentActiveStatModel + AgentWithdrawalModel model.AgentWithdrawalModel + AgentWalletTransactionModel model.AgentWalletTransactionModel + AsynqService *AsynqService +} + +func NewAgentService(c config.Config, orderModel model.OrderModel, agentModel model.AgentModel, agentAuditModel model.AgentAuditModel, + agentClosureModel model.AgentClosureModel, agentCommissionModel model.AgentCommissionModel, + agentCommissionDeductionModel model.AgentCommissionDeductionModel, agentWalletModel model.AgentWalletModel, agentLinkModel model.AgentLinkModel, agentOrderModel model.AgentOrderModel, agentRewardsModel model.AgentRewardsModel, + agentMembershipConfigModel model.AgentMembershipConfigModel, + agentMembershipRechargeOrderModel model.AgentMembershipRechargeOrderModel, + agentMembershipUserConfigModel model.AgentMembershipUserConfigModel, + agentProductConfigModel model.AgentProductConfigModel, agentPlatformDeductionModel model.AgentPlatformDeductionModel, + agentActiveStatModel model.AgentActiveStatModel, agentWithdrawalModel model.AgentWithdrawalModel, agentWalletTransactionModel model.AgentWalletTransactionModel, asynqService *AsynqService) *AgentService { + + return &AgentService{ + config: c, + OrderModel: orderModel, + AgentModel: agentModel, + AgentAuditModel: agentAuditModel, + AgentClosureModel: agentClosureModel, + AgentCommissionModel: agentCommissionModel, + AgentCommissionDeductionModel: agentCommissionDeductionModel, + AgentWalletModel: agentWalletModel, + AgentLinkModel: agentLinkModel, + AgentOrderModel: agentOrderModel, + AgentRewardsModel: agentRewardsModel, + AgentMembershipConfigModel: agentMembershipConfigModel, + AgentMembershipRechargeOrderModel: agentMembershipRechargeOrderModel, + AgentMembershipUserConfigModel: agentMembershipUserConfigModel, + AgentProductConfigModel: agentProductConfigModel, + AgentPlatformDeductionModel: agentPlatformDeductionModel, + AgentActiveStatModel: agentActiveStatModel, + AgentWithdrawalModel: agentWithdrawalModel, + AgentWalletTransactionModel: agentWalletTransactionModel, + AsynqService: asynqService, + } +} + +// AgentProcess 推广单成功 +func (l *AgentService) AgentProcess(ctx context.Context, order *model.Order) error { + // 获取是否该订单是代理推广订单 + agentOrderModel, err := l.AgentOrderModel.FindOneByOrderId(ctx, order.Id) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return err + } + if errors.Is(err, model.ErrNotFound) || agentOrderModel == nil { + return nil + } + // 事务 + transErr := l.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error { + agentID := agentOrderModel.AgentId + agentProductConfigModel, findAgentProductConfigModelErr := l.AgentProductConfigModel.FindOneByProductId(transCtx, order.ProductId) + if findAgentProductConfigModelErr != nil { + return findAgentProductConfigModelErr + } + // 平台底价成本 + PlatformCostAmount, platformCostErr := l.PlatformCost(transCtx, agentID, order.Id, agentProductConfigModel, session) + if platformCostErr != nil { + return platformCostErr + } + + // 平台提价成本 + PlatformPricingAmount, platformPricingErr := l.PlatformPricing(transCtx, agentID, order.Id, order.Amount, agentProductConfigModel, session) + if platformPricingErr != nil { + return platformPricingErr + } + + // 查找上级 + AgentClosureModel, findAgentClosureModelErr := l.AgentClosureModel.FindOneByDescendantIdDepth(transCtx, agentID, 1) + if findAgentClosureModelErr != nil && !errors.Is(findAgentClosureModelErr, model.ErrNotFound) { + return findAgentClosureModelErr + } + + var descendantDeductedAmount = 0.00 + if AgentClosureModel != nil { + AncestorId := AgentClosureModel.AncestorId + AncestorModel, findAgentModelErr := l.AgentModel.FindOne(transCtx, AncestorId) + if findAgentModelErr != nil && !errors.Is(findAgentModelErr, model.ErrNotFound) { + return findAgentModelErr + } + if AncestorModel != nil { + if AncestorModel.LevelName == "" { + AncestorModel.LevelName = model.AgentLeveNameNormal + } + AgentMembershipConfigModel, findAgentMembersipConfigModelErr := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, AncestorModel.LevelName) + if findAgentMembersipConfigModelErr != nil { + return findAgentMembersipConfigModelErr + } + // 定价 + commissionCost, commissionCostErr := l.CommissionCost(transCtx, agentID, AncestorId, AgentMembershipConfigModel, order.ProductId, order.Id, session) + if commissionCostErr != nil { + return commissionCostErr + } + // 提价 + commissionPricing, commissionPricingErr := l.CommissionPricing(transCtx, agentID, AncestorId, AgentMembershipConfigModel, order.ProductId, order.Amount, order.Id, session) + if commissionPricingErr != nil { + return commissionPricingErr + } + + // 上级克扣的成本 + descendantDeductedAmount = commissionCost + commissionPricing + // 奖励 + ancestorCommissionReward, ancestorCommissionErr := l.AncestorCommission(transCtx, agentID, AncestorId, session) + if ancestorCommissionErr != nil { + return ancestorCommissionErr + } + + // 给上级成本以及佣金 + ancestorCommissionAmount := commissionCost + commissionPricing + ancestorWallet, findAgentWalletModelErr := l.AgentWalletModel.FindOneByAgentId(transCtx, AncestorId) + if findAgentWalletModelErr != nil { + return findAgentWalletModelErr + } + + // 记录变动前的余额 + balanceBefore := ancestorWallet.Balance + frozenBalanceBefore := ancestorWallet.FrozenBalance + + // 奖励不冻结,直接进入balance + ancestorWallet.Balance += ancestorCommissionReward + + // 根据安全防御模式配置决定佣金处理方式 + var commissionStatus int64 + if l.config.SystemConfig.CommissionSafeMode { + // 安全防御模式:佣金冻结在frozen_balance中 + ancestorWallet.FrozenBalance += ancestorCommissionAmount + commissionStatus = 1 // 冻结状态 + } else { + // 非安全防御模式:佣金直接进入balance + ancestorWallet.Balance += ancestorCommissionAmount + commissionStatus = 0 // 已结算状态 + } + + // 为上级创建佣金记录 + ancestorCommissionRecord := model.AgentCommission{ + AgentId: AncestorId, + OrderId: order.Id, + Amount: ancestorCommissionAmount, + ProductId: order.ProductId, + Status: commissionStatus, + } + insertResult, insertAncestorCommissionErr := l.AgentCommissionModel.Insert(transCtx, session, &ancestorCommissionRecord) + if insertAncestorCommissionErr != nil { + return insertAncestorCommissionErr + } + + ancestorWallet.TotalEarnings += ancestorCommissionAmount + ancestorCommissionReward + updateErr := l.AgentWalletModel.UpdateWithVersion(transCtx, session, ancestorWallet) + if updateErr != nil { + return updateErr + } + + // 获取新插入的佣金记录ID + commissionID, err := insertResult.LastInsertId() + if err != nil { + return err + } + commissionIDStr := fmt.Sprintf("%d", commissionID) // 转换为字符串 + + // 记录交易流水(佣金收入) + transErr := l.CreateWalletTransaction( + transCtx, + session, + AncestorId, + model.WalletTransactionTypeCommission, + ancestorCommissionAmount, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + ancestorWallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + ancestorWallet.FrozenBalance, // 变动后冻结余额 + order.OrderNo, // 关联交易ID(订单号) + commissionID, // 关联佣金记录ID + fmt.Sprintf("订单佣金收入,佣金记录ID: %s", commissionIDStr), // 备注(包含佣金记录ID) + ) + if transErr != nil { + return transErr + } + } + } + + // 推广人扣除金额 = 平台成本价 + 平台提价成本 + 上级佣金 + deductedAmount := PlatformCostAmount + PlatformPricingAmount + descendantDeductedAmount + agentCommissionErr := l.AgentCommission(transCtx, agentID, order, deductedAmount, session) + if agentCommissionErr != nil { + return agentCommissionErr + } + + return nil + }) + if transErr != nil { + return transErr + } + + // 在事务提交后,仅在安全防御模式下触发解冻任务 + // 注意:这里发送的是任务,实际解冻将在指定时间后由队列处理 + if l.AsynqService != nil && l.config.SystemConfig.CommissionSafeMode { + // 仅在安全防御模式下,才需要发送解冻任务 + // 获取刚创建的佣金记录ID + // 由于我们需要佣金记录ID来触发解冻任务,但事务中无法获取,我们可以在事务后查询 + builder := l.AgentCommissionModel.SelectBuilder(). + Where("order_id = ?", order.Id). + Where("status = ?", 1). // 只查询状态为冻结的佣金 + Where("del_state = ?", globalkey.DelStateNo) + + commissions, findErr := l.AgentCommissionModel.FindAll(ctx, builder, "") + if findErr != nil { + logx.Errorf("查询刚创建的佣金记录失败,订单ID: %d, 错误: %v", order.Id, findErr) + return findErr + } + + if len(commissions) > 0 { + // 为所有新创建的冻结佣金记录触发解冻任务 + for _, commission := range commissions { + // 发送解冻任务,将在10小时后执行 + sendTaskErr := l.AsynqService.SendUnfreezeCommissionTask(commission.Id) + if sendTaskErr != nil { + logx.Errorf("发送佣金解冻任务失败,佣金ID: %d, 错误: %v", commission.Id, sendTaskErr) + // 不返回错误,因为佣金记录已创建成功,只是解冻任务失败 + } else { + logx.Infof("已发送佣金解冻任务,佣金ID: %d, 代理ID: %d, 金额: %.2f", + commission.Id, commission.AgentId, commission.Amount) + } + } + } + } + + return nil +} + +// AgentCommission 直推报告推广人佣金 +func (l *AgentService) AgentCommission(ctx context.Context, agentID int64, order *model.Order, deductedAmount float64, session sqlx.Session) error { + agentWalletModel, findAgentWalletModelErr := l.AgentWalletModel.FindOneByAgentId(ctx, agentID) + if findAgentWalletModelErr != nil { + return findAgentWalletModelErr + } + // 推广人最终获得代理佣金 + finalCommission := order.Amount - deductedAmount + + // 记录变动前的余额 + balanceBefore := agentWalletModel.Balance + frozenBalanceBefore := agentWalletModel.FrozenBalance + + // 根据安全防御模式配置决定佣金状态和钱包操作 + if l.config.SystemConfig.CommissionSafeMode { + // 安全防御模式:佣金冻结在frozen_balance中 + agentWalletModel.FrozenBalance += finalCommission + } else { + // 非安全防御模式:佣金直接进入balance + agentWalletModel.Balance += finalCommission + } + agentWalletModel.TotalEarnings += finalCommission + + // 根据安全防御模式配置决定佣金状态 + commissionStatus := int64(1) // 默认为冻结状态 + if !l.config.SystemConfig.CommissionSafeMode { + commissionStatus = 0 // 非安全模式直接设置为已结算 + } + + agentCommission := model.AgentCommission{ + AgentId: agentID, + OrderId: order.Id, + Amount: finalCommission, + ProductId: order.ProductId, + Status: commissionStatus, + } + insertResult, insertAgentCommissionErr := l.AgentCommissionModel.Insert(ctx, session, &agentCommission) + if insertAgentCommissionErr != nil { + return insertAgentCommissionErr + } + + // 获取新插入的佣金记录ID(用于日志记录) + commissionID, err := insertResult.LastInsertId() + if err != nil { + return err + } + _ = commissionID // 暂时忽略该变量,因为我们使用其他方式获取佣金记录 + + // 更新钱包 + updateAgentWalletErr := l.AgentWalletModel.UpdateWithVersion(ctx, session, agentWalletModel) + if updateAgentWalletErr != nil { + return updateAgentWalletErr + } + + // 记录交易流水(佣金收入) + transErr := l.CreateWalletTransaction( + ctx, + session, + agentID, + model.WalletTransactionTypeCommission, + finalCommission, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + agentWalletModel.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + agentWalletModel.FrozenBalance, // 变动后冻结余额 + order.OrderNo, // 关联交易ID(订单号) + 0, // 关联用户ID + "订单佣金收入", // 备注 + ) + if transErr != nil { + return transErr + } + + return nil +} + +// AncestorCommission 直推报告上级佣金(奖励型) +func (l *AgentService) AncestorCommission(ctx context.Context, descendantId int64, ancestorId int64, session sqlx.Session) (float64, error) { + agentModel, err := l.AgentModel.FindOne(ctx, ancestorId) + if err != nil { + return 0, err + } + if agentModel.LevelName == "" { + agentModel.LevelName = model.AgentLeveNameNormal + } + agentMembershipConfigModel, err := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, agentModel.LevelName) + if err != nil { + return 0, err + } + + if agentMembershipConfigModel.ReportCommission.Valid { + reportCommissionAmount := agentMembershipConfigModel.ReportCommission.Float64 + agentRewards := model.AgentRewards{ + AgentId: ancestorId, + Amount: reportCommissionAmount, + RelationAgentId: lzUtils.Int64ToNullInt64(descendantId), + Type: model.AgentRewardsTypeDescendantPromotion, + } + + _, agentRewardsModelInsetErr := l.AgentRewardsModel.Insert(ctx, session, &agentRewards) + if agentRewardsModelInsetErr != nil { + return 0, agentRewardsModelInsetErr + } + return reportCommissionAmount, nil + } + + return 0, nil +} + +// PlatformCost 平台底价成本 +func (l *AgentService) PlatformCost(ctx context.Context, agentID int64, orderID int64, agentProductConfigModel *model.AgentProductConfig, session sqlx.Session) (float64, error) { + + costAgentPlatformDeductionModel := model.AgentPlatformDeduction{ + AgentId: agentID, + OrderId: orderID, + Amount: agentProductConfigModel.CostPrice, + Type: model.AgentDeductionTypeCost, + } + + _, err := l.AgentPlatformDeductionModel.Insert(ctx, session, &costAgentPlatformDeductionModel) + if err != nil { + return 0, err + } + return agentProductConfigModel.CostPrice, nil +} + +// PlatformPricing 平台提价成本 +func (l *AgentService) PlatformPricing(ctx context.Context, agentID int64, orderID int64, pricing float64, agentProductConfigModel *model.AgentProductConfig, session sqlx.Session) (float64, error) { + // 2. 计算平台提价成本 + if pricing > agentProductConfigModel.PricingStandard { + // 超出部分 + overpricing := pricing - agentProductConfigModel.PricingStandard + + // 收取成本 + overpricingCost := overpricing * agentProductConfigModel.OverpricingRatio + + pricingAgentPlatformDeductionModel := model.AgentPlatformDeduction{ + AgentId: agentID, + OrderId: orderID, + Amount: overpricingCost, + Type: model.AgentDeductionTypePricing, + } + + _, err := l.AgentPlatformDeductionModel.Insert(ctx, session, &pricingAgentPlatformDeductionModel) + if err != nil { + return 0, err + } + return overpricingCost, nil + } + return 0, nil +} + +// CommissionCost 上级底价成本 +func (l *AgentService) CommissionCost(ctx context.Context, descendantId int64, AncestorId int64, agentMembershipConfigModel *model.AgentMembershipConfig, productID int64, orderId int64, session sqlx.Session) (float64, error) { + if agentMembershipConfigModel.PriceIncreaseAmount.Valid { + // 拥有则查看该上级设定的成本 + agentMembershipUserConfigModel, findAgentMembershipUserConfigModelErr := l.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(ctx, AncestorId, productID) + if findAgentMembershipUserConfigModelErr != nil { + // 如果上级没有配置该产品的定价规则,则跳过成本计算 + if errors.Is(findAgentMembershipUserConfigModelErr, model.ErrNotFound) { + return 0, nil + } + return 0, findAgentMembershipUserConfigModelErr + } + + deductCostAmount := agentMembershipUserConfigModel.PriceIncreaseAmount + + agentCommissionDeductionModel := model.AgentCommissionDeduction{ + AgentId: AncestorId, + DeductedAgentId: descendantId, + Amount: deductCostAmount, + Type: model.AgentDeductionTypeCost, + ProductId: productID, + OrderId: sql.NullInt64{Int64: orderId, Valid: true}, + } + + _, insertAgentCommissionDeductionModelErr := l.AgentCommissionDeductionModel.Insert(ctx, session, &agentCommissionDeductionModel) + if insertAgentCommissionDeductionModelErr != nil { + return 0, insertAgentCommissionDeductionModelErr + } + + return deductCostAmount, nil + } + return 0, nil +} + +// CommissionPricing 上级提价成本 +func (l *AgentService) CommissionPricing(ctx context.Context, descendantId int64, AncestorId int64, agentMembershipConfigModel *model.AgentMembershipConfig, productID int64, pricing float64, orderId int64, session sqlx.Session) (float64, error) { + //看上级代理等级否有拥有定价标准收益功能 + if agentMembershipConfigModel.PriceIncreaseMax.Valid && agentMembershipConfigModel.PriceRatio.Valid { + // 拥有则查看该上级设定的成本 + agentMembershipUserConfigModel, findAgentMembershipUserConfigModelErr := l.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(ctx, AncestorId, productID) + if findAgentMembershipUserConfigModelErr != nil { + // 如果上级没有配置该产品的定价规则,则跳过成本计算 + if errors.Is(findAgentMembershipUserConfigModelErr, model.ErrNotFound) { + return 0, nil + } + return 0, findAgentMembershipUserConfigModelErr + } + + // 计算是否在范围内 + var pricingRange float64 + if pricing > agentMembershipUserConfigModel.PriceRangeFrom { + if pricing > agentMembershipUserConfigModel.PriceRangeTo { + pricingRange = agentMembershipUserConfigModel.PriceRangeTo - agentMembershipUserConfigModel.PriceRangeFrom + } else { + pricingRange = pricing - agentMembershipUserConfigModel.PriceRangeFrom + } + } + + deductCostAmount := pricingRange * agentMembershipUserConfigModel.PriceRatio + + agentCommissionDeductionModel := model.AgentCommissionDeduction{ + AgentId: AncestorId, + DeductedAgentId: descendantId, + Amount: deductCostAmount, + Type: model.AgentDeductionTypePricing, + ProductId: productID, + OrderId: sql.NullInt64{Int64: orderId, Valid: true}, + } + _, insertAgentCommissionDeductionModelErr := l.AgentCommissionDeductionModel.Insert(ctx, session, &agentCommissionDeductionModel) + if insertAgentCommissionDeductionModelErr != nil { + return 0, insertAgentCommissionDeductionModelErr + } + return deductCostAmount, nil + } + return 0, nil +} + +// GiveUpgradeReward 给上级代理发放下级升级奖励 +func (l *AgentService) GiveUpgradeReward(ctx context.Context, agentID int64, oldLevel, newLevel string, session sqlx.Session) error { + // 查找上级代理 + agentClosureModel, err := l.AgentClosureModel.FindOneByDescendantIdDepth(ctx, agentID, 1) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 没有上级代理,直接返回 + return nil + } + return err + } + + ancestorID := agentClosureModel.AncestorId + ancestorModel, err := l.AgentModel.FindOne(ctx, ancestorID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 上级代理不存在,直接返回 + return nil + } + return err + } + if ancestorModel == nil { + // 上级代理不存在,直接返回 + return nil + } + + // 获取上级代理的等级配置 + if ancestorModel.LevelName == "" { + ancestorModel.LevelName = model.AgentLeveNameNormal + } + agentMembershipConfigModel, err := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, ancestorModel.LevelName) + if err != nil { + return err + } + + // 根据升级路径计算奖励金额差额 + var rewardAmount float64 + var rewardType string + + // 获取各等级的奖励金额 + var vipRewardAmount float64 + var svipRewardAmount float64 + + if agentMembershipConfigModel.LowerConvertVipReward.Valid { + vipRewardAmount = agentMembershipConfigModel.LowerConvertVipReward.Float64 + } + if agentMembershipConfigModel.LowerConvertSvipReward.Valid { + svipRewardAmount = agentMembershipConfigModel.LowerConvertSvipReward.Float64 + } + + // 根据升级路径计算实际奖励金额 + switch { + case oldLevel == "" || oldLevel == model.AgentLeveNameNormal: + // 普通代理升级 + switch newLevel { + case model.AgentLeveNameVIP: + rewardAmount = vipRewardAmount + rewardType = model.AgentRewardsTypeDescendantUpgradeVip + case model.AgentLeveNameSVIP: + rewardAmount = svipRewardAmount + rewardType = model.AgentRewardsTypeDescendantUpgradeSvip + default: + // 无效的升级路径,直接返回 + return nil + } + case oldLevel == model.AgentLeveNameVIP && newLevel == model.AgentLeveNameSVIP: + // VIP升级到SVIP,发放差额奖励 + rewardAmount = svipRewardAmount - vipRewardAmount + rewardType = model.AgentRewardsTypeDescendantUpgradeSvip + // 如果差额为负数或零,不发放奖励 + if rewardAmount <= 0 { + return nil + } + default: + // 其他无效的升级路径(如SVIP降级等),直接返回 + return nil + } + + // 如果有奖励金额,则发放奖励 + if rewardAmount > 0 { + // 创建奖励记录 + agentRewards := model.AgentRewards{ + AgentId: ancestorID, + Amount: rewardAmount, + RelationAgentId: lzUtils.Int64ToNullInt64(agentID), + Type: rewardType, + } + + insertResult, err := l.AgentRewardsModel.Insert(ctx, session, &agentRewards) + if err != nil { + return err + } + + // 更新上级代理钱包 + ancestorWallet, err := l.AgentWalletModel.FindOneByAgentId(ctx, ancestorID) + if err != nil { + return err + } + + // 记录变动前的余额 + balanceBefore := ancestorWallet.Balance + frozenBalanceBefore := ancestorWallet.FrozenBalance + + ancestorWallet.Balance += rewardAmount + ancestorWallet.TotalEarnings += rewardAmount + err = l.AgentWalletModel.UpdateWithVersion(ctx, session, ancestorWallet) + if err != nil { + return err + } + + // 获取新插入的奖励记录ID + rewardID, err := insertResult.LastInsertId() + if err != nil { + return err + } + rewardIDStr := fmt.Sprintf("%d", rewardID) // 转换为字符串 + + // 记录交易流水(奖励收入) + transErr := l.CreateWalletTransaction( + ctx, + session, + ancestorID, + model.WalletTransactionTypeReward, + rewardAmount, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + ancestorWallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + ancestorWallet.FrozenBalance, // 变动后冻结余额 + rewardIDStr, // 关联交易ID(奖励记录ID) + agentID, // 关联用户ID(下级代理ID) + "下级升级奖励", // 备注 + ) + if transErr != nil { + return transErr + } + } + + return nil +} + +// GiveWithdrawReward 给上级代理发放下级提现奖励 +func (l *AgentService) GiveWithdrawReward(ctx context.Context, agentID int64, withdrawAmount float64, session sqlx.Session) error { + // 验证提现金额 + if withdrawAmount <= 0 { + return nil + } + // 查找上级代理 + agentClosureModel, err := l.AgentClosureModel.FindOneByDescendantIdDepth(ctx, agentID, 1) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 没有上级代理,直接返回 + return nil + } + return err + } + + ancestorID := agentClosureModel.AncestorId + ancestorModel, err := l.AgentModel.FindOne(ctx, ancestorID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 上级代理不存在,直接返回 + return nil + } + return err + } + if ancestorModel == nil { + // 上级代理不存在,直接返回 + return nil + } + + // 获取上级代理的等级配置 + if ancestorModel.LevelName == "" { + ancestorModel.LevelName = model.AgentLeveNameNormal + } + agentMembershipConfigModel, err := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, ancestorModel.LevelName) + if err != nil { + return err + } + + // 计算提现奖励金额 + if agentMembershipConfigModel.LowerWithdrawRewardRatio.Valid { + rewardRatio := agentMembershipConfigModel.LowerWithdrawRewardRatio.Float64 + // 验证奖励比例的有效性(0-1之间) + if rewardRatio < 0 || rewardRatio > 1 { + // 无效的奖励比例,直接返回 + return nil + } + rewardAmount := withdrawAmount * rewardRatio + + if rewardAmount > 0 { + // 创建奖励记录 + agentRewards := model.AgentRewards{ + AgentId: ancestorID, + Amount: rewardAmount, + RelationAgentId: lzUtils.Int64ToNullInt64(agentID), + Type: model.AgentRewardsTypeDescendantWithdraw, + } + + insertResult, err := l.AgentRewardsModel.Insert(ctx, session, &agentRewards) + if err != nil { + return err + } + + // 更新上级代理钱包 + ancestorWallet, err := l.AgentWalletModel.FindOneByAgentId(ctx, ancestorID) + if err != nil { + return err + } + + // 记录变动前的余额 + balanceBefore := ancestorWallet.Balance + frozenBalanceBefore := ancestorWallet.FrozenBalance + + ancestorWallet.Balance += rewardAmount + ancestorWallet.TotalEarnings += rewardAmount + err = l.AgentWalletModel.UpdateWithVersion(ctx, session, ancestorWallet) + if err != nil { + return err + } + + // 获取新插入的奖励记录ID + rewardID, err := insertResult.LastInsertId() + if err != nil { + return err + } + rewardIDStr := fmt.Sprintf("%d", rewardID) // 转换为字符串 + + // 记录交易流水(奖励收入) + transErr := l.CreateWalletTransaction( + ctx, + session, + ancestorID, + model.WalletTransactionTypeReward, + rewardAmount, // 变动金额(正数表示增加) + balanceBefore, // 变动前余额 + ancestorWallet.Balance, // 变动后余额 + frozenBalanceBefore, // 变动前冻结余额 + ancestorWallet.FrozenBalance, // 变动后冻结余额 + rewardIDStr, // 关联交易ID(奖励记录ID) + agentID, // 关联用户ID(下级代理ID) + "下级提现奖励", // 备注 + ) + if transErr != nil { + return transErr + } + } + } + + return nil +} + +// CheckAgentProcessStatus 检查代理处理事务是否已成功 +func (l *AgentService) CheckAgentProcessStatus(ctx context.Context, orderID int64) (bool, error) { + // 检查是否存在代理订单记录 + _, err := l.AgentOrderModel.FindOneByOrderId(ctx, orderID) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + // 没有代理订单记录,说明不是代理推广订单 + return true, nil + } + return false, err + } + + // 检查是否存在代理佣金记录 + // 使用SelectBuilder查询该订单的佣金记录 + selectBuilder := l.AgentCommissionModel.SelectBuilder() + selectBuilder = selectBuilder.Where("order_id = ?", orderID) + selectBuilder = selectBuilder.Where("del_state = ?", 0) // 未删除 + + commissions, err := l.AgentCommissionModel.FindAll(ctx, selectBuilder, "") + if err != nil { + return false, err + } + + // 如果存在佣金记录,说明代理处理已成功 + return len(commissions) > 0, nil +} + +// RetryAgentProcess 重新执行代理处理事务 +func (l *AgentService) RetryAgentProcess(ctx context.Context, orderID int64) error { + // 首先检查订单是否存在 + order, err := l.OrderModel.FindOne(ctx, orderID) + if err != nil { + return err + } + + // 检查订单状态是否为已支付 + if order.Status != "paid" { + return errors.New("订单状态不是已支付,无法执行代理处理") + } + + // 检查代理处理是否已经成功 + alreadyProcessed, err := l.CheckAgentProcessStatus(ctx, orderID) + if err != nil { + return err + } + + if alreadyProcessed { + return errors.New("代理处理已经成功,无需重新执行") + } + + // 执行代理处理 + return l.AgentProcess(ctx, order) +} + +// CreateWalletTransaction 创建代理钱包流水记录 +// ctx: 上下文 +// session: 数据库会话(事务) +// agentID: 代理ID +// transactionType: 交易类型 (commission/withdraw/freeze/unfreeze/reward/refund/adjust) +// amount: 变动金额(正数为增加,负数为减少) +// balanceBefore: 变动前余额 +// balanceAfter: 变动后余额 +// frozenBalanceBefore: 变动前冻结余额 +// frozenBalanceAfter: 变动后冻结余额 +// transactionID: 关联交易ID(订单号、提现申请号等) +// relatedUserID: 关联用户ID(如佣金来源用户) +// remark: 备注说明 +func (l *AgentService) CreateWalletTransaction(ctx context.Context, session sqlx.Session, + agentID int64, transactionType string, amount float64, + balanceBefore, balanceAfter, frozenBalanceBefore, frozenBalanceAfter float64, + transactionID string, relatedUserID int64, remark string) error { + + // 处理可空字段 + var transactionIDField sql.NullString + if transactionID != "" { + transactionIDField = sql.NullString{String: transactionID, Valid: true} + } + + var relatedUserIDField sql.NullInt64 + if relatedUserID > 0 { + relatedUserIDField = sql.NullInt64{Int64: relatedUserID, Valid: true} + } + + var remarkField sql.NullString + if remark != "" { + remarkField = sql.NullString{String: remark, Valid: true} + } + + transaction := &model.AgentWalletTransaction{ + AgentId: agentID, + TransactionType: transactionType, + Amount: amount, + BalanceBefore: balanceBefore, + BalanceAfter: balanceAfter, + FrozenBalanceBefore: frozenBalanceBefore, + FrozenBalanceAfter: frozenBalanceAfter, + TransactionId: transactionIDField, + RelatedUserId: relatedUserIDField, + Remark: remarkField, + } + + _, err := l.AgentWalletTransactionModel.Insert(ctx, session, transaction) + if err != nil { + return errors.Wrapf(err, "创建代理钱包流水记录失败,agentID: %d, type: %s, amount: %.2f", agentID, transactionType, amount) + } + return nil +} diff --git a/app/main/api/internal/service/alipayService.go b/app/main/api/internal/service/alipayService.go new file mode 100644 index 0000000..e396e1f --- /dev/null +++ b/app/main/api/internal/service/alipayService.go @@ -0,0 +1,258 @@ +package service + +import ( + "context" + "crypto/rand" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + "bdrp-server/pkg/lzkit/lzUtils" + "encoding/hex" + "fmt" + "net/http" + "strconv" + "sync/atomic" + "time" + + "github.com/smartwalle/alipay/v3" +) + +type AliPayService struct { + config config.AlipayConfig + AlipayClient *alipay.Client +} + +// NewAliPayService 是一个构造函数,用于初始化 AliPayService +func NewAliPayService(c config.Config) *AliPayService { + client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction) + if err != nil { + panic(fmt.Sprintf("创建支付宝客户端失败: %v", err)) + } + // 加载支付宝公钥 + err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey) + if err != nil { + panic(fmt.Sprintf("加载支付宝公钥失败: %v", err)) + } + + // 加载证书 + if err = client.LoadAppCertPublicKeyFromFile(c.Alipay.AppCertPath); err != nil { + panic(fmt.Sprintf("加载应用公钥证书失败: %v", err)) + } + if err = client.LoadAlipayCertPublicKeyFromFile(c.Alipay.AlipayCertPath); err != nil { + panic(fmt.Sprintf("加载支付宝公钥证书失败: %v", err)) + } + if err = client.LoadAliPayRootCertFromFile(c.Alipay.AlipayRootCertPath); err != nil { + panic(fmt.Sprintf("加载根证书失败: %v", err)) + } + + return &AliPayService{ + config: c.Alipay, + AlipayClient: client, + } +} + +func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) { + client := a.AlipayClient + totalAmount := lzUtils.ToAlipayAmount(amount) + // 构造移动支付请求 + p := alipay.TradeAppPay{ + Trade: alipay.Trade{ + Subject: subject, + OutTradeNo: outTradeNo, + TotalAmount: totalAmount, + ProductCode: "QUICK_MSECURITY_PAY", // 移动端支付专用代码 + NotifyURL: a.config.NotifyUrl, // 异步回调通知地址 + }, + } + + // 获取APP支付字符串,这里会签名 + payStr, err := client.TradeAppPay(p) + if err != nil { + return "", fmt.Errorf("创建支付宝订单失败: %v", err) + } + + return payStr, nil +} + +// CreateAlipayH5Order 创建支付宝H5支付订单 +func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string) (string, error) { + client := a.AlipayClient + totalAmount := lzUtils.ToAlipayAmount(amount) + // 构造H5支付请求 + p := alipay.TradeWapPay{ + Trade: alipay.Trade{ + Subject: subject, + OutTradeNo: outTradeNo, + TotalAmount: totalAmount, + ProductCode: "QUICK_WAP_PAY", // H5支付专用产品码 + NotifyURL: a.config.NotifyUrl, // 异步回调通知地址 + ReturnURL: a.config.ReturnURL, + }, + } + // 获取H5支付请求字符串,这里会签名 + payUrl, err := client.TradeWapPay(p) + if err != nil { + return "", fmt.Errorf("创建支付宝H5订单失败: %v", err) + } + + return payUrl.String(), nil +} + +// CreateAlipayOrder 根据平台类型创建支付宝支付订单 +func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string) (string, error) { + // 根据 ctx 中的 platform 判断平台 + platform, platformOk := ctx.Value("platform").(string) + if !platformOk { + return "", fmt.Errorf("无的支付平台: %s", platform) + } + switch platform { + case model.PlatformApp: + // 调用App支付的创建方法 + return a.CreateAlipayAppOrder(amount, subject, outTradeNo) + case model.PlatformH5: + // 调用H5支付的创建方法,并传入 returnUrl + return a.CreateAlipayH5Order(amount, subject, outTradeNo) + default: + return "", fmt.Errorf("不支持的支付平台: %s", platform) + } +} + +// AliRefund 发起支付宝退款 +func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) { + refund := alipay.TradeRefund{ + OutTradeNo: outTradeNo, + RefundAmount: lzUtils.ToAlipayAmount(refundAmount), + OutRequestNo: fmt.Sprintf("refund-%s", outTradeNo), + } + + // 发起退款请求 + refundResp, err := a.AlipayClient.TradeRefund(ctx, refund) + if err != nil { + return nil, fmt.Errorf("支付宝退款请求错误:%v", err) + } + return refundResp, nil +} + +// HandleAliPaymentNotification 支付宝支付回调 +func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) { + // 解析表单 + err := r.ParseForm() + if err != nil { + return nil, fmt.Errorf("解析请求表单失败:%v", err) + } + // 解析并验证通知,DecodeNotification 会自动验证签名 + notification, err := a.AlipayClient.DecodeNotification(r.Form) + if err != nil { + return nil, fmt.Errorf("验证签名失败: %v", err) + } + return notification, nil +} +func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) { + queryRequest := alipay.TradeQuery{ + OutTradeNo: outTradeNo, + } + + // 发起查询请求 + resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest) + if err != nil { + return nil, fmt.Errorf("查询支付宝订单失败: %v", err) + } + + // 返回交易状态 + if resp.IsSuccess() { + return resp, nil + } + + return nil, fmt.Errorf("查询支付宝订单失败: %v", resp.SubMsg) +} + +// 添加全局原子计数器 +var alipayOrderCounter uint32 = 0 + +// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本 +func (a *AliPayService) GenerateOutTradeNo() string { + + // 获取当前时间戳(毫秒级) + timestamp := time.Now().UnixMilli() + timeStr := strconv.FormatInt(timestamp, 10) + + // 原子递增计数器 + counter := atomic.AddUint32(&alipayOrderCounter, 1) + + // 生成4字节真随机数 + randomBytes := make([]byte, 4) + _, err := rand.Read(randomBytes) + if err != nil { + // 如果随机数生成失败,回退到使用时间纳秒数据 + randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16)) + } + randomHex := hex.EncodeToString(randomBytes) + + // 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数 + orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6]) + + // 确保长度不超过32字符(大多数支付平台的限制) + if len(orderNo) > 32 { + orderNo = orderNo[:32] + } + + return orderNo +} + +// AliTransfer 支付宝单笔转账到支付宝账户(提现功能) +func (a *AliPayService) AliTransfer( + ctx context.Context, + payeeAccount string, // 收款方支付宝账户 + payeeName string, // 收款方姓名 + amount float64, // 转账金额 + remark string, // 转账备注 + outBizNo string, // 商户转账唯一订单号(可使用GenerateOutTradeNo生成) +) (*alipay.FundTransUniTransferRsp, error) { + // 参数校验 + if payeeAccount == "" { + return nil, fmt.Errorf("收款账户不能为空") + } + if amount <= 0 { + return nil, fmt.Errorf("转账金额必须大于0") + } + + // 构造转账请求 + req := alipay.FundTransUniTransfer{ + OutBizNo: outBizNo, + TransAmount: lzUtils.ToAlipayAmount(amount), // 金额格式转换 + ProductCode: "TRANS_ACCOUNT_NO_PWD", // 单笔无密转账到支付宝账户 + BizScene: "DIRECT_TRANSFER", // 单笔转账 + OrderTitle: "账户提现", // 转账标题 + Remark: remark, + PayeeInfo: &alipay.PayeeInfo{ + Identity: payeeAccount, + IdentityType: "ALIPAY_LOGON_ID", // 根据账户类型选择: + Name: payeeName, + // ALIPAY_USER_ID/ALIPAY_LOGON_ID + }, + } + + // 执行转账请求 + transferRsp, err := a.AlipayClient.FundTransUniTransfer(ctx, req) + if err != nil { + return nil, fmt.Errorf("支付宝转账请求失败: %v", err) + } + + return transferRsp, nil +} +func (a *AliPayService) QueryTransferStatus( + ctx context.Context, + outBizNo string, +) (*alipay.FundTransOrderQueryRsp, error) { + req := alipay.FundTransOrderQuery{ + OutBizNo: outBizNo, + } + response, err := a.AlipayClient.FundTransOrderQuery(ctx, req) + if err != nil { + return nil, fmt.Errorf("支付宝接口调用失败: %v", err) + } + // 处理响应 + if response.Code.IsFailure() { + return nil, fmt.Errorf("支付宝返回错误: %s-%s", response.Code, response.Msg) + } + return response, nil +} diff --git a/app/main/api/internal/service/apiRegistryService.go b/app/main/api/internal/service/apiRegistryService.go new file mode 100644 index 0000000..ae4884d --- /dev/null +++ b/app/main/api/internal/service/apiRegistryService.go @@ -0,0 +1,223 @@ +package service + +import ( + "context" + "fmt" + "regexp" + "strings" + + "bdrp-server/app/main/model" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest" +) + +type ApiRegistryService struct { + adminApiModel model.AdminApiModel +} + +func NewApiRegistryService(adminApiModel model.AdminApiModel) *ApiRegistryService { + return &ApiRegistryService{ + adminApiModel: adminApiModel, + } +} + +// RegisterAllApis 自动注册所有API到数据库 +func (s *ApiRegistryService) RegisterAllApis(ctx context.Context, routes []rest.Route) error { + logx.Infof("开始注册API,共 %d 个路由", len(routes)) + + registeredCount := 0 + skippedCount := 0 + + for _, route := range routes { + // 跳过不需要权限控制的API + if s.shouldSkipApi(route.Path) { + skippedCount++ + continue + } + + // 解析API信息 + apiInfo := s.parseRouteToApi(route) + + // 检查是否已存在 + existing, err := s.adminApiModel.FindOneByApiCode(ctx, apiInfo.ApiCode) + if err != nil && !errors.Is(err, model.ErrNotFound) { + logx.Errorf("查询API失败: %v, apiCode: %s", err, apiInfo.ApiCode) + continue + } + + // 如果不存在则插入 + if existing == nil { + _, err = s.adminApiModel.Insert(ctx, nil, apiInfo) + if err != nil { + logx.Errorf("插入API失败: %v, apiCode: %s", err, apiInfo.ApiCode) + continue + } + registeredCount++ + logx.Infof("注册API成功: %s %s", apiInfo.Method, apiInfo.Url) + } else { + // 如果存在但信息有变化,则更新 + if s.shouldUpdateApi(existing, apiInfo) { + existing.ApiName = apiInfo.ApiName + existing.Method = apiInfo.Method + existing.Url = apiInfo.Url + existing.Description = apiInfo.Description + + _, err = s.adminApiModel.Update(ctx, nil, existing) + if err != nil { + logx.Errorf("更新API失败: %v, apiCode: %s", err, apiInfo.ApiCode) + continue + } + logx.Infof("更新API成功: %s %s", apiInfo.Method, apiInfo.Url) + } + } + } + + logx.Infof("API注册完成,新增: %d, 跳过: %d", registeredCount, skippedCount) + return nil +} + +// shouldSkipApi 判断是否应该跳过此API +func (s *ApiRegistryService) shouldSkipApi(path string) bool { + // 跳过公开API + skipPaths := []string{ + "/api/v1/admin/auth/login", // 登录接口 + "/api/v1/app/", // 前端应用接口 + "/api/v1/agent/", // 代理接口 + "/api/v1/user/", // 用户接口 + "/api/v1/auth/", // 认证接口 + "/api/v1/notification/", // 通知接口 + "/api/v1/pay/", // 支付接口 + "/api/v1/query/", // 查询接口 + "/api/v1/product/", // 产品接口 + "/api/v1/authorization/", // 授权接口 + "/health", // 健康检查 + } + + for _, skipPath := range skipPaths { + if strings.HasPrefix(path, skipPath) { + return true + } + } + + return false +} + +// parseRouteToApi 将路由解析为API信息 +func (s *ApiRegistryService) parseRouteToApi(route rest.Route) *model.AdminApi { + // 生成API编码 + apiCode := s.generateApiCode(route.Method, route.Path) + + // 生成API名称 + apiName := s.generateApiName(route.Path) + + // 生成描述 + description := s.generateDescription(route.Method, route.Path) + + return &model.AdminApi{ + ApiName: apiName, + ApiCode: apiCode, + Method: route.Method, + Url: route.Path, + Status: 1, // 默认启用 + Description: description, + } +} + +// generateApiCode 生成API编码 +func (s *ApiRegistryService) generateApiCode(method, path string) string { + // 移除路径参数,如 :id + cleanPath := regexp.MustCompile(`/:[\w]+`).ReplaceAllString(path, "") + + // 转换为小写并替换特殊字符 + apiCode := strings.ToLower(method) + "_" + strings.ReplaceAll(cleanPath, "/", "_") + apiCode = strings.TrimPrefix(apiCode, "_") + apiCode = strings.TrimSuffix(apiCode, "_") + + return apiCode +} + +// generateApiName 生成API名称 +func (s *ApiRegistryService) generateApiName(path string) string { + // 从路径中提取模块和操作 + parts := strings.Split(strings.Trim(path, "/"), "/") + if len(parts) < 3 { + return path + } + + // 获取模块名和操作名 + module := parts[len(parts)-2] + action := parts[len(parts)-1] + + // 转换为中文描述 + moduleMap := map[string]string{ + "agent": "代理管理", + "auth": "认证管理", + "feature": "功能管理", + "menu": "菜单管理", + "notification": "通知管理", + "order": "订单管理", + "platform_user": "平台用户", + "product": "产品管理", + "promotion": "推广管理", + "query": "查询管理", + "role": "角色管理", + "user": "用户管理", + } + + actionMap := map[string]string{ + "list": "列表", + "create": "创建", + "update": "更新", + "delete": "删除", + "detail": "详情", + "login": "登录", + "config": "配置", + "example": "示例", + "refund": "退款", + "link": "链接", + "stats": "统计", + "cleanup": "清理", + "record": "记录", + } + + moduleName := moduleMap[module] + if moduleName == "" { + moduleName = module + } + + actionName := actionMap[action] + if actionName == "" { + actionName = action + } + + return fmt.Sprintf("%s-%s", moduleName, actionName) +} + +// generateDescription 生成API描述 +func (s *ApiRegistryService) generateDescription(method, path string) string { + methodMap := map[string]string{ + "GET": "查询", + "POST": "创建", + "PUT": "更新", + "DELETE": "删除", + } + + methodDesc := methodMap[method] + if methodDesc == "" { + methodDesc = method + } + + apiName := s.generateApiName(path) + + return fmt.Sprintf("%s%s", methodDesc, apiName) +} + +// shouldUpdateApi 判断是否需要更新API +func (s *ApiRegistryService) shouldUpdateApi(existing, new *model.AdminApi) bool { + return existing.ApiName != new.ApiName || + existing.Method != new.Method || + existing.Url != new.Url || + existing.Description != new.Description +} diff --git a/app/main/api/internal/service/apirequestService.go b/app/main/api/internal/service/apirequestService.go new file mode 100644 index 0000000..64e3215 --- /dev/null +++ b/app/main/api/internal/service/apirequestService.go @@ -0,0 +1,1458 @@ +package service + +import ( + "bdrp-server/app/main/api/internal/config" + tianyuanapi "bdrp-server/app/main/api/internal/service/tianyuanapi_sdk" + "bdrp-server/app/main/model" + "context" + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/Masterminds/squirrel" + "github.com/tidwall/gjson" + "github.com/zeromicro/go-zero/core/logx" +) + +// 辅助函数:将天远API响应转换为JSON字节数组 +func convertTianyuanResponse(resp *tianyuanapi.Response) ([]byte, error) { + return json.Marshal(resp.Data) +} + +// 生成认证时间范围:当前时间前后两天的YYYYMMDD-YYMMDD格式 +func generateAuthDateRange() string { + now := time.Now() + start := now.AddDate(0, 0, -2).Format("20060102") + end := now.AddDate(0, 0, 2).Format("20060102") + return fmt.Sprintf("%s-%s", start, end) +} + +type ApiRequestService struct { + config config.Config + featureModel model.FeatureModel + productFeatureModel model.ProductFeatureModel + tianyuanapi *tianyuanapi.Client +} + +// NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService +func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client) *ApiRequestService { + return &ApiRequestService{ + config: c, + featureModel: featureModel, + productFeatureModel: productFeatureModel, + tianyuanapi: tianyuanapi, + } +} + +type APIResponseData struct { + ApiID string `json:"apiID"` + Data json.RawMessage `json:"data"` // 这里用 RawMessage 来存储原始的 data + Success bool `json:"success"` + Timestamp string `json:"timestamp"` + Error string `json:"error,omitempty"` +} + +// ProcessRequests 处理请求 +func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]APIResponseData, error) { + var ctx, cancel = context.WithCancel(context.Background()) + defer cancel() + build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{ + "product_id": productID, + }) + productFeatureList, findProductFeatureErr := a.productFeatureModel.FindAll(ctx, build, "") + if findProductFeatureErr != nil { + return nil, findProductFeatureErr + } + var featureIDs []int64 + isImportantMap := make(map[int64]int64, len(productFeatureList)) + for _, pf := range productFeatureList { + featureIDs = append(featureIDs, pf.FeatureId) + isImportantMap[pf.FeatureId] = pf.IsImportant + } + if len(featureIDs) == 0 { + return nil, errors.New("featureIDs 是空的") + } + builder := a.featureModel.SelectBuilder().Where(squirrel.Eq{"id": featureIDs}) + featureList, findFeatureErr := a.featureModel.FindAll(ctx, builder, "") + if findFeatureErr != nil { + return nil, findFeatureErr + } + if len(featureList) == 0 { + return nil, errors.New("处理请求错误,产品无对应接口功能") + } + var ( + wg sync.WaitGroup + resultsCh = make(chan APIResponseData, len(featureList)) + errorsCh = make(chan error, len(featureList)) + errorCount int32 + errorLimit = len(featureList) + retryNum = 5 + ) + + for i, feature := range featureList { + wg.Add(1) + go func(i int, feature *model.Feature) { + defer wg.Done() + + select { + case <-ctx.Done(): + return + default: + } + result := APIResponseData{ + ApiID: feature.ApiId, + Success: false, + } + timestamp := time.Now().Format("2006-01-02 15:04:05") + var ( + resp json.RawMessage + preprocessErr error + ) + // 若 isImportantMap[feature.ID] == 1,则表示需要在出错时重试 + isImportant := isImportantMap[feature.Id] == 1 + tryCount := 0 + for { + tryCount++ + resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId) + if preprocessErr == nil { + break + } + if isImportant && tryCount < retryNum { + continue + } else { + break + } + } + if preprocessErr != nil { + result.Timestamp = timestamp + result.Error = preprocessErr.Error() + result.Data = resp + resultsCh <- result + errorsCh <- fmt.Errorf("请求失败: %v", preprocessErr) + atomic.AddInt32(&errorCount, 1) + if atomic.LoadInt32(&errorCount) >= int32(errorLimit) { + cancel() + } + return + } + + result.Data = resp + result.Success = true + result.Timestamp = timestamp + resultsCh <- result + }(i, feature) + } + + go func() { + wg.Wait() + close(resultsCh) + close(errorsCh) + }() + // 收集所有结果并合并z + var responseData []APIResponseData + for result := range resultsCh { + responseData = append(responseData, result) + } + if atomic.LoadInt32(&errorCount) >= int32(errorLimit) { + var allErrors []error + for err := range errorsCh { + allErrors = append(allErrors, err) + } + return nil, fmt.Errorf("请求失败次数超过 %d 次: %v", errorLimit, allErrors) + } + + return responseData, nil +} + +// ------------------------------------请求处理器-------------------------- +var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, error){ + "PersonEnterprisePro": (*ApiRequestService).ProcessPersonEnterpriseProRequest, + "BehaviorRiskScan": (*ApiRequestService).ProcessBehaviorRiskScanRequest, + "YYSYBE08": (*ApiRequestService).ProcessYYSYBE08Request, + "YYSY09CD": (*ApiRequestService).ProcessYYSY09CDRequest, + "FLXG0687": (*ApiRequestService).ProcessFLXG0687Request, + "FLXG3D56": (*ApiRequestService).ProcessFLXG3D56Request, + "FLXG0V4B": (*ApiRequestService).ProcesFLXG0V4BRequest, + "QYGL8271": (*ApiRequestService).ProcessQYGL8271Request, + "IVYZ5733": (*ApiRequestService).ProcessIVYZ5733Request, + "IVYZ9A2B": (*ApiRequestService).ProcessIVYZ9A2BRequest, + "JRZQ0A03": (*ApiRequestService).ProcessJRZQ0A03Request, + "QYGL6F2D": (*ApiRequestService).ProcessQYGL6F2DRequest, + "JRZQ8203": (*ApiRequestService).ProcessJRZQ8203Request, + "JRZQ4AA8": (*ApiRequestService).ProcessJRZQ4AA8Request, + "QCXG7A2B": (*ApiRequestService).ProcessQCXG7A2BRequest, + "DWBG8B4D": (*ApiRequestService).ProcessDWBG8B4DRequest, + "DWBG6A2C": (*ApiRequestService).ProcessDWBG6A2CRequest, + "JRZQ4B6C": (*ApiRequestService).ProcessJRZQ4B6CRequest, + "JRZQ09J8": (*ApiRequestService).ProcessJRZQ09J8Request, + "JRZQ5E9F": (*ApiRequestService).ProcessJRZQ5E9FRequest, + "QYGL3F8E": (*ApiRequestService).ProcessQYGL3F8ERequest, + "IVYZ81NC": (*ApiRequestService).ProcessIVYZ81NCRequest, + "IVYZ7F3A": (*ApiRequestService).ProcessIVYZ7F3ARequest, + "IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest, + "FLXG7E8F": (*ApiRequestService).ProcessFLXG7E8FRequest, +} + +// PreprocessRequestApi 调用指定的请求处理函数 +func (a *ApiRequestService) PreprocessRequestApi(params []byte, apiID string) ([]byte, error) { + if processor, exists := requestProcessors[apiID]; exists { + return processor(a, params) // 调用 ApiRequestService 方法 + } + + return nil, errors.New("api请求, 未找到相应的处理程序") +} + +// PersonEnterprisePro 人企业关系加强版 +func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + // 设置最大调用次数上限 + maxApiCalls := 20 // 允许最多查询20个企业 + + if !idCard.Exists() { + return nil, errors.New("api请求, PersonEnterprisePro, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGLB4C0", map[string]interface{}{ + "id_card": idCard.String(), + }) + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 处理股东人企关系的响应数据 + code := gjson.GetBytes(respBytes, "code") + if !code.Exists() { + return nil, fmt.Errorf("响应中缺少 code 字段") + } + + // 判断 code 是否等于 "0000" + if code.String() == "0000" { + // 获取 data 字段的值 + data := gjson.GetBytes(respBytes, "data") + if !data.Exists() { + return nil, fmt.Errorf("响应中缺少 data 字段") + } + + // 使用gjson获取企业列表 + datalistResult := gjson.Get(data.Raw, "datalist") + if !datalistResult.Exists() { + return nil, fmt.Errorf("datalist字段不存在") + } + + // 获取所有企业并进行排序 + companies := datalistResult.Array() + + // 创建企业对象切片,用于排序 + type CompanyWithPriority struct { + Index int + Data gjson.Result + RelationshipVal int // 关系权重值 + RelationCount int // 关系数量 + AdminPenalty int // 行政处罚数量 + Executed int // 被执行人数量 + Dishonest int // 失信被执行人数量 + } + + companiesWithPriority := make([]CompanyWithPriority, 0, len(companies)) + + // 遍历企业,计算优先级 + for i, companyJson := range companies { + // 统计行政处罚、被执行人、失信被执行人 + adminPenalty := 0 + executed := 0 + dishonest := 0 + + // 检查行政处罚字段是否存在并获取数组长度 + adminPenaltyResult := companyJson.Get("adminPenalty") + if adminPenaltyResult.Exists() && adminPenaltyResult.IsArray() { + adminPenalty = len(adminPenaltyResult.Array()) + } + + // 检查被执行人字段是否存在并获取数组长度 + executedPersonResult := companyJson.Get("executedPerson") + if executedPersonResult.Exists() && executedPersonResult.IsArray() { + executed = len(executedPersonResult.Array()) + } + + // 检查失信被执行人字段是否存在并获取数组长度 + dishonestExecutedPersonResult := companyJson.Get("dishonestExecutedPerson") + if dishonestExecutedPersonResult.Exists() && dishonestExecutedPersonResult.IsArray() { + dishonest = len(dishonestExecutedPersonResult.Array()) + } + + // 计算relationship权重 + relationshipVal := 0 + relationCount := 0 + + // 获取relationship数组 + relationshipResult := companyJson.Get("relationship") + if relationshipResult.Exists() && relationshipResult.IsArray() { + relationships := relationshipResult.Array() + // 统计各类关系的数量和权重 + for _, rel := range relationships { + relationCount++ + relStr := rel.String() + + // 根据关系类型设置权重,权重顺序: + // 股东(6) > 历史股东(5) > 法人(4) > 历史法人(3) > 高管(2) > 历史高管(1) + switch relStr { + case "sh": // 股东 + if relationshipVal < 6 { + relationshipVal = 6 + } + case "his_sh": // 历史股东 + if relationshipVal < 5 { + relationshipVal = 5 + } + case "lp": // 法人 + if relationshipVal < 4 { + relationshipVal = 4 + } + case "his_lp": // 历史法人 + if relationshipVal < 3 { + relationshipVal = 3 + } + case "tm": // 高管 + if relationshipVal < 2 { + relationshipVal = 2 + } + case "his_tm": // 历史高管 + if relationshipVal < 1 { + relationshipVal = 1 + } + } + } + } + + companiesWithPriority = append(companiesWithPriority, CompanyWithPriority{ + Index: i, + Data: companyJson, + RelationshipVal: relationshipVal, + RelationCount: relationCount, + AdminPenalty: adminPenalty, + Executed: executed, + Dishonest: dishonest, + }) + } + + // 按优先级排序 + sort.Slice(companiesWithPriority, func(i, j int) bool { + // 首先根据是否有失信被执行人排序 + if companiesWithPriority[i].Dishonest != companiesWithPriority[j].Dishonest { + return companiesWithPriority[i].Dishonest > companiesWithPriority[j].Dishonest + } + + // 然后根据是否有被执行人排序 + if companiesWithPriority[i].Executed != companiesWithPriority[j].Executed { + return companiesWithPriority[i].Executed > companiesWithPriority[j].Executed + } + + // 然后根据是否有行政处罚排序 + if companiesWithPriority[i].AdminPenalty != companiesWithPriority[j].AdminPenalty { + return companiesWithPriority[i].AdminPenalty > companiesWithPriority[j].AdminPenalty + } + + // 然后按relationship类型排序 + if companiesWithPriority[i].RelationshipVal != companiesWithPriority[j].RelationshipVal { + return companiesWithPriority[i].RelationshipVal > companiesWithPriority[j].RelationshipVal + } + + // 最后按relationship数量排序 + return companiesWithPriority[i].RelationCount > companiesWithPriority[j].RelationCount + }) + + // 限制处理的企业数量 + processCount := len(companiesWithPriority) + if processCount > maxApiCalls { + processCount = maxApiCalls + } + + // 只处理前N个优先级高的企业 + prioritizedCompanies := companiesWithPriority[:processCount] + + // 使用WaitGroup和chan处理并发 + var wg sync.WaitGroup + results := make(chan struct { + index int + data []byte + err error + }, processCount) + + // 对按优先级排序的前N个企业进行涉诉信息查询 + for _, company := range prioritizedCompanies { + wg.Add(1) + go func(origIndex int, companyInfo gjson.Result) { + defer wg.Done() + logx.Infof("开始处理企业[%d],企业名称: %s,统一社会信用代码: %s", origIndex, companyInfo.Get("basicInfo.name").String(), companyInfo.Get("basicInfo.creditCode").String()) + // 提取企业名称和统一社会信用代码 + orgName := companyInfo.Get("basicInfo.name") + creditCode := companyInfo.Get("basicInfo.creditCode") + + if !orgName.Exists() || !creditCode.Exists() { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("企业名称或统一社会信用代码不存在")} + return + } + + // 解析原始公司信息为map + var companyMap map[string]interface{} + if err := json.Unmarshal([]byte(companyInfo.Raw), &companyMap); err != nil { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("解析企业信息失败: %v", err)} + return + } + + // 调用QYGL8271接口获取企业涉诉信息 + lawsuitResp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + "ent_name": orgName.String(), + "ent_code": creditCode.String(), + "auth_date": generateAuthDateRange(), + }) + // 无论是否有错误,都继续处理 + if err != nil { + // 可能是正常没有涉诉数据,设置为空对象 + logx.Infof("企业[%s]涉诉信息查询结果: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 转换响应数据 + lawsuitRespBytes, err := convertTianyuanResponse(lawsuitResp) + if err != nil { + logx.Errorf("转换企业[%s]涉诉响应失败: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else if len(lawsuitRespBytes) == 0 || string(lawsuitRespBytes) == "{}" || string(lawsuitRespBytes) == "null" { + // 无涉诉数据 + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 解析涉诉信息 + var lawsuitInfo interface{} + if err := json.Unmarshal(lawsuitRespBytes, &lawsuitInfo); err != nil { + logx.Errorf("解析企业[%s]涉诉信息失败: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 添加涉诉信息到企业信息中 + companyMap["lawsuitInfo"] = lawsuitInfo + } + } + } + + // 序列化更新后的企业信息 + companyData, err := json.Marshal(companyMap) + if err != nil { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("序列化企业信息失败: %v", err)} + return + } + + results <- struct { + index int + data []byte + err error + }{origIndex, companyData, nil} + }(company.Index, company.Data) + } + + // 关闭结果通道 + go func() { + wg.Wait() + close(results) + }() + + // 解析原始数据为map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(data.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析data字段失败: %v", err) + } + + // 获取原始企业列表 + originalDatalist, ok := dataMap["datalist"].([]interface{}) + if !ok { + return nil, fmt.Errorf("无法获取原始企业列表") + } + + // 创建结果映射,用于保存已处理的企业 + processedCompanies := make(map[int]interface{}) + + // 收集处理过的企业数据 + for result := range results { + if result.err != nil { + logx.Errorf("处理企业失败: %v", result.err) + continue + } + + if result.data != nil { + var companyMap interface{} + if err := json.Unmarshal(result.data, &companyMap); err == nil { + processedCompanies[result.index] = companyMap + } + } + } + + // 更新企业列表 + // 处理过的用新数据,未处理的保留原样 + updatedDatalist := make([]interface{}, len(originalDatalist)) + for i, company := range originalDatalist { + if processed, exists := processedCompanies[i]; exists { + // 已处理的企业,使用新数据 + updatedDatalist[i] = processed + } else { + // 未处理的企业,保留原始数据并添加空的涉诉信息 + companyMap, ok := company.(map[string]interface{}) + if ok { + // 为未处理的企业添加空的涉诉信息 + companyMap["lawsuitInfo"] = map[string]interface{}{} + updatedDatalist[i] = companyMap + } else { + updatedDatalist[i] = company + } + } + } + + // 更新原始数据中的企业列表 + dataMap["datalist"] = updatedDatalist + + // 序列化最终结果 + result, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("序列化最终结果失败: %v", err) + } + + return result, nil + } + + // code不等于"0000",返回错误 + return nil, fmt.Errorf("响应code错误: %s", code.String()) +} + +// ProcesFLXG0V4BRequest 个人司法涉诉(详版) +func (a *ApiRequestService) ProcesFLXG0V4BRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, FLXG0V4B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0V4B", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "auth_date": generateAuthDateRange(), + }) + if err != nil { + return nil, err + } + respBytes, err := json.Marshal(resp.Data) + if err != nil { + return nil, err + } + return respBytes, nil +} + +// ProcessFLXG0687Request 反诈反赌核验 +func (a *ApiRequestService) ProcessFLXG0687Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, FLXG0687, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0687", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + Value := gjson.GetBytes(respBytes, "value") + if !Value.Exists() { + return nil, fmt.Errorf("自然人反诈反赌核验查询失败") + } + + return []byte(Value.Raw), nil +} + +// ProcessFLXG3D56Request 违约失信 +func (a *ApiRequestService) ProcessFLXG3D56Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, FLXG3D56, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG3D56", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessIVYZ5733Request 婚姻状况 +func (a *ApiRequestService) ProcessIVYZ5733Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ5733, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ5733", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + result := gjson.GetBytes(respBytes, "data.data") + if !result.Exists() { + return nil, fmt.Errorf("婚姻状态查询失败") + } + + // 获取原始结果 + rawResult := result.String() + + // 根据结果转换状态码 + var statusCode string + switch { + case strings.HasPrefix(rawResult, "INR"): + statusCode = "0" // 匹配不成功 + case strings.HasPrefix(rawResult, "IA"): + statusCode = "1" // 结婚 + case strings.HasPrefix(rawResult, "IB"): + statusCode = "2" // 离婚 + default: + return nil, fmt.Errorf("婚姻状态查询失败,未知状态码: %s", statusCode) + } + + // 构建新的返回结果 + response := map[string]string{ + "status": statusCode, + } + // 序列化为JSON + jsonResponse, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("序列化结果失败: %v", err) + } + + return jsonResponse, nil +} + +// ProcessIVYZ9A2BRequest 学历查询 +func (a *ApiRequestService) ProcessIVYZ9A2BRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ9A2B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ9A2B", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 解析响应 + codeResult := gjson.GetBytes(respBytes, "data.education_background.code") + if !codeResult.Exists() { + return nil, fmt.Errorf("教育经历核验查询失败: 返回数据缺少code字段") + } + + code := codeResult.String() + var result map[string]interface{} + + switch code { + case "9100": + // 查询成功有结果 + eduResultArray := gjson.GetBytes(respBytes, "data.education_background.data").Array() + var processedEduData []interface{} + + // 提取每个元素中Raw字段的实际内容 + for _, item := range eduResultArray { + var eduInfo interface{} + if err := json.Unmarshal([]byte(item.Raw), &eduInfo); err != nil { + return nil, fmt.Errorf("解析教育信息失败: %v", err) + } + processedEduData = append(processedEduData, eduInfo) + } + + result = map[string]interface{}{ + "data": processedEduData, + "status": 1, + } + case "9000": + // 查询成功无结果 + result = map[string]interface{}{ + "data": []interface{}{}, + "status": 0, + } + default: + // 其他情况视为错误 + errMsg := gjson.GetBytes(respBytes, "data.education_background.msg").String() + return nil, fmt.Errorf("教育经历核验查询失败: %s (code: %s)", errMsg, code) + } + + // 将结果转为JSON字节 + jsonResult, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("处理教育经历查询结果失败: %v", err) + } + + return jsonResult, nil +} + +// ProcessYYSYBE08Request 二要素 +func (a *ApiRequestService) ProcessYYSYBE08Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, YYSYBE08, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("YYSYBE08", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 使用gjson获取resultCode + resultCode := gjson.GetBytes(respBytes, "ctidRequest.ctidAuth.resultCode") + if !resultCode.Exists() { + return nil, errors.New("获取resultCode失败") + } + + // 获取resultCode的第一个字符 + resultCodeStr := resultCode.String() + if len(resultCodeStr) == 0 { + return nil, errors.New("resultCode为空") + } + + firstChar := string(resultCodeStr[0]) + if firstChar != "0" && firstChar != "5" { + return nil, errors.New("resultCode的第一个字符既不是0也不是5") + } + return []byte(firstChar), nil +} + +// ProcessJRZQ0A03Request 借贷申请 +func (a *ApiRequestService) ProcessJRZQ0A03Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ0A03, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ0A03", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取 code 字段 + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessJRZQ8203Request 借贷行为 +func (a *ApiRequestService) ProcessJRZQ8203Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ8203, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ8203", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取 code 字段 + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessJRZQ4AA8Request 还款压力 +func (a *ApiRequestService) ProcessJRZQ4AA8Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ4AA8, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ4AA8", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取响应码和偿贷压力标志 + code := gjson.GetBytes(respBytes, "code").String() + flagDebtRepayStress := gjson.GetBytes(respBytes, "flag_debtrepaystress").String() + + // 判断是否成功 + if code != "00" || flagDebtRepayStress != "1" { + return nil, fmt.Errorf("偿贷压力查询失败: %+v", respBytes) + } + // 获取偿贷压力分数 + drsNoDebtScore := gjson.GetBytes(respBytes, "drs_nodebtscore").String() + + // 构建结果 + result := map[string]interface{}{ + "score": drsNoDebtScore, + } + + // 将结果转为JSON + jsonResult, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("处理偿贷压力查询结果失败: %v", err) + } + + return jsonResult, nil +} + +// ProcessQYGL8271Request 企业涉诉 +func (a *ApiRequestService) ProcessQYGL8271Request(params []byte) ([]byte, error) { + entName := gjson.GetBytes(params, "ent_name") + entCode := gjson.GetBytes(params, "ent_code") + + if !entName.Exists() || !entCode.Exists() { + return nil, errors.New("api请求, QYGL8271, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + "ent_name": entName.String(), + "ent_code": entCode.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 第一步:提取外层的 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("外层 data 字段不存在") + } + + // 第二步:解析外层 data 的 JSON 字符串 + var outerDataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.String()), &outerDataMap); err != nil { + return nil, fmt.Errorf("解析外层 data 字段失败: %v", err) + } + + // 第三步:提取内层的 data 字段 + innerData, ok := outerDataMap["data"].(string) + if !ok { + return nil, fmt.Errorf("内层 data 字段不存在或类型错误") + } + + // 第四步:解析内层 data 的 JSON 字符串 + var finalDataMap map[string]interface{} + if err := json.Unmarshal([]byte(innerData), &finalDataMap); err != nil { + return nil, fmt.Errorf("解析内层 data 字段失败: %v", err) + } + + // 将最终的 JSON 对象编码为字节数组返回 + finalDataBytes, err := json.Marshal(finalDataMap) + if err != nil { + return nil, fmt.Errorf("编码最终的 JSON 对象失败: %v", err) + } + + statusResult := gjson.GetBytes(finalDataBytes, "status.status") + if statusResult.Exists() || statusResult.Int() == -1 { + return nil, fmt.Errorf("企业涉诉为空: %+v", finalDataBytes) + } + return finalDataBytes, nil +} + +// ProcessFLXG0V4BRequest 个人涉诉 +func (a *ApiRequestService) ProcessFLXG0V4BRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, FLXG0V4B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0V4B", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "auth_date": generateAuthDateRange(), + }, &tianyuanapi.ApiCallOptions{ + Json: true, + }) + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessQYGL6F2DRequest 人企关联 +func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QYGL6F2D, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL6F2D", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 处理股东人企关系的响应数据 + code := gjson.GetBytes(respBytes, "code") + if !code.Exists() { + return nil, fmt.Errorf("响应中缺少 code 字段") + } + + // 判断 code 是否等于 "0000" + if code.String() == "0000" { + // 获取 data 字段的值 + data := gjson.GetBytes(respBytes, "data") + if !data.Exists() { + return nil, fmt.Errorf("响应中缺少 data 字段") + } + // 返回 data 字段的内容 + return []byte(data.Raw), nil + } + + // code 不等于 "0000",返回错误 + return nil, fmt.Errorf("响应code错误%s", code.String()) +} + +// ProcessQCXG7A2BRequest 名下车辆 +func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessYYSY09CDRequest 三要素 +func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, YYSY09CD, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("YYSY09CD", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 使用gjson获取resultCode + resultCode := gjson.GetBytes(respBytes, "ctidRequest.ctidAuth.resultCode") + if !resultCode.Exists() { + return nil, errors.New("获取resultCode失败") + } + + // 获取resultCode的第一个字符 + resultCodeStr := resultCode.String() + if len(resultCodeStr) == 0 { + return nil, errors.New("resultCode为空") + } + + firstChar := string(resultCodeStr[0]) + if firstChar != "0" && firstChar != "5" { + return nil, errors.New("resultCode的第一个字符既不是0也不是5") + } + return []byte(firstChar), nil +} + +// ProcessBehaviorRiskScanRequest 行为风险扫描 +func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, BehaviorRiskScan, 获取相关参数失败") + } + + var wg sync.WaitGroup + type apiResult struct { + name string + data []byte + err error + } + results := make(chan apiResult, 1) // 4个风险检测项 + + // 并行调用两个不同的风险检测API + wg.Add(1) + + // 反赌反诈 + go func() { + defer wg.Done() + respBytes, err := a.ProcessFLXG0687Request(params) + results <- apiResult{name: "anti_fraud_gaming", data: respBytes, err: err} + }() + + // 关闭结果通道 + go func() { + wg.Wait() + close(results) + }() + + // 收集所有结果 + resultMap := make(map[string]interface{}) + var errors []string + + for result := range results { + if result.err != nil { + // 记录错误但继续处理其他结果 + errors = append(errors, fmt.Sprintf("%s: %v", result.name, result.err)) + continue + } + + // 解析JSON结果并添加到结果映射 + var parsedData interface{} + if err := json.Unmarshal(result.data, &parsedData); err != nil { + errors = append(errors, fmt.Sprintf("解析%s数据失败: %v", result.name, err)) + } else { + resultMap[result.name] = parsedData + } + } + + // 添加错误信息到结果中(如果存在) + if len(errors) > 0 { + resultMap["errors"] = errors + } + + // 序列化最终结果 + finalResult, err := json.Marshal(resultMap) + if err != nil { + return nil, fmt.Errorf("序列化行为风险扫描结果失败: %v", err) + } + + return finalResult, nil +} + +// ProcessDWBG8B4DRequest 谛听多维报告 +func (a *ApiRequestService) ProcessDWBG8B4DRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + AuthorizationURL := gjson.GetBytes(params, "authorization_url") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() || !AuthorizationURL.Exists() { + return nil, errors.New("api请求, DWBG8B4D, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("DWBG8B4D", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorization_url": AuthorizationURL.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessDWBG6A2CRequest 司南报告服务 +func (a *ApiRequestService) ProcessDWBG6A2CRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + AuthorizationURL := gjson.GetBytes(params, "authorization_url") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() || !AuthorizationURL.Exists() { + return nil, errors.New("api请求, DWBG6A2C, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("DWBG6A2C", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorization_url": AuthorizationURL.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ4B6CRequest 探针C风险评估 +func (a *ApiRequestService) ProcessJRZQ4B6CRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ4B6C, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ4B6C", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ09J8Request 收入评估 +func (a *ApiRequestService) ProcessJRZQ09J8Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ09J8, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ09J8", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ5E9FRequest 借选指数 +func (a *ApiRequestService) ProcessJRZQ5E9FRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ5E9F, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ5E9F", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessQYGL3F8ERequest 人企关系加强版2 +func (a *ApiRequestService) ProcessQYGL3F8ERequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QYGL3F8E, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL3F8E", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ81NCRequest 婚姻,登记时间版 +func (a *ApiRequestService) ProcessIVYZ81NCRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, IVYZ81NC, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ81NC", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ7F3ARequest 学历查询版B +func (a *ApiRequestService) ProcessIVYZ7F3ARequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ7F3A, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ7F3A", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ3P9MRequest 学历实时查询 +func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ3P9M, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ3P9M", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessFLXG7E8FRequest 个人涉诉 +func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, FLXG7E8F, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG7E8F", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} diff --git a/app/main/api/internal/service/applepayService.go b/app/main/api/internal/service/applepayService.go new file mode 100644 index 0000000..0a520cd --- /dev/null +++ b/app/main/api/internal/service/applepayService.go @@ -0,0 +1,169 @@ +package service + +import ( + "context" + "crypto/ecdsa" + "crypto/x509" + "bdrp-server/app/main/api/internal/config" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +// ApplePayService 是 Apple IAP 支付服务的结构体 +type ApplePayService struct { + config config.ApplepayConfig // 配置项 +} + +// NewApplePayService 是一个构造函数,用于初始化 ApplePayService +func NewApplePayService(c config.Config) *ApplePayService { + return &ApplePayService{ + config: c.Applepay, + } +} +func (a *ApplePayService) GetIappayAppID(productName string) string { + return fmt.Sprintf("%s.%s", a.config.BundleID, productName) +} + +// VerifyReceipt 验证苹果支付凭证 +func (a *ApplePayService) VerifyReceipt(ctx context.Context, receipt string) (*AppleVerifyResponse, error) { + var reqUrl string + if a.config.Sandbox { + reqUrl = a.config.SandboxVerifyURL + } else { + reqUrl = a.config.ProductionVerifyURL + } + + // 读取私钥 + privateKey, err := loadPrivateKey(a.config.LoadPrivateKeyPath) + if err != nil { + return nil, fmt.Errorf("加载私钥失败:%v", err) + } + + // 生成 JWT + token, err := generateJWT(privateKey, a.config.KeyID, a.config.IssuerID) + if err != nil { + return nil, fmt.Errorf("生成JWT失败:%v", err) + } + + // 构造查询参数 + queryParams := fmt.Sprintf("?receipt-data=%s", receipt) + fullUrl := reqUrl + queryParams + + // 构建 HTTP GET 请求 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullUrl, nil) + if err != nil { + return nil, fmt.Errorf("创建 HTTP 请求失败:%v", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + // 发送请求 + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("请求苹果验证接口失败:%v", err) + } + defer resp.Body.Close() + + // 解析响应 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应体失败:%v", err) + } + + var verifyResponse AppleVerifyResponse + err = json.Unmarshal(body, &verifyResponse) + if err != nil { + return nil, fmt.Errorf("解析响应体失败:%v", err) + } + + // 根据实际响应处理逻辑 + if verifyResponse.Status != 0 { + return nil, fmt.Errorf("验证失败,状态码:%d", verifyResponse.Status) + } + + return &verifyResponse, nil +} + +func loadPrivateKey(path string) (*ecdsa.PrivateKey, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + block, _ := pem.Decode(data) + if block == nil || block.Type != "PRIVATE KEY" { + return nil, fmt.Errorf("无效的私钥数据") + } + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + ecdsaKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("私钥类型错误") + } + return ecdsaKey, nil +} + +func generateJWT(privateKey *ecdsa.PrivateKey, keyID, issuerID string) (string, error) { + now := time.Now() + claims := jwt.RegisteredClaims{ + Issuer: issuerID, + IssuedAt: jwt.NewNumericDate(now), + ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)), + Audience: jwt.ClaimStrings{"appstoreconnect-v1"}, + } + token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + token.Header["kid"] = keyID + tokenString, err := token.SignedString(privateKey) + if err != nil { + return "", err + } + return tokenString, nil +} + +// GenerateOutTradeNo 生成唯一订单号 +func (a *ApplePayService) GenerateOutTradeNo() string { + length := 16 + timestamp := time.Now().UnixNano() + timeStr := strconv.FormatInt(timestamp, 10) + randomPart := strconv.Itoa(int(timestamp % 1e6)) + combined := timeStr + randomPart + + if len(combined) >= length { + return combined[:length] + } + + for len(combined) < length { + combined += strconv.Itoa(int(timestamp % 10)) + } + + return combined +} + +// AppleVerifyResponse 定义苹果验证接口的响应结构 +type AppleVerifyResponse struct { + Status int `json:"status"` // 验证状态码:0 表示收据有效 + Receipt *Receipt `json:"receipt"` // 收据信息 +} + +// Receipt 定义收据的精简结构 +type Receipt struct { + BundleID string `json:"bundle_id"` // 应用的 Bundle ID + InApp []InAppItem `json:"in_app"` // 应用内购买记录 +} + +// InAppItem 定义单条交易记录 +type InAppItem struct { + ProductID string `json:"product_id"` // 商品 ID + TransactionID string `json:"transaction_id"` // 交易 ID + PurchaseDate string `json:"purchase_date"` // 购买日期 (ISO 8601) + OriginalTransID string `json:"original_transaction_id"` // 原始交易 ID +} diff --git a/app/main/api/internal/service/asynqService.go b/app/main/api/internal/service/asynqService.go new file mode 100644 index 0000000..fa8ce1b --- /dev/null +++ b/app/main/api/internal/service/asynqService.go @@ -0,0 +1,92 @@ +// asynq_service.go + +package service + +import ( + "encoding/json" + "time" + + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/api/internal/types" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" +) + +type AsynqService struct { + client *asynq.Client + config config.Config +} + +// NewAsynqService 创建并初始化 Asynq 客户端 +func NewAsynqService(c config.Config) *AsynqService { + client := asynq.NewClient(asynq.RedisClientOpt{ + Addr: c.CacheRedis[0].Host, + Password: c.CacheRedis[0].Pass, + }) + + return &AsynqService{client: client, config: c} +} + +// Close 关闭 Asynq 客户端 +func (s *AsynqService) Close() error { + return s.client.Close() +} +func (s *AsynqService) SendQueryTask(orderID int64) error { + // 准备任务的 payload + payload := types.MsgPaySuccessQueryPayload{ + OrderID: orderID, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + logx.Errorf("发送异步任务失败 (无法编码 payload): %v, 订单号: %d", err, orderID) + return err // 直接返回错误,避免继续执行 + } + + options := []asynq.Option{ + asynq.MaxRetry(5), // 设置最大重试次数 + } + // 创建任务 + task := asynq.NewTask(types.MsgPaySuccessQuery, payloadBytes, options...) + + // 将任务加入队列并获取任务信息 + info, err := s.client.Enqueue(task) + if err != nil { + logx.Errorf("发送异步任务失败 (加入队列失败): %+v, 订单号: %d", err, orderID) + return err + } + + // 记录成功日志,带上任务 ID 和队列信息 + logx.Infof("发送异步任务成功,任务ID: %s, 队列: %s, 订单号: %d", info.ID, info.Queue, orderID) + return nil +} + +// SendUnfreezeCommissionTask 发送佣金解冻任务 +func (s *AsynqService) SendUnfreezeCommissionTask(commissionID int64) error { + // 准备任务的 payload + payload := types.MsgUnfreezeCommissionPayload{ + CommissionID: commissionID, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + logx.Errorf("发送佣金解冻任务失败 (无法编码 payload): %v, 佣金ID: %d", err, commissionID) + return err + } + + options := []asynq.Option{ + asynq.ProcessIn(time.Duration(s.config.ExtensionTime) * time.Hour), // 10小时后执行 + asynq.MaxRetry(5), // 设置最大重试次数 + } + task := asynq.NewTask(types.MsgUnfreezeCommission, payloadBytes, options...) + + // 将任务加入队列并获取任务信息 + info, err := s.client.Enqueue(task) + if err != nil { + logx.Errorf("发送佣金解冻任务失败 (加入队列失败): %+v, 佣金ID: %d", err, commissionID) + return err + } + + // 记录成功日志,带上任务 ID 和队列信息 + logx.Infof("发送佣金解冻任务成功,任务ID: %s, 队列: %s, 佣金ID: %d", info.ID, info.Queue, commissionID) + return nil +} diff --git a/app/main/api/internal/service/authorizationService.go b/app/main/api/internal/service/authorizationService.go new file mode 100644 index 0000000..0ec3ca4 --- /dev/null +++ b/app/main/api/internal/service/authorizationService.go @@ -0,0 +1,311 @@ +package service + +import ( + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + "bytes" + "context" + "database/sql" + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/jung-kurt/gofpdf" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type AuthorizationService struct { + config config.Config + authDocModel model.AuthorizationDocumentModel + fileStoragePath string + fileBaseURL string +} + +// NewAuthorizationService 创建授权书服务实例 +func NewAuthorizationService(c config.Config, authDocModel model.AuthorizationDocumentModel) *AuthorizationService { + absStoragePath := determineStoragePath() + + return &AuthorizationService{ + config: c, + authDocModel: authDocModel, + fileStoragePath: absStoragePath, + fileBaseURL: c.Authorization.FileBaseURL, // 从配置文件读取 + } +} + +// GenerateAuthorizationDocument 生成授权书PDF +func (s *AuthorizationService) GenerateAuthorizationDocument( + ctx context.Context, + userID int64, + orderID int64, + queryID int64, + userInfo map[string]interface{}, +) (*model.AuthorizationDocument, error) { + // 1. 生成PDF内容 + pdfBytes, err := s.generatePDFContent(userInfo) + if err != nil { + return nil, errors.Wrapf(err, "生成PDF内容失败") + } + + // 2. 创建文件存储目录 + year := time.Now().Format("2006") + month := time.Now().Format("01") + dirPath := filepath.Join(s.fileStoragePath, year, month) + if err := os.MkdirAll(dirPath, 0755); err != nil { + return nil, errors.Wrapf(err, "创建存储目录失败: %s", dirPath) + } + + // 3. 生成文件名和路径 + fileName := fmt.Sprintf("auth_%d_%d_%s.pdf", userID, orderID, time.Now().Format("20060102_150405")) + filePath := filepath.Join(dirPath, fileName) + // 只存储相对路径,不包含域名 + relativePath := fmt.Sprintf("%s/%s/%s", year, month, fileName) + + // 4. 保存PDF文件 + if err := os.WriteFile(filePath, pdfBytes, 0644); err != nil { + return nil, errors.Wrapf(err, "保存PDF文件失败: %s", filePath) + } + + // 5. 保存到数据库 + authDoc := &model.AuthorizationDocument{ + UserId: userID, + OrderId: orderID, + QueryId: queryID, + FileName: fileName, + FilePath: filePath, + FileUrl: relativePath, // 只存储相对路径 + FileSize: int64(len(pdfBytes)), + FileType: "pdf", + Status: "active", + ExpireTime: sql.NullTime{Valid: false}, // 永久保留,不设置过期时间 + } + + result, err := s.authDocModel.Insert(ctx, nil, authDoc) + if err != nil { + // 如果数据库保存失败,删除已创建的文件 + os.Remove(filePath) + return nil, errors.Wrapf(err, "保存授权书记录失败") + } + + authDoc.Id, _ = result.LastInsertId() + logx.Infof("授权书生成成功: userID=%d, orderID=%d, filePath=%s", userID, orderID, filePath) + + return authDoc, nil +} + +// GetFullFileURL 获取完整的文件访问URL +func (s *AuthorizationService) GetFullFileURL(relativePath string) string { + if relativePath == "" { + return "" + } + return fmt.Sprintf("%s/%s", s.fileBaseURL, relativePath) +} + +// ResolveFilePath 根据存储的路径信息解析出本地文件的绝对路径 +func (s *AuthorizationService) ResolveFilePath(filePath string, relativePath string) string { + candidates := []string{} + + cleanRelative := func(p string) string { + p = strings.TrimPrefix(p, "/") + p = strings.TrimPrefix(p, "\\") + normalized := filepath.ToSlash(p) + if strings.HasPrefix(normalized, "http://") || strings.HasPrefix(normalized, "https://") { + if parsed, err := url.Parse(normalized); err == nil { + normalized = filepath.ToSlash(strings.TrimPrefix(parsed.Path, "/")) + } + } + normalized = strings.TrimPrefix(normalized, "data/authorization_docs/") + normalized = strings.TrimPrefix(normalized, "authorization_docs/") + normalized = strings.TrimPrefix(normalized, "./") + normalized = strings.TrimPrefix(normalized, "../") + return normalized + } + + if filePath != "" { + candidates = append(candidates, filePath) + } + if relativePath != "" { + candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(relativePath)))) + } + if filePath != "" { + cleaned := filepath.Clean(filePath) + if strings.HasPrefix(cleaned, s.fileStoragePath) { + candidates = append(candidates, cleaned) + } else { + trimmed := strings.TrimPrefix(cleaned, "data"+string(os.PathSeparator)+"authorization_docs"+string(os.PathSeparator)) + trimmed = strings.TrimPrefix(trimmed, "data/authorization_docs/") + if trimmed != cleaned { + candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(trimmed)))) + } + if !filepath.IsAbs(cleaned) { + candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(cleaned)))) + } + } + } + + for _, candidate := range candidates { + if candidate == "" { + continue + } + pathCandidate := candidate + if !filepath.IsAbs(pathCandidate) { + if absPath, err := filepath.Abs(pathCandidate); err == nil { + pathCandidate = absPath + } + } + if statErr := checkFileExists(pathCandidate); statErr == nil { + return pathCandidate + } + } + + logx.Errorf("授权书文件路径解析失败 filePath=%s fileUrl=%s candidates=%v", filePath, relativePath, candidates) + + return "" +} + +func determineStoragePath() string { + candidatePaths := []string{ + "data/authorization_docs", + "../data/authorization_docs", + "../../data/authorization_docs", + "../../../data/authorization_docs", + } + + for _, candidate := range candidatePaths { + absPath, err := filepath.Abs(candidate) + if err != nil { + logx.Errorf("解析授权书存储路径失败: %s, err=%v", candidate, err) + continue + } + if info, err := os.Stat(absPath); err == nil && info.IsDir() { + logx.Infof("授权书存储路径选择: %s", absPath) + return absPath + } + } + + // 如果没有现成的目录,使用第一个候选的绝对路径 + absPath, err := filepath.Abs(candidatePaths[0]) + if err != nil { + logx.Errorf("解析默认授权书存储路径失败,使用相对路径: %s, err=%v", candidatePaths[0], err) + return candidatePaths[0] + } + logx.Infof("授权书存储路径创建: %s", absPath) + return absPath +} + +func checkFileExists(path string) error { + info, err := os.Stat(path) + if err != nil { + return err + } + if info.IsDir() { + return fmt.Errorf("path %s is directory", path) + } + return nil +} + +// generatePDFContent 生成PDF内容 +func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{}) ([]byte, error) { + // 创建PDF文档 + pdf := gofpdf.New("P", "mm", "A4", "") + pdf.AddPage() + + // 添加中文字体支持 - 参考imageService的路径处理方式 + fontPaths := []string{ + "static/SIMHEI.TTF", // 相对于工作目录的路径(与imageService一致) + "/app/static/SIMHEI.TTF", // Docker容器内的字体文件 + "app/main/api/static/SIMHEI.TTF", // 开发环境备用路径 + } + + // 尝试添加字体 + fontAdded := false + for _, fontPath := range fontPaths { + if _, err := os.Stat(fontPath); err == nil { + pdf.AddUTF8Font("ChineseFont", "", fontPath) + fontAdded = true + logx.Infof("成功加载字体: %s", fontPath) + break + } else { + logx.Debugf("字体文件不存在: %s, 错误: %v", fontPath, err) + } + } + + // 如果没有找到字体文件,使用默认字体,并记录警告 + if !fontAdded { + pdf.SetFont("Arial", "", 12) + logx.Errorf("未找到中文字体文件,使用默认Arial字体,可能无法正确显示中文") + } else { + // 设置默认字体 + pdf.SetFont("ChineseFont", "", 12) + } + + // 获取用户信息 + name := getUserInfoString(userInfo, "name") + idCard := getUserInfoString(userInfo, "id_card") + + // 生成当前日期 + currentDate := time.Now().Format("2006年1月2日") + + // 设置标题样式 - 大字体、居中 + if fontAdded { + pdf.SetFont("ChineseFont", "", 20) // 使用20号字体 + } else { + pdf.SetFont("Arial", "", 20) + } + pdf.CellFormat(0, 15, "授权书", "", 1, "C", false, 0, "") + + // 添加空行 + pdf.Ln(5) + + // 设置正文样式 - 正常字体 + if fontAdded { + pdf.SetFont("ChineseFont", "", 12) + } else { + pdf.SetFont("Arial", "", 12) + } + + // 构建授权书内容(与前端 Authorization.vue 保持一致) + content := fmt.Sprintf(`戎行技术有限公司: +本人(姓名:%s/身份证号码:%s)拟向贵司申请业务,贵司需要了解本人相关状况,用于查询大数据分析报告,因此本人特同意并不可撤销的授权: + +(一)贵司向依法成立的第三方服务商(包括但不限于天津津北数字产业发展集团有限公司)根据本人提交的信息进行核实;并有权通过前述第三方服务机构查询、使用本人的身份信息、电话号码等,查询本人信息(包括但不限于学历、婚姻、资产状况及对信息主体产生负面影响的不良信息),出具相关报告。 +(二)第三方服务商应当在上述处理目的、处理方式和个人信息的种类等范围内处理个人信息。变更原先的处理目的、处理方式的,应当依法重新取得您的同意。 + +本人在此声明已充分理解上述授权条款含义,知晓并自愿承担上述因收集等本人数据可能会给本人的生活行为(评分)结果产生不利影响,以及该等数据被使用者依法提供给第三方后被他人不当利用的风险,但本人仍同意上述授权。 + +特别提示: +为了保障您的合法权益,请您务必阅读并充分理解与遵守本授权书;若您不接受本授权书的任何条款,请您立即终止授权。贵司已经对上述事宜及其风险向本人做了充分说明,本人已知晓并同意。 +你通过“赤眉”APP或代理商推广查询模式,自愿支付相应费用,用于购买戎行技术有限公司的大数据报告产品。 +你向戎行技术有限公司的支付方式为:戎行技术有限公司及其关联公司的支付宝及微信账户。 +本授权书一经本人在网上点击勾选同意即完成签署。本授权书是本人真实意思表示,本人同意承担由此带来的一切法律后果。 + +授权人:%s +身份证号:%s +签署时间:%s`, name, idCard, name, idCard, currentDate) + + // 将内容写入PDF + pdf.MultiCell(0, 6, content, "", "", false) + + // 生成PDF字节数组 + var buf bytes.Buffer + err := pdf.Output(&buf) + if err != nil { + return nil, errors.Wrapf(err, "生成PDF字节数组失败") + } + + return buf.Bytes(), nil +} + +// getUserInfoString 安全获取用户信息字符串 +func getUserInfoString(userInfo map[string]interface{}, key string) string { + if value, exists := userInfo[key]; exists { + if str, ok := value.(string); ok { + return str + } + } + return "" +} diff --git a/app/main/api/internal/service/authorizationService_test.go b/app/main/api/internal/service/authorizationService_test.go new file mode 100644 index 0000000..0adbef2 --- /dev/null +++ b/app/main/api/internal/service/authorizationService_test.go @@ -0,0 +1,670 @@ +package service + +import ( + "context" + "database/sql" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + + "github.com/Masterminds/squirrel" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +// mockResult 模拟sql.Result +type mockResult struct { + lastInsertId int64 + rowsAffected int64 +} + +func (m *mockResult) LastInsertId() (int64, error) { + return m.lastInsertId, nil +} + +func (m *mockResult) RowsAffected() (int64, error) { + return m.rowsAffected, nil +} + +// MockAuthorizationDocumentModel 模拟授权书模型 +type MockAuthorizationDocumentModel struct { + mock.Mock +} + +func (m *MockAuthorizationDocumentModel) Insert(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) (sql.Result, error) { + args := m.Called(ctx, session, data) + return args.Get(0).(sql.Result), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindOne(ctx context.Context, id int64) (*model.AuthorizationDocument, error) { + args := m.Called(ctx, id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*model.AuthorizationDocument), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) Update(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) (sql.Result, error) { + args := m.Called(ctx, session, data) + return args.Get(0).(sql.Result), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) error { + args := m.Called(ctx, session, data) + return args.Error(0) +} + +func (m *MockAuthorizationDocumentModel) Trans(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { + args := m.Called(ctx, fn) + return args.Error(0) +} + +func (m *MockAuthorizationDocumentModel) SelectBuilder() squirrel.SelectBuilder { + args := m.Called() + return args.Get(0).(squirrel.SelectBuilder) +} + +func (m *MockAuthorizationDocumentModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) error { + args := m.Called(ctx, session, data) + return args.Error(0) +} + +func (m *MockAuthorizationDocumentModel) FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error) { + args := m.Called(ctx, sumBuilder, field) + return args.Get(0).(float64), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error) { + args := m.Called(ctx, countBuilder, field) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*model.AuthorizationDocument, error) { + args := m.Called(ctx, rowBuilder, orderBy) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*model.AuthorizationDocument, error) { + args := m.Called(ctx, rowBuilder, page, pageSize, orderBy) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*model.AuthorizationDocument, int64, error) { + args := m.Called(ctx, rowBuilder, page, pageSize, orderBy) + if args.Get(0) == nil { + return nil, args.Get(1).(int64), args.Error(2) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Get(1).(int64), args.Error(2) +} + +func (m *MockAuthorizationDocumentModel) FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*model.AuthorizationDocument, error) { + args := m.Called(ctx, rowBuilder, preMinId, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*model.AuthorizationDocument, error) { + args := m.Called(ctx, rowBuilder, preMaxId, pageSize) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) +} + +func (m *MockAuthorizationDocumentModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + args := m.Called(ctx, session, id) + return args.Error(0) +} + +func (m *MockAuthorizationDocumentModel) FindByOrderId(ctx context.Context, orderId int64) ([]*model.AuthorizationDocument, error) { + args := m.Called(ctx, orderId) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) +} + +// MockResult 模拟数据库结果 +type MockResult struct { + lastInsertId int64 +} + +func (m *MockResult) LastInsertId() (int64, error) { + return m.lastInsertId, nil +} + +func (m *MockResult) RowsAffected() (int64, error) { + return 1, nil +} + +// TestNewAuthorizationService 测试创建授权书服务 +func TestNewAuthorizationService(t *testing.T) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + + service := NewAuthorizationService(config, mockModel) + + assert.NotNil(t, service) + assert.Equal(t, "data/authorization_docs", service.fileStoragePath) + assert.Equal(t, "https://test.com/api/v1/auth-docs", service.fileBaseURL) +} + +// TestGetFullFileURL 测试获取完整文件URL +func TestGetFullFileURL(t *testing.T) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + service := NewAuthorizationService(config, mockModel) + + tests := []struct { + name string + relativePath string + expected string + }{ + { + name: "正常相对路径", + relativePath: "2025/09/auth_123_456_20250913_160800.pdf", + expected: "https://test.com/api/v1/auth-docs/2025/09/auth_123_456_20250913_160800.pdf", + }, + { + name: "空路径", + relativePath: "", + expected: "", + }, + { + name: "只有文件名", + relativePath: "test.pdf", + expected: "https://test.com/api/v1/auth-docs/test.pdf", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.GetFullFileURL(tt.relativePath) + assert.Equal(t, tt.expected, result) + }) + } +} + +// TestGenerateAuthorizationDocument 测试生成授权书 +func TestGenerateAuthorizationDocument(t *testing.T) { + // 创建测试配置 + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.example.com/api/v1/auth-docs", + }, + } + + // 创建模拟的数据库模型 + mockModel := &MockAuthorizationDocumentModel{} + + // 创建授权书服务 + service := NewAuthorizationService(config, mockModel) + + // 准备测试数据 + userInfo := map[string]interface{}{ + "name": "张三", + "id_card": "110101199001011234", + "mobile": "13800138000", + } + + // 模拟数据库插入成功 + mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( + &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) + + // 执行测试 + authDoc, err := service.GenerateAuthorizationDocument( + context.Background(), + 1, // userID + 2, // orderID + 3, // queryID + userInfo, + ) + + // 验证结果 + assert.NoError(t, err) + assert.NotNil(t, authDoc) + assert.Equal(t, int64(1), authDoc.UserId) + assert.Equal(t, int64(2), authDoc.OrderId) + assert.Equal(t, int64(3), authDoc.QueryId) + assert.Equal(t, "pdf", authDoc.FileType) + assert.Equal(t, "active", authDoc.Status) + assert.False(t, authDoc.ExpireTime.Valid) // 永久保留,不设置过期时间 + + // 验证文件路径格式(兼容Windows和Unix路径分隔符) + assert.True(t, strings.Contains(authDoc.FilePath, "data/authorization_docs") || + strings.Contains(authDoc.FilePath, "data\\authorization_docs")) + assert.Contains(t, authDoc.FileName, "auth_") + assert.Contains(t, authDoc.FileName, ".pdf") + + // 验证相对路径格式 + assert.Regexp(t, `^\d{4}/\d{2}/auth_\d+_\d+_\d{8}_\d{6}\.pdf$`, authDoc.FileUrl) + + // 验证文件大小 + assert.Greater(t, authDoc.FileSize, int64(0)) + + // 验证数据库调用 + mockModel.AssertExpectations(t) + + // 验证文件是否真的被创建 + if _, err := os.Stat(authDoc.FilePath); err == nil { + t.Logf("✅ 授权书文件已创建: %s", authDoc.FilePath) + t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) + } else { + t.Logf("⚠️ 文件未找到: %s", authDoc.FilePath) + } +} + +// TestGenerateAuthorizationDocument_DatabaseError 测试数据库错误 +func TestGenerateAuthorizationDocument_DatabaseError(t *testing.T) { + // 创建测试配置 + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.example.com/api/v1/auth-docs", + }, + } + + // 创建模拟的数据库模型 + mockModel := &MockAuthorizationDocumentModel{} + + // 创建授权书服务 + service := NewAuthorizationService(config, mockModel) + + // 准备测试数据 + userInfo := map[string]interface{}{ + "name": "李四", + "id_card": "110101199001011235", + "mobile": "13800138001", + } + + // 模拟数据库插入失败 + mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( + (*mockResult)(nil), errors.New("数据库连接失败")) + + // 执行测试 + authDoc, err := service.GenerateAuthorizationDocument( + context.Background(), + 1, // userID + 2, // orderID + 3, // queryID + userInfo, + ) + + // 验证结果 + assert.Error(t, err) + assert.Nil(t, authDoc) + assert.Contains(t, err.Error(), "数据库连接失败") + + // 验证数据库调用 + mockModel.AssertExpectations(t) +} + +// TestGeneratePDFContent 测试生成PDF内容 +func TestGeneratePDFContent(t *testing.T) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + service := NewAuthorizationService(config, mockModel) + + userInfo := map[string]interface{}{ + "name": "张三", + "id_card": "110101199001011234", + } + + pdfBytes, err := service.generatePDFContent(userInfo) + + // 验证结果 + assert.NoError(t, err) + assert.NotNil(t, pdfBytes) + assert.Greater(t, len(pdfBytes), 0) + + // 验证PDF内容(PDF是二进制格式,只验证基本结构) + assert.Contains(t, string(pdfBytes), "%PDF") // PDF文件头 + + // 按照 GenerateAuthorizationDocument 的方式保存文件到本地 + // 1. 创建文件存储目录 + year := time.Now().Format("2006") + month := time.Now().Format("01") + dirPath := filepath.Join("data", "authorization_docs", year, month) + if err := os.MkdirAll(dirPath, 0755); err != nil { + t.Fatalf("创建存储目录失败: %v", err) + } + + // 2. 生成文件名和路径 + fileName := fmt.Sprintf("test_auth_%s.pdf", time.Now().Format("20060102_150405")) + filePath := filepath.Join(dirPath, fileName) + relativePath := fmt.Sprintf("%s/%s/%s", year, month, fileName) + + // 3. 保存PDF文件 + if err := os.WriteFile(filePath, pdfBytes, 0644); err != nil { + t.Fatalf("保存PDF文件失败: %v", err) + } + + // 4. 验证文件是否保存成功 + if _, err := os.Stat(filePath); err == nil { + t.Logf("✅ PDF文件已保存到本地") + t.Logf("📄 文件名: %s", fileName) + t.Logf("📁 文件路径: %s", filePath) + t.Logf("🔗 相对路径: %s", relativePath) + t.Logf("📊 文件大小: %d 字节", len(pdfBytes)) + + // 获取绝对路径 + absPath, _ := filepath.Abs(filePath) + t.Logf("📍 绝对路径: %s", absPath) + } else { + t.Errorf("❌ PDF文件保存失败: %v", err) + } +} + +// TestSavePDFToLocal 专门测试保存PDF文件到本地 +func TestSavePDFToLocal(t *testing.T) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + service := NewAuthorizationService(config, mockModel) + + // 准备测试数据 + userInfo := map[string]interface{}{ + "name": "张三", + "id_card": "110101199001011234", + "mobile": "13800138000", + } + + // 生成PDF内容 + pdfBytes, err := service.generatePDFContent(userInfo) + assert.NoError(t, err) + assert.NotNil(t, pdfBytes) + assert.Greater(t, len(pdfBytes), 0) + + // 按照 GenerateAuthorizationDocument 的方式保存文件到本地 + // 1. 创建文件存储目录 + year := time.Now().Format("2006") + month := time.Now().Format("01") + dirPath := filepath.Join("data", "authorization_docs", year, month) + if err := os.MkdirAll(dirPath, 0755); err != nil { + t.Fatalf("创建存储目录失败: %v", err) + } + + // 2. 生成文件名和路径 + fileName := fmt.Sprintf("local_test_auth_%s.pdf", time.Now().Format("20060102_150405")) + filePath := filepath.Join(dirPath, fileName) + relativePath := fmt.Sprintf("%s/%s/%s", year, month, fileName) + + // 3. 保存PDF文件 + if err := os.WriteFile(filePath, pdfBytes, 0644); err != nil { + t.Fatalf("保存PDF文件失败: %v", err) + } + + // 4. 验证文件是否保存成功 + if _, err := os.Stat(filePath); err == nil { + t.Logf("✅ PDF文件已保存到本地") + t.Logf("📄 文件名: %s", fileName) + t.Logf("📁 文件路径: %s", filePath) + t.Logf("🔗 相对路径: %s", relativePath) + t.Logf("📊 文件大小: %d 字节", len(pdfBytes)) + + // 获取绝对路径 + absPath, _ := filepath.Abs(filePath) + t.Logf("📍 绝对路径: %s", absPath) + + // 验证文件内容 + fileInfo, err := os.Stat(filePath) + assert.NoError(t, err) + assert.Greater(t, fileInfo.Size(), int64(1000)) // 文件应该大于1KB + + t.Logf("🎉 文件保存验证通过!") + } else { + t.Errorf("❌ PDF文件保存失败: %v", err) + } +} + +// TestGeneratePDFContent_EmptyUserInfo 测试空用户信息 +func TestGeneratePDFContent_EmptyUserInfo(t *testing.T) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + service := NewAuthorizationService(config, mockModel) + + userInfo := map[string]interface{}{} + + pdfBytes, err := service.generatePDFContent(userInfo) + + // 验证结果 + assert.NoError(t, err) + assert.NotNil(t, pdfBytes) + assert.Greater(t, len(pdfBytes), 0) + + // 验证PDF内容(PDF是二进制格式,只验证基本结构) + assert.Contains(t, string(pdfBytes), "%PDF") // PDF文件头 +} + +// TestGetUserInfoString 测试获取用户信息字符串 +func TestGetUserInfoString(t *testing.T) { + userInfo := map[string]interface{}{ + "name": "张三", + "id_card": "110101199001011234", + "age": 30, + "empty": "", + } + + tests := []struct { + name string + key string + expected string + }{ + { + name: "正常字符串", + key: "name", + expected: "张三", + }, + { + name: "身份证号", + key: "id_card", + expected: "110101199001011234", + }, + { + name: "空字符串", + key: "empty", + expected: "", + }, + { + name: "不存在的键", + key: "not_exist", + expected: "", + }, + { + name: "非字符串类型", + key: "age", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getUserInfoString(userInfo, tt.key) + assert.Equal(t, tt.expected, result) + }) + } +} + +// BenchmarkGeneratePDFContent 性能测试 +func BenchmarkGeneratePDFContent(b *testing.B) { + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.com/api/v1/auth-docs", + }, + } + mockModel := &MockAuthorizationDocumentModel{} + service := NewAuthorizationService(config, mockModel) + + userInfo := map[string]interface{}{ + "name": "张三", + "id_card": "110101199001011234", + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := service.generatePDFContent(userInfo) + if err != nil { + b.Fatal(err) + } + } +} + +// TestAuthorizationService_Integration 集成测试(需要真实文件系统) +func TestAuthorizationService_Integration(t *testing.T) { + // 创建测试配置 + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.example.com/api/v1/auth-docs", + }, + } + + // 创建模拟的数据库模型 + mockModel := &MockAuthorizationDocumentModel{} + + // 创建授权书服务 + service := NewAuthorizationService(config, mockModel) + + // 准备测试数据 + userInfo := map[string]interface{}{ + "name": "王五", + "id_card": "110101199001011236", + "mobile": "13800138002", + } + + // 模拟数据库插入成功 + mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( + &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) + + // 执行测试 + authDoc, err := service.GenerateAuthorizationDocument( + context.Background(), + 1, // userID + 2, // orderID + 3, // queryID + userInfo, + ) + + // 验证结果 + assert.NoError(t, err) + assert.NotNil(t, authDoc) + + // 测试GetFullFileURL方法 + fullURL := service.GetFullFileURL(authDoc.FileUrl) + expectedURL := fmt.Sprintf("%s/%s", config.Authorization.FileBaseURL, authDoc.FileUrl) + assert.Equal(t, expectedURL, fullURL) + + // 验证文件是否真的被创建 + if _, err := os.Stat(authDoc.FilePath); err == nil { + t.Logf("✅ 集成测试成功 - 授权书文件已创建") + t.Logf("📄 文件名: %s", authDoc.FileName) + t.Logf("📁 文件路径: %s", authDoc.FilePath) + t.Logf("🔗 相对路径: %s", authDoc.FileUrl) + t.Logf("🌐 完整URL: %s", fullURL) + t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) + } else { + t.Logf("⚠️ 集成测试 - 文件未找到: %s", authDoc.FilePath) + } + + // 验证数据库调用 + mockModel.AssertExpectations(t) +} + +// TestGeneratePDFFile 专门测试PDF文件生成 +func TestGeneratePDFFile(t *testing.T) { + // 创建测试配置 + config := config.Config{ + Authorization: config.AuthorizationConfig{ + FileBaseURL: "https://test.example.com/api/v1/auth-docs", + }, + } + + // 创建模拟的数据库模型 + mockModel := &MockAuthorizationDocumentModel{} + + // 创建授权书服务 + service := NewAuthorizationService(config, mockModel) + + // 准备测试数据 + userInfo := map[string]interface{}{ + "name": "测试用户", + "id_card": "110101199001011237", + "mobile": "13800138003", + } + + // 模拟数据库插入成功 + mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( + &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) + + // 执行测试 + authDoc, err := service.GenerateAuthorizationDocument( + context.Background(), + 999, // userID + 888, // orderID + 777, // queryID + userInfo, + ) + + // 验证结果 + assert.NoError(t, err) + assert.NotNil(t, authDoc) + + // 验证文件是否真的被创建 + if _, err := os.Stat(authDoc.FilePath); err == nil { + t.Logf("✅ PDF文件生成成功!") + t.Logf("📄 文件名: %s", authDoc.FileName) + t.Logf("📁 文件路径: %s", authDoc.FilePath) + t.Logf("🔗 相对路径: %s", authDoc.FileUrl) + t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) + + // 验证文件内容 + fileInfo, err := os.Stat(authDoc.FilePath) + assert.NoError(t, err) + assert.Greater(t, fileInfo.Size(), int64(1000)) // 文件应该大于1KB + + // 验证文件名格式 + assert.Regexp(t, `^auth_999_888_\d{8}_\d{6}\.pdf$`, authDoc.FileName) + + // 验证路径格式 + assert.Regexp(t, `^\d{4}/\d{2}/auth_999_888_\d{8}_\d{6}\.pdf$`, authDoc.FileUrl) + + t.Logf("🎉 所有验证通过!") + } else { + t.Errorf("❌ PDF文件未创建: %s", authDoc.FilePath) + } + + // 验证数据库调用 + mockModel.AssertExpectations(t) +} diff --git a/app/main/api/internal/service/dictService.go b/app/main/api/internal/service/dictService.go new file mode 100644 index 0000000..8f0769c --- /dev/null +++ b/app/main/api/internal/service/dictService.go @@ -0,0 +1,47 @@ +package service + +import ( + "context" + "bdrp-server/app/main/model" + "errors" +) + +type DictService struct { + adminDictTypeModel model.AdminDictTypeModel + adminDictDataModel model.AdminDictDataModel +} + +func NewDictService(adminDictTypeModel model.AdminDictTypeModel, adminDictDataModel model.AdminDictDataModel) *DictService { + return &DictService{adminDictTypeModel: adminDictTypeModel, adminDictDataModel: adminDictDataModel} +} +func (s *DictService) GetDictLabel(ctx context.Context, dictType string, dictValue int64) (string, error) { + dictTypeModel, err := s.adminDictTypeModel.FindOneByDictType(ctx, dictType) + if err != nil { + return "", err + } + if dictTypeModel.Status != 1 { + return "", errors.New("字典类型未启用") + } + dictData, err := s.adminDictDataModel.FindOneByDictTypeDictValue(ctx, dictTypeModel.DictType, dictValue) + if err != nil { + return "", err + } + if dictData.Status != 1 { + return "", errors.New("字典数据未启用") + } + return dictData.DictLabel, nil +} +func (s *DictService) GetDictValue(ctx context.Context, dictType string, dictLabel string) (int64, error) { + dictTypeModel, err := s.adminDictTypeModel.FindOneByDictType(ctx, dictType) + if err != nil { + return 0, err + } + if dictTypeModel.Status != 1 { + return 0, errors.New("字典类型未启用") + } + dictData, err := s.adminDictDataModel.FindOneByDictTypeDictLabel(ctx, dictTypeModel.DictType, dictLabel) + if err != nil { + return 0, err + } + return dictData.DictValue, nil +} diff --git a/app/main/api/internal/service/imageService.go b/app/main/api/internal/service/imageService.go new file mode 100644 index 0000000..6c5ad79 --- /dev/null +++ b/app/main/api/internal/service/imageService.go @@ -0,0 +1,173 @@ +package service + +import ( + "bytes" + "fmt" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + + "github.com/fogleman/gg" + "github.com/skip2/go-qrcode" + "github.com/zeromicro/go-zero/core/logx" +) + +type ImageService struct { + baseImagePath string +} + +func NewImageService() *ImageService { + return &ImageService{ + baseImagePath: "static/images", // 原图存放目录 + } +} + +// ProcessImageWithQRCode 处理图片,在中间添加二维码 +func (s *ImageService) ProcessImageWithQRCode(qrcodeType, qrcodeUrl string) ([]byte, string, error) { + // 1. 根据qrcodeType确定使用哪张背景图 + var backgroundImageName string + switch qrcodeType { + case "promote": + backgroundImageName = "tg_qrcode_1.png" + case "invitation": + backgroundImageName = "yq_qrcode_1.png" + default: + backgroundImageName = "tg_qrcode_1.png" // 默认使用第一张图片 + } + + // 2. 读取原图 + originalImagePath := filepath.Join(s.baseImagePath, backgroundImageName) + originalImage, err := s.loadImage(originalImagePath) + if err != nil { + logx.Errorf("加载原图失败: %v, 图片路径: %s", err, originalImagePath) + return nil, "", fmt.Errorf("加载原图失败: %v", err) + } + + // 3. 获取原图尺寸 + bounds := originalImage.Bounds() + imgWidth := bounds.Dx() + imgHeight := bounds.Dy() + + // 4. 创建绘图上下文 + dc := gg.NewContext(imgWidth, imgHeight) + + // 5. 绘制原图作为背景 + dc.DrawImageAnchored(originalImage, imgWidth/2, imgHeight/2, 0.5, 0.5) + + // 6. 生成二维码(去掉白边) + qrCode, err := qrcode.New(qrcodeUrl, qrcode.Medium) + if err != nil { + logx.Errorf("生成二维码失败: %v, 二维码内容: %s", err, qrcodeUrl) + return nil, "", fmt.Errorf("生成二维码失败: %v", err) + } + // 禁用二维码边框,去掉白边 + qrCode.DisableBorder = true + + // 7. 根据二维码类型设置不同的尺寸和位置 + var qrSize int + var qrX, qrY int + + switch qrcodeType { + case "promote": + // promote类型:精确设置二维码尺寸 + qrSize = 280 // 固定尺寸280px + // 左下角位置:距左边和底边留一些边距 + qrX = 192 // 距左边180px + qrY = imgHeight - qrSize - 190 // 距底边100px + + case "invitation": + // invitation类型:精确设置二维码尺寸 + qrSize = 360 // 固定尺寸320px + // 中间偏上位置 + qrX = (imgWidth - qrSize) / 2 // 水平居中 + qrY = 555 // 垂直位置200px + + default: + // 默认(promote样式) + qrSize = 280 // 固定尺寸280px + qrX = 200 // 距左边180px + qrY = imgHeight - qrSize - 200 // 距底边100px + } + + // 8. 生成指定尺寸的二维码图片 + qrCodeImage := qrCode.Image(qrSize) + + // 9. 直接绘制二维码(不添加背景) + dc.DrawImageAnchored(qrCodeImage, qrX+qrSize/2, qrY+qrSize/2, 0.5, 0.5) + + // 11. 输出为字节数组 + var buf bytes.Buffer + err = png.Encode(&buf, dc.Image()) + if err != nil { + logx.Errorf("编码图片失败: %v", err) + return nil, "", fmt.Errorf("编码图片失败: %v", err) + } + + logx.Infof("成功生成带二维码的图片,类型: %s, 二维码内容: %s, 图片尺寸: %dx%d, 二维码尺寸: %dx%d, 位置: (%d,%d)", + qrcodeType, qrcodeUrl, imgWidth, imgHeight, qrSize, qrSize, qrX, qrY) + + return buf.Bytes(), "image/png", nil +} + +// loadImage 加载图片文件 +func (s *ImageService) loadImage(path string) (image.Image, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + // 尝试解码PNG + img, err := png.Decode(file) + if err != nil { + // 如果PNG解码失败,重新打开文件尝试JPEG + file.Close() + file, err = os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + img, err = jpeg.Decode(file) + if err != nil { + // 如果还是失败,使用通用解码器 + file.Close() + file, err = os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + img, _, err = image.Decode(file) + if err != nil { + return nil, err + } + } + } + + return img, nil +} + +// GetSupportedImageTypes 获取支持的图片类型列表 +func (s *ImageService) GetSupportedImageTypes() []string { + return []string{"promote", "invitation"} +} + +// CheckImageExists 检查指定类型的背景图是否存在 +func (s *ImageService) CheckImageExists(qrcodeType string) bool { + var backgroundImageName string + switch qrcodeType { + case "promote": + backgroundImageName = "tg_qrcode_1.png" + case "invitation": + backgroundImageName = "yq_qrcode_1.png" + default: + backgroundImageName = "tg_qrcode_1.png" + } + + imagePath := filepath.Join(s.baseImagePath, backgroundImageName) + _, err := os.Stat(imagePath) + return err == nil +} diff --git a/app/main/api/internal/service/tianyuanapi_sdk/client.go b/app/main/api/internal/service/tianyuanapi_sdk/client.go new file mode 100644 index 0000000..cdb699c --- /dev/null +++ b/app/main/api/internal/service/tianyuanapi_sdk/client.go @@ -0,0 +1,416 @@ +package tianyuanapi + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "time" +) + +// API调用相关错误类型 +var ( + ErrQueryEmpty = errors.New("查询为空") + ErrSystem = errors.New("接口异常") + ErrDecryptFail = errors.New("解密失败") + ErrRequestParam = errors.New("请求参数结构不正确") + ErrInvalidParam = errors.New("参数校验不正确") + ErrInvalidIP = errors.New("未经授权的IP") + ErrMissingAccessId = errors.New("缺少Access-Id") + ErrInvalidAccessId = errors.New("未经授权的AccessId") + ErrFrozenAccount = errors.New("账户已冻结") + ErrArrears = errors.New("账户余额不足,无法请求") + ErrProductNotFound = errors.New("产品不存在") + ErrProductDisabled = errors.New("产品已停用") + ErrNotSubscribed = errors.New("未订阅此产品") + ErrBusiness = errors.New("业务失败") +) + +// 错误码映射 - 严格按照用户要求 +var ErrorCodeMap = map[error]int{ + ErrQueryEmpty: 1000, + ErrSystem: 1001, + ErrDecryptFail: 1002, + ErrRequestParam: 1003, + ErrInvalidParam: 1003, + ErrInvalidIP: 1004, + ErrMissingAccessId: 1005, + ErrInvalidAccessId: 1006, + ErrFrozenAccount: 1007, + ErrArrears: 1007, + ErrProductNotFound: 1008, + ErrProductDisabled: 1008, + ErrNotSubscribed: 1008, + ErrBusiness: 2001, +} + +// ApiCallOptions API调用选项 +type ApiCallOptions struct { + Json bool `json:"json,omitempty"` // 是否返回JSON格式 +} + +// Client 天元API客户端 +type Client struct { + accessID string + key string + baseURL string + timeout time.Duration + client *http.Client +} + +// Config 客户端配置 +type Config struct { + AccessID string // 访问ID + Key string // AES密钥(16进制) + BaseURL string // API基础URL + Timeout time.Duration // 超时时间 +} + +// Request 请求参数 +type Request struct { + InterfaceName string `json:"interfaceName"` // 接口名称 + Params map[string]interface{} `json:"params"` // 请求参数 + Timeout int `json:"timeout"` // 超时时间(毫秒) + Options *ApiCallOptions `json:"options"` // 调用选项 +} + +// ApiResponse HTTP API响应 +type ApiResponse struct { + Code int `json:"code"` + Message string `json:"message"` + TransactionID string `json:"transaction_id"` // 流水号 + Data string `json:"data"` // 加密的数据 +} + +// Response Call方法的响应 +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Success bool `json:"success"` + TransactionID string `json:"transaction_id"` // 流水号 + Data interface{} `json:"data"` // 解密后的数据 + Timeout int64 `json:"timeout"` // 请求耗时(毫秒) + Error string `json:"error,omitempty"` +} + +// NewClient 创建新的客户端实例 +func NewClient(config Config) (*Client, error) { + // 参数校验 + if config.AccessID == "" { + return nil, fmt.Errorf("accessID不能为空") + } + if config.Key == "" { + return nil, fmt.Errorf("key不能为空") + } + if config.BaseURL == "" { + config.BaseURL = "http://127.0.0.1:8080" + } + if config.Timeout == 0 { + config.Timeout = 60 * time.Second + } + + // 验证密钥格式 + if _, err := hex.DecodeString(config.Key); err != nil { + return nil, fmt.Errorf("无效的密钥格式,必须是16进制字符串: %v", err) + } + + return &Client{ + accessID: config.AccessID, + key: config.Key, + baseURL: config.BaseURL, + timeout: config.Timeout, + client: &http.Client{ + Timeout: config.Timeout, + }, + }, nil +} + +// Call 调用API接口 +func (c *Client) Call(req Request) (*Response, error) { + startTime := time.Now() + + // 参数校验 + if err := c.validateRequest(req); err != nil { + return nil, fmt.Errorf("请求参数校验失败: %v", err) + } + + // 加密参数 + jsonData, err := json.Marshal(req.Params) + if err != nil { + return nil, fmt.Errorf("参数序列化失败: %v", err) + } + + encryptedData, err := c.encrypt(string(jsonData)) + if err != nil { + return nil, fmt.Errorf("数据加密失败: %v", err) + } + + // 构建请求体 + requestBody := map[string]interface{}{ + "data": encryptedData, + } + + // 添加选项 + if req.Options != nil { + requestBody["options"] = req.Options + } else { + // 默认选项 + defaultOptions := &ApiCallOptions{ + Json: true, + } + requestBody["options"] = defaultOptions + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("请求体序列化失败: %v", err) + } + + // 创建HTTP请求 + url := fmt.Sprintf("%s/api/v1/%s", c.baseURL, req.InterfaceName) + + httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBodyBytes)) + if err != nil { + return nil, fmt.Errorf("创建HTTP请求失败: %v", err) + } + + // 设置请求头 + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Access-Id", c.accessID) + httpReq.Header.Set("User-Agent", "TianyuanAPI-Go-SDK/1.0.0") + + // 发送请求 + resp, err := c.client.Do(httpReq) + if err != nil { + endTime := time.Now() + requestTime := endTime.Sub(startTime).Milliseconds() + return &Response{ + Success: false, + Message: "请求失败", + Error: err.Error(), + Timeout: requestTime, + }, nil + } + defer resp.Body.Close() + + // 读取响应 + body, err := io.ReadAll(resp.Body) + if err != nil { + endTime := time.Now() + requestTime := endTime.Sub(startTime).Milliseconds() + return &Response{ + Success: false, + Message: "读取响应失败", + Error: err.Error(), + Timeout: requestTime, + }, nil + } + + // 解析HTTP API响应 + var apiResp ApiResponse + if err := json.Unmarshal(body, &apiResp); err != nil { + endTime := time.Now() + requestTime := endTime.Sub(startTime).Milliseconds() + return &Response{ + Success: false, + Message: "响应解析失败", + Error: err.Error(), + Timeout: requestTime, + }, nil + } + + // 计算请求耗时 + endTime := time.Now() + requestTime := endTime.Sub(startTime).Milliseconds() + + // 构建Call方法的响应 + response := &Response{ + Code: apiResp.Code, + Message: apiResp.Message, + Success: apiResp.Code == 0, + TransactionID: apiResp.TransactionID, + Timeout: requestTime, + } + + // 如果有加密数据,尝试解密 + if apiResp.Data != "" { + decryptedData, err := c.decrypt(apiResp.Data) + if err == nil { + var decryptedMap interface{} + if json.Unmarshal([]byte(decryptedData), &decryptedMap) == nil { + response.Data = decryptedMap + } + } + } + + // 根据响应码返回对应的错误 + if apiResp.Code != 0 { + err := GetErrorByCode(apiResp.Code) + return nil, err + } + + return response, nil +} + +// CallInterface 简化接口调用方法 +func (c *Client) CallInterface(interfaceName string, params map[string]interface{}, options ...*ApiCallOptions) (*Response, error) { + var opts *ApiCallOptions + if len(options) > 0 { + opts = options[0] + } + + req := Request{ + InterfaceName: interfaceName, + Params: params, + Timeout: 60000, + Options: opts, + } + + return c.Call(req) +} + +// validateRequest 校验请求参数 +func (c *Client) validateRequest(req Request) error { + if req.InterfaceName == "" { + return fmt.Errorf("interfaceName不能为空") + } + if req.Params == nil { + return fmt.Errorf("params不能为空") + } + return nil +} + +// encrypt AES CBC加密 +func (c *Client) encrypt(plainText string) (string, error) { + keyBytes, err := hex.DecodeString(c.key) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", err + } + + // 生成随机IV + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + // 填充数据 + paddedData := c.pkcs7Pad([]byte(plainText), aes.BlockSize) + + // 加密 + ciphertext := make([]byte, len(iv)+len(paddedData)) + copy(ciphertext, iv) + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[len(iv):], paddedData) + + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// decrypt AES CBC解密 +func (c *Client) decrypt(encryptedText string) (string, error) { + keyBytes, err := hex.DecodeString(c.key) + if err != nil { + return "", err + } + + ciphertext, err := base64.StdEncoding.DecodeString(encryptedText) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", err + } + + if len(ciphertext) < aes.BlockSize { + return "", fmt.Errorf("密文太短") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + if len(ciphertext)%aes.BlockSize != 0 { + return "", fmt.Errorf("密文长度不是块大小的倍数") + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + + // 去除填充 + unpaddedData, err := c.pkcs7Unpad(ciphertext) + if err != nil { + return "", err + } + + return string(unpaddedData), nil +} + +// pkcs7Pad PKCS7填充 +func (c *Client) pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +// pkcs7Unpad PKCS7去除填充 +func (c *Client) pkcs7Unpad(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, fmt.Errorf("数据为空") + } + unpadding := int(data[length-1]) + if unpadding > length { + return nil, fmt.Errorf("无效的填充") + } + return data[:length-unpadding], nil +} + +// GetErrorByCode 根据错误码获取错误 +func GetErrorByCode(code int) error { + // 对于有多个错误对应同一错误码的情况,返回第一个 + switch code { + case 1000: + return ErrQueryEmpty + case 1001: + return ErrSystem + case 1002: + return ErrDecryptFail + case 1003: + return ErrRequestParam + case 1004: + return ErrInvalidIP + case 1005: + return ErrMissingAccessId + case 1006: + return ErrInvalidAccessId + case 1007: + return ErrFrozenAccount + case 1008: + return ErrProductNotFound + case 2001: + return ErrBusiness + default: + return fmt.Errorf("未知错误码: %d", code) + } +} + +// GetCodeByError 根据错误获取错误码 +func GetCodeByError(err error) int { + if code, exists := ErrorCodeMap[err]; exists { + return code + } + return -1 +} diff --git a/app/main/api/internal/service/userService.go b/app/main/api/internal/service/userService.go new file mode 100644 index 0000000..a82192e --- /dev/null +++ b/app/main/api/internal/service/userService.go @@ -0,0 +1,296 @@ +package service + +import ( + "context" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + jwtx "bdrp-server/common/jwt" + "bdrp-server/common/xerr" + "database/sql" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type UserService struct { + Config *config.Config + userModel model.UserModel + userAuthModel model.UserAuthModel + userTempModel model.UserTempModel + agentModel model.AgentModel +} + +// NewUserService 创建UserService实例 +func NewUserService(config *config.Config, userModel model.UserModel, userAuthModel model.UserAuthModel, userTempModel model.UserTempModel, agentModel model.AgentModel) *UserService { + return &UserService{ + Config: config, + userModel: userModel, + userAuthModel: userAuthModel, + userTempModel: userTempModel, + agentModel: agentModel, + } +} + +// GenerateUUIDUserId 生成UUID用户ID +func (s *UserService) GenerateUUIDUserId(ctx context.Context) (string, error) { + id := uuid.NewString() + return id, nil +} + +// RegisterUUIDUser 注册UUID用户,返回用户ID +func (s *UserService) RegisterUUIDUser(ctx context.Context) (int64, error) { + // 生成UUID + uuidStr, err := s.GenerateUUIDUserId(ctx) + if err != nil { + return 0, err + } + + var userId int64 + err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建用户记录 + user := &model.User{} + result, err := s.userModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err = result.LastInsertId() + if err != nil { + return err + } + + // 创建用户认证记录 + userAuth := &model.UserAuth{ + UserId: userId, + AuthType: model.UserAuthTypeUUID, + AuthKey: uuidStr, + } + _, err = s.userAuthModel.Insert(ctx, session, userAuth) + return err + }) + if err != nil { + return 0, err + } + + return userId, nil +} + +// generalUserToken 生成用户token +func (s *UserService) GeneralUserToken(ctx context.Context, userID int64, userType int64) (string, error) { + platform, err := ctxdata.GetPlatformFromCtx(ctx) + if err != nil { + return "", err + } + + var isAgent int64 + var agentID int64 + if userType == model.UserTypeNormal { + agent, err := s.agentModel.FindOneByUserId(ctx, userID) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return "", err + } + if agent != nil { + agentID = agent.Id + isAgent = model.AgentStatusYes + } + } else { + userTemp, err := s.userTempModel.FindOne(ctx, userID) + if err != nil { + return "", err + } + if userTemp != nil { + userID = userTemp.Id + } + } + token, generaErr := jwtx.GenerateJwtToken(jwtx.JwtClaims{ + UserId: userID, + AgentId: agentID, + Platform: platform, + UserType: userType, + IsAgent: isAgent, + }, s.Config.JwtAuth.AccessSecret, s.Config.JwtAuth.AccessExpire) + if generaErr != nil { + return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新token, 生成token失败 : %d", userID) + } + return token, nil +} + +// RegisterUser 注册用户,返回用户ID +// 传入手机号,自动注册,如果ctx存在临时用户则临时用户转为正式用户 +func (s *UserService) RegisterUser(ctx context.Context, mobile string) (int64, error) { + claims, err := ctxdata.GetClaimsFromCtx(ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return 0, err + } + user, err := s.userModel.FindOneByMobile(ctx, sql.NullString{String: mobile, Valid: true}) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return 0, err + } + if user != nil { + return 0, errors.New("用户已注册") + } + // 普通注册 + if claims == nil { + var userId int64 + err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + user := &model.User{ + Mobile: sql.NullString{String: mobile, Valid: true}, + } + result, err := s.userModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err = result.LastInsertId() + if err != nil { + return err + } + s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: userId, + AuthType: model.UserAuthTypeMobile, + AuthKey: mobile, + }) + return nil + }) + if err != nil { + return 0, err + } + return userId, nil + } + + // 双重判断是否已经注册 + if claims.UserType == model.UserTypeNormal { + return 0, errors.New("用户已注册") + } + var userId int64 + // 临时转正式注册 + err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + user := &model.User{ + Mobile: sql.NullString{String: mobile, Valid: true}, + } + result, err := s.userModel.Insert(ctx, session, user) + if err != nil { + return err + } + userId, err = result.LastInsertId() + if err != nil { + return err + } + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: userId, + AuthType: model.UserAuthTypeMobile, + AuthKey: mobile, + }) + if err != nil { + return err + } + err = s.TempUserBindUser(ctx, session, userId) + if err != nil { + return err + } + return nil + }) + if err != nil { + return 0, err + } + return userId, nil +} + +// TempUserBindUser 临时用户绑定用户 +func (s *UserService) TempUserBindUser(ctx context.Context, session sqlx.Session, normalUserID int64) error { + claims, err := ctxdata.GetClaimsFromCtx(ctx) + if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) { + return err + } + + if claims == nil || claims.UserType != model.UserTypeTemp { + return errors.New("无临时用户") + } + + // 使用事务上下文查询临时用户 + userTemp, err := s.userTempModel.FindOne(ctx, claims.UserId) + if err != nil { + return err + } + + // 检查是否已经注册过 + userAuth, err := s.userAuthModel.FindOneByAuthTypeAuthKey(ctx, userTemp.AuthType, userTemp.AuthKey) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return err + } + if userAuth != nil { + return errors.New("临时用户已注册") + } + + if session == nil { + err := s.userAuthModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: normalUserID, + AuthType: userTemp.AuthType, + AuthKey: userTemp.AuthKey, + }) + if err != nil { + return err + } + + // 重新获取最新的userTemp数据,确保版本号是最新的 + latestUserTemp, err := s.userTempModel.FindOne(ctx, claims.UserId) + if err != nil { + return err + } + err = s.userTempModel.DeleteSoft(ctx, session, latestUserTemp) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + return nil + } else { + _, err = s.userAuthModel.Insert(ctx, session, &model.UserAuth{ + UserId: normalUserID, + AuthType: userTemp.AuthType, + AuthKey: userTemp.AuthKey, + }) + if err != nil { + return err + } + + // 重新获取最新的userTemp数据,确保版本号是最新的 + latestUserTemp, err := s.userTempModel.FindOne(ctx, claims.UserId) + if err != nil { + return err + } + err = s.userTempModel.DeleteSoft(ctx, session, latestUserTemp) + if err != nil { + return err + } + return nil + } +} + +// _bak_RegisterUUIDUser 注册UUID用户,返回用户ID +func (s *UserService) _bak_RegisterUUIDUser(ctx context.Context) error { + // 生成UUID + uuidStr, err := s.GenerateUUIDUserId(ctx) + if err != nil { + return err + } + + err = s.userTempModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + // 创建用户临时记录 + userTemp := &model.UserTemp{ + AuthType: model.UserAuthTypeUUID, + AuthKey: uuidStr, + } + _, err := s.userTempModel.Insert(ctx, session, userTemp) + return err + }) + if err != nil { + return err + } + + return nil +} diff --git a/app/main/api/internal/service/verificationService.go b/app/main/api/internal/service/verificationService.go new file mode 100644 index 0000000..36a9eba --- /dev/null +++ b/app/main/api/internal/service/verificationService.go @@ -0,0 +1,152 @@ +package service + +import ( + "bdrp-server/app/main/api/internal/config" + tianyuanapi "bdrp-server/app/main/api/internal/service/tianyuanapi_sdk" + "encoding/json" + "fmt" + + "github.com/tidwall/gjson" +) +type VerificationService struct { + c config.Config + tianyuanapi *tianyuanapi.Client + apiRequestService *ApiRequestService +} + +func NewVerificationService(c config.Config, tianyuanapi *tianyuanapi.Client, apiRequestService *ApiRequestService) *VerificationService { + return &VerificationService{ + c: c, + tianyuanapi: tianyuanapi, + apiRequestService: apiRequestService, + } +} + +// 二要素 +type TwoFactorVerificationRequest struct { + Name string + IDCard string +} +type TwoFactorVerificationResp struct { + Msg string `json:"msg"` + Success bool `json:"success"` + Code int `json:"code"` + Data *TwoFactorVerificationData `json:"data"` // +} +type TwoFactorVerificationData struct { + Birthday string `json:"birthday"` + Result int `json:"result"` + Address string `json:"address"` + OrderNo string `json:"orderNo"` + Sex string `json:"sex"` + Desc string `json:"desc"` +} + +// 三要素 +type ThreeFactorVerificationRequest struct { + Name string + IDCard string + Mobile string +} + +// VerificationResult 定义校验结果结构体 +type VerificationResult struct { + Passed bool + Err error +} + +// ValidationError 定义校验错误类型 +type ValidationError struct { + Message string +} + +func (e *ValidationError) Error() string { + return e.Message +} + +func (r *VerificationService) TwoFactorVerification(request TwoFactorVerificationRequest) (*VerificationResult, error) { + resp, err := r.tianyuanapi.CallInterface("YYSYBE08", map[string]interface{}{ + "name": request.Name, + "id_card": request.IDCard, + }) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + respBytes, err := json.Marshal(resp.Data) + if err != nil { + return nil, fmt.Errorf("转换响应失败: %v", err) + } + + // 使用gjson获取resultCode + resultCode := gjson.GetBytes(respBytes, "ctidRequest.ctidAuth.resultCode") + if !resultCode.Exists() { + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "获取resultCode失败"}, + }, nil + } + + // 获取resultCode的第一个字符 + resultCodeStr := resultCode.String() + if len(resultCodeStr) == 0 { + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "resultCode为空"}, + }, nil + } + + firstChar := string(resultCodeStr[0]) + if firstChar != "0" && firstChar != "5" { + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "姓名与身份证不一致"}, + }, nil + } + + return &VerificationResult{Passed: true, Err: nil}, nil +} + +func (r *VerificationService) ThreeFactorVerification(request ThreeFactorVerificationRequest) (*VerificationResult, error) { + resp, err := r.tianyuanapi.CallInterface("YYSY09CD", map[string]interface{}{ + "name": request.Name, + "id_card": request.IDCard, + "mobile_no": request.Mobile, + }) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + respBytes, err := json.Marshal(resp.Data) + if err != nil { + return nil, fmt.Errorf("转换响应失败: %v", err) + } + + // 解析data.code + code := gjson.GetBytes(respBytes, "code") + if !code.Exists() { + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "身份信息异常"}, + }, nil + } + + codeStr := code.String() + switch codeStr { + case "1000": + // 一致 + return &VerificationResult{Passed: true, Err: nil}, nil + case "1001": + // 不一致 + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "姓名、证件号、手机号信息不一致"}, + }, nil + default: + // 其他异常 + return &VerificationResult{ + Passed: false, + Err: &ValidationError{Message: "身份信息异常"}, + }, nil + } +} diff --git a/app/main/api/internal/service/wechatpayService.go b/app/main/api/internal/service/wechatpayService.go new file mode 100644 index 0000000..a84fdad --- /dev/null +++ b/app/main/api/internal/service/wechatpayService.go @@ -0,0 +1,403 @@ +package service + +import ( + "context" + "fmt" + "net/http" + "strconv" + "time" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/model" + "bdrp-server/common/ctxdata" + "bdrp-server/pkg/lzkit/lzUtils" + + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers" + "github.com/wechatpay-apiv3/wechatpay-go/core/downloader" + "github.com/wechatpay-apiv3/wechatpay-go/core/notify" + "github.com/wechatpay-apiv3/wechatpay-go/core/option" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/app" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" + "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" + "github.com/wechatpay-apiv3/wechatpay-go/utils" + "github.com/zeromicro/go-zero/core/logx" +) + +const ( + TradeStateSuccess = "SUCCESS" // 支付成功 + TradeStateRefund = "REFUND" // 转入退款 + TradeStateNotPay = "NOTPAY" // 未支付 + TradeStateClosed = "CLOSED" // 已关闭 + TradeStateRevoked = "REVOKED" // 已撤销(付款码支付) + TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付) + TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败) +) + +// InitType 初始化类型 +type InitType string + +const ( + InitTypePlatformCert InitType = "platform_cert" // 平台证书初始化 + InitTypeWxPayPubKey InitType = "wxpay_pubkey" // 微信支付公钥初始化 +) + +type WechatPayService struct { + config config.Config + wechatClient *core.Client + notifyHandler *notify.Handler + userAuthModel model.UserAuthModel +} + +// NewWechatPayService 创建微信支付服务实例 +func NewWechatPayService(c config.Config, userAuthModel model.UserAuthModel, initType InitType) *WechatPayService { + switch initType { + case InitTypePlatformCert: + return newWechatPayServiceWithPlatformCert(c, userAuthModel) + case InitTypeWxPayPubKey: + return newWechatPayServiceWithWxPayPubKey(c, userAuthModel) + default: + logx.Errorf("不支持的初始化类型: %s", initType) + panic(fmt.Sprintf("初始化失败,服务停止: %s", initType)) + } +} + +// newWechatPayServiceWithPlatformCert 使用平台证书初始化微信支付服务 +func newWechatPayServiceWithPlatformCert(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService { + // 从配置中加载商户信息 + mchID := c.Wxpay.MchID + mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber + mchAPIv3Key := c.Wxpay.MchApiv3Key + + // 从文件中加载商户私钥 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath) + if err != nil { + logx.Errorf("加载商户私钥失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序 + } + + // 使用商户私钥和其他参数初始化微信支付客户端 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(context.Background(), opts...) + if err != nil { + logx.Errorf("创建微信支付客户端失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序 + } + + // 在初始化时获取证书访问器并创建 notifyHandler + certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) + notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor)) + if err != nil { + logx.Errorf("获取证书访问器失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) + } + + logx.Infof("微信支付客户端初始化成功(平台证书方式)") + return &WechatPayService{ + config: c, + wechatClient: client, + notifyHandler: notifyHandler, + userAuthModel: userAuthModel, + } +} + +// newWechatPayServiceWithWxPayPubKey 使用微信支付公钥初始化微信支付服务 +func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService { + // 从配置中加载商户信息 + mchID := c.Wxpay.MchID + mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber + mchAPIv3Key := c.Wxpay.MchApiv3Key + mchPrivateKeyPath := c.Wxpay.MchPrivateKeyPath + mchPublicKeyID := c.Wxpay.MchPublicKeyID + mchPublicKeyPath := c.Wxpay.MchPublicKeyPath + // 从文件中加载商户私钥 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath(mchPrivateKeyPath) + if err != nil { + logx.Errorf("加载商户私钥失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) + } + + // 从文件中加载微信支付平台证书 + mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath) + if err != nil { + logx.Errorf("加载微信支付平台证书失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) + } + + // 使用商户私钥和其他参数初始化微信支付客户端 + opts := []core.ClientOption{ + option.WithWechatPayPublicKeyAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchPublicKeyID, mchPublicKey), + } + client, err := core.NewClient(context.Background(), opts...) + if err != nil { + logx.Errorf("创建微信支付客户端失败: %v", err) + panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) + } + + // 初始化 notify.Handler + certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) + notifyHandler := notify.NewNotifyHandler( + mchAPIv3Key, + verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, mchPublicKeyID, *mchPublicKey)) + + logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)") + return &WechatPayService{ + config: c, + wechatClient: client, + notifyHandler: notifyHandler, + userAuthModel: userAuthModel, + } +} + +// CreateWechatAppOrder 创建微信APP支付订单 +func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) { + totalAmount := lzUtils.ToWechatAmount(amount) + + // 构建支付请求参数 + payRequest := app.PrepayRequest{ + Appid: core.String(w.config.Wxpay.AppID), + Mchid: core.String(w.config.Wxpay.MchID), + Description: core.String(description), + OutTradeNo: core.String(outTradeNo), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), + Amount: &app.Amount{ + Total: core.Int64(totalAmount), + }, + } + + // 初始化 AppApiService + svc := app.AppApiService{Client: w.wechatClient} + + // 发起预支付请求 + resp, result, err := svc.Prepay(ctx, payRequest) + if err != nil { + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + } + + // 返回预支付交易会话标识 + return *resp.PrepayId, nil +} + +// CreateWechatMiniProgramOrder 创建微信小程序支付订单 +func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { + totalAmount := lzUtils.ToWechatAmount(amount) + + // 构建支付请求参数 + payRequest := jsapi.PrepayRequest{ + Appid: core.String(w.config.WechatMini.AppID), + Mchid: core.String(w.config.Wxpay.MchID), + Description: core.String(description), + OutTradeNo: core.String(outTradeNo), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), + Amount: &jsapi.Amount{ + Total: core.Int64(totalAmount), + }, + Payer: &jsapi.Payer{ + Openid: core.String(openid), // 用户的 OpenID,通过前端传入 + }} + + // 初始化 AppApiService + svc := jsapi.JsapiApiService{Client: w.wechatClient} + + // 发起预支付请求 + resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) + if err != nil { + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + } + // 显式转为 map,确保小程序 uni.requestPayment 能正确解析(避免指针序列化问题) + return jsapiRespToMap(resp), nil +} + +// jsapiRespToMap 将 PrepayWithRequestPaymentResponse 转为 map,供小程序/JSAPI 调起支付 +func jsapiRespToMap(resp *jsapi.PrepayWithRequestPaymentResponse) map[string]string { + m := make(map[string]string) + if resp == nil { + return m + } + if resp.Appid != nil { + m["appId"] = *resp.Appid + } + if resp.TimeStamp != nil { + m["timeStamp"] = *resp.TimeStamp + } + if resp.NonceStr != nil { + m["nonceStr"] = *resp.NonceStr + } + if resp.Package != nil { + m["package"] = *resp.Package + } + if resp.SignType != nil { + m["signType"] = *resp.SignType + } + if resp.PaySign != nil { + m["paySign"] = *resp.PaySign + } + return m +} + +// CreateWechatH5Order 创建微信H5支付订单 +func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { + totalAmount := lzUtils.ToWechatAmount(amount) + + // 构建支付请求参数 + payRequest := jsapi.PrepayRequest{ + Appid: core.String(w.config.WechatH5.AppID), + Mchid: core.String(w.config.Wxpay.MchID), + Description: core.String(description), + OutTradeNo: core.String(outTradeNo), + NotifyUrl: core.String(w.config.Wxpay.NotifyUrl), + Amount: &jsapi.Amount{ + Total: core.Int64(totalAmount), + }, + Payer: &jsapi.Payer{ + Openid: core.String(openid), // 用户的 OpenID,通过前端传入 + }} + + // 初始化 AppApiService + svc := jsapi.JsapiApiService{Client: w.wechatClient} + + // 发起预支付请求 + resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) + logx.Infof("微信h5支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) + if err != nil { + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + } + return jsapiRespToMap(resp), nil +} + +// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序) +func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) { + // 根据 ctx 中的 platform 判断平台 + platform := ctx.Value("platform").(string) + + var prepayData interface{} + var err error + + switch platform { + case model.PlatformWxMini: + userID, getUidErr := ctxdata.GetUidFromCtx(ctx) + if getUidErr != nil { + return "", getUidErr + } + userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID) + if findAuthModelErr != nil { + return "", findAuthModelErr + } + prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) + if err != nil { + return "", err + } + case model.PlatformWxH5: + userID, getUidErr := ctxdata.GetUidFromCtx(ctx) + if getUidErr != nil { + return "", getUidErr + } + userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) + if findAuthModelErr != nil { + return "", findAuthModelErr + } + prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) + if err != nil { + return "", err + } + case model.PlatformApp: + // 如果是 APP 平台,调用 APP 支付订单创建 + prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo) + default: + return "", fmt.Errorf("不支持的支付平台: %s", platform) + } + + // 如果创建支付订单失败,返回错误 + if err != nil { + return "", fmt.Errorf("支付订单创建失败: %v", err) + } + + // 返回预支付ID + return prepayData, nil +} + +// HandleWechatPayNotification 处理微信支付回调 +func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) { + transaction := new(payments.Transaction) + _, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction) + if err != nil { + return nil, fmt.Errorf("微信支付通知处理失败: %v", err) + } + // 返回交易信息 + return transaction, nil +} + +// HandleRefundNotification 处理微信退款回调 +func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) { + refund := new(refunddomestic.Refund) + _, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund) + if err != nil { + return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err) + } + return refund, nil +} + +// QueryOrderStatus 主动查询订单状态 +func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) { + svc := jsapi.JsapiApiService{Client: w.wechatClient} + + // 调用 QueryOrderById 方法查询订单状态 + resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{ + TransactionId: core.String(transactionID), + Mchid: core.String(w.config.Wxpay.MchID), + }) + if err != nil { + return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode) + } + return resp, nil + +} + +// WeChatRefund 申请微信退款 +func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error { + + // 生成唯一的退款单号 + outRefundNo := fmt.Sprintf("%s-refund", outTradeNo) + + // 初始化退款服务 + svc := refunddomestic.RefundsApiService{Client: w.wechatClient} + + // 创建退款请求 + resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{ + OutTradeNo: core.String(outTradeNo), + OutRefundNo: core.String(outRefundNo), + NotifyUrl: core.String(w.config.Wxpay.RefundNotifyUrl), + Amount: &refunddomestic.AmountReq{ + Currency: core.String("CNY"), + Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)), + Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)), + }, + }) + if err != nil { + return fmt.Errorf("微信订单申请退款错误: %v", err) + } + // 打印退款结果 + logx.Infof("退款申请成功,状态码=%d,退款单号=%s,微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId) + return nil +} + +// GenerateOutTradeNo 生成唯一订单号 +func (w *WechatPayService) GenerateOutTradeNo() string { + length := 16 + timestamp := time.Now().UnixNano() + timeStr := strconv.FormatInt(timestamp, 10) + randomPart := strconv.Itoa(int(timestamp % 1e6)) + combined := timeStr + randomPart + + if len(combined) >= length { + return combined[:length] + } + + for len(combined) < length { + combined += strconv.Itoa(int(timestamp % 10)) + } + + return combined +} diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go new file mode 100644 index 0000000..5e66e6c --- /dev/null +++ b/app/main/api/internal/svc/servicecontext.go @@ -0,0 +1,312 @@ +package svc + +import ( + "time" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/api/internal/middleware" + "bdrp-server/app/main/api/internal/service" + tianyuanapi "bdrp-server/app/main/api/internal/service/tianyuanapi_sdk" + "bdrp-server/app/main/model" + + "github.com/hibiken/asynq" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest" +) + +// ServiceContext 服务上下文 +type ServiceContext struct { + Config config.Config + Redis *redis.Redis + + // 中间件 + AuthInterceptor rest.Middleware + UserAuthInterceptor rest.Middleware + AdminAuthInterceptor rest.Middleware + + // 用户相关模型 + UserModel model.UserModel + UserAuthModel model.UserAuthModel + UserTempModel model.UserTempModel + + // 产品相关模型 + ProductModel model.ProductModel + FeatureModel model.FeatureModel + ProductFeatureModel model.ProductFeatureModel + + // 订单相关模型 + OrderModel model.OrderModel + OrderRefundModel model.OrderRefundModel + QueryModel model.QueryModel + QueryUserRecordModel model.QueryUserRecordModel + QueryCleanupLogModel model.QueryCleanupLogModel + QueryCleanupDetailModel model.QueryCleanupDetailModel + QueryCleanupConfigModel model.QueryCleanupConfigModel + + // 代理相关模型 + AgentModel model.AgentModel + AgentAuditModel model.AgentAuditModel + AgentClosureModel model.AgentClosureModel + AgentCommissionModel model.AgentCommissionModel + AgentCommissionDeductionModel model.AgentCommissionDeductionModel + AgentWalletModel model.AgentWalletModel + AgentLinkModel model.AgentLinkModel + AgentOrderModel model.AgentOrderModel + AgentRewardsModel model.AgentRewardsModel + AgentMembershipConfigModel model.AgentMembershipConfigModel + AgentMembershipRechargeOrderModel model.AgentMembershipRechargeOrderModel + AgentMembershipUserConfigModel model.AgentMembershipUserConfigModel + AgentProductConfigModel model.AgentProductConfigModel + AgentPlatformDeductionModel model.AgentPlatformDeductionModel + AgentActiveStatModel model.AgentActiveStatModel + AgentWithdrawalModel model.AgentWithdrawalModel + AgentRealNameModel model.AgentRealNameModel + AgentWithdrawalTaxModel model.AgentWithdrawalTaxModel + AgentWithdrawalTaxExemptionModel model.AgentWithdrawalTaxExemptionModel + AgentWalletTransactionModel model.AgentWalletTransactionModel + + // 管理后台相关模型 + AdminApiModel model.AdminApiModel + AdminMenuModel model.AdminMenuModel + AdminRoleModel model.AdminRoleModel + AdminRoleApiModel model.AdminRoleApiModel + AdminRoleMenuModel model.AdminRoleMenuModel + AdminUserModel model.AdminUserModel + AdminUserRoleModel model.AdminUserRoleModel + AdminDictDataModel model.AdminDictDataModel + AdminDictTypeModel model.AdminDictTypeModel + AdminPromotionLinkModel model.AdminPromotionLinkModel + AdminPromotionLinkStatsTotalModel model.AdminPromotionLinkStatsTotalModel + AdminPromotionLinkStatsHistoryModel model.AdminPromotionLinkStatsHistoryModel + AdminPromotionOrderModel model.AdminPromotionOrderModel + + // 其他模型 + ExampleModel model.ExampleModel + GlobalNotificationsModel model.GlobalNotificationsModel + AuthorizationDocumentModel model.AuthorizationDocumentModel + + // 服务 + 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 + AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService + ImageService *service.ImageService + AuthorizationService *service.AuthorizationService +} + +// NewServiceContext 创建服务上下文 +func NewServiceContext(c config.Config) *ServiceContext { + // ============================== 基础设施初始化 ============================== + db := sqlx.NewMysql(c.DataSource) + cacheConf := c.CacheRedis + + // 初始化Redis客户端 + redisConf := redis.RedisConf{ + Host: cacheConf[0].Host, + Pass: cacheConf[0].Pass, + Type: cacheConf[0].Type, + } + redisClient := redis.MustNewRedis(redisConf) + + // ============================== 用户相关模型 ============================== + userModel := model.NewUserModel(db, cacheConf) + userAuthModel := model.NewUserAuthModel(db, cacheConf) + userTempModel := model.NewUserTempModel(db, cacheConf) + + // ============================== 产品相关模型 ============================== + productModel := model.NewProductModel(db, cacheConf) + featureModel := model.NewFeatureModel(db, cacheConf) + productFeatureModel := model.NewProductFeatureModel(db, cacheConf) + + // ============================== 订单相关模型 ============================== + orderModel := model.NewOrderModel(db, cacheConf) + queryModel := model.NewQueryModel(db, cacheConf) + queryUserRecordModel := model.NewQueryUserRecordModel(db, cacheConf) + orderRefundModel := model.NewOrderRefundModel(db, cacheConf) + queryCleanupLogModel := model.NewQueryCleanupLogModel(db, cacheConf) + queryCleanupDetailModel := model.NewQueryCleanupDetailModel(db, cacheConf) + queryCleanupConfigModel := model.NewQueryCleanupConfigModel(db, cacheConf) + + // ============================== 代理相关模型 ============================== + agentModel := model.NewAgentModel(db, cacheConf) + agentAuditModel := model.NewAgentAuditModel(db, cacheConf) + agentClosureModel := model.NewAgentClosureModel(db, cacheConf) + agentCommissionModel := model.NewAgentCommissionModel(db, cacheConf) + agentCommissionDeductionModel := model.NewAgentCommissionDeductionModel(db, cacheConf) + agentWalletModel := model.NewAgentWalletModel(db, cacheConf) + agentLinkModel := model.NewAgentLinkModel(db, cacheConf) + agentOrderModel := model.NewAgentOrderModel(db, cacheConf) + agentRewardsModel := model.NewAgentRewardsModel(db, cacheConf) + agentMembershipConfigModel := model.NewAgentMembershipConfigModel(db, cacheConf) + agentMembershipRechargeOrderModel := model.NewAgentMembershipRechargeOrderModel(db, cacheConf) + agentMembershipUserConfigModel := model.NewAgentMembershipUserConfigModel(db, cacheConf) + agentProductConfigModel := model.NewAgentProductConfigModel(db, cacheConf) + agentPlatformDeductionModel := model.NewAgentPlatformDeductionModel(db, cacheConf) + agentActiveStatModel := model.NewAgentActiveStatModel(db, cacheConf) + agentWithdrawalModel := model.NewAgentWithdrawalModel(db, cacheConf) + agentRealNameModel := model.NewAgentRealNameModel(db, cacheConf) + agentWithdrawalTaxModel := model.NewAgentWithdrawalTaxModel(db, cacheConf) + agentWithdrawalTaxExemptionModel := model.NewAgentWithdrawalTaxExemptionModel(db, cacheConf) + agentWalletTransactionModel := model.NewAgentWalletTransactionModel(db, cacheConf) + // ============================== 管理后台相关模型 ============================== + adminApiModel := model.NewAdminApiModel(db, cacheConf) + adminMenuModel := model.NewAdminMenuModel(db, cacheConf) + adminRoleModel := model.NewAdminRoleModel(db, cacheConf) + adminRoleApiModel := model.NewAdminRoleApiModel(db, cacheConf) + adminRoleMenuModel := model.NewAdminRoleMenuModel(db, cacheConf) + adminUserModel := model.NewAdminUserModel(db, cacheConf) + adminUserRoleModel := model.NewAdminUserRoleModel(db, cacheConf) + adminDictDataModel := model.NewAdminDictDataModel(db, cacheConf) + adminDictTypeModel := model.NewAdminDictTypeModel(db, cacheConf) + adminPromotionLinkModel := model.NewAdminPromotionLinkModel(db, cacheConf) + adminPromotionLinkStatsTotalModel := model.NewAdminPromotionLinkStatsTotalModel(db, cacheConf) + adminPromotionLinkStatsHistoryModel := model.NewAdminPromotionLinkStatsHistoryModel(db, cacheConf) + adminPromotionOrderModel := model.NewAdminPromotionOrderModel(db, cacheConf) + + // ============================== 其他模型 ============================== + exampleModel := model.NewExampleModel(db, cacheConf) + globalNotificationsModel := model.NewGlobalNotificationsModel(db, cacheConf) + authorizationDocumentModel := model.NewAuthorizationDocumentModel(db, cacheConf) + + // ============================== 第三方服务初始化 ============================== + tianyuanapi, err := tianyuanapi.NewClient(tianyuanapi.Config{ + AccessID: c.Tianyuanapi.AccessID, + Key: c.Tianyuanapi.Key, + BaseURL: c.Tianyuanapi.BaseURL, + Timeout: time.Duration(c.Tianyuanapi.Timeout) * time.Second, + }) + if err != nil { + logx.Errorf("初始化天远API失败: %+v", err) + } + + // ============================== 业务服务初始化 ============================== + alipayService := service.NewAliPayService(c) + wechatPayService := service.NewWechatPayService(c, userAuthModel, service.InitTypeWxPayPubKey) + applePayService := service.NewApplePayService(c) + apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi) + verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService) + asynqService := service.NewAsynqService(c) + agentService := service.NewAgentService(c, orderModel, agentModel, agentAuditModel, agentClosureModel, + agentCommissionModel, agentCommissionDeductionModel, agentWalletModel, agentLinkModel, + agentOrderModel, agentRewardsModel, agentMembershipConfigModel, agentMembershipRechargeOrderModel, + agentMembershipUserConfigModel, agentProductConfigModel, agentPlatformDeductionModel, + agentActiveStatModel, agentWithdrawalModel, agentWalletTransactionModel, asynqService) + userService := service.NewUserService(&c, userModel, userAuthModel, userTempModel, agentModel) + dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel) + adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel, + adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel) + imageService := service.NewImageService() + authorizationService := service.NewAuthorizationService(c, authorizationDocumentModel) + + // ============================== 异步任务服务 ============================== + asynqServer := asynq.NewServer( + asynq.RedisClientOpt{Addr: c.CacheRedis[0].Host, Password: c.CacheRedis[0].Pass}, + asynq.Config{ + IsFailure: func(err error) bool { + logx.Errorf("异步任务失败: %+v \n", err) + return true + }, + Concurrency: 10, + }, + ) + + // ============================== 返回服务上下文 ============================== + return &ServiceContext{ + Config: c, + Redis: redisClient, + AuthInterceptor: middleware.NewAuthInterceptorMiddleware(c).Handle, + UserAuthInterceptor: middleware.NewUserAuthInterceptorMiddleware(userModel).Handle, + AdminAuthInterceptor: middleware.NewAdminAuthInterceptorMiddleware(c, + adminUserModel, adminUserRoleModel, adminRoleModel, adminApiModel, adminRoleApiModel).Handle, + + // 用户相关模型 + UserModel: userModel, + UserAuthModel: userAuthModel, + UserTempModel: userTempModel, + + // 产品相关模型 + ProductModel: productModel, + FeatureModel: featureModel, + ProductFeatureModel: productFeatureModel, + + // 订单相关模型 + OrderModel: orderModel, + QueryModel: queryModel, + QueryUserRecordModel: queryUserRecordModel, + OrderRefundModel: orderRefundModel, + QueryCleanupLogModel: queryCleanupLogModel, + QueryCleanupDetailModel: queryCleanupDetailModel, + QueryCleanupConfigModel: queryCleanupConfigModel, + + // 代理相关模型 + AgentModel: agentModel, + AgentAuditModel: agentAuditModel, + AgentClosureModel: agentClosureModel, + AgentCommissionModel: agentCommissionModel, + AgentCommissionDeductionModel: agentCommissionDeductionModel, + AgentWalletModel: agentWalletModel, + AgentLinkModel: agentLinkModel, + AgentOrderModel: agentOrderModel, + AgentRewardsModel: agentRewardsModel, + AgentMembershipConfigModel: agentMembershipConfigModel, + AgentMembershipRechargeOrderModel: agentMembershipRechargeOrderModel, + AgentMembershipUserConfigModel: agentMembershipUserConfigModel, + AgentProductConfigModel: agentProductConfigModel, + AgentPlatformDeductionModel: agentPlatformDeductionModel, + AgentActiveStatModel: agentActiveStatModel, + AgentWithdrawalModel: agentWithdrawalModel, + AgentRealNameModel: agentRealNameModel, + AgentWithdrawalTaxModel: agentWithdrawalTaxModel, + AgentWithdrawalTaxExemptionModel: agentWithdrawalTaxExemptionModel, + AgentWalletTransactionModel: agentWalletTransactionModel, + + // 管理后台相关模型 + AdminApiModel: adminApiModel, + AdminMenuModel: adminMenuModel, + AdminRoleModel: adminRoleModel, + AdminRoleApiModel: adminRoleApiModel, + AdminRoleMenuModel: adminRoleMenuModel, + AdminUserModel: adminUserModel, + AdminUserRoleModel: adminUserRoleModel, + AdminDictDataModel: adminDictDataModel, + AdminDictTypeModel: adminDictTypeModel, + AdminPromotionLinkModel: adminPromotionLinkModel, + AdminPromotionLinkStatsTotalModel: adminPromotionLinkStatsTotalModel, + AdminPromotionLinkStatsHistoryModel: adminPromotionLinkStatsHistoryModel, + AdminPromotionOrderModel: adminPromotionOrderModel, + + // 其他模型 + ExampleModel: exampleModel, + GlobalNotificationsModel: globalNotificationsModel, + AuthorizationDocumentModel: authorizationDocumentModel, + + // 服务 + AlipayService: alipayService, + WechatPayService: wechatPayService, + ApplePayService: applePayService, + ApiRequestService: apiRequestService, + AsynqServer: asynqServer, + AsynqService: asynqService, + VerificationService: verificationService, + AgentService: agentService, + UserService: userService, + DictService: dictService, + AdminPromotionLinkStatsService: adminPromotionLinkStatsService, + ImageService: imageService, + AuthorizationService: authorizationService, + } +} + +func (s *ServiceContext) Close() { + if s.AsynqService != nil { + s.AsynqService.Close() + } +} diff --git a/app/main/api/internal/types/cache.go b/app/main/api/internal/types/cache.go new file mode 100644 index 0000000..ecd68cc --- /dev/null +++ b/app/main/api/internal/types/cache.go @@ -0,0 +1,20 @@ +package types + +const QueryCacheKey = "query:%d:%s" +const AgentVipCacheKey = "agentVip:%d:%s" + +type QueryCache struct { + Name string `json:"name"` + IDCard string `json:"id_card"` + Mobile string `json:"mobile"` + Product string `json:"product_id"` +} +type QueryCacheLoad struct { + Product string `json:"product_en"` + Params string `json:"params"` + AgentIdentifier string `json:"agent_dentifier"` +} + +type AgentVipCache struct { + Type string `json:"type"` +} diff --git a/app/main/api/internal/types/encrypPayload.go b/app/main/api/internal/types/encrypPayload.go new file mode 100644 index 0000000..3c6e385 --- /dev/null +++ b/app/main/api/internal/types/encrypPayload.go @@ -0,0 +1,6 @@ +package types + +type QueryShareLinkPayload struct { + OrderId int64 `json:"order_id"` + ExpireAt int64 `json:"expire_at"` +} diff --git a/app/main/api/internal/types/payload.go b/app/main/api/internal/types/payload.go new file mode 100644 index 0000000..6e0310b --- /dev/null +++ b/app/main/api/internal/types/payload.go @@ -0,0 +1,9 @@ +package types + +type MsgPaySuccessQueryPayload struct { + OrderID int64 `json:"order_id"` +} + +type MsgUnfreezeCommissionPayload struct { + CommissionID int64 `json:"commission_id"` +} diff --git a/app/main/api/internal/types/query.go b/app/main/api/internal/types/query.go new file mode 100644 index 0000000..2712197 --- /dev/null +++ b/app/main/api/internal/types/query.go @@ -0,0 +1,119 @@ +package types + +type MarriageReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} +type HomeServiceReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +// RiskAssessment 查询请求结构 +type RiskAssessmentReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +// CompanyInfo 查询请求结构 +type CompanyInfoReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + // Code string `json:"code" validate:"required"` // 暂不校验验证码 +} + +// RentalInfo 查询请求结构 +type RentalInfoReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +// PreLoanBackgroundCheck 查询请求结构 +type PreLoanBackgroundCheckReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +// BackgroundCheck 查询请求结构 +type BackgroundCheckReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} +type PersonalDataReq struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} +type EntLawsuitReq struct { + EntName string `json:"ent_name" validate:"required,name"` + EntCode string `json:"ent_code" validate:"required,USCI"` + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} +type TocPhoneThreeElements struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` +} +type TocPhoneTwoElements struct { + Name string `json:"name" validate:"required,name"` + Mobile string `json:"mobile" validate:"required,mobile"` +} +type TocIDCardTwoElements struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` +} +type TocDualMarriage struct { + NameMan string `json:"name_man" validate:"required,name"` + IDCardMan string `json:"id_card_man" validate:"required,idCard"` + NameWoman string `json:"name_woman" validate:"required,name"` + IDCardWoman string `json:"id_card_woman" validate:"required,idCard"` +} +type TocPersonVehicleVerification struct { + Name string `json:"name" validate:"required,name"` + CarType string `json:"car_type" validate:"required"` + CarLicense string `json:"car_license" validate:"required"` +} + +// 银行卡黑名单 +type TocBankCardBlacklist struct { + Name string `json:"name" validate:"required,name"` + IDCard string `json:"id_card" validate:"required,idCard"` + Mobile string `json:"mobile" validate:"required,mobile"` + BankCard string `json:"bank_card" validate:"required"` +} + +// 手机号码风险 +type TocPhoneNumberRisk struct { + Mobile string `json:"mobile" validate:"required,mobile"` +} + +// 手机二次卡 +type TocPhoneSecondaryCard struct { + Mobile string `json:"mobile" validate:"required,mobile"` + StartDate string `json:"start_date" validate:"required"` +} + +type AgentQueryData struct { + Mobile string `json:"mobile"` + Code string `json:"code"` +} +type AgentIdentifier struct { + Product string `json:"product"` + AgentID int64 `json:"agent_id"` + Price string `json:"price"` +} diff --git a/app/main/api/internal/types/queryMap.go b/app/main/api/internal/types/queryMap.go new file mode 100644 index 0000000..13256a6 --- /dev/null +++ b/app/main/api/internal/types/queryMap.go @@ -0,0 +1,47 @@ +package types + +// 特殊名单 G26BJ05 +var G26BJ05FieldMapping = map[string]string{ + "IDCard": "id", + "Name": "name", + "Mobile": "cell", + "TimeRange": "time_range", +} + +// 个人不良 +var G34BJ03FieldMapping = map[string]string{ + "IDCard": "id_card", + "Name": "name", +} + +// 个人涉诉 G35SC01 +var G35SC01FieldMapping = map[string]string{ + "Name": "name", + "IDCard": "idcard", + "InquiredAuth": "inquired_auth", +} + +// 单人婚姻 G09SC02 +var G09SC02FieldMapping = map[string]string{ + "IDCard": "certNumMan", + "Name": "nameMan", +} + +// 借贷意向 G27BJ05 +var G27BJ05FieldMapping = map[string]string{ + "IDCard": "id", + "Name": "name", + "Mobile": "cell", +} + +// 借贷行为 G28BJ05 +var G28BJ05FieldMapping = map[string]string{ + "IDCard": "id", + "Name": "name", + "Mobile": "cell", +} + +// 股东人企关系精准版 G05HZ01 +var G05HZ01FieldMapping = map[string]string{ + "IDCard": "pid", +} diff --git a/app/main/api/internal/types/queryParams.go b/app/main/api/internal/types/queryParams.go new file mode 100644 index 0000000..1c243d5 --- /dev/null +++ b/app/main/api/internal/types/queryParams.go @@ -0,0 +1,73 @@ +package types + +type WestDexServiceRequestParams struct { + FieldMapping map[string]string + ApiID string +} + +var WestDexParams = map[string][]WestDexServiceRequestParams{ + "marriage": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + + }, + "backgroundcheck": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, + "companyinfo": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, + "homeservice": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, + "preloanbackgroundcheck": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, + "rentalinfo": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, + "riskassessment": { + {FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻 + {FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向 + {FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为 + {FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单 + {FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系 + {FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良 + {FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉 + }, +} diff --git a/app/main/api/internal/types/taskname.go b/app/main/api/internal/types/taskname.go new file mode 100644 index 0000000..714bae6 --- /dev/null +++ b/app/main/api/internal/types/taskname.go @@ -0,0 +1,5 @@ +package types + +const MsgPaySuccessQuery = "msg:pay_success:query" +const MsgCleanQueryData = "msg:clean_query_data" +const MsgUnfreezeCommission = "msg:unfreeze_commission" diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go new file mode 100644 index 0000000..f18ff43 --- /dev/null +++ b/app/main/api/internal/types/types.go @@ -0,0 +1,2226 @@ +// Code generated by goctl. DO NOT EDIT. +package types + +type ActiveReward struct { + TotalReward float64 `json:"total_reward"` + Today ActiveRewardData `json:"today"` // 今日数据 + Last7D ActiveRewardData `json:"last7d"` // 近7天数据 + Last30D ActiveRewardData `json:"last30d"` // 近30天数据 +} + +type ActiveRewardData struct { + NewActiveReward float64 `json:"active_reward"` + SubPromoteReward float64 `json:"sub_promote_reward"` + SubUpgradeReward float64 `json:"sub_upgrade_reward"` + SubWithdrawReward float64 `json:"sub_withdraw_reward"` +} + +type AdminApiInfo struct { + Id int64 `json:"id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` +} + +type AdminAssignRoleApiReq struct { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` +} + +type AdminAssignRoleApiResp struct { + Success bool `json:"success"` +} + +type AdminBatchUnfreezeAgentCommissionReq struct { + AgentId *int64 `json:"agent_id,optional"` // 代理ID,可选。如果不传则解冻所有冻结中的佣金 +} + +type AdminBatchUnfreezeAgentCommissionResp struct { + Success bool `json:"success"` // 是否成功 + Count int64 `json:"count"` // 解冻的数量 + Amount float64 `json:"amount"` // 解冻的总金额 +} + +type AdminBatchUpdateApiStatusReq struct { + Ids []int64 `json:"ids"` + Status int64 `json:"status"` +} + +type AdminBatchUpdateApiStatusResp struct { + Success bool `json:"success"` +} + +type AdminConfigFeatureExampleReq struct { + FeatureId int64 `json:"feature_id"` // 功能ID + Data string `json:"data"` // 示例数据JSON +} + +type AdminConfigFeatureExampleResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminCreateApiReq struct { + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status,default=1"` + Description string `json:"description,optional"` +} + +type AdminCreateApiResp struct { + Id int64 `json:"id"` +} + +type AdminCreateFeatureReq struct { + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 +} + +type AdminCreateFeatureResp struct { + Id int64 `json:"id"` // 功能ID +} + +type AdminCreateNotificationReq struct { + Title string `json:"title"` // 通知标题 + NotificationPage string `json:"notification_page"` // 通知页面 + Content string `json:"content"` // 通知内容 + StartDate string `json:"start_date"` // 生效开始日期(yyyy-MM-dd) + StartTime string `json:"start_time"` // 生效开始时间(HH:mm:ss) + EndDate string `json:"end_date"` // 生效结束日期(yyyy-MM-dd) + EndTime string `json:"end_time"` // 生效结束时间(HH:mm:ss) + Status int64 `json:"status"` // 状态:1-启用,0-禁用 +} + +type AdminCreateNotificationResp struct { + Id int64 `json:"id"` // 通知ID +} + +type AdminCreateOrderReq struct { + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + Status string `json:"status,default=pending"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + IsPromotion int64 `json:"is_promotion,default=0"` // 是否推广订单:0-否,1-是 +} + +type AdminCreateOrderResp struct { + Id int64 `json:"id"` // 订单ID +} + +type AdminCreatePlatformUserReq struct { + Mobile string `json:"mobile"` // 手机号 + Password string `json:"password"` // 密码 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 +} + +type AdminCreatePlatformUserResp struct { + Id int64 `json:"id"` // 用户ID +} + +type AdminCreateProductReq struct { + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes,optional"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 +} + +type AdminCreateProductResp struct { + Id int64 `json:"id"` // 产品ID +} + +type AdminCreateUserReq struct { + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status,default=1"` // 状态:0-禁用,1-启用 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 +} + +type AdminCreateUserResp struct { + Id int64 `json:"id"` // 用户ID +} + +type AdminDeleteApiReq struct { + Id int64 `path:"id"` +} + +type AdminDeleteApiResp struct { + Success bool `json:"success"` +} + +type AdminDeleteFeatureReq struct { + Id int64 `path:"id"` // 功能ID +} + +type AdminDeleteFeatureResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminDeleteNotificationReq struct { + Id int64 `path:"id"` // 通知ID +} + +type AdminDeleteNotificationResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminDeleteOrderReq struct { + Id int64 `path:"id"` // 订单ID +} + +type AdminDeleteOrderResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminDeletePlatformUserReq struct { + Id int64 `path:"id"` // 用户ID +} + +type AdminDeletePlatformUserResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminDeleteProductReq struct { + Id int64 `path:"id"` // 产品ID +} + +type AdminDeleteProductResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminDeleteUserReq struct { + Id int64 `path:"id"` // 用户ID +} + +type AdminDeleteUserResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminGetAgentCommissionDeductionListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Type *string `form:"type,optional"` // 类型(cost/pricing,可选) + Status *int64 `form:"status,optional"` // 状态(可选) +} + +type AdminGetAgentCommissionDeductionListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentCommissionDeductionListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentCommissionListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + OrderId *int64 `form:"order_id,optional"` // 订单ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + CreateTimeStart *string `form:"create_time_start,optional"` // 创建时间开始(可选) + CreateTimeEnd *string `form:"create_time_end,optional"` // 创建时间结束(可选) +} + +type AdminGetAgentCommissionListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentCommissionListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentLinkListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + ProductName *string `form:"product_name,optional"` // 产品名(可选) + LinkIdentifier *string `form:"link_identifier,optional"` // 推广码(可选) +} + +type AdminGetAgentLinkListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentLinkListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentLinkProductStatisticsReq struct { +} + +type AdminGetAgentLinkProductStatisticsResp struct { + Items []AgentLinkProductStatisticsItem `json:"items"` // 列表数据 +} + +type AdminGetAgentListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + Mobile *string `form:"mobile,optional"` // 手机号(可选) + Region *string `form:"region,optional"` // 区域(可选) + ParentAgentId *int64 `form:"parent_agent_id,optional"` // 上级代理ID(可选) +} + +type AdminGetAgentListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentMembershipConfigListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + LevelName *string `form:"level_name,optional"` // 会员级别名称(可选) +} + +type AdminGetAgentMembershipConfigListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentMembershipConfigListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentMembershipRechargeOrderListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + UserId *int64 `form:"user_id,optional"` // 用户ID(可选) + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + OrderNo *string `form:"order_no,optional"` // 订单号(可选) + PlatformOrderId *string `form:"platform_order_id,optional"` // 平台订单号(可选) + Status *string `form:"status,optional"` // 状态(可选) + PaymentMethod *string `form:"payment_method,optional"` // 支付方式(可选) +} + +type AdminGetAgentMembershipRechargeOrderListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentMembershipRechargeOrderListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentOrderStatisticsReq struct { +} + +type AdminGetAgentOrderStatisticsResp struct { + TotalAgentOrderCount int64 `json:"total_agent_order_count"` // 总代理订单数 + TodayAgentOrderCount int64 `json:"today_agent_order_count"` // 今日代理订单数 +} + +type AdminGetAgentPlatformDeductionListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + Type *string `form:"type,optional"` // 类型(cost/pricing,可选) + Status *int64 `form:"status,optional"` // 状态(可选) +} + +type AdminGetAgentPlatformDeductionListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentPlatformDeductionListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentProductionConfigListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ProductName *string `form:"product_name,optional"` // 产品名(可选) + Id *int64 `form:"id,optional"` // 配置ID(可选) +} + +type AdminGetAgentProductionConfigListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentProductionConfigItem `json:"items"` // 列表数据 +} + +type AdminGetAgentRewardListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + RelationAgentId *int64 `form:"relation_agent_id,optional"` // 关联代理ID(可选) + Type *string `form:"type,optional"` // 奖励类型(可选) +} + +type AdminGetAgentRewardListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentRewardListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentStatisticsReq struct { +} + +type AdminGetAgentStatisticsResp struct { + TotalAgentCount int64 `json:"total_agent_count"` // 总代理数 + TodayAgentCount int64 `json:"today_agent_count"` // 今日新增代理数 +} + +type AdminGetAgentWalletReq struct { + AgentId int64 `path:"agent_id"` // 代理ID +} + +type AdminGetAgentWalletResp struct { + Balance float64 `json:"balance"` // 可用余额 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + TotalEarnings float64 `json:"total_earnings"` // 总收益 +} + +type AdminGetAgentWalletTransactionListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId int64 `form:"agent_id"` // 代理ID + TransactionType *string `form:"transaction_type,optional"` // 交易类型(可选) + CreateTimeStart *string `form:"create_time_start,optional"` // 创建时间开始(可选) + CreateTimeEnd *string `form:"create_time_end,optional"` // 创建时间结束(可选) +} + +type AdminGetAgentWalletTransactionListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentWalletTransactionListItem `json:"items"` // 列表数据 +} + +type AdminGetAgentWithdrawalListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + AgentId *int64 `form:"agent_id,optional"` // 代理ID(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + WithdrawNo *string `form:"withdraw_no,optional"` // 提现单号(可选) + WithdrawType *int64 `form:"withdraw_type,optional"` // 提现类型(可选)1-支付宝,2-银行卡 +} + +type AdminGetAgentWithdrawalListResp struct { + Total int64 `json:"total"` // 总数 + Items []AgentWithdrawalListItem `json:"items"` // 列表数据 +} + +type AdminGetAllApiListReq struct { + Status int64 `form:"status,optional,default=1"` +} + +type AdminGetAllApiListResp struct { + Items []AdminRoleApiInfo `json:"items"` +} + +type AdminGetApiDetailReq struct { + Id int64 `path:"id"` +} + +type AdminGetApiDetailResp struct { + AdminApiInfo +} + +type AdminGetApiListReq struct { + Page int64 `form:"page,default=1"` + PageSize int64 `form:"page_size,default=20"` + ApiName string `form:"api_name,optional"` + Method string `form:"method,optional"` + Status int64 `form:"status,optional"` +} + +type AdminGetApiListResp struct { + Items []AdminApiInfo `json:"items"` + Total int64 `json:"total"` +} + +type AdminGetFeatureDetailReq struct { + Id int64 `path:"id"` // 功能ID +} + +type AdminGetFeatureDetailResp struct { + Id int64 `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetFeatureExampleReq struct { + FeatureId int64 `path:"feature_id"` // 功能ID +} + +type AdminGetFeatureExampleResp struct { + Id int64 `json:"id"` // 示例数据ID + FeatureId int64 `json:"feature_id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Data string `json:"data"` // 示例数据JSON + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetFeatureListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ApiId *string `form:"api_id,optional"` // API标识 + Name *string `form:"name,optional"` // 描述 +} + +type AdminGetFeatureListResp struct { + Total int64 `json:"total"` // 总数 + Items []FeatureListItem `json:"items"` // 列表数据 +} + +type AdminGetNotificationDetailReq struct { + Id int64 `path:"id"` // 通知ID +} + +type AdminGetNotificationDetailResp struct { + Id int64 `json:"id"` // 通知ID + Title string `json:"title"` // 通知标题 + Content string `json:"content"` // 通知内容 + NotificationPage string `json:"notification_page"` // 通知页面 + StartDate string `json:"start_date"` // 生效开始日期 + StartTime string `json:"start_time"` // 生效开始时间 + EndDate string `json:"end_date"` // 生效结束日期 + EndTime string `json:"end_time"` // 生效结束时间 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetNotificationListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + Title *string `form:"title,optional"` // 通知标题(可选) + NotificationPage *string `form:"notification_page,optional"` // 通知页面(可选) + Status *int64 `form:"status,optional"` // 状态(可选) + StartDate *string `form:"start_date,optional"` // 开始日期范围(可选) + EndDate *string `form:"end_date,optional"` // 结束日期范围(可选) +} + +type AdminGetNotificationListResp struct { + Total int64 `json:"total"` // 总数 + Items []NotificationListItem `json:"items"` // 列表数据 +} + +type AdminGetOrderDetailReq struct { + Id int64 `path:"id"` // 订单ID +} + +type AdminGetOrderDetailResp struct { + Id int64 `json:"id"` // 订单ID + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + SalesCost float64 `json:"sales_cost"` // 成本价 + Status string `json:"status"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + QueryState string `json:"query_state"` // 查询状态:pending-待查询,success-查询成功,failed-查询失败 processing-查询中 + CreateTime string `json:"create_time"` // 创建时间 + PayTime string `json:"pay_time"` // 支付时间 + RefundTime string `json:"refund_time"` // 退款时间 + IsPromotion int64 `json:"is_promotion"` // 是否推广订单:0-否,1-是 + UpdateTime string `json:"update_time"` // 更新时间 + IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 + AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理 +} + +type AdminGetOrderListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + OrderNo string `form:"order_no,optional"` // 商户订单号 + PlatformOrderId string `form:"platform_order_id,optional"` // 支付订单号 + ProductName string `form:"product_name,optional"` // 产品名称 + PaymentPlatform string `form:"payment_platform,optional"` // 支付方式 + PaymentScene string `form:"payment_scene,optional"` // 支付平台 + Amount float64 `form:"amount,optional"` // 金额 + Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + IsPromotion int64 `form:"is_promotion,optional,default=-1"` // 是否推广订单:0-否,1-是 + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始 + PayTimeEnd string `form:"pay_time_end,optional"` // 支付时间结束 + RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 + RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 + SalesCost float64 `form:"sales_cost,optional"` // 成本价 + QueryName string `form:"query_name,optional"` // 被查询人姓名(通过 query_user_record 表追溯订单) + QueryIdCard string `form:"query_id_card,optional"` // 被查询人身份证(通过 query_user_record 表追溯订单) + QueryMobile string `form:"query_mobile,optional"` // 被查询人手机号(通过 query_user_record 表追溯订单) +} + +type AdminGetOrderListResp struct { + Total int64 `json:"total"` // 总数 + Items []OrderListItem `json:"items"` // 列表 +} + +type AdminGetOrderSourceStatisticsReq struct { +} + +type AdminGetOrderSourceStatisticsResp struct { + Items []OrderSourceStatisticsItem `json:"items"` // 订单来源统计列表 +} + +type AdminGetOrderStatisticsReq struct { + Dimension string `form:"dimension"` // 时间维度:day-日(当月1号到今天),month-月(今年1月到当月),year-年(过去5年),all-全部(按日统计) +} + +type AdminGetOrderStatisticsResp struct { + Items []OrderStatisticsItem `json:"items"` // 订单统计列表 +} + +type AdminGetPlatformUserDetailReq struct { + Id int64 `path:"id"` // 用户ID +} + +type AdminGetPlatformUserDetailResp struct { + Id int64 `json:"id"` // 用户ID + Mobile string `json:"mobile"` // 手机号 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 + Disable int64 `json:"disable"` // 封禁状态 0-可用 1-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetPlatformUserListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Mobile string `form:"mobile,optional"` // 手机号 + Nickname string `form:"nickname,optional"` // 昵称 + Inside int64 `form:"inside,optional"` // 是否内部用户 1-是 0-否 + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + OrderBy string `form:"order_by,optional"` // 排序字段 + OrderType string `form:"order_type,optional"` // 排序类型 +} + +type AdminGetPlatformUserListResp struct { + Total int64 `json:"total"` // 总数 + Items []PlatformUserListItem `json:"items"` // 列表 +} + +type AdminGetProductDetailReq struct { + Id int64 `path:"id"` // 产品ID +} + +type AdminGetProductDetailResp struct { + Id int64 `json:"id"` // 产品ID + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetProductFeatureListReq struct { + ProductId int64 `path:"product_id"` // 产品ID +} + +type AdminGetProductFeatureListResp struct { + Id int64 `json:"id"` // 关联ID + ProductId int64 `json:"product_id"` // 产品ID + FeatureId int64 `json:"feature_id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 功能描述 + Sort int64 `json:"sort"` // 排序 + Enable int64 `json:"enable"` // 是否启用 + IsImportant int64 `json:"is_important"` // 是否重要 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type AdminGetProductListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"pageSize"` // 每页数量 + ProductName *string `form:"product_name,optional"` // 服务名 + ProductEn *string `form:"product_en,optional"` // 英文名 +} + +type AdminGetProductListResp struct { + Total int64 `json:"total"` // 总数 + Items []ProductListItem `json:"items"` // 列表数据 +} + +type AdminGetQueryCleanupConfigListReq struct { + Status int64 `form:"status,optional"` // 状态:1-启用,0-禁用 +} + +type AdminGetQueryCleanupConfigListResp struct { + Items []QueryCleanupConfigItem `json:"items"` // 配置列表 +} + +type AdminGetQueryCleanupDetailListReq struct { + LogId int64 `path:"log_id"` // 清理日志ID + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"page_size,default=20"` // 每页数量 +} + +type AdminGetQueryCleanupDetailListResp struct { + Total int64 `json:"total"` // 总数 + Items []QueryCleanupDetailItem `json:"items"` // 列表 +} + +type AdminGetQueryCleanupLogListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"page_size,default=20"` // 每页数量 + Status int64 `form:"status,optional"` // 状态:1-成功,2-失败 + StartTime string `form:"start_time,optional"` // 开始时间 + EndTime string `form:"end_time,optional"` // 结束时间 +} + +type AdminGetQueryCleanupLogListResp struct { + Total int64 `json:"total"` // 总数 + Items []QueryCleanupLogItem `json:"items"` // 列表 +} + +type AdminGetQueryDetailByOrderIdReq struct { + OrderId int64 `path:"order_id"` +} + +type AdminGetQueryDetailByOrderIdResp struct { + Id int64 `json:"id"` // 主键ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品ID + QueryParams map[string]interface{} `json:"query_params"` + QueryData []AdminQueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 +} + +type AdminGetRefundStatisticsReq struct { +} + +type AdminGetRefundStatisticsResp struct { + TotalRefundAmount float64 `json:"total_refund_amount"` // 总退款金额 + TodayRefundAmount float64 `json:"today_refund_amount"` // 今日退款金额 +} + +type AdminGetRevenueStatisticsReq struct { +} + +type AdminGetRevenueStatisticsResp struct { + TotalRevenueAmount float64 `json:"total_revenue_amount"` // 总收入金额 + TodayRevenueAmount float64 `json:"today_revenue_amount"` // 今日收入金额 + TotalProfitAmount float64 `json:"total_profit_amount"` // 总利润金额 + TodayProfitAmount float64 `json:"today_profit_amount"` // 今日利润金额 +} + +type AdminGetRoleApiListReq struct { + RoleId int64 `path:"role_id"` +} + +type AdminGetRoleApiListResp struct { + Items []AdminRoleApiInfo `json:"items"` +} + +type AdminGetSystemConfigResp struct { + CommissionSafeMode bool `json:"commission_safe_mode"` // 佣金安全防御模式 +} + +type AdminGetUserDetailReq struct { + Id int64 `path:"id"` // 用户ID +} + +type AdminGetUserDetailResp struct { + Id int64 `json:"id"` // 用户ID + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 +} + +type AdminGetUserListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Username string `form:"username,optional"` // 用户名 + RealName string `form:"real_name,optional"` // 真实姓名 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 +} + +type AdminGetUserListResp struct { + Total int64 `json:"total"` // 总数 + Items []AdminUserListItem `json:"items"` // 列表 +} + +type AdminGetWithdrawalStatisticsReq struct { +} + +type AdminGetWithdrawalStatisticsResp struct { + TotalWithdrawalAmount float64 `json:"total_withdrawal_amount"` // 总提现金额 + TodayWithdrawalAmount float64 `json:"today_withdrawal_amount"` // 今日提现金额 + TotalActualAmount float64 `json:"total_actual_amount"` // 总实际到账金额 + TotalTaxAmount float64 `json:"total_tax_amount"` // 总扣税金额 +} + +type AdminLoginReq struct { + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Captcha bool `json:"captcha" validate:"required"` +} + +type AdminLoginResp struct { + AccessToken string `json:"access_token"` + AccessExpire int64 `json:"access_expire"` + RefreshAfter int64 `json:"refresh_after"` + Roles []string `json:"roles"` +} + +type AdminQueryItem struct { + Feature interface{} `json:"feature"` + Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct +} + +type AdminRefundOrderReq struct { + Id int64 `path:"id"` // 订单ID + RefundAmount float64 `json:"refund_amount"` // 退款金额 + RefundReason string `json:"refund_reason"` // 退款原因 +} + +type AdminRefundOrderResp struct { + Status string `json:"status"` // 退款状态 + RefundNo string `json:"refund_no"` // 退款单号 + Amount float64 `json:"amount"` // 退款金额 +} + +type AdminRemoveRoleApiReq struct { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` +} + +type AdminRemoveRoleApiResp struct { + Success bool `json:"success"` +} + +type AdminResetPasswordReq struct { + Id int64 `path:"id"` // 用户ID + Password string `json:"password"` // 新密码 +} + +type AdminResetPasswordResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminRetryAgentProcessReq struct { + Id int64 `path:"id"` // 订单ID +} + +type AdminRetryAgentProcessResp struct { + Status string `json:"status"` // 执行状态:success-成功,already_processed-已处理,failed-失败 + Message string `json:"message"` // 执行结果消息 + ProcessedAt string `json:"processed_at"` // 处理时间 +} + +type AdminReviewBankCardWithdrawalReq struct { + WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID + Action int64 `json:"action"` // 操作:1-确认,2-拒绝 + Remark string `json:"remark"` // 备注(拒绝时必填) +} + +type AdminReviewBankCardWithdrawalResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminRoleApiInfo struct { + Id int64 `json:"id"` + RoleId int64 `json:"role_id"` + ApiId int64 `json:"api_id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description"` +} + +type AdminUpdateAgentCommissionStatusReq struct { + Id int64 `json:"id"` // 佣金记录ID + Status int64 `json:"status"` // 状态:0-已结算,1-冻结中,2-已取消 +} + +type AdminUpdateAgentCommissionStatusResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateAgentMembershipConfigReq struct { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price float64 `json:"price"` // 会员年费 + ReportCommission float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward *float64 `json:"lower_activity_reward,optional,omitempty"` // 下级活跃奖励金额 + NewActivityReward *float64 `json:"new_activity_reward,optional,omitempty"` // 新增活跃奖励金额 + LowerStandardCount *int64 `json:"lower_standard_count,optional,omitempty"` // 活跃下级达标个数 + NewLowerStandardCount *int64 `json:"new_lower_standard_count,optional,omitempty"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio *float64 `json:"lower_withdraw_reward_ratio,optional,omitempty"` // 下级提现奖励比例 + LowerConvertVipReward *float64 `json:"lower_convert_vip_reward,optional,omitempty"` // 下级转化VIP奖励 + LowerConvertSvipReward *float64 `json:"lower_convert_svip_reward,optional,omitempty"` // 下级转化SVIP奖励 + ExemptionAmount *float64 `json:"exemption_amount,optional,omitempty"` // 免责金额 + PriceIncreaseMax *float64 `json:"price_increase_max,optional,omitempty"` // 提价最高金额 + PriceRatio *float64 `json:"price_ratio,optional,omitempty"` // 提价区间收取比例 + PriceIncreaseAmount *float64 `json:"price_increase_amount,optional,omitempty"` // 在原本成本上加价的金额 +} + +type AdminUpdateAgentMembershipConfigResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateAgentProductionConfigReq struct { + Id int64 `json:"id"` // 主键 + CostPrice float64 `json:"cost_price"` // 成本 + PriceRangeMin float64 `json:"price_range_min"` // 最低定价 + PriceRangeMax float64 `json:"price_range_max"` // 最高定价 + PricingStandard float64 `json:"pricing_standard"` // 定价标准 + OverpricingRatio float64 `json:"overpricing_ratio"` // 超价比例 +} + +type AdminUpdateAgentProductionConfigResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateAgentWalletBalanceReq struct { + AgentId int64 `json:"agent_id"` // 代理ID + Amount float64 `json:"amount"` // 修改金额(正数增加,负数减少) +} + +type AdminUpdateAgentWalletBalanceResp struct { + Success bool `json:"success"` // 是否成功 + Balance float64 `json:"balance"` // 修改后的余额 +} + +type AdminUpdateApiReq struct { + Id int64 `path:"id"` + ApiName string `json:"api_name"` + ApiCode string `json:"api_code"` + Method string `json:"method"` + Url string `json:"url"` + Status int64 `json:"status"` + Description string `json:"description,optional"` +} + +type AdminUpdateApiResp struct { + Success bool `json:"success"` +} + +type AdminUpdateFeatureReq struct { + Id int64 `path:"id"` // 功能ID + ApiId *string `json:"api_id,optional"` // API标识 + Name *string `json:"name,optional"` // 描述 + CostPrice *float64 `json:"cost_price,optional"` // 成本价 +} + +type AdminUpdateFeatureResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateNotificationReq struct { + Id int64 `path:"id"` // 通知ID + Title *string `json:"title,optional"` // 通知标题 + Content *string `json:"content,optional"` // 通知内容 + NotificationPage *string `json:"notification_page,optional"` // 通知页面 + StartDate *string `json:"start_date,optional"` // 生效开始日期 + StartTime *string `json:"start_time,optional"` // 生效开始时间 + EndDate *string `json:"end_date,optional"` // 生效结束日期 + EndTime *string `json:"end_time,optional"` // 生效结束时间 + Status *int64 `json:"status,optional"` // 状态 +} + +type AdminUpdateNotificationResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateOrderReq struct { + Id int64 `path:"id"` // 订单ID + OrderNo *string `json:"order_no,optional"` // 商户订单号 + PlatformOrderId *string `json:"platform_order_id,optional"` // 支付订单号 + ProductName *string `json:"product_name,optional"` // 产品名称 + PaymentPlatform *string `json:"payment_platform,optional"` // 支付方式 + PaymentScene *string `json:"payment_scene,optional"` // 支付平台 + Amount *float64 `json:"amount,optional"` // 金额 + Status *string `json:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + PayTime *string `json:"pay_time,optional"` // 支付时间 + RefundTime *string `json:"refund_time,optional"` // 退款时间 + IsPromotion *int64 `json:"is_promotion,optional"` // 是否推广订单:0-否,1-是 +} + +type AdminUpdateOrderResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdatePlatformUserReq struct { + Id int64 `path:"id"` // 用户ID + Mobile *string `json:"mobile,optional"` // 手机号 + Password *string `json:"password,optional"` // 密码 + Nickname *string `json:"nickname,optional"` // 昵称 + Info *string `json:"info,optional"` // 备注信息 + Inside *int64 `json:"inside,optional"` // 是否内部用户 1-是 0-否 + Disable *int64 `json:"disable,optional"` // 封禁状态 0-可用 1-禁用 +} + +type AdminUpdatePlatformUserResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateProductFeaturesReq struct { + ProductId int64 `path:"product_id"` // 产品ID + Features []ProductFeatureItem `json:"features"` // 功能列表 +} + +type AdminUpdateProductFeaturesResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateProductReq struct { + Id int64 `path:"id"` // 产品ID + ProductName *string `json:"product_name,optional"` // 服务名 + ProductEn *string `json:"product_en,optional"` // 英文名 + Description *string `json:"description,optional"` // 描述 + Notes *string `json:"notes,optional"` // 备注 + CostPrice *float64 `json:"cost_price,optional"` // 成本 + SellPrice *float64 `json:"sell_price,optional"` // 售价 +} + +type AdminUpdateProductResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateQueryCleanupConfigReq struct { + Id int64 `json:"id"` // 主键ID + ConfigValue string `json:"config_value"` // 配置值 + Status int64 `json:"status"` // 状态:1-启用,0-禁用 +} + +type AdminUpdateQueryCleanupConfigResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateRoleApiReq struct { + RoleId int64 `json:"role_id"` + ApiIds []int64 `json:"api_ids"` +} + +type AdminUpdateRoleApiResp struct { + Success bool `json:"success"` +} + +type AdminUpdateSystemConfigReq struct { + CommissionSafeMode *bool `json:"commission_safe_mode,optional"` // 佣金安全防御模式:true-冻结模式,false-直接结算模式 +} + +type AdminUpdateSystemConfigResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUpdateUserReq struct { + Id int64 `path:"id"` // 用户ID + Username *string `json:"username,optional"` // 用户名 + RealName *string `json:"real_name,optional"` // 真实姓名 + Status *int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + RoleIds []int64 `json:"role_ids,optional"` // 关联的角色ID列表 +} + +type AdminUpdateUserResp struct { + Success bool `json:"success"` // 是否成功 +} + +type AdminUserInfoReq struct { +} + +type AdminUserInfoResp struct { + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Roles []string `json:"roles"` // 角色编码列表 +} + +type AdminUserListItem struct { + Id int64 `json:"id"` // 用户ID + Username string `json:"username"` // 用户名 + RealName string `json:"real_name"` // 真实姓名 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + CreateTime string `json:"create_time"` // 创建时间 + RoleIds []int64 `json:"role_ids"` // 关联的角色ID列表 +} + +type AgentActivateMembershipReq struct { + Type string `json:"type,oneof=VIP SVIP"` // 会员类型:vip/svip +} + +type AgentActivateMembershipResp struct { + Id string `json:"id"` +} + +type AgentApplyReq struct { + Region string `json:"region"` + Mobile string `json:"mobile"` + Code string `json:"code"` + Ancestor string `json:"ancestor,optional"` +} + +type AgentApplyResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type AgentAuditStatusResp struct { + Status int64 `json:"status"` // 0=待审核,1=审核通过,2=审核未通过 + AuditReason string `json:"audit_reason"` +} + +type AgentCommissionDeductionListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + DeductedAgentId int64 `json:"deducted_agent_id"` // 被扣代理ID + Amount float64 `json:"amount"` // 金额 + ProductName string `json:"product_name"` // 产品名 + Type string `json:"type"` // 类型(cost/pricing) + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentCommissionListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + OrderId int64 `json:"order_id"` // 订单ID + Amount float64 `json:"amount"` // 金额 + ProductName string `json:"product_name"` // 产品名 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentGeneratingLinkReq struct { + Product string `json:"product"` + Price string `json:"price"` +} + +type AgentGeneratingLinkResp struct { + LinkIdentifier string `json:"link_identifier"` +} + +type AgentInfoResp struct { + Status int64 `json:"status"` // 0=待审核,1=审核通过,2=审核未通过,3=未申请 + IsAgent bool `json:"is_agent"` + AgentID int64 `json:"agent_id"` + Level string `json:"level"` + Region string `json:"region"` + Mobile string `json:"mobile"` + ExpiryTime string `json:"expiry_time"` + IsRealName bool `json:"is_real_name"` +} + +type AgentLinkListItem struct { + AgentId int64 `json:"agent_id"` // 代理ID + ProductName string `json:"product_name"` // 产品名 + Price float64 `json:"price"` // 价格 + LinkIdentifier string `json:"link_identifier"` // 推广码 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentLinkProductStatisticsItem struct { + ProductName string `json:"product_name"` // 产品名称 + LinkCount int64 `json:"link_count"` // 推广链接数量 +} + +type AgentListItem struct { + Id int64 `json:"id"` // 主键 + UserId int64 `json:"user_id"` // 用户ID + ParentAgentId int64 `json:"parent_agent_id"` // 上级代理ID + LevelName string `json:"level_name"` // 等级名称 + Region string `json:"region"` // 区域 + Mobile string `json:"mobile"` // 手机号 + MembershipExpiryTime string `json:"membership_expiry_time"` // 会员到期时间 + Balance float64 `json:"balance"` // 钱包余额 + TotalEarnings float64 `json:"total_earnings"` // 累计收益 + FrozenBalance float64 `json:"frozen_balance"` // 冻结余额 + WithdrawnAmount float64 `json:"withdrawn_amount"` // 提现总额 + CreateTime string `json:"create_time"` // 创建时间 + IsRealNameVerified bool `json:"is_real_name_verified"` // 是否已实名认证 + RealName string `json:"real_name"` // 实名姓名 + IdCard string `json:"id_card"` // 身份证号 + RealNameStatus string `json:"real_name_status"` // 实名状态(pending/approved/rejected) +} + +type AgentMembershipConfigListItem struct { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price *float64 `json:"price"` // 会员年费 + ReportCommission *float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward *float64 `json:"lower_activity_reward"` // 下级活跃奖励金额 + NewActivityReward *float64 `json:"new_activity_reward"` // 新增活跃奖励金额 + LowerStandardCount *int64 `json:"lower_standard_count"` // 活跃下级达标个数 + NewLowerStandardCount *int64 `json:"new_lower_standard_count"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio *float64 `json:"lower_withdraw_reward_ratio"` // 下级提现奖励比例 + LowerConvertVipReward *float64 `json:"lower_convert_vip_reward"` // 下级转化VIP奖励 + LowerConvertSvipReward *float64 `json:"lower_convert_svip_reward"` // 下级转化SVIP奖励 + ExemptionAmount *float64 `json:"exemption_amount"` // 免责金额 + PriceIncreaseMax *float64 `json:"price_increase_max"` // 提价最高金额 + PriceRatio *float64 `json:"price_ratio"` // 提价区间收取比例 + PriceIncreaseAmount *float64 `json:"price_increase_amount"` // 在原本成本上加价的金额 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentMembershipProductConfigReq struct { + ProductID int64 `form:"product_id"` +} + +type AgentMembershipProductConfigResp struct { + AgentMembershipUserConfig AgentMembershipUserConfig `json:"agent_membership_user_config"` + ProductConfig ProductConfig `json:"product_config"` + PriceIncreaseMax float64 `json:"price_increase_max"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRatio float64 `json:"price_ratio"` +} + +type AgentMembershipRechargeOrderListItem struct { + Id int64 `json:"id"` // 主键 + UserId int64 `json:"user_id"` // 用户ID + AgentId int64 `json:"agent_id"` // 代理ID + LevelName string `json:"level_name"` // 等级名称 + Amount float64 `json:"amount"` // 金额 + PaymentMethod string `json:"payment_method"` // 支付方式 + OrderNo string `json:"order_no"` // 订单号 + PlatformOrderId string `json:"platform_order_id"` // 平台订单号 + Status string `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentMembershipUserConfig struct { + ProductID int64 `json:"product_id"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRangeFrom float64 `json:"price_range_from"` + PriceRangeTo float64 `json:"price_range_to"` + PriceRatio float64 `json:"price_ratio"` +} + +type AgentPlatformDeductionListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + Amount float64 `json:"amount"` // 金额 + Type string `json:"type"` // 类型(cost/pricing) + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentProductConfig struct { + ProductID int64 `json:"product_id"` + CostPrice float64 `json:"cost_price"` + PriceRangeMin float64 `json:"price_range_min"` + PriceRangeMax float64 `json:"price_range_max"` + PPricingStandard float64 `json:"p_pricing_standard"` + POverpricingRatio float64 `json:"p_overpricing_ratio"` + APricingStandard float64 `json:"a_pricing_standard"` + APricingEnd float64 `json:"a_pricing_end"` + AOverpricingRatio float64 `json:"a_overpricing_ratio"` +} + +type AgentProductConfigResp struct { + AgentProductConfig []AgentProductConfig +} + +type AgentProductionConfigItem struct { + Id int64 `json:"id"` // 主键 + ProductName string `json:"product_name"` // 产品名 + CostPrice float64 `json:"cost_price"` // 成本 + PriceRangeMin float64 `json:"price_range_min"` // 最低定价 + PriceRangeMax float64 `json:"price_range_max"` // 最高定价 + PricingStandard float64 `json:"pricing_standard"` // 定价标准 + OverpricingRatio float64 `json:"overpricing_ratio"` // 超价比例 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentRealNameReq struct { + Name string `json:"name"` + IDCard string `json:"id_card"` + Mobile string `json:"mobile"` + Code string `json:"code"` +} + +type AgentRealNameResp struct { + Status string `json:"status"` +} + +type AgentRewardListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + RelationAgentId int64 `json:"relation_agent_id"` // 关联代理ID + Amount float64 `json:"amount"` // 金额 + Type string `json:"type"` // 奖励类型 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentSubordinateContributionDetail struct { + ID int64 `json:"id"` + CreateTime string `json:"create_time"` + Amount float64 `json:"amount"` + Type string `json:"type"` +} + +type AgentSubordinateContributionStats struct { + CostCount int64 `json:"cost_count"` // 成本扣除次数 + CostAmount float64 `json:"cost_amount"` // 成本扣除总额 + PricingCount int64 `json:"pricing_count"` // 定价扣除次数 + PricingAmount float64 `json:"pricing_amount"` // 定价扣除总额 + DescendantPromotionCount int64 `json:"descendant_promotion_count"` // 下级推广次数 + DescendantPromotionAmount float64 `json:"descendant_promotion_amount"` // 下级推广总额 + DescendantUpgradeVipCount int64 `json:"descendant_upgrade_vip_count"` // 下级升级VIP次数 + DescendantUpgradeVipAmount float64 `json:"descendant_upgrade_vip_amount"` // 下级升级VIP总额 + DescendantUpgradeSvipCount int64 `json:"descendant_upgrade_svip_count"` // 下级升级SVIP次数 + DescendantUpgradeSvipAmount float64 `json:"descendant_upgrade_svip_amount"` // 下级升级SVIP总额 + DescendantStayActiveCount int64 `json:"descendant_stay_active_count"` // 下级保持活跃次数 + DescendantStayActiveAmount float64 `json:"descendant_stay_active_amount"` // 下级保持活跃总额 + DescendantNewActiveCount int64 `json:"descendant_new_active_count"` // 下级新增活跃次数 + DescendantNewActiveAmount float64 `json:"descendant_new_active_amount"` // 下级新增活跃总额 + DescendantWithdrawCount int64 `json:"descendant_withdraw_count"` // 下级提现次数 + DescendantWithdrawAmount float64 `json:"descendant_withdraw_amount"` // 下级提现总额 +} + +type AgentSubordinateList struct { + ID int64 `json:"id"` + Mobile string `json:"mobile"` + CreateTime string `json:"create_time"` + LevelName string `json:"level_name"` + TotalOrders int64 `json:"total_orders"` // 总单量 + TotalEarnings float64 `json:"total_earnings"` // 总金额 + TotalContribution float64 `json:"total_contribution"` // 总贡献 +} + +type AgentWalletTransactionListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + TransactionType string `json:"transaction_type"` // 交易类型 + Amount float64 `json:"amount"` // 变动金额 + BalanceBefore float64 `json:"balance_before"` // 变动前余额 + BalanceAfter float64 `json:"balance_after"` // 变动后余额 + FrozenBalanceBefore float64 `json:"frozen_balance_before"` // 变动前冻结余额 + FrozenBalanceAfter float64 `json:"frozen_balance_after"` // 变动后冻结余额 + TransactionId *string `json:"transaction_id"` // 关联交易ID + RelatedUserId *int64 `json:"related_user_id"` // 关联用户ID + Remark *string `json:"remark"` // 备注说明 + CreateTime string `json:"create_time"` // 创建时间 +} + +type AgentWithdrawalListItem struct { + Id int64 `json:"id"` // 主键 + AgentId int64 `json:"agent_id"` // 代理ID + WithdrawNo string `json:"withdraw_no"` // 提现单号 + Amount float64 `json:"amount"` // 金额 + ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后) + TaxAmount float64 `json:"tax_amount"` // 扣税金额 + Status int64 `json:"status"` // 状态 + PayeeAccount string `json:"payee_account"` // 收款账户 + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 + WithdrawType int64 `json:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡 + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + PayeeName string `json:"payee_name"` // 收款人姓名 +} + +type AuthorizationDocumentInfo struct { + DocumentId int64 `json:"documentId"` // 授权书ID + UserId int64 `json:"userId"` // 用户ID + OrderId int64 `json:"orderId"` // 订单ID + QueryId int64 `json:"queryId"` // 查询ID + FileName string `json:"fileName"` // 文件名 + FileUrl string `json:"fileUrl"` // 文件访问URL + FileSize int64 `json:"fileSize"` // 文件大小 + FileType string `json:"fileType"` // 文件类型 + Status string `json:"status"` // 状态 + CreateTime string `json:"createTime"` // 创建时间 +} + +type BankCardWithdrawalReq struct { + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + Amount float64 `json:"amount"` // 提现金额 +} + +type BindMobileReq struct { + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +type BindMobileResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type Commission struct { + OrderId string `json:"order_id"` // 订单号 + ProductName string `json:"product_name"` + Amount float64 `json:"amount"` // 原始佣金金额 + RefundedAmount float64 `json:"refunded_amount"` // 已退款佣金金额 + NetAmount float64 `json:"net_amount"` // 剩余净佣金金额 = amount - refunded_amount + Status int64 `json:"status"` // 状态:0-已结算,1-冻结中,2-已退款 + CreateTime string `json:"create_time"` + QueryParams map[string]interface{} `json:"query_params,omitempty"` +} + +type CreateMenuReq struct { + Pid int64 `json:"pid,optional"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path,optional"` // 路由路径 + Component string `json:"component,optional"` // 组件路径 + Redirect string `json:"redirect,optional"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status,optional,default=1"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort,optional"` // 排序 +} + +type CreateMenuResp struct { + Id int64 `json:"id"` // 菜单ID +} + +type CreatePromotionLinkReq struct { + Name string `json:"name"` // 链接名称 +} + +type CreatePromotionLinkResp struct { + Id int64 `json:"id"` // 链接ID + Url string `json:"url"` // 生成的推广链接URL +} + +type CreateRoleReq struct { + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status,default=1"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort,default=0"` // 排序 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 +} + +type CreateRoleResp struct { + Id int64 `json:"id"` // 角色ID +} + +type DeleteMenuReq struct { + Id int64 `path:"id"` // 菜单ID +} + +type DeleteMenuResp struct { + Success bool `json:"success"` // 是否成功 +} + +type DeletePromotionLinkReq struct { + Id int64 `path:"id"` // 链接ID +} + +type DeletePromotionLinkResp struct { + Success bool `json:"success"` // 是否成功 +} + +type DeleteRoleReq struct { + Id int64 `path:"id"` // 角色ID +} + +type DeleteRoleResp struct { + Success bool `json:"success"` // 是否成功 +} + +type DirectPushReport struct { + TotalCommission float64 `json:"total_commission"` + TotalReport int `json:"total_report"` + Today TimeRangeReport `json:"today"` // 近24小时数据 + Last7D TimeRangeReport `json:"last7d"` // 近7天数据 + Last30D TimeRangeReport `json:"last30d"` // 近30天数据 +} + +type DownloadAuthorizationDocumentByNameReq struct { + FileName string `path:"fileName" validate:"required"` // 授权书文件名 +} + +type DownloadAuthorizationDocumentReq struct { + DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID +} + +type DownloadAuthorizationDocumentResp struct { + FileName string `json:"fileName"` // 文件名 + FilePath string `json:"filePath"` // 文件存储路径 +} + +type Feature struct { + ID int64 `json:"id"` // 功能ID + ApiID string `json:"api_id"` // API标识 + Name string `json:"name"` // 功能描述 +} + +type FeatureListItem struct { + Id int64 `json:"id"` // 功能ID + ApiId string `json:"api_id"` // API标识 + Name string `json:"name"` // 描述 + CostPrice float64 `json:"cost_price"` // 成本价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type GetAgentPromotionQrcodeReq struct { + QrcodeType string `form:"qrcode_type"` + QrcodeUrl string `form:"qrcode_url"` +} + +type GetAgentRevenueInfoReq struct { +} + +type GetAgentRevenueInfoResp struct { + Balance float64 `json:"balance"` + FrozenBalance float64 `json:"frozen_balance"` + TotalEarnings float64 `json:"total_earnings"` + DirectPush DirectPushReport `json:"direct_push"` // 直推报告数据 + ActiveReward ActiveReward `json:"active_reward"` // 活跃下级奖励数据 +} + +type GetAgentSubordinateContributionDetailReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 + SubordinateID int64 `form:"subordinate_id"` // 下级ID +} + +type GetAgentSubordinateContributionDetailResp struct { + Mobile string `json:"mobile"` + Total int64 `json:"total"` // 总记录数 + CreateTime string `json:"create_time"` + TotalEarnings float64 `json:"total_earnings"` // 总金额 + TotalContribution float64 `json:"total_contribution"` // 总贡献 + TotalOrders int64 `json:"total_orders"` // 总单量 + LevelName string `json:"level_name"` // 等级名称 + List []AgentSubordinateContributionDetail `json:"list"` // 查询列表 + Stats AgentSubordinateContributionStats `json:"stats"` // 统计数据 +} + +type GetAgentSubordinateListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 +} + +type GetAgentSubordinateListResp struct { + Total int64 `json:"total"` // 总记录数 + List []AgentSubordinateList `json:"list"` // 查询列表 +} + +type GetAuthorizationDocumentByOrderReq struct { + OrderId int64 `json:"orderId" validate:"required"` // 订单ID +} + +type GetAuthorizationDocumentByOrderResp struct { + Documents []AuthorizationDocumentInfo `json:"documents"` // 授权书列表 +} + +type GetAuthorizationDocumentReq struct { + DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID +} + +type GetAuthorizationDocumentResp struct { + DocumentId int64 `json:"documentId"` // 授权书ID + UserId int64 `json:"userId"` // 用户ID + OrderId int64 `json:"orderId"` // 订单ID + QueryId int64 `json:"queryId"` // 查询ID + FileName string `json:"fileName"` // 文件名 + FileUrl string `json:"fileUrl"` // 文件访问URL + FileSize int64 `json:"fileSize"` // 文件大小 + FileType string `json:"fileType"` // 文件类型 + Status string `json:"status"` // 状态 + CreateTime string `json:"createTime"` // 创建时间 +} + +type GetBankCardInfoReq struct { +} + +type GetBankCardInfoResp struct { + BankCardNo string `json:"bank_card_no"` // 银行卡号 + BankName string `json:"bank_name"` // 开户支行 + PayeeName string `json:"payee_name"` // 收款人姓名 + IdCard string `json:"id_card"` // 身份证号 +} + +type GetCommissionReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 +} + +type GetCommissionResp struct { + Total int64 `json:"total"` // 总记录数 + List []Commission `json:"list"` // 查询列表 +} + +type GetLinkDataReq struct { + LinkIdentifier string `form:"link_identifier"` +} + +type GetLinkDataResp struct { + Product +} + +type GetMembershipInfoResp struct { + NormalConfig MembershipConfigInfo `json:"normal_config"` // 普通代理配置 + VipConfig MembershipConfigInfo `json:"vip_config"` // VIP会员配置 + SvipConfig MembershipConfigInfo `json:"svip_config"` // SVIP会员配置 +} + +type GetMenuAllReq struct { +} + +type GetMenuAllResp struct { + Name string `json:"name"` + Path string `json:"path"` + Redirect string `json:"redirect,omitempty"` + Component string `json:"component,omitempty"` + Sort int64 `json:"sort"` + Meta map[string]interface{} `json:"meta"` + Children []GetMenuAllResp `json:"children"` +} + +type GetMenuDetailReq struct { + Id int64 `path:"id"` // 菜单ID +} + +type GetMenuDetailResp struct { + Id int64 `json:"id"` // 菜单ID + Pid int64 `json:"pid"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path"` // 路由路径 + Component string `json:"component"` // 组件路径 + Redirect string `json:"redirect"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"createTime"` // 创建时间 + UpdateTime string `json:"updateTime"` // 更新时间 +} + +type GetMenuListReq struct { + Name string `form:"name,optional"` // 菜单名称 + Path string `form:"path,optional"` // 路由路径 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 + Type string `form:"type,optional"` // 类型 +} + +type GetNotificationsResp struct { + Notifications []Notification `json:"notifications"` // 通知列表 + Total int64 `json:"total"` // 总记录数 +} + +type GetProductByEnRequest struct { + ProductEn string `path:"product_en"` +} + +type GetProductByIDRequest struct { + Id int64 `path:"id"` +} + +type GetPromotionLinkDetailReq struct { + Id int64 `path:"id"` // 链接ID +} + +type GetPromotionLinkDetailResp struct { + Name string `json:"name"` // 链接名称 + Url string `json:"url"` // 推广链接URL + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + PayAmount string `json:"pay_amount"` // 付费金额 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + LastClickTime string `json:"last_click_time,optional"` // 最后点击时间 + LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间 +} + +type GetPromotionLinkListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Name string `form:"name,optional"` // 链接名称 + Url string `form:"url,optional"` // 推广链接URL +} + +type GetPromotionLinkListResp struct { + Total int64 `json:"total"` // 总数 + Items []PromotionLinkItem `json:"items"` // 列表 +} + +type GetPromotionStatsHistoryReq struct { + StartDate string `form:"start_date"` // 开始日期,格式:YYYY-MM-DD + EndDate string `form:"end_date"` // 结束日期,格式:YYYY-MM-DD +} + +type GetPromotionStatsTotalReq struct { +} + +type GetPromotionStatsTotalResp struct { + TodayPayAmount float64 `json:"today_pay_amount"` // 今日金额 + TodayClickCount int64 `json:"today_click_count"` // 今日点击数 + TodayPayCount int64 `json:"today_pay_count"` // 今日付费次数 + TotalPayAmount float64 `json:"total_pay_amount"` // 总金额 + TotalClickCount int64 `json:"total_click_count"` // 总点击数 + TotalPayCount int64 `json:"total_pay_count"` // 总付费次数 +} + +type GetRewardsReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 +} + +type GetRewardsResp struct { + Total int64 `json:"total"` // 总记录数 + List []Rewards `json:"list"` // 查询列表 +} + +type GetRoleDetailReq struct { + Id int64 `path:"id"` // 角色ID +} + +type GetRoleDetailResp struct { + Id int64 `json:"id"` // 角色ID + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 +} + +type GetRoleListReq struct { + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + Name string `form:"name,optional"` // 角色名称 + Code string `form:"code,optional"` // 角色编码 + Status int64 `form:"status,optional,default=-1"` // 状态:0-禁用,1-启用 +} + +type GetRoleListResp struct { + Total int64 `json:"total"` // 总数 + Items []RoleListItem `json:"items"` // 列表 +} + +type GetSignatureReq struct { + Url string `json:"url"` +} + +type GetSignatureResp struct { + AppId string `json:"appId"` + Timestamp int64 `json:"timestamp"` + NonceStr string `json:"nonceStr"` + Signature string `json:"signature"` +} + +type GetWithdrawalReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 +} + +type GetWithdrawalResp struct { + Total int64 `json:"total"` // 总记录数 + List []Withdrawal `json:"list"` // 查询列表 +} + +type GetWithdrawalTaxExemptionReq struct { +} + +type GetWithdrawalTaxExemptionResp struct { + TotalExemptionAmount float64 `json:"total_exemption_amount"` + UsedExemptionAmount float64 `json:"used_exemption_amount"` + RemainingExemptionAmount float64 `json:"remaining_exemption_amount"` + TaxRate float64 `json:"tax_rate"` +} + +type HealthCheckResp struct { + Status string `json:"status"` // 服务状态 + Message string `json:"message"` // 状态信息 +} + +type IapCallbackReq struct { + OrderID int64 `json:"order_id" validate:"required"` + TransactionReceipt string `json:"transaction_receipt" validate:"required"` +} + +type MembershipConfigInfo struct { + Id int64 `json:"id"` // 主键 + LevelName string `json:"level_name"` // 会员级别名称 + Price float64 `json:"price"` // 会员年费 + ReportCommission float64 `json:"report_commission"` // 直推报告收益 + LowerActivityReward float64 `json:"lower_activity_reward"` // 下级活跃奖励金额 + NewActivityReward float64 `json:"new_activity_reward"` // 新增活跃奖励金额 + LowerStandardCount int64 `json:"lower_standard_count"` // 活跃下级达标个数 + NewLowerStandardCount int64 `json:"new_lower_standard_count"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio float64 `json:"lower_withdraw_reward_ratio"` // 下级提现奖励比例 + LowerConvertVipReward float64 `json:"lower_convert_vip_reward"` // 下级转化VIP奖励 + LowerConvertSvipReward float64 `json:"lower_convert_svip_reward"` // 下级转化SVIP奖励 + ExemptionAmount float64 `json:"exemption_amount"` // 免审核金额 + PriceIncreaseMax float64 `json:"price_increase_max"` // 提价最高金额 + PriceRatio float64 `json:"price_ratio"` // 提价区间收取比例 + PriceIncreaseAmount float64 `json:"price_increase_amount"` // 在原本成本上加价的金额 +} + +type MenuListItem struct { + Id int64 `json:"id"` // 菜单ID + Pid int64 `json:"pid"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path"` // 路由路径 + Component string `json:"component"` // 组件路径 + Redirect string `json:"redirect"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"createTime"` // 创建时间 + Children []MenuListItem `json:"children"` // 子菜单 +} + +type MobileCodeLoginReq struct { + Mobile string `json:"mobile"` + Code string `json:"code" validate:"required"` +} + +type MobileCodeLoginResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type Notification struct { + Title string `json:"title"` // 通知标题 + Content string `json:"content"` // 通知内容 (富文本) + NotificationPage string `json:"notificationPage"` // 通知页面 + StartDate string `json:"startDate"` // 通知开始日期,格式 "YYYY-MM-DD" + EndDate string `json:"endDate"` // 通知结束日期,格式 "YYYY-MM-DD" + StartTime string `json:"startTime"` // 每天通知开始时间,格式 "HH:MM:SS" + EndTime string `json:"endTime"` // 每天通知结束时间,格式 "HH:MM:SS" +} + +type NotificationListItem struct { + Id int64 `json:"id"` // 通知ID + Title string `json:"title"` // 通知标题 + NotificationPage string `json:"notification_page"` // 通知页面 + Content string `json:"content"` // 通知内容 + StartDate string `json:"start_date"` // 生效开始日期 + StartTime string `json:"start_time"` // 生效开始时间 + EndDate string `json:"end_date"` // 生效结束日期 + EndTime string `json:"end_time"` // 生效结束时间 + Status int64 `json:"status"` // 状态 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type OrderListItem struct { + Id int64 `json:"id"` // 订单ID + OrderNo string `json:"order_no"` // 商户订单号 + PlatformOrderId string `json:"platform_order_id"` // 支付订单号 + ProductName string `json:"product_name"` // 产品名称 + PaymentPlatform string `json:"payment_platform"` // 支付方式 + PaymentScene string `json:"payment_scene"` // 支付平台 + Amount float64 `json:"amount"` // 金额 + SalesCost float64 `json:"sales_cost"` // 成本价 + Status string `json:"status"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + QueryState string `json:"query_state"` // 查询状态:pending-待查询,success-查询成功,failed-查询失败 processing-查询中 + CreateTime string `json:"create_time"` // 创建时间 + PayTime string `json:"pay_time"` // 支付时间 + RefundTime string `json:"refund_time"` // 退款时间 + IsPromotion int64 `json:"is_promotion"` // 是否推广订单:0-否,1-是 + IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单 + AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态:not_agent-非代理订单,success-处理成功,failed-处理失败,pending-待处理 +} + +type OrderSourceStatisticsItem struct { + ProductName string `json:"product_name"` // 产品名称 + OrderCount int64 `json:"order_count"` // 订单数量 +} + +type OrderStatisticsItem struct { + Date string `json:"date"` // 日期 + Count int64 `json:"count"` // 订单数量 + Amount float64 `json:"amount"` // 订单金额 +} + +type PaymentCheckReq struct { + OrderNo string `json:"order_no" validate:"required"` +} + +type PaymentCheckResp struct { + Type string `json:"type"` + Status string `json:"status"` +} + +type PaymentReq struct { + Id string `json:"id"` + PayMethod string `json:"pay_method"` + PayType string `json:"pay_type" validate:"required,oneof=query agent_vip agent_upgrade"` +} + +type PaymentResp struct { + PrepayData interface{} `json:"prepay_data"` + PrepayId string `json:"prepay_id"` + OrderNo string `json:"order_no"` +} + +type PlatformUserListItem struct { + Id int64 `json:"id"` // 用户ID + Mobile string `json:"mobile"` // 手机号 + Nickname string `json:"nickname"` // 昵称 + Info string `json:"info"` // 备注信息 + Inside int64 `json:"inside"` // 是否内部用户 1-是 0-否 + Disable int64 `json:"disable"` // 封禁状态 0-可用 1-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type Product struct { + ProductName string `json:"product_name"` + ProductEn string `json:"product_en"` + Description string `json:"description"` + Notes string `json:"notes,optional"` + SellPrice float64 `json:"sell_price"` + Features []Feature `json:"features"` // 关联功能列表 +} + +type ProductConfig struct { + ProductID int64 `json:"product_id"` + CostPrice float64 `json:"cost_price"` + PriceRangeMin float64 `json:"price_range_min"` + PriceRangeMax float64 `json:"price_range_max"` +} + +type ProductFeatureItem struct { + FeatureId int64 `json:"feature_id"` // 功能ID + Sort int64 `json:"sort"` // 排序 + Enable int64 `json:"enable"` // 是否启用 + IsImportant int64 `json:"is_important"` // 是否重要 +} + +type ProductListItem struct { + Id int64 `json:"id"` // 产品ID + ProductName string `json:"product_name"` // 服务名 + ProductEn string `json:"product_en"` // 英文名 + Description string `json:"description"` // 描述 + Notes string `json:"notes"` // 备注 + CostPrice float64 `json:"cost_price"` // 成本 + SellPrice float64 `json:"sell_price"` // 售价 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type ProductResponse struct { + Product +} + +type PromotionLinkItem struct { + Id int64 `json:"id"` // 链接ID + Name string `json:"name"` // 链接名称 + Url string `json:"url"` // 推广链接URL + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + PayAmount string `json:"pay_amount"` // 付费金额 + CreateTime string `json:"create_time"` // 创建时间 + LastClickTime string `json:"last_click_time,optional"` // 最后点击时间 + LastPayTime string `json:"last_pay_time,optional"` // 最后付费时间 +} + +type PromotionStatsHistoryItem struct { + Id int64 `json:"id"` // 记录ID + LinkId int64 `json:"link_id"` // 链接ID + PayAmount float64 `json:"pay_amount"` // 金额 + ClickCount int64 `json:"click_count"` // 点击数 + PayCount int64 `json:"pay_count"` // 付费次数 + StatsDate string `json:"stats_date"` // 统计日期 +} + +type Query struct { + Id int64 `json:"id"` // 主键ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + Product string `json:"product"` // 产品ID + ProductName string `json:"product_name"` // 产品ID + QueryParams map[string]interface{} `json:"query_params"` + QueryData []QueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 +} + +type QueryCleanupConfigItem struct { + Id int64 `json:"id"` // 主键ID + ConfigKey string `json:"config_key"` // 配置键 + ConfigValue string `json:"config_value"` // 配置值 + ConfigDesc string `json:"config_desc"` // 配置描述 + Status int64 `json:"status"` // 状态:1-启用,0-禁用 + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 +} + +type QueryCleanupDetailItem struct { + Id int64 `json:"id"` // 主键ID + CleanupLogId int64 `json:"cleanup_log_id"` // 清理日志ID + QueryId int64 `json:"query_id"` // 查询ID + OrderId int64 `json:"order_id"` // 订单ID + UserId int64 `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品名称 + QueryState string `json:"query_state"` // 查询状态 + CreateTimeOld string `json:"create_time_old"` // 原创建时间 + CreateTime string `json:"create_time"` // 创建时间 +} + +type QueryCleanupLogItem struct { + Id int64 `json:"id"` // 主键ID + CleanupTime string `json:"cleanup_time"` // 清理时间 + CleanupBefore string `json:"cleanup_before"` // 清理截止时间 + Status int64 `json:"status"` // 状态:1-成功,2-失败 + AffectedRows int64 `json:"affected_rows"` // 影响行数 + ErrorMsg string `json:"error_msg"` // 错误信息 + Remark string `json:"remark"` // 备注 + CreateTime string `json:"create_time"` // 创建时间 +} + +type QueryDetailByOrderIdReq struct { + OrderId int64 `path:"order_id"` +} + +type QueryDetailByOrderIdResp struct { + Query +} + +type QueryDetailByOrderNoReq struct { + OrderNo string `path:"order_no"` +} + +type QueryDetailByOrderNoResp struct { + Query +} + +type QueryExampleReq struct { + Feature string `form:"feature"` +} + +type QueryExampleResp struct { + Query +} + +type QueryGenerateShareLinkReq struct { + OrderId *int64 `json:"order_id,optional"` + OrderNo *string `json:"order_no,optional"` +} + +type QueryGenerateShareLinkResp struct { + ShareLink string `json:"share_link"` +} + +type QueryItem struct { + Feature interface{} `json:"feature"` + Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct +} + +type QueryListReq struct { + Page int64 `form:"page"` // 页码 + PageSize int64 `form:"page_size"` // 每页数据量 +} + +type QueryListResp struct { + Total int64 `json:"total"` // 总记录数 + List []Query `json:"list"` // 查询列表 +} + +type QueryProvisionalOrderReq struct { + Id string `path:"id"` +} + +type QueryProvisionalOrderResp struct { + Name string `json:"name"` + IdCard string `json:"id_card"` + Mobile string `json:"mobile"` + Product Product `json:"product"` +} + +type QueryReq struct { + Data string `json:"data" validate:"required"` +} + +type QueryResp struct { + Id string `json:"id"` +} + +type QueryRetryReq struct { + Id int64 `path:"id"` +} + +type QueryRetryResp struct { + Query +} + +type QueryServiceReq struct { + Product string `path:"product"` + Data string `json:"data" validate:"required"` + AgentIdentifier string `json:"agent_identifier,optional"` + App bool `json:"app,optional"` +} + +type QueryServiceResp struct { + Id string `json:"id"` + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type QueryShareDetailReq struct { + Id string `path:"id"` +} + +type QueryShareDetailResp struct { + Status string `json:"status"` + Query +} + +type QuerySingleTestReq struct { + Params map[string]interface{} `json:"params"` + Api string `json:"api"` +} + +type QuerySingleTestResp struct { + Data interface{} `json:"data"` + Api string `json:"api"` +} + +type RecordLinkClickReq struct { + Path string `path:"path"` // 链接路径 +} + +type RecordLinkClickResp struct { + Success bool `json:"success"` // 是否成功 +} + +type Rewards struct { + Type string `json:"type"` + Amount float64 `json:"amount"` + CreateTime string `json:"create_time"` +} + +type RoleListItem struct { + Id int64 `json:"id"` // 角色ID + RoleName string `json:"role_name"` // 角色名称 + RoleCode string `json:"role_code"` // 角色编码 + Description string `json:"description"` // 角色描述 + Status int64 `json:"status"` // 状态:0-禁用,1-启用 + Sort int64 `json:"sort"` // 排序 + CreateTime string `json:"create_time"` // 创建时间 + MenuIds []int64 `json:"menu_ids"` // 关联的菜单ID列表 +} + +type SaveAgentMembershipUserConfigReq struct { + ProductID int64 `json:"product_id"` + PriceIncreaseAmount float64 `json:"price_increase_amount"` + PriceRangeFrom float64 `json:"price_range_from"` + PriceRangeTo float64 `json:"price_range_to"` + PriceRatio float64 `json:"price_ratio"` +} + +type TimeRangeReport struct { + Commission float64 `json:"commission"` // 佣金 + Report int `json:"report"` // 报告量 +} + +type UpdateMenuReq struct { + Id int64 `path:"id"` // 菜单ID + Pid int64 `json:"pid,optional"` // 父菜单ID + Name string `json:"name"` // 路由名称 + Path string `json:"path,optional"` // 路由路径 + Component string `json:"component,optional"` // 组件路径 + Redirect string `json:"redirect,optional"` // 重定向路径 + Meta map[string]interface{} `json:"meta"` // 路由元数据 + Status int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + Type string `json:"type"` // 类型 + Sort int64 `json:"sort,optional"` // 排序 +} + +type UpdateMenuResp struct { + Success bool `json:"success"` // 是否成功 +} + +type UpdatePromotionLinkReq struct { + Id int64 `path:"id"` // 链接ID + Name *string `json:"name,optional"` // 链接名称 +} + +type UpdatePromotionLinkResp struct { + Success bool `json:"success"` // 是否成功 +} + +type UpdateQueryDataReq struct { + Id int64 `json:"id"` // 查询ID + QueryData string `json:"query_data"` // 查询数据(未加密的JSON) +} + +type UpdateQueryDataResp struct { + Id int64 `json:"id"` + UpdatedAt string `json:"updated_at"` // 更新时间 +} + +type UpdateRoleReq struct { + Id int64 `path:"id"` // 角色ID + RoleName *string `json:"role_name,optional"` // 角色名称 + RoleCode *string `json:"role_code,optional"` // 角色编码 + Description *string `json:"description,optional"` // 角色描述 + Status *int64 `json:"status,optional"` // 状态:0-禁用,1-启用 + Sort *int64 `json:"sort,optional"` // 排序 + MenuIds []int64 `json:"menu_ids,optional"` // 关联的菜单ID列表 +} + +type UpdateRoleResp struct { + Success bool `json:"success"` // 是否成功 +} + +type User struct { + Id int64 `json:"id"` + Mobile string `json:"mobile"` + NickName string `json:"nickName"` + UserType int64 `json:"userType"` +} + +type UserInfoResp struct { + UserInfo User `json:"userInfo"` +} + +type WXH5AuthReq struct { + Code string `json:"code"` +} + +type WXH5AuthResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type WXMiniAuthReq struct { + Code string `json:"code"` +} + +type WXMiniAuthResp struct { + AccessToken string `json:"accessToken"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type Withdrawal struct { + Status int64 `json:"status"` + Amount float64 `json:"amount"` + WithdrawalNo string `json:"withdrawal_no"` + Remark string `json:"remark"` + PayeeAccount string `json:"payee_account"` + CreateTime string `json:"create_time"` +} + +type WithdrawalReq struct { + Amount float64 `json:"amount"` // 提现金额 + PayeeAccount string `json:"payee_account"` + PayeeName string `json:"payee_name"` +} + +type WithdrawalResp struct { + Status int64 `json:"status"` // 1申请中 2成功 3失败 + FailMsg string `json:"fail_msg"` +} + +type GetAppVersionResp struct { + Version string `json:"version"` + WgtUrl string `json:"wgtUrl"` +} + +type SendSmsReq struct { + Mobile string `json:"mobile" validate:"required,mobile"` + CaptchaVerifyParam string `json:"captchaVerifyParam"` + ActionType string `json:"actionType" validate:"omitempty,oneof=login register query agentApply realName bindMobile"` +} diff --git a/app/main/api/main.go b/app/main/api/main.go new file mode 100644 index 0000000..ab2c396 --- /dev/null +++ b/app/main/api/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "bdrp-server/app/main/api/internal/config" + "bdrp-server/app/main/api/internal/handler" + "bdrp-server/app/main/api/internal/middleware" + "bdrp-server/app/main/api/internal/queue" + "bdrp-server/app/main/api/internal/service" + "bdrp-server/app/main/api/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +func main() { + // 读取环境变量 ENV,默认为 "prod" + env := os.Getenv("ENV") + if env == "" { + env = "production" + } + + // 根据 ENV 加载不同的配置文件 + var defaultConfigFile string + if env == "development" { + defaultConfigFile = "app/main/api/etc/main.dev.yaml" + } else { + defaultConfigFile = "etc/main.yaml" + } + configFile := flag.String("f", defaultConfigFile, "the config file") + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + svcContext := svc.NewServiceContext(c) + defer svcContext.Close() + + // 启动 asynq 消费者 + go func() { + ctx := context.Background() + // 初始化 cron job 或异步任务队列 + asynq := queue.NewCronJob(ctx, svcContext) + mux := asynq.Register() + + // 启动 asynq 消费者 + if err := svcContext.AsynqServer.Run(mux); err != nil { + logx.WithContext(ctx).Errorf("异步任务启动失败: %v", err) + os.Exit(1) + } + fmt.Println("异步任务启动!!!") + }() + + server := rest.MustNewServer(c.RestConf) + server.Use(middleware.GlobalSourceInterceptor) + defer server.Stop() + + handler.RegisterHandlers(server, svcContext) + + // 自动注册API到数据库 + apiRegistry := service.NewApiRegistryService(svcContext.AdminApiModel) + routes := server.Routes() + if err := apiRegistry.RegisterAllApis(context.Background(), routes); err != nil { + logx.Errorf("API注册失败: %v", err) + } else { + logx.Infof("API注册成功,共注册 %d 个路由", len(routes)) + } + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/app/main/api/static/SIMHEI.TTF b/app/main/api/static/SIMHEI.TTF new file mode 100644 index 0000000..5bd4687 Binary files /dev/null and b/app/main/api/static/SIMHEI.TTF differ diff --git a/app/main/api/static/images/tg_qrcode_1.png b/app/main/api/static/images/tg_qrcode_1.png new file mode 100644 index 0000000..1157bb4 Binary files /dev/null and b/app/main/api/static/images/tg_qrcode_1.png differ diff --git a/app/main/api/static/images/yq_qrcode_1.png b/app/main/api/static/images/yq_qrcode_1.png new file mode 100644 index 0000000..d8d4fcc Binary files /dev/null and b/app/main/api/static/images/yq_qrcode_1.png differ diff --git a/app/main/model/adminApiModel.go b/app/main/model/adminApiModel.go new file mode 100644 index 0000000..6ed3633 --- /dev/null +++ b/app/main/model/adminApiModel.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 _ AdminApiModel = (*customAdminApiModel)(nil) + +type ( + // AdminApiModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminApiModel. + AdminApiModel interface { + adminApiModel + } + + customAdminApiModel struct { + *defaultAdminApiModel + } +) + +// NewAdminApiModel returns a model for the database table. +func NewAdminApiModel(conn sqlx.SqlConn, c cache.CacheConf) AdminApiModel { + return &customAdminApiModel{ + defaultAdminApiModel: newAdminApiModel(conn, c), + } +} diff --git a/app/main/model/adminApiModel_gen.go b/app/main/model/adminApiModel_gen.go new file mode 100644 index 0000000..6b8b1ad --- /dev/null +++ b/app/main/model/adminApiModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminApiFieldNames = builder.RawFieldNames(&AdminApi{}) + adminApiRows = strings.Join(adminApiFieldNames, ",") + adminApiRowsExpectAutoSet = strings.Join(stringx.Remove(adminApiFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminApiRowsWithPlaceHolder = strings.Join(stringx.Remove(adminApiFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminApiIdPrefix = "cache:bdrp:adminApi:id:" + cacheHmAdminApiApiCodePrefix = "cache:bdrp:adminApi:apiCode:" +) + +type ( + adminApiModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminApi) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminApi, error) + FindOneByApiCode(ctx context.Context, apiCode string) (*AdminApi, error) + Update(ctx context.Context, session sqlx.Session, data *AdminApi) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminApi) 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 *AdminApi) 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) ([]*AdminApi, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminApi, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminApi, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminApi, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminApi, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminApiModel struct { + sqlc.CachedConn + table string + } + + AdminApi struct { + Id int64 `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"` // 版本号 + ApiName string `db:"api_name"` // 接口名称 + ApiCode string `db:"api_code"` // 接口编码 + Method string `db:"method"` // 请求方法:GET、POST等 + Url string `db:"url"` // 接口URL + Status int64 `db:"status"` // 状态:0-禁用,1-启用 + Description string `db:"description"` // 接口描述 + } +) + +func newAdminApiModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminApiModel { + return &defaultAdminApiModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_api`", + } +} + +func (m *defaultAdminApiModel) Insert(ctx context.Context, session sqlx.Session, data *AdminApi) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminApiApiCodeKey := fmt.Sprintf("%s%v", cacheHmAdminApiApiCodePrefix, data.ApiCode) + hmAdminApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, 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, adminApiRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiName, data.ApiCode, data.Method, data.Url, data.Status, data.Description) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiName, data.ApiCode, data.Method, data.Url, data.Status, data.Description) + }, hmAdminApiApiCodeKey, hmAdminApiIdKey) +} + +func (m *defaultAdminApiModel) FindOne(ctx context.Context, id int64) (*AdminApi, error) { + hmAdminApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, id) + var resp AdminApi + err := m.QueryRowCtx(ctx, &resp, hmAdminApiIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminApiRows, 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 *defaultAdminApiModel) FindOneByApiCode(ctx context.Context, apiCode string) (*AdminApi, error) { + hmAdminApiApiCodeKey := fmt.Sprintf("%s%v", cacheHmAdminApiApiCodePrefix, apiCode) + var resp AdminApi + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminApiApiCodeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `api_code` = ? and del_state = ? limit 1", adminApiRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, apiCode, 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 *defaultAdminApiModel) Update(ctx context.Context, session sqlx.Session, newData *AdminApi) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminApiApiCodeKey := fmt.Sprintf("%s%v", cacheHmAdminApiApiCodePrefix, data.ApiCode) + hmAdminApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, 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, adminApiRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiName, newData.ApiCode, newData.Method, newData.Url, newData.Status, newData.Description, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiName, newData.ApiCode, newData.Method, newData.Url, newData.Status, newData.Description, newData.Id) + }, hmAdminApiApiCodeKey, hmAdminApiIdKey) +} + +func (m *defaultAdminApiModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminApi) 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 + } + hmAdminApiApiCodeKey := fmt.Sprintf("%s%v", cacheHmAdminApiApiCodePrefix, data.ApiCode) + hmAdminApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, 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, adminApiRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiName, newData.ApiCode, newData.Method, newData.Url, newData.Status, newData.Description, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiName, newData.ApiCode, newData.Method, newData.Url, newData.Status, newData.Description, newData.Id, oldVersion) + }, hmAdminApiApiCodeKey, hmAdminApiIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminApiModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminApi) 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 "), "AdminApiModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminApiModel) 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 *defaultAdminApiModel) 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 *defaultAdminApiModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminApi, error) { + + builder = builder.Columns(adminApiRows) + + 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 []*AdminApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminApiModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminApi, error) { + + builder = builder.Columns(adminApiRows) + + 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 []*AdminApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminApiModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminApi, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminApiRows) + + 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 []*AdminApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminApiModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminApi, error) { + + builder = builder.Columns(adminApiRows) + + 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 []*AdminApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminApiModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminApi, error) { + + builder = builder.Columns(adminApiRows) + + 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 []*AdminApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminApiModel) 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 *defaultAdminApiModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminApiModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminApiApiCodeKey := fmt.Sprintf("%s%v", cacheHmAdminApiApiCodePrefix, data.ApiCode) + hmAdminApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, 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) + }, hmAdminApiApiCodeKey, hmAdminApiIdKey) + return err +} +func (m *defaultAdminApiModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminApiIdPrefix, primary) +} +func (m *defaultAdminApiModel) 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", adminApiRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminApiModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminDictDataModel.go b/app/main/model/adminDictDataModel.go new file mode 100644 index 0000000..cbec9c9 --- /dev/null +++ b/app/main/model/adminDictDataModel.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 _ AdminDictDataModel = (*customAdminDictDataModel)(nil) + +type ( + // AdminDictDataModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminDictDataModel. + AdminDictDataModel interface { + adminDictDataModel + } + + customAdminDictDataModel struct { + *defaultAdminDictDataModel + } +) + +// NewAdminDictDataModel returns a model for the database table. +func NewAdminDictDataModel(conn sqlx.SqlConn, c cache.CacheConf) AdminDictDataModel { + return &customAdminDictDataModel{ + defaultAdminDictDataModel: newAdminDictDataModel(conn, c), + } +} diff --git a/app/main/model/adminDictDataModel_gen.go b/app/main/model/adminDictDataModel_gen.go new file mode 100644 index 0000000..f4dff17 --- /dev/null +++ b/app/main/model/adminDictDataModel_gen.go @@ -0,0 +1,438 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminDictDataFieldNames = builder.RawFieldNames(&AdminDictData{}) + adminDictDataRows = strings.Join(adminDictDataFieldNames, ",") + adminDictDataRowsExpectAutoSet = strings.Join(stringx.Remove(adminDictDataFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminDictDataRowsWithPlaceHolder = strings.Join(stringx.Remove(adminDictDataFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminDictDataIdPrefix = "cache:bdrp:adminDictbdrp:id:" + cacheHmAdminDictDataDictTypeDictLabelPrefix = "cache:bdrp:adminDictbdrp:dictType:dictLabel:" + cacheHmAdminDictDataDictTypeDictValuePrefix = "cache:bdrp:adminDictbdrp:dictType:dictValue:" +) + +type ( + adminDictDataModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminDictData) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminDictData, error) + FindOneByDictTypeDictLabel(ctx context.Context, dictType string, dictLabel string) (*AdminDictData, error) + FindOneByDictTypeDictValue(ctx context.Context, dictType string, dictValue int64) (*AdminDictData, error) + Update(ctx context.Context, session sqlx.Session, data *AdminDictData) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminDictData) 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 *AdminDictData) 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) ([]*AdminDictData, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictData, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictData, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminDictData, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminDictData, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminDictDataModel struct { + sqlc.CachedConn + table string + } + + AdminDictData struct { + Id int64 `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"` // 版本号 + DictType string `db:"dict_type"` // 字典类型编码 + DictLabel string `db:"dict_label"` // 字典标签 + DictValue int64 `db:"dict_value"` // 字典键值 + DictSort int64 `db:"dict_sort"` // 字典排序 + Status int64 `db:"status"` // 状态:0-禁用,1-启用 + Remark sql.NullString `db:"remark"` // 备注 + } +) + +func newAdminDictDataModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminDictDataModel { + return &defaultAdminDictDataModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_dict_data`", + } +} + +func (m *defaultAdminDictDataModel) Insert(ctx context.Context, session sqlx.Session, data *AdminDictData) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminDictDataDictTypeDictLabelKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictLabelPrefix, data.DictType, data.DictLabel) + hmAdminDictDataDictTypeDictValueKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictValuePrefix, data.DictType, data.DictValue) + hmAdminDictDataIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, 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, adminDictDataRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.DictType, data.DictLabel, data.DictValue, data.DictSort, data.Status, data.Remark) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.DictType, data.DictLabel, data.DictValue, data.DictSort, data.Status, data.Remark) + }, hmAdminDictDataDictTypeDictLabelKey, hmAdminDictDataDictTypeDictValueKey, hmAdminDictDataIdKey) +} + +func (m *defaultAdminDictDataModel) FindOne(ctx context.Context, id int64) (*AdminDictData, error) { + hmAdminDictDataIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, id) + var resp AdminDictData + err := m.QueryRowCtx(ctx, &resp, hmAdminDictDataIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminDictDataRows, 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 *defaultAdminDictDataModel) FindOneByDictTypeDictLabel(ctx context.Context, dictType string, dictLabel string) (*AdminDictData, error) { + hmAdminDictDataDictTypeDictLabelKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictLabelPrefix, dictType, dictLabel) + var resp AdminDictData + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminDictDataDictTypeDictLabelKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `dict_type` = ? and `dict_label` = ? and del_state = ? limit 1", adminDictDataRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, dictType, dictLabel, 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 *defaultAdminDictDataModel) FindOneByDictTypeDictValue(ctx context.Context, dictType string, dictValue int64) (*AdminDictData, error) { + hmAdminDictDataDictTypeDictValueKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictValuePrefix, dictType, dictValue) + var resp AdminDictData + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminDictDataDictTypeDictValueKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `dict_type` = ? and `dict_value` = ? and del_state = ? limit 1", adminDictDataRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, dictType, dictValue, 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 *defaultAdminDictDataModel) Update(ctx context.Context, session sqlx.Session, newData *AdminDictData) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminDictDataDictTypeDictLabelKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictLabelPrefix, data.DictType, data.DictLabel) + hmAdminDictDataDictTypeDictValueKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictValuePrefix, data.DictType, data.DictValue) + hmAdminDictDataIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, 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, adminDictDataRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictLabel, newData.DictValue, newData.DictSort, newData.Status, newData.Remark, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictLabel, newData.DictValue, newData.DictSort, newData.Status, newData.Remark, newData.Id) + }, hmAdminDictDataDictTypeDictLabelKey, hmAdminDictDataDictTypeDictValueKey, hmAdminDictDataIdKey) +} + +func (m *defaultAdminDictDataModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminDictData) 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 + } + hmAdminDictDataDictTypeDictLabelKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictLabelPrefix, data.DictType, data.DictLabel) + hmAdminDictDataDictTypeDictValueKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictValuePrefix, data.DictType, data.DictValue) + hmAdminDictDataIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, 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, adminDictDataRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictLabel, newData.DictValue, newData.DictSort, newData.Status, newData.Remark, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictLabel, newData.DictValue, newData.DictSort, newData.Status, newData.Remark, newData.Id, oldVersion) + }, hmAdminDictDataDictTypeDictLabelKey, hmAdminDictDataDictTypeDictValueKey, hmAdminDictDataIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminDictDataModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminDictData) 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 "), "AdminDictDataModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminDictDataModel) 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 *defaultAdminDictDataModel) 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 *defaultAdminDictDataModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminDictData, error) { + + builder = builder.Columns(adminDictDataRows) + + 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 []*AdminDictData + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictDataModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictData, error) { + + builder = builder.Columns(adminDictDataRows) + + 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 []*AdminDictData + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictDataModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictData, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminDictDataRows) + + 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 []*AdminDictData + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminDictDataModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminDictData, error) { + + builder = builder.Columns(adminDictDataRows) + + 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 []*AdminDictData + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictDataModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminDictData, error) { + + builder = builder.Columns(adminDictDataRows) + + 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 []*AdminDictData + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictDataModel) 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 *defaultAdminDictDataModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminDictDataModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminDictDataDictTypeDictLabelKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictLabelPrefix, data.DictType, data.DictLabel) + hmAdminDictDataDictTypeDictValueKey := fmt.Sprintf("%s%v:%v", cacheHmAdminDictDataDictTypeDictValuePrefix, data.DictType, data.DictValue) + hmAdminDictDataIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, 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) + }, hmAdminDictDataDictTypeDictLabelKey, hmAdminDictDataDictTypeDictValueKey, hmAdminDictDataIdKey) + return err +} +func (m *defaultAdminDictDataModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminDictDataIdPrefix, primary) +} +func (m *defaultAdminDictDataModel) 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", adminDictDataRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminDictDataModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminDictTypeModel.go b/app/main/model/adminDictTypeModel.go new file mode 100644 index 0000000..1153feb --- /dev/null +++ b/app/main/model/adminDictTypeModel.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 _ AdminDictTypeModel = (*customAdminDictTypeModel)(nil) + +type ( + // AdminDictTypeModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminDictTypeModel. + AdminDictTypeModel interface { + adminDictTypeModel + } + + customAdminDictTypeModel struct { + *defaultAdminDictTypeModel + } +) + +// NewAdminDictTypeModel returns a model for the database table. +func NewAdminDictTypeModel(conn sqlx.SqlConn, c cache.CacheConf) AdminDictTypeModel { + return &customAdminDictTypeModel{ + defaultAdminDictTypeModel: newAdminDictTypeModel(conn, c), + } +} diff --git a/app/main/model/adminDictTypeModel_gen.go b/app/main/model/adminDictTypeModel_gen.go new file mode 100644 index 0000000..9823b9a --- /dev/null +++ b/app/main/model/adminDictTypeModel_gen.go @@ -0,0 +1,410 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminDictTypeFieldNames = builder.RawFieldNames(&AdminDictType{}) + adminDictTypeRows = strings.Join(adminDictTypeFieldNames, ",") + adminDictTypeRowsExpectAutoSet = strings.Join(stringx.Remove(adminDictTypeFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminDictTypeRowsWithPlaceHolder = strings.Join(stringx.Remove(adminDictTypeFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminDictTypeIdPrefix = "cache:bdrp:adminDictType:id:" + cacheHmAdminDictTypeDictTypePrefix = "cache:bdrp:adminDictType:dictType:" +) + +type ( + adminDictTypeModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminDictType) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminDictType, error) + FindOneByDictType(ctx context.Context, dictType string) (*AdminDictType, error) + Update(ctx context.Context, session sqlx.Session, data *AdminDictType) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminDictType) 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 *AdminDictType) 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) ([]*AdminDictType, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictType, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictType, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminDictType, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminDictType, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminDictTypeModel struct { + sqlc.CachedConn + table string + } + + AdminDictType struct { + Id int64 `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"` // 版本号 + DictType string `db:"dict_type"` // 字典类型编码 + DictName string `db:"dict_name"` // 字典类型名称 + Status int64 `db:"status"` // 状态:0-禁用,1-启用 + Remark sql.NullString `db:"remark"` // 备注 + } +) + +func newAdminDictTypeModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminDictTypeModel { + return &defaultAdminDictTypeModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_dict_type`", + } +} + +func (m *defaultAdminDictTypeModel) Insert(ctx context.Context, session sqlx.Session, data *AdminDictType) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminDictTypeDictTypeKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeDictTypePrefix, data.DictType) + hmAdminDictTypeIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, 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, adminDictTypeRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.DictType, data.DictName, data.Status, data.Remark) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.DictType, data.DictName, data.Status, data.Remark) + }, hmAdminDictTypeDictTypeKey, hmAdminDictTypeIdKey) +} + +func (m *defaultAdminDictTypeModel) FindOne(ctx context.Context, id int64) (*AdminDictType, error) { + hmAdminDictTypeIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, id) + var resp AdminDictType + err := m.QueryRowCtx(ctx, &resp, hmAdminDictTypeIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminDictTypeRows, 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 *defaultAdminDictTypeModel) FindOneByDictType(ctx context.Context, dictType string) (*AdminDictType, error) { + hmAdminDictTypeDictTypeKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeDictTypePrefix, dictType) + var resp AdminDictType + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminDictTypeDictTypeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `dict_type` = ? and del_state = ? limit 1", adminDictTypeRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, dictType, 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 *defaultAdminDictTypeModel) Update(ctx context.Context, session sqlx.Session, newData *AdminDictType) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminDictTypeDictTypeKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeDictTypePrefix, data.DictType) + hmAdminDictTypeIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, 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, adminDictTypeRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictName, newData.Status, newData.Remark, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictName, newData.Status, newData.Remark, newData.Id) + }, hmAdminDictTypeDictTypeKey, hmAdminDictTypeIdKey) +} + +func (m *defaultAdminDictTypeModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminDictType) 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 + } + hmAdminDictTypeDictTypeKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeDictTypePrefix, data.DictType) + hmAdminDictTypeIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, 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, adminDictTypeRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictName, newData.Status, newData.Remark, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.DictType, newData.DictName, newData.Status, newData.Remark, newData.Id, oldVersion) + }, hmAdminDictTypeDictTypeKey, hmAdminDictTypeIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminDictTypeModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminDictType) 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 "), "AdminDictTypeModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminDictTypeModel) 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 *defaultAdminDictTypeModel) 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 *defaultAdminDictTypeModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminDictType, error) { + + builder = builder.Columns(adminDictTypeRows) + + 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 []*AdminDictType + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictTypeModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictType, error) { + + builder = builder.Columns(adminDictTypeRows) + + 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 []*AdminDictType + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictTypeModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminDictType, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminDictTypeRows) + + 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 []*AdminDictType + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminDictTypeModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminDictType, error) { + + builder = builder.Columns(adminDictTypeRows) + + 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 []*AdminDictType + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictTypeModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminDictType, error) { + + builder = builder.Columns(adminDictTypeRows) + + 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 []*AdminDictType + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminDictTypeModel) 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 *defaultAdminDictTypeModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminDictTypeModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminDictTypeDictTypeKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeDictTypePrefix, data.DictType) + hmAdminDictTypeIdKey := fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, 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) + }, hmAdminDictTypeDictTypeKey, hmAdminDictTypeIdKey) + return err +} +func (m *defaultAdminDictTypeModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminDictTypeIdPrefix, primary) +} +func (m *defaultAdminDictTypeModel) 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", adminDictTypeRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminDictTypeModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminMenuModel.go b/app/main/model/adminMenuModel.go new file mode 100644 index 0000000..c2dc171 --- /dev/null +++ b/app/main/model/adminMenuModel.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 _ AdminMenuModel = (*customAdminMenuModel)(nil) + +type ( + // AdminMenuModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminMenuModel. + AdminMenuModel interface { + adminMenuModel + } + + customAdminMenuModel struct { + *defaultAdminMenuModel + } +) + +// NewAdminMenuModel returns a model for the database table. +func NewAdminMenuModel(conn sqlx.SqlConn, c cache.CacheConf) AdminMenuModel { + return &customAdminMenuModel{ + defaultAdminMenuModel: newAdminMenuModel(conn, c), + } +} diff --git a/app/main/model/adminMenuModel_gen.go b/app/main/model/adminMenuModel_gen.go new file mode 100644 index 0000000..1a4352c --- /dev/null +++ b/app/main/model/adminMenuModel_gen.go @@ -0,0 +1,415 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminMenuFieldNames = builder.RawFieldNames(&AdminMenu{}) + adminMenuRows = strings.Join(adminMenuFieldNames, ",") + adminMenuRowsExpectAutoSet = strings.Join(stringx.Remove(adminMenuFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminMenuRowsWithPlaceHolder = strings.Join(stringx.Remove(adminMenuFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminMenuIdPrefix = "cache:bdrp:adminMenu:id:" + cacheHmAdminMenuNamePathPrefix = "cache:bdrp:adminMenu:name:path:" +) + +type ( + adminMenuModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminMenu) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminMenu, error) + FindOneByNamePath(ctx context.Context, name string, path string) (*AdminMenu, error) + Update(ctx context.Context, session sqlx.Session, data *AdminMenu) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminMenu) 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 *AdminMenu) 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) ([]*AdminMenu, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminMenu, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminMenu, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminMenu, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminMenu, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminMenuModel struct { + sqlc.CachedConn + table string + } + + AdminMenu struct { + Id int64 `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"` // 版本号 + Pid int64 `db:"pid"` // 父菜单ID + Name string `db:"name"` // 路由名称 + Path string `db:"path"` // 路由路径 + Component string `db:"component"` // 组件路径 + Redirect sql.NullString `db:"redirect"` // 重定向路径 + Meta string `db:"meta"` // 路由元数据配置 + Status int64 `db:"status"` // 状态: 0-禁用, 1-启用 + Type int64 `db:"type"` + Sort int64 `db:"sort"` // 排序号 + } +) + +func newAdminMenuModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminMenuModel { + return &defaultAdminMenuModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_menu`", + } +} + +func (m *defaultAdminMenuModel) Insert(ctx context.Context, session sqlx.Session, data *AdminMenu) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, data.Id) + hmAdminMenuNamePathKey := fmt.Sprintf("%s%v:%v", cacheHmAdminMenuNamePathPrefix, data.Name, data.Path) + 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, adminMenuRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Pid, data.Name, data.Path, data.Component, data.Redirect, data.Meta, data.Status, data.Type, data.Sort) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Pid, data.Name, data.Path, data.Component, data.Redirect, data.Meta, data.Status, data.Type, data.Sort) + }, hmAdminMenuIdKey, hmAdminMenuNamePathKey) +} + +func (m *defaultAdminMenuModel) FindOne(ctx context.Context, id int64) (*AdminMenu, error) { + hmAdminMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, id) + var resp AdminMenu + err := m.QueryRowCtx(ctx, &resp, hmAdminMenuIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminMenuRows, 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 *defaultAdminMenuModel) FindOneByNamePath(ctx context.Context, name string, path string) (*AdminMenu, error) { + hmAdminMenuNamePathKey := fmt.Sprintf("%s%v:%v", cacheHmAdminMenuNamePathPrefix, name, path) + var resp AdminMenu + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminMenuNamePathKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `name` = ? and `path` = ? and del_state = ? limit 1", adminMenuRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, name, path, 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 *defaultAdminMenuModel) Update(ctx context.Context, session sqlx.Session, newData *AdminMenu) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, data.Id) + hmAdminMenuNamePathKey := fmt.Sprintf("%s%v:%v", cacheHmAdminMenuNamePathPrefix, data.Name, data.Path) + 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, adminMenuRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Pid, newData.Name, newData.Path, newData.Component, newData.Redirect, newData.Meta, newData.Status, newData.Type, newData.Sort, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Pid, newData.Name, newData.Path, newData.Component, newData.Redirect, newData.Meta, newData.Status, newData.Type, newData.Sort, newData.Id) + }, hmAdminMenuIdKey, hmAdminMenuNamePathKey) +} + +func (m *defaultAdminMenuModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminMenu) 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 + } + hmAdminMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, data.Id) + hmAdminMenuNamePathKey := fmt.Sprintf("%s%v:%v", cacheHmAdminMenuNamePathPrefix, data.Name, data.Path) + 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, adminMenuRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Pid, newData.Name, newData.Path, newData.Component, newData.Redirect, newData.Meta, newData.Status, newData.Type, newData.Sort, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Pid, newData.Name, newData.Path, newData.Component, newData.Redirect, newData.Meta, newData.Status, newData.Type, newData.Sort, newData.Id, oldVersion) + }, hmAdminMenuIdKey, hmAdminMenuNamePathKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminMenuModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminMenu) 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 "), "AdminMenuModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminMenuModel) 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 *defaultAdminMenuModel) 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 *defaultAdminMenuModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminMenu, error) { + + builder = builder.Columns(adminMenuRows) + + 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 []*AdminMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminMenuModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminMenu, error) { + + builder = builder.Columns(adminMenuRows) + + 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 []*AdminMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminMenuModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminMenu, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminMenuRows) + + 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 []*AdminMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminMenuModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminMenu, error) { + + builder = builder.Columns(adminMenuRows) + + 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 []*AdminMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminMenuModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminMenu, error) { + + builder = builder.Columns(adminMenuRows) + + 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 []*AdminMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminMenuModel) 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 *defaultAdminMenuModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminMenuModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, id) + hmAdminMenuNamePathKey := fmt.Sprintf("%s%v:%v", cacheHmAdminMenuNamePathPrefix, data.Name, data.Path) + _, 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) + }, hmAdminMenuIdKey, hmAdminMenuNamePathKey) + return err +} +func (m *defaultAdminMenuModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminMenuIdPrefix, primary) +} +func (m *defaultAdminMenuModel) 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", adminMenuRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminMenuModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminPromotionLinkModel.go b/app/main/model/adminPromotionLinkModel.go new file mode 100644 index 0000000..d82c42e --- /dev/null +++ b/app/main/model/adminPromotionLinkModel.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 _ AdminPromotionLinkModel = (*customAdminPromotionLinkModel)(nil) + +type ( + // AdminPromotionLinkModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminPromotionLinkModel. + AdminPromotionLinkModel interface { + adminPromotionLinkModel + } + + customAdminPromotionLinkModel struct { + *defaultAdminPromotionLinkModel + } +) + +// NewAdminPromotionLinkModel returns a model for the database table. +func NewAdminPromotionLinkModel(conn sqlx.SqlConn, c cache.CacheConf) AdminPromotionLinkModel { + return &customAdminPromotionLinkModel{ + defaultAdminPromotionLinkModel: newAdminPromotionLinkModel(conn, c), + } +} diff --git a/app/main/model/adminPromotionLinkModel_gen.go b/app/main/model/adminPromotionLinkModel_gen.go new file mode 100644 index 0000000..1332e18 --- /dev/null +++ b/app/main/model/adminPromotionLinkModel_gen.go @@ -0,0 +1,409 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminPromotionLinkFieldNames = builder.RawFieldNames(&AdminPromotionLink{}) + adminPromotionLinkRows = strings.Join(adminPromotionLinkFieldNames, ",") + adminPromotionLinkRowsExpectAutoSet = strings.Join(stringx.Remove(adminPromotionLinkFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminPromotionLinkRowsWithPlaceHolder = strings.Join(stringx.Remove(adminPromotionLinkFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminPromotionLinkIdPrefix = "cache:bdrp:adminPromotionLink:id:" + cacheHmAdminPromotionLinkUrlPrefix = "cache:bdrp:adminPromotionLink:url:" +) + +type ( + adminPromotionLinkModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLink) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminPromotionLink, error) + FindOneByUrl(ctx context.Context, url string) (*AdminPromotionLink, error) + Update(ctx context.Context, session sqlx.Session, data *AdminPromotionLink) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminPromotionLink) 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 *AdminPromotionLink) 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) ([]*AdminPromotionLink, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLink, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLink, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLink, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLink, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminPromotionLinkModel struct { + sqlc.CachedConn + table string + } + + AdminPromotionLink struct { + Id int64 `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"` // 版本号 + Url string `db:"url"` // 推广链接URL + Name string `db:"name"` // 推广链接名称 + AdminUserId int64 `db:"admin_user_id"` // 推广者账号ID + } +) + +func newAdminPromotionLinkModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminPromotionLinkModel { + return &defaultAdminPromotionLinkModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_promotion_link`", + } +} + +func (m *defaultAdminPromotionLinkModel) Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLink) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminPromotionLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, data.Id) + hmAdminPromotionLinkUrlKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkUrlPrefix, data.Url) + 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, adminPromotionLinkRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Url, data.Name, data.AdminUserId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Url, data.Name, data.AdminUserId) + }, hmAdminPromotionLinkIdKey, hmAdminPromotionLinkUrlKey) +} + +func (m *defaultAdminPromotionLinkModel) FindOne(ctx context.Context, id int64) (*AdminPromotionLink, error) { + hmAdminPromotionLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, id) + var resp AdminPromotionLink + err := m.QueryRowCtx(ctx, &resp, hmAdminPromotionLinkIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminPromotionLinkRows, 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 *defaultAdminPromotionLinkModel) FindOneByUrl(ctx context.Context, url string) (*AdminPromotionLink, error) { + hmAdminPromotionLinkUrlKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkUrlPrefix, url) + var resp AdminPromotionLink + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminPromotionLinkUrlKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `url` = ? and del_state = ? limit 1", adminPromotionLinkRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, url, 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 *defaultAdminPromotionLinkModel) Update(ctx context.Context, session sqlx.Session, newData *AdminPromotionLink) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminPromotionLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, data.Id) + hmAdminPromotionLinkUrlKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkUrlPrefix, data.Url) + 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, adminPromotionLinkRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Url, newData.Name, newData.AdminUserId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Url, newData.Name, newData.AdminUserId, newData.Id) + }, hmAdminPromotionLinkIdKey, hmAdminPromotionLinkUrlKey) +} + +func (m *defaultAdminPromotionLinkModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminPromotionLink) 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 + } + hmAdminPromotionLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, data.Id) + hmAdminPromotionLinkUrlKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkUrlPrefix, data.Url) + 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, adminPromotionLinkRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Url, newData.Name, newData.AdminUserId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Url, newData.Name, newData.AdminUserId, newData.Id, oldVersion) + }, hmAdminPromotionLinkIdKey, hmAdminPromotionLinkUrlKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminPromotionLinkModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminPromotionLink) 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 "), "AdminPromotionLinkModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminPromotionLinkModel) 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 *defaultAdminPromotionLinkModel) 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 *defaultAdminPromotionLinkModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminPromotionLink, error) { + + builder = builder.Columns(adminPromotionLinkRows) + + 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 []*AdminPromotionLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLink, error) { + + builder = builder.Columns(adminPromotionLinkRows) + + 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 []*AdminPromotionLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLink, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminPromotionLinkRows) + + 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 []*AdminPromotionLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminPromotionLinkModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLink, error) { + + builder = builder.Columns(adminPromotionLinkRows) + + 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 []*AdminPromotionLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLink, error) { + + builder = builder.Columns(adminPromotionLinkRows) + + 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 []*AdminPromotionLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkModel) 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 *defaultAdminPromotionLinkModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminPromotionLinkModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminPromotionLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, id) + hmAdminPromotionLinkUrlKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkUrlPrefix, data.Url) + _, 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) + }, hmAdminPromotionLinkIdKey, hmAdminPromotionLinkUrlKey) + return err +} +func (m *defaultAdminPromotionLinkModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkIdPrefix, primary) +} +func (m *defaultAdminPromotionLinkModel) 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", adminPromotionLinkRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminPromotionLinkModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminPromotionLinkStatsHistoryModel.go b/app/main/model/adminPromotionLinkStatsHistoryModel.go new file mode 100644 index 0000000..685db69 --- /dev/null +++ b/app/main/model/adminPromotionLinkStatsHistoryModel.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 _ AdminPromotionLinkStatsHistoryModel = (*customAdminPromotionLinkStatsHistoryModel)(nil) + +type ( + // AdminPromotionLinkStatsHistoryModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminPromotionLinkStatsHistoryModel. + AdminPromotionLinkStatsHistoryModel interface { + adminPromotionLinkStatsHistoryModel + } + + customAdminPromotionLinkStatsHistoryModel struct { + *defaultAdminPromotionLinkStatsHistoryModel + } +) + +// NewAdminPromotionLinkStatsHistoryModel returns a model for the database table. +func NewAdminPromotionLinkStatsHistoryModel(conn sqlx.SqlConn, c cache.CacheConf) AdminPromotionLinkStatsHistoryModel { + return &customAdminPromotionLinkStatsHistoryModel{ + defaultAdminPromotionLinkStatsHistoryModel: newAdminPromotionLinkStatsHistoryModel(conn, c), + } +} diff --git a/app/main/model/adminPromotionLinkStatsHistoryModel_gen.go b/app/main/model/adminPromotionLinkStatsHistoryModel_gen.go new file mode 100644 index 0000000..fde29ab --- /dev/null +++ b/app/main/model/adminPromotionLinkStatsHistoryModel_gen.go @@ -0,0 +1,413 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminPromotionLinkStatsHistoryFieldNames = builder.RawFieldNames(&AdminPromotionLinkStatsHistory{}) + adminPromotionLinkStatsHistoryRows = strings.Join(adminPromotionLinkStatsHistoryFieldNames, ",") + adminPromotionLinkStatsHistoryRowsExpectAutoSet = strings.Join(stringx.Remove(adminPromotionLinkStatsHistoryFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminPromotionLinkStatsHistoryRowsWithPlaceHolder = strings.Join(stringx.Remove(adminPromotionLinkStatsHistoryFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminPromotionLinkStatsHistoryIdPrefix = "cache:bdrp:adminPromotionLinkStatsHistory:id:" + cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix = "cache:bdrp:adminPromotionLinkStatsHistory:linkId:statsDate:" +) + +type ( + adminPromotionLinkStatsHistoryModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsHistory) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminPromotionLinkStatsHistory, error) + FindOneByLinkIdStatsDate(ctx context.Context, linkId int64, statsDate time.Time) (*AdminPromotionLinkStatsHistory, error) + Update(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsHistory) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsHistory) 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 *AdminPromotionLinkStatsHistory) 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) ([]*AdminPromotionLinkStatsHistory, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsHistory, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsHistory, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLinkStatsHistory, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLinkStatsHistory, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminPromotionLinkStatsHistoryModel struct { + sqlc.CachedConn + table string + } + + AdminPromotionLinkStatsHistory struct { + Id int64 `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"` // 版本号 + LinkId int64 `db:"link_id"` // 推广链接ID + StatsDate time.Time `db:"stats_date"` // 统计日期 + ClickCount int64 `db:"click_count"` // 点击数 + PayCount int64 `db:"pay_count"` // 付费次数 + PayAmount float64 `db:"pay_amount"` // 付费金额 + LastClickTime sql.NullTime `db:"last_click_time"` // 最后点击时间 + LastPayTime sql.NullTime `db:"last_pay_time"` // 最后付费时间 + } +) + +func newAdminPromotionLinkStatsHistoryModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminPromotionLinkStatsHistoryModel { + return &defaultAdminPromotionLinkStatsHistoryModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_promotion_link_stats_history`", + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsHistory) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminPromotionLinkStatsHistoryIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, data.Id) + hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey := fmt.Sprintf("%s%v:%v", cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix, data.LinkId, data.StatsDate) + 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, adminPromotionLinkStatsHistoryRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.StatsDate, data.ClickCount, data.PayCount, data.PayAmount, data.LastClickTime, data.LastPayTime) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.StatsDate, data.ClickCount, data.PayCount, data.PayAmount, data.LastClickTime, data.LastPayTime) + }, hmAdminPromotionLinkStatsHistoryIdKey, hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey) +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) FindOne(ctx context.Context, id int64) (*AdminPromotionLinkStatsHistory, error) { + hmAdminPromotionLinkStatsHistoryIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, id) + var resp AdminPromotionLinkStatsHistory + err := m.QueryRowCtx(ctx, &resp, hmAdminPromotionLinkStatsHistoryIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminPromotionLinkStatsHistoryRows, 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 *defaultAdminPromotionLinkStatsHistoryModel) FindOneByLinkIdStatsDate(ctx context.Context, linkId int64, statsDate time.Time) (*AdminPromotionLinkStatsHistory, error) { + hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey := fmt.Sprintf("%s%v:%v", cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix, linkId, statsDate) + var resp AdminPromotionLinkStatsHistory + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `link_id` = ? and `stats_date` = ? and del_state = ? limit 1", adminPromotionLinkStatsHistoryRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, linkId, statsDate, 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 *defaultAdminPromotionLinkStatsHistoryModel) Update(ctx context.Context, session sqlx.Session, newData *AdminPromotionLinkStatsHistory) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminPromotionLinkStatsHistoryIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, data.Id) + hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey := fmt.Sprintf("%s%v:%v", cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix, data.LinkId, data.StatsDate) + 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, adminPromotionLinkStatsHistoryRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.StatsDate, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.StatsDate, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id) + }, hmAdminPromotionLinkStatsHistoryIdKey, hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey) +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminPromotionLinkStatsHistory) 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 + } + hmAdminPromotionLinkStatsHistoryIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, data.Id) + hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey := fmt.Sprintf("%s%v:%v", cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix, data.LinkId, data.StatsDate) + 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, adminPromotionLinkStatsHistoryRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.StatsDate, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.StatsDate, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id, oldVersion) + }, hmAdminPromotionLinkStatsHistoryIdKey, hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsHistory) 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 "), "AdminPromotionLinkStatsHistoryModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) 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 *defaultAdminPromotionLinkStatsHistoryModel) 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 *defaultAdminPromotionLinkStatsHistoryModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminPromotionLinkStatsHistory, error) { + + builder = builder.Columns(adminPromotionLinkStatsHistoryRows) + + 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 []*AdminPromotionLinkStatsHistory + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsHistory, error) { + + builder = builder.Columns(adminPromotionLinkStatsHistoryRows) + + 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 []*AdminPromotionLinkStatsHistory + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsHistory, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminPromotionLinkStatsHistoryRows) + + 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 []*AdminPromotionLinkStatsHistory + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLinkStatsHistory, error) { + + builder = builder.Columns(adminPromotionLinkStatsHistoryRows) + + 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 []*AdminPromotionLinkStatsHistory + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLinkStatsHistory, error) { + + builder = builder.Columns(adminPromotionLinkStatsHistoryRows) + + 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 []*AdminPromotionLinkStatsHistory + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) 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 *defaultAdminPromotionLinkStatsHistoryModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminPromotionLinkStatsHistoryModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminPromotionLinkStatsHistoryIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, id) + hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey := fmt.Sprintf("%s%v:%v", cacheHmAdminPromotionLinkStatsHistoryLinkIdStatsDatePrefix, data.LinkId, data.StatsDate) + _, 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) + }, hmAdminPromotionLinkStatsHistoryIdKey, hmAdminPromotionLinkStatsHistoryLinkIdStatsDateKey) + return err +} +func (m *defaultAdminPromotionLinkStatsHistoryModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsHistoryIdPrefix, primary) +} +func (m *defaultAdminPromotionLinkStatsHistoryModel) 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", adminPromotionLinkStatsHistoryRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminPromotionLinkStatsHistoryModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminPromotionLinkStatsTotalModel.go b/app/main/model/adminPromotionLinkStatsTotalModel.go new file mode 100644 index 0000000..e5dfacc --- /dev/null +++ b/app/main/model/adminPromotionLinkStatsTotalModel.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 _ AdminPromotionLinkStatsTotalModel = (*customAdminPromotionLinkStatsTotalModel)(nil) + +type ( + // AdminPromotionLinkStatsTotalModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminPromotionLinkStatsTotalModel. + AdminPromotionLinkStatsTotalModel interface { + adminPromotionLinkStatsTotalModel + } + + customAdminPromotionLinkStatsTotalModel struct { + *defaultAdminPromotionLinkStatsTotalModel + } +) + +// NewAdminPromotionLinkStatsTotalModel returns a model for the database table. +func NewAdminPromotionLinkStatsTotalModel(conn sqlx.SqlConn, c cache.CacheConf) AdminPromotionLinkStatsTotalModel { + return &customAdminPromotionLinkStatsTotalModel{ + defaultAdminPromotionLinkStatsTotalModel: newAdminPromotionLinkStatsTotalModel(conn, c), + } +} diff --git a/app/main/model/adminPromotionLinkStatsTotalModel_gen.go b/app/main/model/adminPromotionLinkStatsTotalModel_gen.go new file mode 100644 index 0000000..485ffae --- /dev/null +++ b/app/main/model/adminPromotionLinkStatsTotalModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminPromotionLinkStatsTotalFieldNames = builder.RawFieldNames(&AdminPromotionLinkStatsTotal{}) + adminPromotionLinkStatsTotalRows = strings.Join(adminPromotionLinkStatsTotalFieldNames, ",") + adminPromotionLinkStatsTotalRowsExpectAutoSet = strings.Join(stringx.Remove(adminPromotionLinkStatsTotalFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminPromotionLinkStatsTotalRowsWithPlaceHolder = strings.Join(stringx.Remove(adminPromotionLinkStatsTotalFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminPromotionLinkStatsTotalIdPrefix = "cache:bdrp:adminPromotionLinkStatsTotal:id:" + cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix = "cache:bdrp:adminPromotionLinkStatsTotal:linkId:" +) + +type ( + adminPromotionLinkStatsTotalModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsTotal) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminPromotionLinkStatsTotal, error) + FindOneByLinkId(ctx context.Context, linkId int64) (*AdminPromotionLinkStatsTotal, error) + Update(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsTotal) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsTotal) 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 *AdminPromotionLinkStatsTotal) 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) ([]*AdminPromotionLinkStatsTotal, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsTotal, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsTotal, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLinkStatsTotal, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLinkStatsTotal, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminPromotionLinkStatsTotalModel struct { + sqlc.CachedConn + table string + } + + AdminPromotionLinkStatsTotal struct { + Id int64 `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"` // 版本号 + LinkId int64 `db:"link_id"` // 推广链接ID + ClickCount int64 `db:"click_count"` // 总点击数 + PayCount int64 `db:"pay_count"` // 总付费次数 + PayAmount float64 `db:"pay_amount"` // 总付费金额 + LastClickTime sql.NullTime `db:"last_click_time"` // 最后点击时间 + LastPayTime sql.NullTime `db:"last_pay_time"` // 最后付费时间 + } +) + +func newAdminPromotionLinkStatsTotalModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminPromotionLinkStatsTotalModel { + return &defaultAdminPromotionLinkStatsTotalModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_promotion_link_stats_total`", + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsTotal) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminPromotionLinkStatsTotalIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, data.Id) + hmAdminPromotionLinkStatsTotalLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix, data.LinkId) + 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, adminPromotionLinkStatsTotalRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.ClickCount, data.PayCount, data.PayAmount, data.LastClickTime, data.LastPayTime) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.ClickCount, data.PayCount, data.PayAmount, data.LastClickTime, data.LastPayTime) + }, hmAdminPromotionLinkStatsTotalIdKey, hmAdminPromotionLinkStatsTotalLinkIdKey) +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) FindOne(ctx context.Context, id int64) (*AdminPromotionLinkStatsTotal, error) { + hmAdminPromotionLinkStatsTotalIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, id) + var resp AdminPromotionLinkStatsTotal + err := m.QueryRowCtx(ctx, &resp, hmAdminPromotionLinkStatsTotalIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminPromotionLinkStatsTotalRows, 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 *defaultAdminPromotionLinkStatsTotalModel) FindOneByLinkId(ctx context.Context, linkId int64) (*AdminPromotionLinkStatsTotal, error) { + hmAdminPromotionLinkStatsTotalLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix, linkId) + var resp AdminPromotionLinkStatsTotal + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminPromotionLinkStatsTotalLinkIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `link_id` = ? and del_state = ? limit 1", adminPromotionLinkStatsTotalRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, linkId, 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 *defaultAdminPromotionLinkStatsTotalModel) Update(ctx context.Context, session sqlx.Session, newData *AdminPromotionLinkStatsTotal) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminPromotionLinkStatsTotalIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, data.Id) + hmAdminPromotionLinkStatsTotalLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix, data.LinkId) + 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, adminPromotionLinkStatsTotalRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id) + }, hmAdminPromotionLinkStatsTotalIdKey, hmAdminPromotionLinkStatsTotalLinkIdKey) +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminPromotionLinkStatsTotal) 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 + } + hmAdminPromotionLinkStatsTotalIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, data.Id) + hmAdminPromotionLinkStatsTotalLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix, data.LinkId) + 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, adminPromotionLinkStatsTotalRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.ClickCount, newData.PayCount, newData.PayAmount, newData.LastClickTime, newData.LastPayTime, newData.Id, oldVersion) + }, hmAdminPromotionLinkStatsTotalIdKey, hmAdminPromotionLinkStatsTotalLinkIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminPromotionLinkStatsTotal) 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 "), "AdminPromotionLinkStatsTotalModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) 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 *defaultAdminPromotionLinkStatsTotalModel) 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 *defaultAdminPromotionLinkStatsTotalModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminPromotionLinkStatsTotal, error) { + + builder = builder.Columns(adminPromotionLinkStatsTotalRows) + + 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 []*AdminPromotionLinkStatsTotal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsTotal, error) { + + builder = builder.Columns(adminPromotionLinkStatsTotalRows) + + 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 []*AdminPromotionLinkStatsTotal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionLinkStatsTotal, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminPromotionLinkStatsTotalRows) + + 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 []*AdminPromotionLinkStatsTotal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionLinkStatsTotal, error) { + + builder = builder.Columns(adminPromotionLinkStatsTotalRows) + + 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 []*AdminPromotionLinkStatsTotal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionLinkStatsTotal, error) { + + builder = builder.Columns(adminPromotionLinkStatsTotalRows) + + 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 []*AdminPromotionLinkStatsTotal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) 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 *defaultAdminPromotionLinkStatsTotalModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminPromotionLinkStatsTotalModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminPromotionLinkStatsTotalIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, id) + hmAdminPromotionLinkStatsTotalLinkIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalLinkIdPrefix, data.LinkId) + _, 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) + }, hmAdminPromotionLinkStatsTotalIdKey, hmAdminPromotionLinkStatsTotalLinkIdKey) + return err +} +func (m *defaultAdminPromotionLinkStatsTotalModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminPromotionLinkStatsTotalIdPrefix, primary) +} +func (m *defaultAdminPromotionLinkStatsTotalModel) 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", adminPromotionLinkStatsTotalRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminPromotionLinkStatsTotalModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminPromotionOrderModel.go b/app/main/model/adminPromotionOrderModel.go new file mode 100644 index 0000000..9d8366f --- /dev/null +++ b/app/main/model/adminPromotionOrderModel.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 _ AdminPromotionOrderModel = (*customAdminPromotionOrderModel)(nil) + +type ( + // AdminPromotionOrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminPromotionOrderModel. + AdminPromotionOrderModel interface { + adminPromotionOrderModel + } + + customAdminPromotionOrderModel struct { + *defaultAdminPromotionOrderModel + } +) + +// NewAdminPromotionOrderModel returns a model for the database table. +func NewAdminPromotionOrderModel(conn sqlx.SqlConn, c cache.CacheConf) AdminPromotionOrderModel { + return &customAdminPromotionOrderModel{ + defaultAdminPromotionOrderModel: newAdminPromotionOrderModel(conn, c), + } +} diff --git a/app/main/model/adminPromotionOrderModel_gen.go b/app/main/model/adminPromotionOrderModel_gen.go new file mode 100644 index 0000000..950f58a --- /dev/null +++ b/app/main/model/adminPromotionOrderModel_gen.go @@ -0,0 +1,410 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminPromotionOrderFieldNames = builder.RawFieldNames(&AdminPromotionOrder{}) + adminPromotionOrderRows = strings.Join(adminPromotionOrderFieldNames, ",") + adminPromotionOrderRowsExpectAutoSet = strings.Join(stringx.Remove(adminPromotionOrderFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminPromotionOrderRowsWithPlaceHolder = strings.Join(stringx.Remove(adminPromotionOrderFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminPromotionOrderIdPrefix = "cache:bdrp:adminPromotionOrder:id:" + cacheHmAdminPromotionOrderOrderIdPrefix = "cache:bdrp:adminPromotionOrder:orderId:" +) + +type ( + adminPromotionOrderModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionOrder) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminPromotionOrder, error) + FindOneByOrderId(ctx context.Context, orderId int64) (*AdminPromotionOrder, error) + Update(ctx context.Context, session sqlx.Session, data *AdminPromotionOrder) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminPromotionOrder) 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 *AdminPromotionOrder) 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) ([]*AdminPromotionOrder, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionOrder, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionOrder, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionOrder, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionOrder, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminPromotionOrderModel struct { + sqlc.CachedConn + table string + } + + AdminPromotionOrder struct { + Id int64 `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"` // 版本号 + LinkId int64 `db:"link_id"` // 推广链接ID + OrderId int64 `db:"order_id"` // 订单ID + UserId int64 `db:"user_id"` // 下单用户ID + AdminUserId int64 `db:"admin_user_id"` // 推广者账号ID + } +) + +func newAdminPromotionOrderModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminPromotionOrderModel { + return &defaultAdminPromotionOrderModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_promotion_order`", + } +} + +func (m *defaultAdminPromotionOrderModel) Insert(ctx context.Context, session sqlx.Session, data *AdminPromotionOrder) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminPromotionOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, data.Id) + hmAdminPromotionOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderOrderIdPrefix, data.OrderId) + 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, adminPromotionOrderRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.OrderId, data.UserId, data.AdminUserId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.LinkId, data.OrderId, data.UserId, data.AdminUserId) + }, hmAdminPromotionOrderIdKey, hmAdminPromotionOrderOrderIdKey) +} + +func (m *defaultAdminPromotionOrderModel) FindOne(ctx context.Context, id int64) (*AdminPromotionOrder, error) { + hmAdminPromotionOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, id) + var resp AdminPromotionOrder + err := m.QueryRowCtx(ctx, &resp, hmAdminPromotionOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminPromotionOrderRows, 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 *defaultAdminPromotionOrderModel) FindOneByOrderId(ctx context.Context, orderId int64) (*AdminPromotionOrder, error) { + hmAdminPromotionOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderOrderIdPrefix, orderId) + var resp AdminPromotionOrder + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminPromotionOrderOrderIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `order_id` = ? and del_state = ? limit 1", adminPromotionOrderRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, orderId, 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 *defaultAdminPromotionOrderModel) Update(ctx context.Context, session sqlx.Session, newData *AdminPromotionOrder) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminPromotionOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, data.Id) + hmAdminPromotionOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderOrderIdPrefix, data.OrderId) + 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, adminPromotionOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.OrderId, newData.UserId, newData.AdminUserId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.OrderId, newData.UserId, newData.AdminUserId, newData.Id) + }, hmAdminPromotionOrderIdKey, hmAdminPromotionOrderOrderIdKey) +} + +func (m *defaultAdminPromotionOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminPromotionOrder) 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 + } + hmAdminPromotionOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, data.Id) + hmAdminPromotionOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderOrderIdPrefix, data.OrderId) + 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, adminPromotionOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.OrderId, newData.UserId, newData.AdminUserId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.LinkId, newData.OrderId, newData.UserId, newData.AdminUserId, newData.Id, oldVersion) + }, hmAdminPromotionOrderIdKey, hmAdminPromotionOrderOrderIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminPromotionOrderModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminPromotionOrder) 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 "), "AdminPromotionOrderModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminPromotionOrderModel) 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 *defaultAdminPromotionOrderModel) 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 *defaultAdminPromotionOrderModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminPromotionOrder, error) { + + builder = builder.Columns(adminPromotionOrderRows) + + 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 []*AdminPromotionOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionOrderModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionOrder, error) { + + builder = builder.Columns(adminPromotionOrderRows) + + 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 []*AdminPromotionOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionOrderModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminPromotionOrder, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminPromotionOrderRows) + + 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 []*AdminPromotionOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminPromotionOrderModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminPromotionOrder, error) { + + builder = builder.Columns(adminPromotionOrderRows) + + 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 []*AdminPromotionOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionOrderModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminPromotionOrder, error) { + + builder = builder.Columns(adminPromotionOrderRows) + + 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 []*AdminPromotionOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminPromotionOrderModel) 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 *defaultAdminPromotionOrderModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminPromotionOrderModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminPromotionOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, id) + hmAdminPromotionOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderOrderIdPrefix, data.OrderId) + _, 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) + }, hmAdminPromotionOrderIdKey, hmAdminPromotionOrderOrderIdKey) + return err +} +func (m *defaultAdminPromotionOrderModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminPromotionOrderIdPrefix, primary) +} +func (m *defaultAdminPromotionOrderModel) 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", adminPromotionOrderRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminPromotionOrderModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminRoleApiModel.go b/app/main/model/adminRoleApiModel.go new file mode 100644 index 0000000..bcb9fc7 --- /dev/null +++ b/app/main/model/adminRoleApiModel.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 _ AdminRoleApiModel = (*customAdminRoleApiModel)(nil) + +type ( + // AdminRoleApiModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminRoleApiModel. + AdminRoleApiModel interface { + adminRoleApiModel + } + + customAdminRoleApiModel struct { + *defaultAdminRoleApiModel + } +) + +// NewAdminRoleApiModel returns a model for the database table. +func NewAdminRoleApiModel(conn sqlx.SqlConn, c cache.CacheConf) AdminRoleApiModel { + return &customAdminRoleApiModel{ + defaultAdminRoleApiModel: newAdminRoleApiModel(conn, c), + } +} diff --git a/app/main/model/adminRoleApiModel_gen.go b/app/main/model/adminRoleApiModel_gen.go new file mode 100644 index 0000000..1f4a77a --- /dev/null +++ b/app/main/model/adminRoleApiModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminRoleApiFieldNames = builder.RawFieldNames(&AdminRoleApi{}) + adminRoleApiRows = strings.Join(adminRoleApiFieldNames, ",") + adminRoleApiRowsExpectAutoSet = strings.Join(stringx.Remove(adminRoleApiFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminRoleApiRowsWithPlaceHolder = strings.Join(stringx.Remove(adminRoleApiFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminRoleApiIdPrefix = "cache:bdrp:adminRoleApi:id:" + cacheHmAdminRoleApiRoleIdApiIdPrefix = "cache:bdrp:adminRoleApi:roleId:apiId:" +) + +type ( + adminRoleApiModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminRoleApi) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminRoleApi, error) + FindOneByRoleIdApiId(ctx context.Context, roleId int64, apiId int64) (*AdminRoleApi, error) + Update(ctx context.Context, session sqlx.Session, data *AdminRoleApi) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminRoleApi) 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 *AdminRoleApi) 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) ([]*AdminRoleApi, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleApi, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleApi, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRoleApi, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRoleApi, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminRoleApiModel struct { + sqlc.CachedConn + table string + } + + AdminRoleApi struct { + Id int64 `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"` // 版本号 + RoleId int64 `db:"role_id"` // 关联到角色表的id + ApiId int64 `db:"api_id"` // 关联到接口表的id + } +) + +func newAdminRoleApiModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminRoleApiModel { + return &defaultAdminRoleApiModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_role_api`", + } +} + +func (m *defaultAdminRoleApiModel) Insert(ctx context.Context, session sqlx.Session, data *AdminRoleApi) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminRoleApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, data.Id) + hmAdminRoleApiRoleIdApiIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleApiRoleIdApiIdPrefix, data.RoleId, data.ApiId) + 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, adminRoleApiRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleId, data.ApiId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleId, data.ApiId) + }, hmAdminRoleApiIdKey, hmAdminRoleApiRoleIdApiIdKey) +} + +func (m *defaultAdminRoleApiModel) FindOne(ctx context.Context, id int64) (*AdminRoleApi, error) { + hmAdminRoleApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, id) + var resp AdminRoleApi + err := m.QueryRowCtx(ctx, &resp, hmAdminRoleApiIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminRoleApiRows, 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 *defaultAdminRoleApiModel) FindOneByRoleIdApiId(ctx context.Context, roleId int64, apiId int64) (*AdminRoleApi, error) { + hmAdminRoleApiRoleIdApiIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleApiRoleIdApiIdPrefix, roleId, apiId) + var resp AdminRoleApi + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminRoleApiRoleIdApiIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `role_id` = ? and `api_id` = ? and del_state = ? limit 1", adminRoleApiRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, roleId, apiId, 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 *defaultAdminRoleApiModel) Update(ctx context.Context, session sqlx.Session, newData *AdminRoleApi) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminRoleApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, data.Id) + hmAdminRoleApiRoleIdApiIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleApiRoleIdApiIdPrefix, data.RoleId, data.ApiId) + 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, adminRoleApiRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.ApiId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.ApiId, newData.Id) + }, hmAdminRoleApiIdKey, hmAdminRoleApiRoleIdApiIdKey) +} + +func (m *defaultAdminRoleApiModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminRoleApi) 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 + } + hmAdminRoleApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, data.Id) + hmAdminRoleApiRoleIdApiIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleApiRoleIdApiIdPrefix, data.RoleId, data.ApiId) + 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, adminRoleApiRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.ApiId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.ApiId, newData.Id, oldVersion) + }, hmAdminRoleApiIdKey, hmAdminRoleApiRoleIdApiIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminRoleApiModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminRoleApi) 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 "), "AdminRoleApiModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminRoleApiModel) 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 *defaultAdminRoleApiModel) 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 *defaultAdminRoleApiModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminRoleApi, error) { + + builder = builder.Columns(adminRoleApiRows) + + 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 []*AdminRoleApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleApiModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleApi, error) { + + builder = builder.Columns(adminRoleApiRows) + + 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 []*AdminRoleApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleApiModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleApi, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminRoleApiRows) + + 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 []*AdminRoleApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminRoleApiModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRoleApi, error) { + + builder = builder.Columns(adminRoleApiRows) + + 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 []*AdminRoleApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleApiModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRoleApi, error) { + + builder = builder.Columns(adminRoleApiRows) + + 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 []*AdminRoleApi + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleApiModel) 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 *defaultAdminRoleApiModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminRoleApiModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminRoleApiIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, id) + hmAdminRoleApiRoleIdApiIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleApiRoleIdApiIdPrefix, data.RoleId, data.ApiId) + _, 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) + }, hmAdminRoleApiIdKey, hmAdminRoleApiRoleIdApiIdKey) + return err +} +func (m *defaultAdminRoleApiModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminRoleApiIdPrefix, primary) +} +func (m *defaultAdminRoleApiModel) 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", adminRoleApiRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminRoleApiModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminRoleMenuModel.go b/app/main/model/adminRoleMenuModel.go new file mode 100644 index 0000000..8dc9812 --- /dev/null +++ b/app/main/model/adminRoleMenuModel.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 _ AdminRoleMenuModel = (*customAdminRoleMenuModel)(nil) + +type ( + // AdminRoleMenuModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminRoleMenuModel. + AdminRoleMenuModel interface { + adminRoleMenuModel + } + + customAdminRoleMenuModel struct { + *defaultAdminRoleMenuModel + } +) + +// NewAdminRoleMenuModel returns a model for the database table. +func NewAdminRoleMenuModel(conn sqlx.SqlConn, c cache.CacheConf) AdminRoleMenuModel { + return &customAdminRoleMenuModel{ + defaultAdminRoleMenuModel: newAdminRoleMenuModel(conn, c), + } +} diff --git a/app/main/model/adminRoleMenuModel_gen.go b/app/main/model/adminRoleMenuModel_gen.go new file mode 100644 index 0000000..642111a --- /dev/null +++ b/app/main/model/adminRoleMenuModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminRoleMenuFieldNames = builder.RawFieldNames(&AdminRoleMenu{}) + adminRoleMenuRows = strings.Join(adminRoleMenuFieldNames, ",") + adminRoleMenuRowsExpectAutoSet = strings.Join(stringx.Remove(adminRoleMenuFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminRoleMenuRowsWithPlaceHolder = strings.Join(stringx.Remove(adminRoleMenuFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminRoleMenuIdPrefix = "cache:bdrp:adminRoleMenu:id:" + cacheHmAdminRoleMenuRoleIdMenuIdPrefix = "cache:bdrp:adminRoleMenu:roleId:menuId:" +) + +type ( + adminRoleMenuModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminRoleMenu) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminRoleMenu, error) + FindOneByRoleIdMenuId(ctx context.Context, roleId int64, menuId int64) (*AdminRoleMenu, error) + Update(ctx context.Context, session sqlx.Session, data *AdminRoleMenu) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminRoleMenu) 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 *AdminRoleMenu) 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) ([]*AdminRoleMenu, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleMenu, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleMenu, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRoleMenu, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRoleMenu, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminRoleMenuModel struct { + sqlc.CachedConn + table string + } + + AdminRoleMenu struct { + Id int64 `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"` // 版本号 + RoleId int64 `db:"role_id"` // 关联到角色表的id + MenuId int64 `db:"menu_id"` // 关联到菜单表的id + } +) + +func newAdminRoleMenuModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminRoleMenuModel { + return &defaultAdminRoleMenuModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_role_menu`", + } +} + +func (m *defaultAdminRoleMenuModel) Insert(ctx context.Context, session sqlx.Session, data *AdminRoleMenu) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminRoleMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, data.Id) + hmAdminRoleMenuRoleIdMenuIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleMenuRoleIdMenuIdPrefix, data.RoleId, data.MenuId) + 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, adminRoleMenuRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleId, data.MenuId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleId, data.MenuId) + }, hmAdminRoleMenuIdKey, hmAdminRoleMenuRoleIdMenuIdKey) +} + +func (m *defaultAdminRoleMenuModel) FindOne(ctx context.Context, id int64) (*AdminRoleMenu, error) { + hmAdminRoleMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, id) + var resp AdminRoleMenu + err := m.QueryRowCtx(ctx, &resp, hmAdminRoleMenuIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminRoleMenuRows, 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 *defaultAdminRoleMenuModel) FindOneByRoleIdMenuId(ctx context.Context, roleId int64, menuId int64) (*AdminRoleMenu, error) { + hmAdminRoleMenuRoleIdMenuIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleMenuRoleIdMenuIdPrefix, roleId, menuId) + var resp AdminRoleMenu + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminRoleMenuRoleIdMenuIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `role_id` = ? and `menu_id` = ? and del_state = ? limit 1", adminRoleMenuRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, roleId, menuId, 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 *defaultAdminRoleMenuModel) Update(ctx context.Context, session sqlx.Session, newData *AdminRoleMenu) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminRoleMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, data.Id) + hmAdminRoleMenuRoleIdMenuIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleMenuRoleIdMenuIdPrefix, data.RoleId, data.MenuId) + 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, adminRoleMenuRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.MenuId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.MenuId, newData.Id) + }, hmAdminRoleMenuIdKey, hmAdminRoleMenuRoleIdMenuIdKey) +} + +func (m *defaultAdminRoleMenuModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminRoleMenu) 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 + } + hmAdminRoleMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, data.Id) + hmAdminRoleMenuRoleIdMenuIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleMenuRoleIdMenuIdPrefix, data.RoleId, data.MenuId) + 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, adminRoleMenuRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.MenuId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleId, newData.MenuId, newData.Id, oldVersion) + }, hmAdminRoleMenuIdKey, hmAdminRoleMenuRoleIdMenuIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminRoleMenuModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminRoleMenu) 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 "), "AdminRoleMenuModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminRoleMenuModel) 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 *defaultAdminRoleMenuModel) 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 *defaultAdminRoleMenuModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminRoleMenu, error) { + + builder = builder.Columns(adminRoleMenuRows) + + 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 []*AdminRoleMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleMenuModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleMenu, error) { + + builder = builder.Columns(adminRoleMenuRows) + + 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 []*AdminRoleMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleMenuModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRoleMenu, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminRoleMenuRows) + + 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 []*AdminRoleMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminRoleMenuModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRoleMenu, error) { + + builder = builder.Columns(adminRoleMenuRows) + + 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 []*AdminRoleMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleMenuModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRoleMenu, error) { + + builder = builder.Columns(adminRoleMenuRows) + + 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 []*AdminRoleMenu + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleMenuModel) 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 *defaultAdminRoleMenuModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminRoleMenuModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminRoleMenuIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, id) + hmAdminRoleMenuRoleIdMenuIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminRoleMenuRoleIdMenuIdPrefix, data.RoleId, data.MenuId) + _, 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) + }, hmAdminRoleMenuIdKey, hmAdminRoleMenuRoleIdMenuIdKey) + return err +} +func (m *defaultAdminRoleMenuModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminRoleMenuIdPrefix, primary) +} +func (m *defaultAdminRoleMenuModel) 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", adminRoleMenuRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminRoleMenuModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminRoleModel.go b/app/main/model/adminRoleModel.go new file mode 100644 index 0000000..26dc91d --- /dev/null +++ b/app/main/model/adminRoleModel.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 _ AdminRoleModel = (*customAdminRoleModel)(nil) + +type ( + // AdminRoleModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminRoleModel. + AdminRoleModel interface { + adminRoleModel + } + + customAdminRoleModel struct { + *defaultAdminRoleModel + } +) + +// NewAdminRoleModel returns a model for the database table. +func NewAdminRoleModel(conn sqlx.SqlConn, c cache.CacheConf) AdminRoleModel { + return &customAdminRoleModel{ + defaultAdminRoleModel: newAdminRoleModel(conn, c), + } +} diff --git a/app/main/model/adminRoleModel_gen.go b/app/main/model/adminRoleModel_gen.go new file mode 100644 index 0000000..1cd6f44 --- /dev/null +++ b/app/main/model/adminRoleModel_gen.go @@ -0,0 +1,411 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminRoleFieldNames = builder.RawFieldNames(&AdminRole{}) + adminRoleRows = strings.Join(adminRoleFieldNames, ",") + adminRoleRowsExpectAutoSet = strings.Join(stringx.Remove(adminRoleFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminRoleRowsWithPlaceHolder = strings.Join(stringx.Remove(adminRoleFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminRoleIdPrefix = "cache:bdrp:adminRole:id:" + cacheHmAdminRoleRoleCodePrefix = "cache:bdrp:adminRole:roleCode:" +) + +type ( + adminRoleModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminRole) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminRole, error) + FindOneByRoleCode(ctx context.Context, roleCode string) (*AdminRole, error) + Update(ctx context.Context, session sqlx.Session, data *AdminRole) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminRole) 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 *AdminRole) 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) ([]*AdminRole, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRole, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRole, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRole, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRole, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminRoleModel struct { + sqlc.CachedConn + table string + } + + AdminRole struct { + Id int64 `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"` // 版本号 + RoleName string `db:"role_name"` // 角色名称 + RoleCode string `db:"role_code"` // 角色编码 + Description string `db:"description"` // 角色描述 + Status int64 `db:"status"` // 状态:0-禁用,1-启用 + Sort int64 `db:"sort"` // 排序 + } +) + +func newAdminRoleModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminRoleModel { + return &defaultAdminRoleModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_role`", + } +} + +func (m *defaultAdminRoleModel) Insert(ctx context.Context, session sqlx.Session, data *AdminRole) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, data.Id) + hmAdminRoleRoleCodeKey := fmt.Sprintf("%s%v", cacheHmAdminRoleRoleCodePrefix, data.RoleCode) + 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, adminRoleRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleName, data.RoleCode, data.Description, data.Status, data.Sort) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.RoleName, data.RoleCode, data.Description, data.Status, data.Sort) + }, hmAdminRoleIdKey, hmAdminRoleRoleCodeKey) +} + +func (m *defaultAdminRoleModel) FindOne(ctx context.Context, id int64) (*AdminRole, error) { + hmAdminRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, id) + var resp AdminRole + err := m.QueryRowCtx(ctx, &resp, hmAdminRoleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminRoleRows, 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 *defaultAdminRoleModel) FindOneByRoleCode(ctx context.Context, roleCode string) (*AdminRole, error) { + hmAdminRoleRoleCodeKey := fmt.Sprintf("%s%v", cacheHmAdminRoleRoleCodePrefix, roleCode) + var resp AdminRole + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminRoleRoleCodeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `role_code` = ? and del_state = ? limit 1", adminRoleRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, roleCode, 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 *defaultAdminRoleModel) Update(ctx context.Context, session sqlx.Session, newData *AdminRole) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, data.Id) + hmAdminRoleRoleCodeKey := fmt.Sprintf("%s%v", cacheHmAdminRoleRoleCodePrefix, data.RoleCode) + 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, adminRoleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleName, newData.RoleCode, newData.Description, newData.Status, newData.Sort, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleName, newData.RoleCode, newData.Description, newData.Status, newData.Sort, newData.Id) + }, hmAdminRoleIdKey, hmAdminRoleRoleCodeKey) +} + +func (m *defaultAdminRoleModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminRole) 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 + } + hmAdminRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, data.Id) + hmAdminRoleRoleCodeKey := fmt.Sprintf("%s%v", cacheHmAdminRoleRoleCodePrefix, data.RoleCode) + 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, adminRoleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleName, newData.RoleCode, newData.Description, newData.Status, newData.Sort, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.RoleName, newData.RoleCode, newData.Description, newData.Status, newData.Sort, newData.Id, oldVersion) + }, hmAdminRoleIdKey, hmAdminRoleRoleCodeKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminRoleModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminRole) 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 "), "AdminRoleModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminRoleModel) 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 *defaultAdminRoleModel) 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 *defaultAdminRoleModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminRole, error) { + + builder = builder.Columns(adminRoleRows) + + 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 []*AdminRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRole, error) { + + builder = builder.Columns(adminRoleRows) + + 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 []*AdminRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminRole, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminRoleRows) + + 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 []*AdminRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminRoleModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminRole, error) { + + builder = builder.Columns(adminRoleRows) + + 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 []*AdminRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminRole, error) { + + builder = builder.Columns(adminRoleRows) + + 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 []*AdminRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminRoleModel) 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 *defaultAdminRoleModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminRoleModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, id) + hmAdminRoleRoleCodeKey := fmt.Sprintf("%s%v", cacheHmAdminRoleRoleCodePrefix, data.RoleCode) + _, 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) + }, hmAdminRoleIdKey, hmAdminRoleRoleCodeKey) + return err +} +func (m *defaultAdminRoleModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminRoleIdPrefix, primary) +} +func (m *defaultAdminRoleModel) 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", adminRoleRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminRoleModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminUserModel.go b/app/main/model/adminUserModel.go new file mode 100644 index 0000000..25d463e --- /dev/null +++ b/app/main/model/adminUserModel.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 _ AdminUserModel = (*customAdminUserModel)(nil) + +type ( + // AdminUserModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminUserModel. + AdminUserModel interface { + adminUserModel + } + + customAdminUserModel struct { + *defaultAdminUserModel + } +) + +// NewAdminUserModel returns a model for the database table. +func NewAdminUserModel(conn sqlx.SqlConn, c cache.CacheConf) AdminUserModel { + return &customAdminUserModel{ + defaultAdminUserModel: newAdminUserModel(conn, c), + } +} diff --git a/app/main/model/adminUserModel_gen.go b/app/main/model/adminUserModel_gen.go new file mode 100644 index 0000000..f8ac962 --- /dev/null +++ b/app/main/model/adminUserModel_gen.go @@ -0,0 +1,436 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminUserFieldNames = builder.RawFieldNames(&AdminUser{}) + adminUserRows = strings.Join(adminUserFieldNames, ",") + adminUserRowsExpectAutoSet = strings.Join(stringx.Remove(adminUserFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminUserRowsWithPlaceHolder = strings.Join(stringx.Remove(adminUserFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminUserIdPrefix = "cache:bdrp:adminUser:id:" + cacheHmAdminUserRealNamePrefix = "cache:bdrp:adminUser:realName:" + cacheHmAdminUserUsernamePrefix = "cache:bdrp:adminUser:username:" +) + +type ( + adminUserModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminUser) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminUser, error) + FindOneByRealName(ctx context.Context, realName string) (*AdminUser, error) + FindOneByUsername(ctx context.Context, username string) (*AdminUser, error) + Update(ctx context.Context, session sqlx.Session, data *AdminUser) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminUser) 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 *AdminUser) 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) ([]*AdminUser, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUser, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUser, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminUser, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminUser, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminUserModel struct { + sqlc.CachedConn + table string + } + + AdminUser struct { + Id int64 `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"` // 版本号 + Username string `db:"username"` // 用户名 + Password string `db:"password"` // 密码 + RealName string `db:"real_name"` // 真实姓名 + Status int64 `db:"status"` // 状态:0-禁用,1-启用 + } +) + +func newAdminUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminUserModel { + return &defaultAdminUserModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_user`", + } +} + +func (m *defaultAdminUserModel) Insert(ctx context.Context, session sqlx.Session, data *AdminUser) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminUserIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, data.Id) + hmAdminUserRealNameKey := fmt.Sprintf("%s%v", cacheHmAdminUserRealNamePrefix, data.RealName) + hmAdminUserUsernameKey := fmt.Sprintf("%s%v", cacheHmAdminUserUsernamePrefix, data.Username) + 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, adminUserRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Username, data.Password, data.RealName, data.Status) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Username, data.Password, data.RealName, data.Status) + }, hmAdminUserIdKey, hmAdminUserRealNameKey, hmAdminUserUsernameKey) +} + +func (m *defaultAdminUserModel) FindOne(ctx context.Context, id int64) (*AdminUser, error) { + hmAdminUserIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, id) + var resp AdminUser + err := m.QueryRowCtx(ctx, &resp, hmAdminUserIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminUserRows, 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 *defaultAdminUserModel) FindOneByRealName(ctx context.Context, realName string) (*AdminUser, error) { + hmAdminUserRealNameKey := fmt.Sprintf("%s%v", cacheHmAdminUserRealNamePrefix, realName) + var resp AdminUser + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminUserRealNameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `real_name` = ? and del_state = ? limit 1", adminUserRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, realName, 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 *defaultAdminUserModel) FindOneByUsername(ctx context.Context, username string) (*AdminUser, error) { + hmAdminUserUsernameKey := fmt.Sprintf("%s%v", cacheHmAdminUserUsernamePrefix, username) + var resp AdminUser + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminUserUsernameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `username` = ? and del_state = ? limit 1", adminUserRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, username, 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 *defaultAdminUserModel) Update(ctx context.Context, session sqlx.Session, newData *AdminUser) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminUserIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, data.Id) + hmAdminUserRealNameKey := fmt.Sprintf("%s%v", cacheHmAdminUserRealNamePrefix, data.RealName) + hmAdminUserUsernameKey := fmt.Sprintf("%s%v", cacheHmAdminUserUsernamePrefix, data.Username) + 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, adminUserRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Username, newData.Password, newData.RealName, newData.Status, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Username, newData.Password, newData.RealName, newData.Status, newData.Id) + }, hmAdminUserIdKey, hmAdminUserRealNameKey, hmAdminUserUsernameKey) +} + +func (m *defaultAdminUserModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminUser) 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 + } + hmAdminUserIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, data.Id) + hmAdminUserRealNameKey := fmt.Sprintf("%s%v", cacheHmAdminUserRealNamePrefix, data.RealName) + hmAdminUserUsernameKey := fmt.Sprintf("%s%v", cacheHmAdminUserUsernamePrefix, data.Username) + 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, adminUserRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Username, newData.Password, newData.RealName, newData.Status, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Username, newData.Password, newData.RealName, newData.Status, newData.Id, oldVersion) + }, hmAdminUserIdKey, hmAdminUserRealNameKey, hmAdminUserUsernameKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminUserModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminUser) 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 "), "AdminUserModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminUserModel) 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 *defaultAdminUserModel) 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 *defaultAdminUserModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminUser, error) { + + builder = builder.Columns(adminUserRows) + + 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 []*AdminUser + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUser, error) { + + builder = builder.Columns(adminUserRows) + + 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 []*AdminUser + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUser, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminUserRows) + + 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 []*AdminUser + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminUserModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminUser, error) { + + builder = builder.Columns(adminUserRows) + + 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 []*AdminUser + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminUser, error) { + + builder = builder.Columns(adminUserRows) + + 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 []*AdminUser + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserModel) 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 *defaultAdminUserModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminUserModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminUserIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, id) + hmAdminUserRealNameKey := fmt.Sprintf("%s%v", cacheHmAdminUserRealNamePrefix, data.RealName) + hmAdminUserUsernameKey := fmt.Sprintf("%s%v", cacheHmAdminUserUsernamePrefix, data.Username) + _, 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) + }, hmAdminUserIdKey, hmAdminUserRealNameKey, hmAdminUserUsernameKey) + return err +} +func (m *defaultAdminUserModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminUserIdPrefix, primary) +} +func (m *defaultAdminUserModel) 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", adminUserRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminUserModel) tableName() string { + return m.table +} diff --git a/app/main/model/adminUserRoleModel.go b/app/main/model/adminUserRoleModel.go new file mode 100644 index 0000000..d9772ad --- /dev/null +++ b/app/main/model/adminUserRoleModel.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 _ AdminUserRoleModel = (*customAdminUserRoleModel)(nil) + +type ( + // AdminUserRoleModel is an interface to be customized, add more methods here, + // and implement the added methods in customAdminUserRoleModel. + AdminUserRoleModel interface { + adminUserRoleModel + } + + customAdminUserRoleModel struct { + *defaultAdminUserRoleModel + } +) + +// NewAdminUserRoleModel returns a model for the database table. +func NewAdminUserRoleModel(conn sqlx.SqlConn, c cache.CacheConf) AdminUserRoleModel { + return &customAdminUserRoleModel{ + defaultAdminUserRoleModel: newAdminUserRoleModel(conn, c), + } +} diff --git a/app/main/model/adminUserRoleModel_gen.go b/app/main/model/adminUserRoleModel_gen.go new file mode 100644 index 0000000..6843437 --- /dev/null +++ b/app/main/model/adminUserRoleModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + adminUserRoleFieldNames = builder.RawFieldNames(&AdminUserRole{}) + adminUserRoleRows = strings.Join(adminUserRoleFieldNames, ",") + adminUserRoleRowsExpectAutoSet = strings.Join(stringx.Remove(adminUserRoleFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + adminUserRoleRowsWithPlaceHolder = strings.Join(stringx.Remove(adminUserRoleFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAdminUserRoleIdPrefix = "cache:bdrp:adminUserRole:id:" + cacheHmAdminUserRoleUserIdRoleIdPrefix = "cache:bdrp:adminUserRole:userId:roleId:" +) + +type ( + adminUserRoleModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AdminUserRole) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AdminUserRole, error) + FindOneByUserIdRoleId(ctx context.Context, userId int64, roleId int64) (*AdminUserRole, error) + Update(ctx context.Context, session sqlx.Session, data *AdminUserRole) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AdminUserRole) 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 *AdminUserRole) 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) ([]*AdminUserRole, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUserRole, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUserRole, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminUserRole, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminUserRole, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAdminUserRoleModel struct { + sqlc.CachedConn + table string + } + + AdminUserRole struct { + Id int64 `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"` // 版本号 + UserId int64 `db:"user_id"` // 关联到用户表的id + RoleId int64 `db:"role_id"` // 关联到角色表的id + } +) + +func newAdminUserRoleModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAdminUserRoleModel { + return &defaultAdminUserRoleModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`admin_user_role`", + } +} + +func (m *defaultAdminUserRoleModel) Insert(ctx context.Context, session sqlx.Session, data *AdminUserRole) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAdminUserRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, data.Id) + hmAdminUserRoleUserIdRoleIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminUserRoleUserIdRoleIdPrefix, data.UserId, data.RoleId) + 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, adminUserRoleRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.RoleId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.RoleId) + }, hmAdminUserRoleIdKey, hmAdminUserRoleUserIdRoleIdKey) +} + +func (m *defaultAdminUserRoleModel) FindOne(ctx context.Context, id int64) (*AdminUserRole, error) { + hmAdminUserRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, id) + var resp AdminUserRole + err := m.QueryRowCtx(ctx, &resp, hmAdminUserRoleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", adminUserRoleRows, 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 *defaultAdminUserRoleModel) FindOneByUserIdRoleId(ctx context.Context, userId int64, roleId int64) (*AdminUserRole, error) { + hmAdminUserRoleUserIdRoleIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminUserRoleUserIdRoleIdPrefix, userId, roleId) + var resp AdminUserRole + err := m.QueryRowIndexCtx(ctx, &resp, hmAdminUserRoleUserIdRoleIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `user_id` = ? and `role_id` = ? and del_state = ? limit 1", adminUserRoleRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, userId, roleId, 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 *defaultAdminUserRoleModel) Update(ctx context.Context, session sqlx.Session, newData *AdminUserRole) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAdminUserRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, data.Id) + hmAdminUserRoleUserIdRoleIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminUserRoleUserIdRoleIdPrefix, data.UserId, data.RoleId) + 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, adminUserRoleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.RoleId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.RoleId, newData.Id) + }, hmAdminUserRoleIdKey, hmAdminUserRoleUserIdRoleIdKey) +} + +func (m *defaultAdminUserRoleModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AdminUserRole) 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 + } + hmAdminUserRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, data.Id) + hmAdminUserRoleUserIdRoleIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminUserRoleUserIdRoleIdPrefix, data.UserId, data.RoleId) + 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, adminUserRoleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.RoleId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.RoleId, newData.Id, oldVersion) + }, hmAdminUserRoleIdKey, hmAdminUserRoleUserIdRoleIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAdminUserRoleModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AdminUserRole) 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 "), "AdminUserRoleModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAdminUserRoleModel) 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 *defaultAdminUserRoleModel) 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 *defaultAdminUserRoleModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AdminUserRole, error) { + + builder = builder.Columns(adminUserRoleRows) + + 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 []*AdminUserRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserRoleModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUserRole, error) { + + builder = builder.Columns(adminUserRoleRows) + + 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 []*AdminUserRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserRoleModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AdminUserRole, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(adminUserRoleRows) + + 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 []*AdminUserRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAdminUserRoleModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AdminUserRole, error) { + + builder = builder.Columns(adminUserRoleRows) + + 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 []*AdminUserRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserRoleModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AdminUserRole, error) { + + builder = builder.Columns(adminUserRoleRows) + + 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 []*AdminUserRole + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAdminUserRoleModel) 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 *defaultAdminUserRoleModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAdminUserRoleModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAdminUserRoleIdKey := fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, id) + hmAdminUserRoleUserIdRoleIdKey := fmt.Sprintf("%s%v:%v", cacheHmAdminUserRoleUserIdRoleIdPrefix, data.UserId, data.RoleId) + _, 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) + }, hmAdminUserRoleIdKey, hmAdminUserRoleUserIdRoleIdKey) + return err +} +func (m *defaultAdminUserRoleModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAdminUserRoleIdPrefix, primary) +} +func (m *defaultAdminUserRoleModel) 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", adminUserRoleRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAdminUserRoleModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentActiveStatModel.go b/app/main/model/agentActiveStatModel.go new file mode 100644 index 0000000..86ce2ef --- /dev/null +++ b/app/main/model/agentActiveStatModel.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 _ AgentActiveStatModel = (*customAgentActiveStatModel)(nil) + +type ( + // AgentActiveStatModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentActiveStatModel. + AgentActiveStatModel interface { + agentActiveStatModel + } + + customAgentActiveStatModel struct { + *defaultAgentActiveStatModel + } +) + +// NewAgentActiveStatModel returns a model for the database table. +func NewAgentActiveStatModel(conn sqlx.SqlConn, c cache.CacheConf) AgentActiveStatModel { + return &customAgentActiveStatModel{ + defaultAgentActiveStatModel: newAgentActiveStatModel(conn, c), + } +} diff --git a/app/main/model/agentActiveStatModel_gen.go b/app/main/model/agentActiveStatModel_gen.go new file mode 100644 index 0000000..42f63aa --- /dev/null +++ b/app/main/model/agentActiveStatModel_gen.go @@ -0,0 +1,409 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentActiveStatFieldNames = builder.RawFieldNames(&AgentActiveStat{}) + agentActiveStatRows = strings.Join(agentActiveStatFieldNames, ",") + agentActiveStatRowsExpectAutoSet = strings.Join(stringx.Remove(agentActiveStatFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentActiveStatRowsWithPlaceHolder = strings.Join(stringx.Remove(agentActiveStatFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentActiveStatIdPrefix = "cache:bdrp:agentActiveStat:id:" + cacheHmAgentActiveStatAgentIdStatDatePrefix = "cache:bdrp:agentActiveStat:agentId:statDate:" +) + +type ( + agentActiveStatModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentActiveStat) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentActiveStat, error) + FindOneByAgentIdStatDate(ctx context.Context, agentId int64, statDate time.Time) (*AgentActiveStat, error) + Update(ctx context.Context, session sqlx.Session, data *AgentActiveStat) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentActiveStat) 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 *AgentActiveStat) 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) ([]*AgentActiveStat, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentActiveStat, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentActiveStat, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentActiveStat, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentActiveStat, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentActiveStatModel struct { + sqlc.CachedConn + table string + } + + AgentActiveStat struct { + Id int64 `db:"id"` // 主键ID + AgentId int64 `db:"agent_id"` // 代理ID + StatDate time.Time `db:"stat_date"` // 统计日期 + ActiveNumber int64 `db:"active_number"` // 下级活跃数 + 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"` // 版本号 + } +) + +func newAgentActiveStatModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentActiveStatModel { + return &defaultAgentActiveStatModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_active_stat`", + } +} + +func (m *defaultAgentActiveStatModel) Insert(ctx context.Context, session sqlx.Session, data *AgentActiveStat) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentActiveStatAgentIdStatDateKey := fmt.Sprintf("%s%v:%v", cacheHmAgentActiveStatAgentIdStatDatePrefix, data.AgentId, data.StatDate) + hmAgentActiveStatIdKey := fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, 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, agentActiveStatRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.StatDate, data.ActiveNumber, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.StatDate, data.ActiveNumber, data.DeleteTime, data.DelState, data.Version) + }, hmAgentActiveStatAgentIdStatDateKey, hmAgentActiveStatIdKey) +} + +func (m *defaultAgentActiveStatModel) FindOne(ctx context.Context, id int64) (*AgentActiveStat, error) { + hmAgentActiveStatIdKey := fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, id) + var resp AgentActiveStat + err := m.QueryRowCtx(ctx, &resp, hmAgentActiveStatIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentActiveStatRows, 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 *defaultAgentActiveStatModel) FindOneByAgentIdStatDate(ctx context.Context, agentId int64, statDate time.Time) (*AgentActiveStat, error) { + hmAgentActiveStatAgentIdStatDateKey := fmt.Sprintf("%s%v:%v", cacheHmAgentActiveStatAgentIdStatDatePrefix, agentId, statDate) + var resp AgentActiveStat + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentActiveStatAgentIdStatDateKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `agent_id` = ? and `stat_date` = ? and del_state = ? limit 1", agentActiveStatRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, agentId, statDate, 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 *defaultAgentActiveStatModel) Update(ctx context.Context, session sqlx.Session, newData *AgentActiveStat) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentActiveStatAgentIdStatDateKey := fmt.Sprintf("%s%v:%v", cacheHmAgentActiveStatAgentIdStatDatePrefix, data.AgentId, data.StatDate) + hmAgentActiveStatIdKey := fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, 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, agentActiveStatRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.StatDate, newData.ActiveNumber, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.StatDate, newData.ActiveNumber, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentActiveStatAgentIdStatDateKey, hmAgentActiveStatIdKey) +} + +func (m *defaultAgentActiveStatModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentActiveStat) 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 + } + hmAgentActiveStatAgentIdStatDateKey := fmt.Sprintf("%s%v:%v", cacheHmAgentActiveStatAgentIdStatDatePrefix, data.AgentId, data.StatDate) + hmAgentActiveStatIdKey := fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, 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, agentActiveStatRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.StatDate, newData.ActiveNumber, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.StatDate, newData.ActiveNumber, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentActiveStatAgentIdStatDateKey, hmAgentActiveStatIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentActiveStatModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentActiveStat) 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 "), "AgentActiveStatModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentActiveStatModel) 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 *defaultAgentActiveStatModel) 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 *defaultAgentActiveStatModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentActiveStat, error) { + + builder = builder.Columns(agentActiveStatRows) + + 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 []*AgentActiveStat + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentActiveStatModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentActiveStat, error) { + + builder = builder.Columns(agentActiveStatRows) + + 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 []*AgentActiveStat + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentActiveStatModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentActiveStat, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentActiveStatRows) + + 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 []*AgentActiveStat + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentActiveStatModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentActiveStat, error) { + + builder = builder.Columns(agentActiveStatRows) + + 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 []*AgentActiveStat + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentActiveStatModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentActiveStat, error) { + + builder = builder.Columns(agentActiveStatRows) + + 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 []*AgentActiveStat + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentActiveStatModel) 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 *defaultAgentActiveStatModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentActiveStatModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentActiveStatAgentIdStatDateKey := fmt.Sprintf("%s%v:%v", cacheHmAgentActiveStatAgentIdStatDatePrefix, data.AgentId, data.StatDate) + hmAgentActiveStatIdKey := fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, 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) + }, hmAgentActiveStatAgentIdStatDateKey, hmAgentActiveStatIdKey) + return err +} +func (m *defaultAgentActiveStatModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentActiveStatIdPrefix, primary) +} +func (m *defaultAgentActiveStatModel) 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", agentActiveStatRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentActiveStatModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentAuditModel.go b/app/main/model/agentAuditModel.go new file mode 100644 index 0000000..80ddf43 --- /dev/null +++ b/app/main/model/agentAuditModel.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 _ AgentAuditModel = (*customAgentAuditModel)(nil) + +type ( + // AgentAuditModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentAuditModel. + AgentAuditModel interface { + agentAuditModel + } + + customAgentAuditModel struct { + *defaultAgentAuditModel + } +) + +// NewAgentAuditModel returns a model for the database table. +func NewAgentAuditModel(conn sqlx.SqlConn, c cache.CacheConf) AgentAuditModel { + return &customAgentAuditModel{ + defaultAgentAuditModel: newAgentAuditModel(conn, c), + } +} diff --git a/app/main/model/agentAuditModel_gen.go b/app/main/model/agentAuditModel_gen.go new file mode 100644 index 0000000..7a2c772 --- /dev/null +++ b/app/main/model/agentAuditModel_gen.go @@ -0,0 +1,413 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentAuditFieldNames = builder.RawFieldNames(&AgentAudit{}) + agentAuditRows = strings.Join(agentAuditFieldNames, ",") + agentAuditRowsExpectAutoSet = strings.Join(stringx.Remove(agentAuditFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentAuditRowsWithPlaceHolder = strings.Join(stringx.Remove(agentAuditFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentAuditIdPrefix = "cache:bdrp:agentAudit:id:" + cacheHmAgentAuditUserIdPrefix = "cache:bdrp:agentAudit:userId:" +) + +type ( + agentAuditModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentAudit) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentAudit, error) + FindOneByUserId(ctx context.Context, userId int64) (*AgentAudit, error) + Update(ctx context.Context, session sqlx.Session, data *AgentAudit) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentAudit) 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 *AgentAudit) 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) ([]*AgentAudit, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentAudit, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentAudit, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentAudit, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentAudit, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentAuditModel struct { + sqlc.CachedConn + table string + } + + AgentAudit struct { + Id int64 `db:"id"` + UserId int64 `db:"user_id"` + Region string `db:"region"` + Mobile string `db:"mobile"` + WechatId sql.NullString `db:"wechat_id"` + Status int64 `db:"status"` + AuditReason sql.NullString `db:"audit_reason"` + CreateTime time.Time `db:"create_time"` + AuditTime sql.NullTime `db:"audit_time"` + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + } +) + +func newAgentAuditModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentAuditModel { + return &defaultAgentAuditModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_audit`", + } +} + +func (m *defaultAgentAuditModel) Insert(ctx context.Context, session sqlx.Session, data *AgentAudit) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentAuditIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, data.Id) + hmAgentAuditUserIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditUserIdPrefix, data.UserId) + 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, agentAuditRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.Region, data.Mobile, data.WechatId, data.Status, data.AuditReason, data.AuditTime, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.UserId, data.Region, data.Mobile, data.WechatId, data.Status, data.AuditReason, data.AuditTime, data.DeleteTime, data.DelState, data.Version) + }, hmAgentAuditIdKey, hmAgentAuditUserIdKey) +} + +func (m *defaultAgentAuditModel) FindOne(ctx context.Context, id int64) (*AgentAudit, error) { + hmAgentAuditIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, id) + var resp AgentAudit + err := m.QueryRowCtx(ctx, &resp, hmAgentAuditIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentAuditRows, 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 *defaultAgentAuditModel) FindOneByUserId(ctx context.Context, userId int64) (*AgentAudit, error) { + hmAgentAuditUserIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditUserIdPrefix, userId) + var resp AgentAudit + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentAuditUserIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `user_id` = ? and del_state = ? limit 1", agentAuditRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, userId, 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 *defaultAgentAuditModel) Update(ctx context.Context, session sqlx.Session, newData *AgentAudit) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentAuditIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, data.Id) + hmAgentAuditUserIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditUserIdPrefix, data.UserId) + 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, agentAuditRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.Region, newData.Mobile, newData.WechatId, newData.Status, newData.AuditReason, newData.AuditTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.Region, newData.Mobile, newData.WechatId, newData.Status, newData.AuditReason, newData.AuditTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentAuditIdKey, hmAgentAuditUserIdKey) +} + +func (m *defaultAgentAuditModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentAudit) 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 + } + hmAgentAuditIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, data.Id) + hmAgentAuditUserIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditUserIdPrefix, data.UserId) + 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, agentAuditRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.Region, newData.Mobile, newData.WechatId, newData.Status, newData.AuditReason, newData.AuditTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.Region, newData.Mobile, newData.WechatId, newData.Status, newData.AuditReason, newData.AuditTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentAuditIdKey, hmAgentAuditUserIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentAuditModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentAudit) 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 "), "AgentAuditModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentAuditModel) 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 *defaultAgentAuditModel) 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 *defaultAgentAuditModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentAudit, error) { + + builder = builder.Columns(agentAuditRows) + + 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 []*AgentAudit + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentAuditModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentAudit, error) { + + builder = builder.Columns(agentAuditRows) + + 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 []*AgentAudit + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentAuditModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentAudit, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentAuditRows) + + 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 []*AgentAudit + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentAuditModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentAudit, error) { + + builder = builder.Columns(agentAuditRows) + + 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 []*AgentAudit + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentAuditModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentAudit, error) { + + builder = builder.Columns(agentAuditRows) + + 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 []*AgentAudit + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentAuditModel) 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 *defaultAgentAuditModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentAuditModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentAuditIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, id) + hmAgentAuditUserIdKey := fmt.Sprintf("%s%v", cacheHmAgentAuditUserIdPrefix, data.UserId) + _, 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) + }, hmAgentAuditIdKey, hmAgentAuditUserIdKey) + return err +} +func (m *defaultAgentAuditModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentAuditIdPrefix, primary) +} +func (m *defaultAgentAuditModel) 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", agentAuditRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentAuditModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentClosureModel.go b/app/main/model/agentClosureModel.go new file mode 100644 index 0000000..d9e8212 --- /dev/null +++ b/app/main/model/agentClosureModel.go @@ -0,0 +1,99 @@ +package model + +import ( + "context" + "fmt" + "bdrp-server/common/globalkey" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AgentClosureModel = (*customAgentClosureModel)(nil) + +type ( + // AgentClosureModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentClosureModel. + AgentClosureModel interface { + agentClosureModel + FindUnionPageListByPageWithTotal(ctx context.Context, agentId, subordinateId int64, page, pageSize int64) ([]*UnionDetail, int64, error) + } + + customAgentClosureModel struct { + *defaultAgentClosureModel + } + + // UnionDetail 合并后的详情结构 + UnionDetail struct { + Id int64 `db:"id"` + CreateTime string `db:"create_time"` + Amount float64 `db:"amount"` + Type string `db:"type"` + } +) + +// NewAgentClosureModel returns a model for the database table. +func NewAgentClosureModel(conn sqlx.SqlConn, c cache.CacheConf) AgentClosureModel { + return &customAgentClosureModel{ + defaultAgentClosureModel: newAgentClosureModel(conn, c), + } +} + +// FindUnionPageListByPageWithTotal 获取合并后的分页列表 +func (m *customAgentClosureModel) FindUnionPageListByPageWithTotal(ctx context.Context, agentId, subordinateId int64, page, pageSize int64) ([]*UnionDetail, int64, error) { + // 构建UNION ALL查询 + deductionQuery := fmt.Sprintf(` + SELECT id, create_time, amount, type + FROM agent_commission_deduction + WHERE agent_id = ? AND deducted_agent_id = ? AND del_state = ? + `) + + rewardsQuery := fmt.Sprintf(` + SELECT id, create_time, amount, type + FROM agent_rewards + WHERE agent_id = ? AND relation_agent_id = ? AND del_state = ? + `) + + // 计算总记录数 + countQuery := fmt.Sprintf(` + SELECT COUNT(*) FROM ( + %s + UNION ALL + %s + ) AS union_table + `, deductionQuery, rewardsQuery) + + var total int64 + err := m.QueryRowNoCacheCtx(ctx, &total, countQuery, agentId, subordinateId, globalkey.DelStateNo, agentId, subordinateId, globalkey.DelStateNo) + if err != nil { + return nil, 0, errors.Wrapf(err, "查询总记录数失败") + } + + // 构建分页查询 + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + unionQuery := fmt.Sprintf(` + SELECT * FROM ( + %s + UNION ALL + %s + ) AS union_table + ORDER BY create_time DESC + LIMIT ? OFFSET ? + `, deductionQuery, rewardsQuery) + + var resp []*UnionDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, unionQuery, + agentId, subordinateId, globalkey.DelStateNo, + agentId, subordinateId, globalkey.DelStateNo, + pageSize, offset) + if err != nil { + return nil, 0, errors.Wrapf(err, "查询分页数据失败") + } + + return resp, total, nil +} diff --git a/app/main/model/agentClosureModel_gen.go b/app/main/model/agentClosureModel_gen.go new file mode 100644 index 0000000..2121cfc --- /dev/null +++ b/app/main/model/agentClosureModel_gen.go @@ -0,0 +1,435 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentClosureFieldNames = builder.RawFieldNames(&AgentClosure{}) + agentClosureRows = strings.Join(agentClosureFieldNames, ",") + agentClosureRowsExpectAutoSet = strings.Join(stringx.Remove(agentClosureFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentClosureRowsWithPlaceHolder = strings.Join(stringx.Remove(agentClosureFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentClosureIdPrefix = "cache:bdrp:agentClosure:id:" + cacheHmAgentClosureAncestorIdDescendantIdPrefix = "cache:bdrp:agentClosure:ancestorId:descendantId:" + cacheHmAgentClosureDescendantIdDepthPrefix = "cache:bdrp:agentClosure:descendantId:depth:" +) + +type ( + agentClosureModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentClosure) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentClosure, error) + FindOneByAncestorIdDescendantId(ctx context.Context, ancestorId int64, descendantId int64) (*AgentClosure, error) + FindOneByDescendantIdDepth(ctx context.Context, descendantId int64, depth int64) (*AgentClosure, error) + Update(ctx context.Context, session sqlx.Session, data *AgentClosure) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentClosure) 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 *AgentClosure) 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) ([]*AgentClosure, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentClosure, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentClosure, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentClosure, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentClosure, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentClosureModel struct { + sqlc.CachedConn + table string + } + + AgentClosure struct { + Id int64 `db:"id"` + AncestorId int64 `db:"ancestor_id"` + DescendantId int64 `db:"descendant_id"` + Depth int64 `db:"depth"` + 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"` // 版本号 + } +) + +func newAgentClosureModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentClosureModel { + return &defaultAgentClosureModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_closure`", + } +} + +func (m *defaultAgentClosureModel) Insert(ctx context.Context, session sqlx.Session, data *AgentClosure) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentClosureAncestorIdDescendantIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureAncestorIdDescendantIdPrefix, data.AncestorId, data.DescendantId) + hmAgentClosureDescendantIdDepthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureDescendantIdDepthPrefix, data.DescendantId, data.Depth) + hmAgentClosureIdKey := fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, 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, agentClosureRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AncestorId, data.DescendantId, data.Depth, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AncestorId, data.DescendantId, data.Depth, data.DeleteTime, data.DelState, data.Version) + }, hmAgentClosureAncestorIdDescendantIdKey, hmAgentClosureDescendantIdDepthKey, hmAgentClosureIdKey) +} + +func (m *defaultAgentClosureModel) FindOne(ctx context.Context, id int64) (*AgentClosure, error) { + hmAgentClosureIdKey := fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, id) + var resp AgentClosure + err := m.QueryRowCtx(ctx, &resp, hmAgentClosureIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentClosureRows, 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 *defaultAgentClosureModel) FindOneByAncestorIdDescendantId(ctx context.Context, ancestorId int64, descendantId int64) (*AgentClosure, error) { + hmAgentClosureAncestorIdDescendantIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureAncestorIdDescendantIdPrefix, ancestorId, descendantId) + var resp AgentClosure + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentClosureAncestorIdDescendantIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `ancestor_id` = ? and `descendant_id` = ? and del_state = ? limit 1", agentClosureRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, ancestorId, descendantId, 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 *defaultAgentClosureModel) FindOneByDescendantIdDepth(ctx context.Context, descendantId int64, depth int64) (*AgentClosure, error) { + hmAgentClosureDescendantIdDepthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureDescendantIdDepthPrefix, descendantId, depth) + var resp AgentClosure + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentClosureDescendantIdDepthKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `descendant_id` = ? and `depth` = ? and del_state = ? limit 1", agentClosureRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, descendantId, depth, 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 *defaultAgentClosureModel) Update(ctx context.Context, session sqlx.Session, newData *AgentClosure) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentClosureAncestorIdDescendantIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureAncestorIdDescendantIdPrefix, data.AncestorId, data.DescendantId) + hmAgentClosureDescendantIdDepthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureDescendantIdDepthPrefix, data.DescendantId, data.Depth) + hmAgentClosureIdKey := fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, 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, agentClosureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AncestorId, newData.DescendantId, newData.Depth, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AncestorId, newData.DescendantId, newData.Depth, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentClosureAncestorIdDescendantIdKey, hmAgentClosureDescendantIdDepthKey, hmAgentClosureIdKey) +} + +func (m *defaultAgentClosureModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentClosure) 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 + } + hmAgentClosureAncestorIdDescendantIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureAncestorIdDescendantIdPrefix, data.AncestorId, data.DescendantId) + hmAgentClosureDescendantIdDepthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureDescendantIdDepthPrefix, data.DescendantId, data.Depth) + hmAgentClosureIdKey := fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, 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, agentClosureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AncestorId, newData.DescendantId, newData.Depth, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AncestorId, newData.DescendantId, newData.Depth, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentClosureAncestorIdDescendantIdKey, hmAgentClosureDescendantIdDepthKey, hmAgentClosureIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentClosureModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentClosure) 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 "), "AgentClosureModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentClosureModel) 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 *defaultAgentClosureModel) 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 *defaultAgentClosureModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentClosure, error) { + + builder = builder.Columns(agentClosureRows) + + 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 []*AgentClosure + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentClosureModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentClosure, error) { + + builder = builder.Columns(agentClosureRows) + + 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 []*AgentClosure + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentClosureModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentClosure, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentClosureRows) + + 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 []*AgentClosure + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentClosureModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentClosure, error) { + + builder = builder.Columns(agentClosureRows) + + 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 []*AgentClosure + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentClosureModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentClosure, error) { + + builder = builder.Columns(agentClosureRows) + + 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 []*AgentClosure + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentClosureModel) 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 *defaultAgentClosureModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentClosureModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentClosureAncestorIdDescendantIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureAncestorIdDescendantIdPrefix, data.AncestorId, data.DescendantId) + hmAgentClosureDescendantIdDepthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentClosureDescendantIdDepthPrefix, data.DescendantId, data.Depth) + hmAgentClosureIdKey := fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, 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) + }, hmAgentClosureAncestorIdDescendantIdKey, hmAgentClosureDescendantIdDepthKey, hmAgentClosureIdKey) + return err +} +func (m *defaultAgentClosureModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentClosureIdPrefix, primary) +} +func (m *defaultAgentClosureModel) 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", agentClosureRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentClosureModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentCommissionDeductionModel.go b/app/main/model/agentCommissionDeductionModel.go new file mode 100644 index 0000000..3318cf1 --- /dev/null +++ b/app/main/model/agentCommissionDeductionModel.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 _ AgentCommissionDeductionModel = (*customAgentCommissionDeductionModel)(nil) + +type ( + // AgentCommissionDeductionModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentCommissionDeductionModel. + AgentCommissionDeductionModel interface { + agentCommissionDeductionModel + } + + customAgentCommissionDeductionModel struct { + *defaultAgentCommissionDeductionModel + } +) + +// NewAgentCommissionDeductionModel returns a model for the database table. +func NewAgentCommissionDeductionModel(conn sqlx.SqlConn, c cache.CacheConf) AgentCommissionDeductionModel { + return &customAgentCommissionDeductionModel{ + defaultAgentCommissionDeductionModel: newAgentCommissionDeductionModel(conn, c), + } +} diff --git a/app/main/model/agentCommissionDeductionModel_gen.go b/app/main/model/agentCommissionDeductionModel_gen.go new file mode 100644 index 0000000..1537d11 --- /dev/null +++ b/app/main/model/agentCommissionDeductionModel_gen.go @@ -0,0 +1,374 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentCommissionDeductionFieldNames = builder.RawFieldNames(&AgentCommissionDeduction{}) + agentCommissionDeductionRows = strings.Join(agentCommissionDeductionFieldNames, ",") + agentCommissionDeductionRowsExpectAutoSet = strings.Join(stringx.Remove(agentCommissionDeductionFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentCommissionDeductionRowsWithPlaceHolder = strings.Join(stringx.Remove(agentCommissionDeductionFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentCommissionDeductionIdPrefix = "cache:bdrp:agentCommissionDeduction:id:" +) + +type ( + agentCommissionDeductionModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentCommissionDeduction, error) + Update(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) 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 *AgentCommissionDeduction) 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) ([]*AgentCommissionDeduction, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommissionDeduction, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommissionDeduction, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentCommissionDeduction, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentCommissionDeduction, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentCommissionDeductionModel struct { + sqlc.CachedConn + table string + } + + AgentCommissionDeduction struct { + Id int64 `db:"id"` + AgentId int64 `db:"agent_id"` + DeductedAgentId int64 `db:"deducted_agent_id"` // 被抽佣代理ID + Amount float64 `db:"amount"` + ProductId int64 `db:"product_id"` // 产品ID + OrderId sql.NullInt64 `db:"order_id"` // 关联订单ID + Type string `db:"type"` + Status int64 `db:"status"` // 状态 + 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"` // 版本号 + } +) + +func newAgentCommissionDeductionModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentCommissionDeductionModel { + return &defaultAgentCommissionDeductionModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_commission_deduction`", + } +} + +func (m *defaultAgentCommissionDeductionModel) Insert(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, 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, agentCommissionDeductionRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentCommissionDeductionIdKey) +} + +func (m *defaultAgentCommissionDeductionModel) FindOne(ctx context.Context, id int64) (*AgentCommissionDeduction, error) { + bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, id) + var resp AgentCommissionDeduction + err := m.QueryRowCtx(ctx, &resp, bdrpAgentCommissionDeductionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentCommissionDeductionRows, 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 *defaultAgentCommissionDeductionModel) Update(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) (sql.Result, error) { + bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, 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, agentCommissionDeductionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + }, bdrpAgentCommissionDeductionIdKey) +} + +func (m *defaultAgentCommissionDeductionModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, 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, agentCommissionDeductionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.DeductedAgentId, data.Amount, data.ProductId, data.OrderId, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, bdrpAgentCommissionDeductionIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentCommissionDeductionModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentCommissionDeduction) 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 "), "AgentCommissionDeductionModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentCommissionDeductionModel) 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 *defaultAgentCommissionDeductionModel) 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 *defaultAgentCommissionDeductionModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentCommissionDeduction, error) { + + builder = builder.Columns(agentCommissionDeductionRows) + + 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 []*AgentCommissionDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionDeductionModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommissionDeduction, error) { + + builder = builder.Columns(agentCommissionDeductionRows) + + 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 []*AgentCommissionDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionDeductionModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommissionDeduction, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentCommissionDeductionRows) + + 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 []*AgentCommissionDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentCommissionDeductionModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentCommissionDeduction, error) { + + builder = builder.Columns(agentCommissionDeductionRows) + + 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 []*AgentCommissionDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionDeductionModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentCommissionDeduction, error) { + + builder = builder.Columns(agentCommissionDeductionRows) + + 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 []*AgentCommissionDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionDeductionModel) 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 *defaultAgentCommissionDeductionModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentCommissionDeductionModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + bdrpAgentCommissionDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, 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) + }, bdrpAgentCommissionDeductionIdKey) + return err +} +func (m *defaultAgentCommissionDeductionModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentCommissionDeductionIdPrefix, primary) +} +func (m *defaultAgentCommissionDeductionModel) 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", agentCommissionDeductionRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentCommissionDeductionModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentCommissionModel.go b/app/main/model/agentCommissionModel.go new file mode 100644 index 0000000..1b9e614 --- /dev/null +++ b/app/main/model/agentCommissionModel.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 _ AgentCommissionModel = (*customAgentCommissionModel)(nil) + +type ( + // AgentCommissionModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentCommissionModel. + AgentCommissionModel interface { + agentCommissionModel + } + + customAgentCommissionModel struct { + *defaultAgentCommissionModel + } +) + +// NewAgentCommissionModel returns a model for the database table. +func NewAgentCommissionModel(conn sqlx.SqlConn, c cache.CacheConf) AgentCommissionModel { + return &customAgentCommissionModel{ + defaultAgentCommissionModel: newAgentCommissionModel(conn, c), + } +} diff --git a/app/main/model/agentCommissionModel_gen.go b/app/main/model/agentCommissionModel_gen.go new file mode 100644 index 0000000..e5a5133 --- /dev/null +++ b/app/main/model/agentCommissionModel_gen.go @@ -0,0 +1,372 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + "bdrp-server/common/globalkey" +) + +var ( + agentCommissionFieldNames = builder.RawFieldNames(&AgentCommission{}) + agentCommissionRows = strings.Join(agentCommissionFieldNames, ",") + agentCommissionRowsExpectAutoSet = strings.Join(stringx.Remove(agentCommissionFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentCommissionRowsWithPlaceHolder = strings.Join(stringx.Remove(agentCommissionFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentCommissionIdPrefix = "cache:bdrp:agentCommission:id:" +) + +type ( + agentCommissionModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentCommission) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentCommission, error) + Update(ctx context.Context, session sqlx.Session, data *AgentCommission) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentCommission) 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 *AgentCommission) 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) ([]*AgentCommission, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommission, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommission, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentCommission, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentCommission, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentCommissionModel struct { + sqlc.CachedConn + table string + } + + AgentCommission struct { + Id int64 `db:"id"` + AgentId int64 `db:"agent_id"` + OrderId int64 `db:"order_id"` + Amount float64 `db:"amount"` + RefundedAmount float64 `db:"refunded_amount"` // 已退款佣金金额 + ProductId int64 `db:"product_id"` // 产品ID + Status int64 `db:"status"` + 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"` // 版本号 + } +) + +func newAgentCommissionModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentCommissionModel { + return &defaultAgentCommissionModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_commission`", + } +} + +func (m *defaultAgentCommissionModel) Insert(ctx context.Context, session sqlx.Session, data *AgentCommission) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentCommissionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, 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, agentCommissionRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentCommissionIdKey) +} + +func (m *defaultAgentCommissionModel) FindOne(ctx context.Context, id int64) (*AgentCommission, error) { + bdrpAgentCommissionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, id) + var resp AgentCommission + err := m.QueryRowCtx(ctx, &resp, bdrpAgentCommissionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentCommissionRows, 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 *defaultAgentCommissionModel) Update(ctx context.Context, session sqlx.Session, data *AgentCommission) (sql.Result, error) { + bdrpAgentCommissionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, 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, agentCommissionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + }, bdrpAgentCommissionIdKey) +} + +func (m *defaultAgentCommissionModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentCommission) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + bdrpAgentCommissionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, 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, agentCommissionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.OrderId, data.Amount, data.RefundedAmount, data.ProductId, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, bdrpAgentCommissionIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentCommissionModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentCommission) 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 "), "AgentCommissionModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentCommissionModel) 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 *defaultAgentCommissionModel) 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 *defaultAgentCommissionModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentCommission, error) { + + builder = builder.Columns(agentCommissionRows) + + 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 []*AgentCommission + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommission, error) { + + builder = builder.Columns(agentCommissionRows) + + 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 []*AgentCommission + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentCommission, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentCommissionRows) + + 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 []*AgentCommission + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentCommissionModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentCommission, error) { + + builder = builder.Columns(agentCommissionRows) + + 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 []*AgentCommission + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentCommission, error) { + + builder = builder.Columns(agentCommissionRows) + + 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 []*AgentCommission + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentCommissionModel) 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 *defaultAgentCommissionModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentCommissionModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + bdrpAgentCommissionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, 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) + }, bdrpAgentCommissionIdKey) + return err +} +func (m *defaultAgentCommissionModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentCommissionIdPrefix, primary) +} +func (m *defaultAgentCommissionModel) 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", agentCommissionRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentCommissionModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentLinkModel.go b/app/main/model/agentLinkModel.go new file mode 100644 index 0000000..ee3c37c --- /dev/null +++ b/app/main/model/agentLinkModel.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 _ AgentLinkModel = (*customAgentLinkModel)(nil) + +type ( + // AgentLinkModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentLinkModel. + AgentLinkModel interface { + agentLinkModel + } + + customAgentLinkModel struct { + *defaultAgentLinkModel + } +) + +// NewAgentLinkModel returns a model for the database table. +func NewAgentLinkModel(conn sqlx.SqlConn, c cache.CacheConf) AgentLinkModel { + return &customAgentLinkModel{ + defaultAgentLinkModel: newAgentLinkModel(conn, c), + } +} diff --git a/app/main/model/agentLinkModel_gen.go b/app/main/model/agentLinkModel_gen.go new file mode 100644 index 0000000..493dd56 --- /dev/null +++ b/app/main/model/agentLinkModel_gen.go @@ -0,0 +1,411 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentLinkFieldNames = builder.RawFieldNames(&AgentLink{}) + agentLinkRows = strings.Join(agentLinkFieldNames, ",") + agentLinkRowsExpectAutoSet = strings.Join(stringx.Remove(agentLinkFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentLinkRowsWithPlaceHolder = strings.Join(stringx.Remove(agentLinkFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentLinkIdPrefix = "cache:bdrp:agentLink:id:" + cacheHmAgentLinkLinkIdentifierPrefix = "cache:bdrp:agentLink:linkIdentifier:" +) + +type ( + agentLinkModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentLink) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentLink, error) + FindOneByLinkIdentifier(ctx context.Context, linkIdentifier string) (*AgentLink, error) + Update(ctx context.Context, session sqlx.Session, data *AgentLink) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentLink) 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 *AgentLink) 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) ([]*AgentLink, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentLink, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentLink, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentLink, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentLink, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentLinkModel struct { + sqlc.CachedConn + table string + } + + AgentLink struct { + Id int64 `db:"id"` + ProductId int64 `db:"product_id"` + Price float64 `db:"price"` + UserId int64 `db:"user_id"` + AgentId int64 `db:"agent_id"` + LinkIdentifier string `db:"link_identifier"` + 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"` + } +) + +func newAgentLinkModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentLinkModel { + return &defaultAgentLinkModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_link`", + } +} + +func (m *defaultAgentLinkModel) Insert(ctx context.Context, session sqlx.Session, data *AgentLink) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentLinkIdKey := fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, data.Id) + hmAgentLinkLinkIdentifierKey := fmt.Sprintf("%s%v", cacheHmAgentLinkLinkIdentifierPrefix, data.LinkIdentifier) + 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, agentLinkRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.ProductId, data.Price, data.UserId, data.AgentId, data.LinkIdentifier, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.ProductId, data.Price, data.UserId, data.AgentId, data.LinkIdentifier, data.DeleteTime, data.DelState, data.Version) + }, hmAgentLinkIdKey, hmAgentLinkLinkIdentifierKey) +} + +func (m *defaultAgentLinkModel) FindOne(ctx context.Context, id int64) (*AgentLink, error) { + hmAgentLinkIdKey := fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, id) + var resp AgentLink + err := m.QueryRowCtx(ctx, &resp, hmAgentLinkIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentLinkRows, 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 *defaultAgentLinkModel) FindOneByLinkIdentifier(ctx context.Context, linkIdentifier string) (*AgentLink, error) { + hmAgentLinkLinkIdentifierKey := fmt.Sprintf("%s%v", cacheHmAgentLinkLinkIdentifierPrefix, linkIdentifier) + var resp AgentLink + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentLinkLinkIdentifierKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `link_identifier` = ? and del_state = ? limit 1", agentLinkRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, linkIdentifier, 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 *defaultAgentLinkModel) Update(ctx context.Context, session sqlx.Session, newData *AgentLink) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentLinkIdKey := fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, data.Id) + hmAgentLinkLinkIdentifierKey := fmt.Sprintf("%s%v", cacheHmAgentLinkLinkIdentifierPrefix, data.LinkIdentifier) + 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, agentLinkRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.Price, newData.UserId, newData.AgentId, newData.LinkIdentifier, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.Price, newData.UserId, newData.AgentId, newData.LinkIdentifier, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentLinkIdKey, hmAgentLinkLinkIdentifierKey) +} + +func (m *defaultAgentLinkModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentLink) 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 + } + hmAgentLinkIdKey := fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, data.Id) + hmAgentLinkLinkIdentifierKey := fmt.Sprintf("%s%v", cacheHmAgentLinkLinkIdentifierPrefix, data.LinkIdentifier) + 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, agentLinkRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.Price, newData.UserId, newData.AgentId, newData.LinkIdentifier, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.Price, newData.UserId, newData.AgentId, newData.LinkIdentifier, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentLinkIdKey, hmAgentLinkLinkIdentifierKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentLinkModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentLink) 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 "), "AgentLinkModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentLinkModel) 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 *defaultAgentLinkModel) 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 *defaultAgentLinkModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentLink, error) { + + builder = builder.Columns(agentLinkRows) + + 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 []*AgentLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentLinkModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentLink, error) { + + builder = builder.Columns(agentLinkRows) + + 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 []*AgentLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentLinkModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentLink, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentLinkRows) + + 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 []*AgentLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentLinkModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentLink, error) { + + builder = builder.Columns(agentLinkRows) + + 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 []*AgentLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentLinkModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentLink, error) { + + builder = builder.Columns(agentLinkRows) + + 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 []*AgentLink + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentLinkModel) 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 *defaultAgentLinkModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentLinkModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentLinkIdKey := fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, id) + hmAgentLinkLinkIdentifierKey := fmt.Sprintf("%s%v", cacheHmAgentLinkLinkIdentifierPrefix, data.LinkIdentifier) + _, 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) + }, hmAgentLinkIdKey, hmAgentLinkLinkIdentifierKey) + return err +} +func (m *defaultAgentLinkModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentLinkIdPrefix, primary) +} +func (m *defaultAgentLinkModel) 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", agentLinkRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentLinkModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentMembershipConfigModel.go b/app/main/model/agentMembershipConfigModel.go new file mode 100644 index 0000000..f1fb428 --- /dev/null +++ b/app/main/model/agentMembershipConfigModel.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 _ AgentMembershipConfigModel = (*customAgentMembershipConfigModel)(nil) + +type ( + // AgentMembershipConfigModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentMembershipConfigModel. + AgentMembershipConfigModel interface { + agentMembershipConfigModel + } + + customAgentMembershipConfigModel struct { + *defaultAgentMembershipConfigModel + } +) + +// NewAgentMembershipConfigModel returns a model for the database table. +func NewAgentMembershipConfigModel(conn sqlx.SqlConn, c cache.CacheConf) AgentMembershipConfigModel { + return &customAgentMembershipConfigModel{ + defaultAgentMembershipConfigModel: newAgentMembershipConfigModel(conn, c), + } +} diff --git a/app/main/model/agentMembershipConfigModel_gen.go b/app/main/model/agentMembershipConfigModel_gen.go new file mode 100644 index 0000000..405ded0 --- /dev/null +++ b/app/main/model/agentMembershipConfigModel_gen.go @@ -0,0 +1,420 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentMembershipConfigFieldNames = builder.RawFieldNames(&AgentMembershipConfig{}) + agentMembershipConfigRows = strings.Join(agentMembershipConfigFieldNames, ",") + agentMembershipConfigRowsExpectAutoSet = strings.Join(stringx.Remove(agentMembershipConfigFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentMembershipConfigRowsWithPlaceHolder = strings.Join(stringx.Remove(agentMembershipConfigFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentMembershipConfigIdPrefix = "cache:bdrp:agentMembershipConfig:id:" + cacheHmAgentMembershipConfigLevelNamePrefix = "cache:bdrp:agentMembershipConfig:levelName:" +) + +type ( + agentMembershipConfigModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipConfig) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentMembershipConfig, error) + FindOneByLevelName(ctx context.Context, levelName string) (*AgentMembershipConfig, error) + Update(ctx context.Context, session sqlx.Session, data *AgentMembershipConfig) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentMembershipConfig) 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 *AgentMembershipConfig) 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) ([]*AgentMembershipConfig, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipConfig, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipConfig, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipConfig, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipConfig, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentMembershipConfigModel struct { + sqlc.CachedConn + table string + } + + AgentMembershipConfig struct { + Id int64 `db:"id"` + LevelName string `db:"level_name"` // 会员级别名称,如 S级,S+级 + Price sql.NullFloat64 `db:"price"` // 会员年费 + ReportCommission sql.NullFloat64 `db:"report_commission"` // 直推报告收益 + LowerActivityReward sql.NullFloat64 `db:"lower_activity_reward"` // 下级活跃奖励金额 + NewActivityReward sql.NullFloat64 `db:"new_activity_reward"` // 新增活跃奖励金额 + LowerStandardCount sql.NullInt64 `db:"lower_standard_count"` // 活跃下级达标个数 + NewLowerStandardCount sql.NullInt64 `db:"new_lower_standard_count"` // 新增活跃下级达标个数 + LowerWithdrawRewardRatio sql.NullFloat64 `db:"lower_withdraw_reward_ratio"` // 下级提现奖励比例 + LowerConvertVipReward sql.NullFloat64 `db:"lower_convert_vip_reward"` // 下级转化VIP奖励 + LowerConvertSvipReward sql.NullFloat64 `db:"lower_convert_svip_reward"` // 下级转化SVIP奖励 + ExemptionAmount sql.NullFloat64 `db:"exemption_amount"` // 免审核金额 + PriceIncreaseMax sql.NullFloat64 `db:"price_increase_max"` // 提价最高金额 + PriceRatio sql.NullFloat64 `db:"price_ratio"` // 提价区间收取比例 + PriceIncreaseAmount sql.NullFloat64 `db:"price_increase_amount"` // 在原本成本上加价的金额 + 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"` + } +) + +func newAgentMembershipConfigModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentMembershipConfigModel { + return &defaultAgentMembershipConfigModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_membership_config`", + } +} + +func (m *defaultAgentMembershipConfigModel) Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipConfig) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentMembershipConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, data.Id) + hmAgentMembershipConfigLevelNameKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigLevelNamePrefix, data.LevelName) + 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, agentMembershipConfigRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.LevelName, data.Price, data.ReportCommission, data.LowerActivityReward, data.NewActivityReward, data.LowerStandardCount, data.NewLowerStandardCount, data.LowerWithdrawRewardRatio, data.LowerConvertVipReward, data.LowerConvertSvipReward, data.ExemptionAmount, data.PriceIncreaseMax, data.PriceRatio, data.PriceIncreaseAmount, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.LevelName, data.Price, data.ReportCommission, data.LowerActivityReward, data.NewActivityReward, data.LowerStandardCount, data.NewLowerStandardCount, data.LowerWithdrawRewardRatio, data.LowerConvertVipReward, data.LowerConvertSvipReward, data.ExemptionAmount, data.PriceIncreaseMax, data.PriceRatio, data.PriceIncreaseAmount, data.DeleteTime, data.DelState, data.Version) + }, hmAgentMembershipConfigIdKey, hmAgentMembershipConfigLevelNameKey) +} + +func (m *defaultAgentMembershipConfigModel) FindOne(ctx context.Context, id int64) (*AgentMembershipConfig, error) { + hmAgentMembershipConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, id) + var resp AgentMembershipConfig + err := m.QueryRowCtx(ctx, &resp, hmAgentMembershipConfigIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentMembershipConfigRows, 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 *defaultAgentMembershipConfigModel) FindOneByLevelName(ctx context.Context, levelName string) (*AgentMembershipConfig, error) { + hmAgentMembershipConfigLevelNameKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigLevelNamePrefix, levelName) + var resp AgentMembershipConfig + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentMembershipConfigLevelNameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `level_name` = ? and del_state = ? limit 1", agentMembershipConfigRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, levelName, 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 *defaultAgentMembershipConfigModel) Update(ctx context.Context, session sqlx.Session, newData *AgentMembershipConfig) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentMembershipConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, data.Id) + hmAgentMembershipConfigLevelNameKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigLevelNamePrefix, data.LevelName) + 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, agentMembershipConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.LevelName, newData.Price, newData.ReportCommission, newData.LowerActivityReward, newData.NewActivityReward, newData.LowerStandardCount, newData.NewLowerStandardCount, newData.LowerWithdrawRewardRatio, newData.LowerConvertVipReward, newData.LowerConvertSvipReward, newData.ExemptionAmount, newData.PriceIncreaseMax, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.LevelName, newData.Price, newData.ReportCommission, newData.LowerActivityReward, newData.NewActivityReward, newData.LowerStandardCount, newData.NewLowerStandardCount, newData.LowerWithdrawRewardRatio, newData.LowerConvertVipReward, newData.LowerConvertSvipReward, newData.ExemptionAmount, newData.PriceIncreaseMax, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentMembershipConfigIdKey, hmAgentMembershipConfigLevelNameKey) +} + +func (m *defaultAgentMembershipConfigModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentMembershipConfig) 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 + } + hmAgentMembershipConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, data.Id) + hmAgentMembershipConfigLevelNameKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigLevelNamePrefix, data.LevelName) + 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, agentMembershipConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.LevelName, newData.Price, newData.ReportCommission, newData.LowerActivityReward, newData.NewActivityReward, newData.LowerStandardCount, newData.NewLowerStandardCount, newData.LowerWithdrawRewardRatio, newData.LowerConvertVipReward, newData.LowerConvertSvipReward, newData.ExemptionAmount, newData.PriceIncreaseMax, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.LevelName, newData.Price, newData.ReportCommission, newData.LowerActivityReward, newData.NewActivityReward, newData.LowerStandardCount, newData.NewLowerStandardCount, newData.LowerWithdrawRewardRatio, newData.LowerConvertVipReward, newData.LowerConvertSvipReward, newData.ExemptionAmount, newData.PriceIncreaseMax, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentMembershipConfigIdKey, hmAgentMembershipConfigLevelNameKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentMembershipConfigModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentMembershipConfig) 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 "), "AgentMembershipConfigModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentMembershipConfigModel) 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 *defaultAgentMembershipConfigModel) 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 *defaultAgentMembershipConfigModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentMembershipConfig, error) { + + builder = builder.Columns(agentMembershipConfigRows) + + 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 []*AgentMembershipConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipConfigModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipConfig, error) { + + builder = builder.Columns(agentMembershipConfigRows) + + 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 []*AgentMembershipConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipConfigModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipConfig, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentMembershipConfigRows) + + 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 []*AgentMembershipConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentMembershipConfigModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipConfig, error) { + + builder = builder.Columns(agentMembershipConfigRows) + + 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 []*AgentMembershipConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipConfigModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipConfig, error) { + + builder = builder.Columns(agentMembershipConfigRows) + + 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 []*AgentMembershipConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipConfigModel) 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 *defaultAgentMembershipConfigModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentMembershipConfigModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentMembershipConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, id) + hmAgentMembershipConfigLevelNameKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigLevelNamePrefix, data.LevelName) + _, 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) + }, hmAgentMembershipConfigIdKey, hmAgentMembershipConfigLevelNameKey) + return err +} +func (m *defaultAgentMembershipConfigModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentMembershipConfigIdPrefix, primary) +} +func (m *defaultAgentMembershipConfigModel) 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", agentMembershipConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentMembershipConfigModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentMembershipRechargeOrderModel.go b/app/main/model/agentMembershipRechargeOrderModel.go new file mode 100644 index 0000000..fb72755 --- /dev/null +++ b/app/main/model/agentMembershipRechargeOrderModel.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 _ AgentMembershipRechargeOrderModel = (*customAgentMembershipRechargeOrderModel)(nil) + +type ( + // AgentMembershipRechargeOrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentMembershipRechargeOrderModel. + AgentMembershipRechargeOrderModel interface { + agentMembershipRechargeOrderModel + } + + customAgentMembershipRechargeOrderModel struct { + *defaultAgentMembershipRechargeOrderModel + } +) + +// NewAgentMembershipRechargeOrderModel returns a model for the database table. +func NewAgentMembershipRechargeOrderModel(conn sqlx.SqlConn, c cache.CacheConf) AgentMembershipRechargeOrderModel { + return &customAgentMembershipRechargeOrderModel{ + defaultAgentMembershipRechargeOrderModel: newAgentMembershipRechargeOrderModel(conn, c), + } +} diff --git a/app/main/model/agentMembershipRechargeOrderModel_gen.go b/app/main/model/agentMembershipRechargeOrderModel_gen.go new file mode 100644 index 0000000..a2e6c66 --- /dev/null +++ b/app/main/model/agentMembershipRechargeOrderModel_gen.go @@ -0,0 +1,440 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentMembershipRechargeOrderFieldNames = builder.RawFieldNames(&AgentMembershipRechargeOrder{}) + agentMembershipRechargeOrderRows = strings.Join(agentMembershipRechargeOrderFieldNames, ",") + agentMembershipRechargeOrderRowsExpectAutoSet = strings.Join(stringx.Remove(agentMembershipRechargeOrderFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentMembershipRechargeOrderRowsWithPlaceHolder = strings.Join(stringx.Remove(agentMembershipRechargeOrderFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentMembershipRechargeOrderIdPrefix = "cache:bdrp:agentMembershipRechargeOrder:id:" + cacheHmAgentMembershipRechargeOrderOrderNoPrefix = "cache:bdrp:agentMembershipRechargeOrder:orderNo:" + cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix = "cache:bdrp:agentMembershipRechargeOrder:platformOrderId:" +) + +type ( + agentMembershipRechargeOrderModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipRechargeOrder) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentMembershipRechargeOrder, error) + FindOneByOrderNo(ctx context.Context, orderNo string) (*AgentMembershipRechargeOrder, error) + FindOneByPlatformOrderId(ctx context.Context, platformOrderId sql.NullString) (*AgentMembershipRechargeOrder, error) + Update(ctx context.Context, session sqlx.Session, data *AgentMembershipRechargeOrder) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentMembershipRechargeOrder) 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 *AgentMembershipRechargeOrder) 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) ([]*AgentMembershipRechargeOrder, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipRechargeOrder, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipRechargeOrder, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipRechargeOrder, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipRechargeOrder, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentMembershipRechargeOrderModel struct { + sqlc.CachedConn + table string + } + + AgentMembershipRechargeOrder struct { + Id int64 `db:"id"` + UserId int64 `db:"user_id"` // 用户ID + AgentId int64 `db:"agent_id"` // 代理ID + LevelName string `db:"level_name"` // 会员级别,如 VIP,SVIP,normal + Amount float64 `db:"amount"` // 充值金额 + PaymentMethod string `db:"payment_method"` // 支付方式:支付宝,微信,苹果支付,其他 + OrderNo string `db:"order_no"` // 交易号 + PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号 + Status string `db:"status"` + 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"` // 版本号 + } +) + +func newAgentMembershipRechargeOrderModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentMembershipRechargeOrderModel { + return &defaultAgentMembershipRechargeOrderModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_membership_recharge_order`", + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipRechargeOrder) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentMembershipRechargeOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, data.Id) + hmAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo) + hmAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId) + 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, agentMembershipRechargeOrderRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.UserId, data.AgentId, data.LevelName, data.Amount, data.PaymentMethod, data.OrderNo, data.PlatformOrderId, data.Status, data.DeleteTime, data.DelState, data.Version) + }, hmAgentMembershipRechargeOrderIdKey, hmAgentMembershipRechargeOrderOrderNoKey, hmAgentMembershipRechargeOrderPlatformOrderIdKey) +} + +func (m *defaultAgentMembershipRechargeOrderModel) FindOne(ctx context.Context, id int64) (*AgentMembershipRechargeOrder, error) { + hmAgentMembershipRechargeOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, id) + var resp AgentMembershipRechargeOrder + err := m.QueryRowCtx(ctx, &resp, hmAgentMembershipRechargeOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentMembershipRechargeOrderRows, 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 *defaultAgentMembershipRechargeOrderModel) FindOneByOrderNo(ctx context.Context, orderNo string) (*AgentMembershipRechargeOrder, error) { + hmAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderOrderNoPrefix, orderNo) + var resp AgentMembershipRechargeOrder + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentMembershipRechargeOrderOrderNoKey, 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", agentMembershipRechargeOrderRows, 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 *defaultAgentMembershipRechargeOrderModel) FindOneByPlatformOrderId(ctx context.Context, platformOrderId sql.NullString) (*AgentMembershipRechargeOrder, error) { + hmAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix, platformOrderId) + var resp AgentMembershipRechargeOrder + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentMembershipRechargeOrderPlatformOrderIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `platform_order_id` = ? and del_state = ? limit 1", agentMembershipRechargeOrderRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, platformOrderId, 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 *defaultAgentMembershipRechargeOrderModel) Update(ctx context.Context, session sqlx.Session, newData *AgentMembershipRechargeOrder) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentMembershipRechargeOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, data.Id) + hmAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo) + hmAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId) + 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, agentMembershipRechargeOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentMembershipRechargeOrderIdKey, hmAgentMembershipRechargeOrderOrderNoKey, hmAgentMembershipRechargeOrderPlatformOrderIdKey) +} + +func (m *defaultAgentMembershipRechargeOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentMembershipRechargeOrder) 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 + } + hmAgentMembershipRechargeOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, data.Id) + hmAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo) + hmAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId) + 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, agentMembershipRechargeOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.LevelName, newData.Amount, newData.PaymentMethod, newData.OrderNo, newData.PlatformOrderId, newData.Status, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentMembershipRechargeOrderIdKey, hmAgentMembershipRechargeOrderOrderNoKey, hmAgentMembershipRechargeOrderPlatformOrderIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentMembershipRechargeOrderModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentMembershipRechargeOrder) 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 "), "AgentMembershipRechargeOrderModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentMembershipRechargeOrderModel) 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 *defaultAgentMembershipRechargeOrderModel) 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 *defaultAgentMembershipRechargeOrderModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentMembershipRechargeOrder, error) { + + builder = builder.Columns(agentMembershipRechargeOrderRows) + + 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 []*AgentMembershipRechargeOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipRechargeOrder, error) { + + builder = builder.Columns(agentMembershipRechargeOrderRows) + + 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 []*AgentMembershipRechargeOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipRechargeOrder, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentMembershipRechargeOrderRows) + + 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 []*AgentMembershipRechargeOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipRechargeOrder, error) { + + builder = builder.Columns(agentMembershipRechargeOrderRows) + + 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 []*AgentMembershipRechargeOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipRechargeOrder, error) { + + builder = builder.Columns(agentMembershipRechargeOrderRows) + + 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 []*AgentMembershipRechargeOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipRechargeOrderModel) 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 *defaultAgentMembershipRechargeOrderModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentMembershipRechargeOrderModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentMembershipRechargeOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, id) + hmAgentMembershipRechargeOrderOrderNoKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderOrderNoPrefix, data.OrderNo) + hmAgentMembershipRechargeOrderPlatformOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderPlatformOrderIdPrefix, data.PlatformOrderId) + _, 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) + }, hmAgentMembershipRechargeOrderIdKey, hmAgentMembershipRechargeOrderOrderNoKey, hmAgentMembershipRechargeOrderPlatformOrderIdKey) + return err +} +func (m *defaultAgentMembershipRechargeOrderModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentMembershipRechargeOrderIdPrefix, primary) +} +func (m *defaultAgentMembershipRechargeOrderModel) 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", agentMembershipRechargeOrderRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentMembershipRechargeOrderModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentMembershipUserConfigModel.go b/app/main/model/agentMembershipUserConfigModel.go new file mode 100644 index 0000000..b5e5a48 --- /dev/null +++ b/app/main/model/agentMembershipUserConfigModel.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 _ AgentMembershipUserConfigModel = (*customAgentMembershipUserConfigModel)(nil) + +type ( + // AgentMembershipUserConfigModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentMembershipUserConfigModel. + AgentMembershipUserConfigModel interface { + agentMembershipUserConfigModel + } + + customAgentMembershipUserConfigModel struct { + *defaultAgentMembershipUserConfigModel + } +) + +// NewAgentMembershipUserConfigModel returns a model for the database table. +func NewAgentMembershipUserConfigModel(conn sqlx.SqlConn, c cache.CacheConf) AgentMembershipUserConfigModel { + return &customAgentMembershipUserConfigModel{ + defaultAgentMembershipUserConfigModel: newAgentMembershipUserConfigModel(conn, c), + } +} diff --git a/app/main/model/agentMembershipUserConfigModel_gen.go b/app/main/model/agentMembershipUserConfigModel_gen.go new file mode 100644 index 0000000..b8e7874 --- /dev/null +++ b/app/main/model/agentMembershipUserConfigModel_gen.go @@ -0,0 +1,413 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentMembershipUserConfigFieldNames = builder.RawFieldNames(&AgentMembershipUserConfig{}) + agentMembershipUserConfigRows = strings.Join(agentMembershipUserConfigFieldNames, ",") + agentMembershipUserConfigRowsExpectAutoSet = strings.Join(stringx.Remove(agentMembershipUserConfigFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentMembershipUserConfigRowsWithPlaceHolder = strings.Join(stringx.Remove(agentMembershipUserConfigFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentMembershipUserConfigIdPrefix = "cache:bdrp:agentMembershipUserConfig:id:" + cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix = "cache:bdrp:agentMembershipUserConfig:agentId:productId:" +) + +type ( + agentMembershipUserConfigModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipUserConfig) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentMembershipUserConfig, error) + FindOneByAgentIdProductId(ctx context.Context, agentId int64, productId int64) (*AgentMembershipUserConfig, error) + Update(ctx context.Context, session sqlx.Session, data *AgentMembershipUserConfig) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentMembershipUserConfig) 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 *AgentMembershipUserConfig) 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) ([]*AgentMembershipUserConfig, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipUserConfig, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipUserConfig, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipUserConfig, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipUserConfig, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentMembershipUserConfigModel struct { + sqlc.CachedConn + table string + } + + AgentMembershipUserConfig struct { + Id int64 `db:"id"` + UserId int64 `db:"user_id"` // 用户ID,标识代理用户 + AgentId int64 `db:"agent_id"` // 代理ID + ProductId int64 `db:"product_id"` // 产品ID + PriceRangeFrom float64 `db:"price_range_from"` // 定价区间最低 + PriceRangeTo float64 `db:"price_range_to"` // 定价区间最高 + PriceRatio float64 `db:"price_ratio"` // 定价区间收取比例 + PriceIncreaseAmount float64 `db:"price_increase_amount"` // 在原本成本上加价的金额 + 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"` // 版本号 + } +) + +func newAgentMembershipUserConfigModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentMembershipUserConfigModel { + return &defaultAgentMembershipUserConfigModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_membership_user_config`", + } +} + +func (m *defaultAgentMembershipUserConfigModel) Insert(ctx context.Context, session sqlx.Session, data *AgentMembershipUserConfig) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentMembershipUserConfigAgentIdProductIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix, data.AgentId, data.ProductId) + hmAgentMembershipUserConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, 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, agentMembershipUserConfigRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.AgentId, data.ProductId, data.PriceRangeFrom, data.PriceRangeTo, data.PriceRatio, data.PriceIncreaseAmount, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.UserId, data.AgentId, data.ProductId, data.PriceRangeFrom, data.PriceRangeTo, data.PriceRatio, data.PriceIncreaseAmount, data.DeleteTime, data.DelState, data.Version) + }, hmAgentMembershipUserConfigAgentIdProductIdKey, hmAgentMembershipUserConfigIdKey) +} + +func (m *defaultAgentMembershipUserConfigModel) FindOne(ctx context.Context, id int64) (*AgentMembershipUserConfig, error) { + hmAgentMembershipUserConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, id) + var resp AgentMembershipUserConfig + err := m.QueryRowCtx(ctx, &resp, hmAgentMembershipUserConfigIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentMembershipUserConfigRows, 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 *defaultAgentMembershipUserConfigModel) FindOneByAgentIdProductId(ctx context.Context, agentId int64, productId int64) (*AgentMembershipUserConfig, error) { + hmAgentMembershipUserConfigAgentIdProductIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix, agentId, productId) + var resp AgentMembershipUserConfig + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentMembershipUserConfigAgentIdProductIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `agent_id` = ? and `product_id` = ? and del_state = ? limit 1", agentMembershipUserConfigRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, agentId, productId, 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 *defaultAgentMembershipUserConfigModel) Update(ctx context.Context, session sqlx.Session, newData *AgentMembershipUserConfig) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentMembershipUserConfigAgentIdProductIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix, data.AgentId, data.ProductId) + hmAgentMembershipUserConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, 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, agentMembershipUserConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.ProductId, newData.PriceRangeFrom, newData.PriceRangeTo, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.ProductId, newData.PriceRangeFrom, newData.PriceRangeTo, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentMembershipUserConfigAgentIdProductIdKey, hmAgentMembershipUserConfigIdKey) +} + +func (m *defaultAgentMembershipUserConfigModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentMembershipUserConfig) 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 + } + hmAgentMembershipUserConfigAgentIdProductIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix, data.AgentId, data.ProductId) + hmAgentMembershipUserConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, 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, agentMembershipUserConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.ProductId, newData.PriceRangeFrom, newData.PriceRangeTo, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.AgentId, newData.ProductId, newData.PriceRangeFrom, newData.PriceRangeTo, newData.PriceRatio, newData.PriceIncreaseAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentMembershipUserConfigAgentIdProductIdKey, hmAgentMembershipUserConfigIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentMembershipUserConfigModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentMembershipUserConfig) 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 "), "AgentMembershipUserConfigModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentMembershipUserConfigModel) 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 *defaultAgentMembershipUserConfigModel) 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 *defaultAgentMembershipUserConfigModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentMembershipUserConfig, error) { + + builder = builder.Columns(agentMembershipUserConfigRows) + + 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 []*AgentMembershipUserConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipUserConfigModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipUserConfig, error) { + + builder = builder.Columns(agentMembershipUserConfigRows) + + 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 []*AgentMembershipUserConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipUserConfigModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentMembershipUserConfig, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentMembershipUserConfigRows) + + 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 []*AgentMembershipUserConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentMembershipUserConfigModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentMembershipUserConfig, error) { + + builder = builder.Columns(agentMembershipUserConfigRows) + + 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 []*AgentMembershipUserConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipUserConfigModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentMembershipUserConfig, error) { + + builder = builder.Columns(agentMembershipUserConfigRows) + + 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 []*AgentMembershipUserConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentMembershipUserConfigModel) 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 *defaultAgentMembershipUserConfigModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentMembershipUserConfigModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentMembershipUserConfigAgentIdProductIdKey := fmt.Sprintf("%s%v:%v", cacheHmAgentMembershipUserConfigAgentIdProductIdPrefix, data.AgentId, data.ProductId) + hmAgentMembershipUserConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, 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) + }, hmAgentMembershipUserConfigAgentIdProductIdKey, hmAgentMembershipUserConfigIdKey) + return err +} +func (m *defaultAgentMembershipUserConfigModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentMembershipUserConfigIdPrefix, primary) +} +func (m *defaultAgentMembershipUserConfigModel) 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", agentMembershipUserConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentMembershipUserConfigModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentModel.go b/app/main/model/agentModel.go new file mode 100644 index 0000000..2aef816 --- /dev/null +++ b/app/main/model/agentModel.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 _ AgentModel = (*customAgentModel)(nil) + +type ( + // AgentModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentModel. + AgentModel interface { + agentModel + } + + customAgentModel struct { + *defaultAgentModel + } +) + +// NewAgentModel returns a model for the database table. +func NewAgentModel(conn sqlx.SqlConn, c cache.CacheConf) AgentModel { + return &customAgentModel{ + defaultAgentModel: newAgentModel(conn, c), + } +} diff --git a/app/main/model/agentModel_gen.go b/app/main/model/agentModel_gen.go new file mode 100644 index 0000000..c463ef7 --- /dev/null +++ b/app/main/model/agentModel_gen.go @@ -0,0 +1,438 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentFieldNames = builder.RawFieldNames(&Agent{}) + agentRows = strings.Join(agentFieldNames, ",") + agentRowsExpectAutoSet = strings.Join(stringx.Remove(agentFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentRowsWithPlaceHolder = strings.Join(stringx.Remove(agentFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentIdPrefix = "cache:bdrp:agent:id:" + cacheBdrpAgentMobilePrefix = "cache:bdrp:agent:mobile:" + cacheBdrpAgentUserIdPrefix = "cache:bdrp:agent:userId:" +) + +type ( + agentModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Agent) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Agent, error) + FindOneByMobile(ctx context.Context, mobile string) (*Agent, error) + FindOneByUserId(ctx context.Context, userId int64) (*Agent, error) + Update(ctx context.Context, session sqlx.Session, data *Agent) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Agent) 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 *Agent) 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) ([]*Agent, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Agent, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Agent, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Agent, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Agent, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentModel struct { + sqlc.CachedConn + table string + } + + Agent struct { + Id int64 `db:"id"` + UserId int64 `db:"user_id"` + LevelName string `db:"level_name"` // 代理等级 + Region string `db:"region"` + Mobile string `db:"mobile"` + WechatId sql.NullString `db:"wechat_id"` + MembershipExpiryTime sql.NullTime `db:"membership_expiry_time"` // 会员过期时间 + 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"` // 版本号 + } +) + +func newAgentModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentModel { + return &defaultAgentModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent`", + } +} + +func (m *defaultAgentModel) Insert(ctx context.Context, session sqlx.Session, data *Agent) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, data.Id) + bdrpAgentMobileKey := fmt.Sprintf("%s%v", cacheBdrpAgentMobilePrefix, data.Mobile) + bdrpAgentUserIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentUserIdPrefix, data.UserId) + 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, agentRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.LevelName, data.Region, data.Mobile, data.WechatId, data.MembershipExpiryTime, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.UserId, data.LevelName, data.Region, data.Mobile, data.WechatId, data.MembershipExpiryTime, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentIdKey, bdrpAgentMobileKey, bdrpAgentUserIdKey) +} + +func (m *defaultAgentModel) FindOne(ctx context.Context, id int64) (*Agent, error) { + bdrpAgentIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, id) + var resp Agent + err := m.QueryRowCtx(ctx, &resp, bdrpAgentIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentRows, 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 *defaultAgentModel) FindOneByMobile(ctx context.Context, mobile string) (*Agent, error) { + bdrpAgentMobileKey := fmt.Sprintf("%s%v", cacheBdrpAgentMobilePrefix, mobile) + var resp Agent + err := m.QueryRowIndexCtx(ctx, &resp, bdrpAgentMobileKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `mobile` = ? and del_state = ? limit 1", agentRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, mobile, 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 *defaultAgentModel) FindOneByUserId(ctx context.Context, userId int64) (*Agent, error) { + bdrpAgentUserIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentUserIdPrefix, userId) + var resp Agent + err := m.QueryRowIndexCtx(ctx, &resp, bdrpAgentUserIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `user_id` = ? and del_state = ? limit 1", agentRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, userId, 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 *defaultAgentModel) Update(ctx context.Context, session sqlx.Session, newData *Agent) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpAgentIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, data.Id) + bdrpAgentMobileKey := fmt.Sprintf("%s%v", cacheBdrpAgentMobilePrefix, data.Mobile) + bdrpAgentUserIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentUserIdPrefix, data.UserId) + 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, agentRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.LevelName, newData.Region, newData.Mobile, newData.WechatId, newData.MembershipExpiryTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.LevelName, newData.Region, newData.Mobile, newData.WechatId, newData.MembershipExpiryTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, bdrpAgentIdKey, bdrpAgentMobileKey, bdrpAgentUserIdKey) +} + +func (m *defaultAgentModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Agent) 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 + } + bdrpAgentIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, data.Id) + bdrpAgentMobileKey := fmt.Sprintf("%s%v", cacheBdrpAgentMobilePrefix, data.Mobile) + bdrpAgentUserIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentUserIdPrefix, data.UserId) + 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, agentRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.UserId, newData.LevelName, newData.Region, newData.Mobile, newData.WechatId, newData.MembershipExpiryTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.UserId, newData.LevelName, newData.Region, newData.Mobile, newData.WechatId, newData.MembershipExpiryTime, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, bdrpAgentIdKey, bdrpAgentMobileKey, bdrpAgentUserIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Agent) 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 "), "AgentModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentModel) 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 *defaultAgentModel) 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 *defaultAgentModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Agent, error) { + + builder = builder.Columns(agentRows) + + 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 []*Agent + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Agent, error) { + + builder = builder.Columns(agentRows) + + 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 []*Agent + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Agent, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentRows) + + 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 []*Agent + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Agent, error) { + + builder = builder.Columns(agentRows) + + 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 []*Agent + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Agent, error) { + + builder = builder.Columns(agentRows) + + 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 []*Agent + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentModel) 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 *defaultAgentModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpAgentIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, id) + bdrpAgentMobileKey := fmt.Sprintf("%s%v", cacheBdrpAgentMobilePrefix, data.Mobile) + bdrpAgentUserIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentUserIdPrefix, data.UserId) + _, 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) + }, bdrpAgentIdKey, bdrpAgentMobileKey, bdrpAgentUserIdKey) + return err +} +func (m *defaultAgentModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentIdPrefix, primary) +} +func (m *defaultAgentModel) 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", agentRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentOrderModel.go b/app/main/model/agentOrderModel.go new file mode 100644 index 0000000..00ded5e --- /dev/null +++ b/app/main/model/agentOrderModel.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 _ AgentOrderModel = (*customAgentOrderModel)(nil) + +type ( + // AgentOrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentOrderModel. + AgentOrderModel interface { + agentOrderModel + } + + customAgentOrderModel struct { + *defaultAgentOrderModel + } +) + +// NewAgentOrderModel returns a model for the database table. +func NewAgentOrderModel(conn sqlx.SqlConn, c cache.CacheConf) AgentOrderModel { + return &customAgentOrderModel{ + defaultAgentOrderModel: newAgentOrderModel(conn, c), + } +} diff --git a/app/main/model/agentOrderModel_gen.go b/app/main/model/agentOrderModel_gen.go new file mode 100644 index 0000000..14c5b76 --- /dev/null +++ b/app/main/model/agentOrderModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentOrderFieldNames = builder.RawFieldNames(&AgentOrder{}) + agentOrderRows = strings.Join(agentOrderFieldNames, ",") + agentOrderRowsExpectAutoSet = strings.Join(stringx.Remove(agentOrderFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentOrderRowsWithPlaceHolder = strings.Join(stringx.Remove(agentOrderFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentOrderIdPrefix = "cache:bdrp:agentOrder:id:" + cacheHmAgentOrderOrderIdPrefix = "cache:bdrp:agentOrder:orderId:" +) + +type ( + agentOrderModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentOrder) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentOrder, error) + FindOneByOrderId(ctx context.Context, orderId int64) (*AgentOrder, error) + Update(ctx context.Context, session sqlx.Session, data *AgentOrder) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentOrder) 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 *AgentOrder) 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) ([]*AgentOrder, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentOrder, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentOrder, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentOrder, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentOrder, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentOrderModel struct { + sqlc.CachedConn + table string + } + + AgentOrder struct { + Id int64 `db:"id"` + OrderId int64 `db:"order_id"` + AgentId int64 `db:"agent_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"` + } +) + +func newAgentOrderModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentOrderModel { + return &defaultAgentOrderModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_order`", + } +} + +func (m *defaultAgentOrderModel) Insert(ctx context.Context, session sqlx.Session, data *AgentOrder) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, data.Id) + hmAgentOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderOrderIdPrefix, data.OrderId) + 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, agentOrderRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.DeleteTime, data.DelState, data.Version) + }, hmAgentOrderIdKey, hmAgentOrderOrderIdKey) +} + +func (m *defaultAgentOrderModel) FindOne(ctx context.Context, id int64) (*AgentOrder, error) { + hmAgentOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, id) + var resp AgentOrder + err := m.QueryRowCtx(ctx, &resp, hmAgentOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentOrderRows, 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 *defaultAgentOrderModel) FindOneByOrderId(ctx context.Context, orderId int64) (*AgentOrder, error) { + hmAgentOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderOrderIdPrefix, orderId) + var resp AgentOrder + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentOrderOrderIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `order_id` = ? and del_state = ? limit 1", agentOrderRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, orderId, 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 *defaultAgentOrderModel) Update(ctx context.Context, session sqlx.Session, newData *AgentOrder) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, data.Id) + hmAgentOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderOrderIdPrefix, data.OrderId) + 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, agentOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderId, newData.AgentId, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.OrderId, newData.AgentId, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentOrderIdKey, hmAgentOrderOrderIdKey) +} + +func (m *defaultAgentOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentOrder) 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 + } + hmAgentOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, data.Id) + hmAgentOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderOrderIdPrefix, data.OrderId) + 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, agentOrderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderId, newData.AgentId, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.OrderId, newData.AgentId, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentOrderIdKey, hmAgentOrderOrderIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentOrderModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentOrder) 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 "), "AgentOrderModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentOrderModel) 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 *defaultAgentOrderModel) 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 *defaultAgentOrderModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentOrder, error) { + + builder = builder.Columns(agentOrderRows) + + 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 []*AgentOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentOrderModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentOrder, error) { + + builder = builder.Columns(agentOrderRows) + + 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 []*AgentOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentOrderModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentOrder, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentOrderRows) + + 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 []*AgentOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentOrderModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentOrder, error) { + + builder = builder.Columns(agentOrderRows) + + 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 []*AgentOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentOrderModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentOrder, error) { + + builder = builder.Columns(agentOrderRows) + + 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 []*AgentOrder + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentOrderModel) 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 *defaultAgentOrderModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentOrderModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, id) + hmAgentOrderOrderIdKey := fmt.Sprintf("%s%v", cacheHmAgentOrderOrderIdPrefix, data.OrderId) + _, 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) + }, hmAgentOrderIdKey, hmAgentOrderOrderIdKey) + return err +} +func (m *defaultAgentOrderModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentOrderIdPrefix, primary) +} +func (m *defaultAgentOrderModel) 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", agentOrderRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentOrderModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentPlatformDeductionModel.go b/app/main/model/agentPlatformDeductionModel.go new file mode 100644 index 0000000..4d64e31 --- /dev/null +++ b/app/main/model/agentPlatformDeductionModel.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 _ AgentPlatformDeductionModel = (*customAgentPlatformDeductionModel)(nil) + +type ( + // AgentPlatformDeductionModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentPlatformDeductionModel. + AgentPlatformDeductionModel interface { + agentPlatformDeductionModel + } + + customAgentPlatformDeductionModel struct { + *defaultAgentPlatformDeductionModel + } +) + +// NewAgentPlatformDeductionModel returns a model for the database table. +func NewAgentPlatformDeductionModel(conn sqlx.SqlConn, c cache.CacheConf) AgentPlatformDeductionModel { + return &customAgentPlatformDeductionModel{ + defaultAgentPlatformDeductionModel: newAgentPlatformDeductionModel(conn, c), + } +} diff --git a/app/main/model/agentPlatformDeductionModel_gen.go b/app/main/model/agentPlatformDeductionModel_gen.go new file mode 100644 index 0000000..686f106 --- /dev/null +++ b/app/main/model/agentPlatformDeductionModel_gen.go @@ -0,0 +1,372 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentPlatformDeductionFieldNames = builder.RawFieldNames(&AgentPlatformDeduction{}) + agentPlatformDeductionRows = strings.Join(agentPlatformDeductionFieldNames, ",") + agentPlatformDeductionRowsExpectAutoSet = strings.Join(stringx.Remove(agentPlatformDeductionFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentPlatformDeductionRowsWithPlaceHolder = strings.Join(stringx.Remove(agentPlatformDeductionFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentPlatformDeductionIdPrefix = "cache:bdrp:agentPlatformDeduction:id:" +) + +type ( + agentPlatformDeductionModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentPlatformDeduction, error) + Update(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) 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 *AgentPlatformDeduction) 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) ([]*AgentPlatformDeduction, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentPlatformDeduction, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentPlatformDeduction, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentPlatformDeduction, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentPlatformDeduction, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentPlatformDeductionModel struct { + sqlc.CachedConn + table string + } + + AgentPlatformDeduction struct { + Id int64 `db:"id"` + OrderId int64 `db:"order_id"` // 订单ID + AgentId int64 `db:"agent_id"` // 被抽佣代理ID + Amount float64 `db:"amount"` + Type string `db:"type"` + Status int64 `db:"status"` // 状态 + 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"` // 版本号 + } +) + +func newAgentPlatformDeductionModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentPlatformDeductionModel { + return &defaultAgentPlatformDeductionModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_platform_deduction`", + } +} + +func (m *defaultAgentPlatformDeductionModel) Insert(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentPlatformDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, 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, agentPlatformDeductionRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentPlatformDeductionIdKey) +} + +func (m *defaultAgentPlatformDeductionModel) FindOne(ctx context.Context, id int64) (*AgentPlatformDeduction, error) { + bdrpAgentPlatformDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, id) + var resp AgentPlatformDeduction + err := m.QueryRowCtx(ctx, &resp, bdrpAgentPlatformDeductionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentPlatformDeductionRows, 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 *defaultAgentPlatformDeductionModel) Update(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) (sql.Result, error) { + bdrpAgentPlatformDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, 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, agentPlatformDeductionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id) + }, bdrpAgentPlatformDeductionIdKey) +} + +func (m *defaultAgentPlatformDeductionModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + bdrpAgentPlatformDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, 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, agentPlatformDeductionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.OrderId, data.AgentId, data.Amount, data.Type, data.Status, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, bdrpAgentPlatformDeductionIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentPlatformDeductionModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentPlatformDeduction) 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 "), "AgentPlatformDeductionModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentPlatformDeductionModel) 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 *defaultAgentPlatformDeductionModel) 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 *defaultAgentPlatformDeductionModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentPlatformDeduction, error) { + + builder = builder.Columns(agentPlatformDeductionRows) + + 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 []*AgentPlatformDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentPlatformDeductionModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentPlatformDeduction, error) { + + builder = builder.Columns(agentPlatformDeductionRows) + + 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 []*AgentPlatformDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentPlatformDeductionModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentPlatformDeduction, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentPlatformDeductionRows) + + 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 []*AgentPlatformDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentPlatformDeductionModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentPlatformDeduction, error) { + + builder = builder.Columns(agentPlatformDeductionRows) + + 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 []*AgentPlatformDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentPlatformDeductionModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentPlatformDeduction, error) { + + builder = builder.Columns(agentPlatformDeductionRows) + + 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 []*AgentPlatformDeduction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentPlatformDeductionModel) 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 *defaultAgentPlatformDeductionModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentPlatformDeductionModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + bdrpAgentPlatformDeductionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, 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) + }, bdrpAgentPlatformDeductionIdKey) + return err +} +func (m *defaultAgentPlatformDeductionModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentPlatformDeductionIdPrefix, primary) +} +func (m *defaultAgentPlatformDeductionModel) 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", agentPlatformDeductionRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentPlatformDeductionModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentProductConfigModel.go b/app/main/model/agentProductConfigModel.go new file mode 100644 index 0000000..de56af8 --- /dev/null +++ b/app/main/model/agentProductConfigModel.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 _ AgentProductConfigModel = (*customAgentProductConfigModel)(nil) + +type ( + // AgentProductConfigModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentProductConfigModel. + AgentProductConfigModel interface { + agentProductConfigModel + } + + customAgentProductConfigModel struct { + *defaultAgentProductConfigModel + } +) + +// NewAgentProductConfigModel returns a model for the database table. +func NewAgentProductConfigModel(conn sqlx.SqlConn, c cache.CacheConf) AgentProductConfigModel { + return &customAgentProductConfigModel{ + defaultAgentProductConfigModel: newAgentProductConfigModel(conn, c), + } +} diff --git a/app/main/model/agentProductConfigModel_gen.go b/app/main/model/agentProductConfigModel_gen.go new file mode 100644 index 0000000..3892ade --- /dev/null +++ b/app/main/model/agentProductConfigModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentProductConfigFieldNames = builder.RawFieldNames(&AgentProductConfig{}) + agentProductConfigRows = strings.Join(agentProductConfigFieldNames, ",") + agentProductConfigRowsExpectAutoSet = strings.Join(stringx.Remove(agentProductConfigFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentProductConfigRowsWithPlaceHolder = strings.Join(stringx.Remove(agentProductConfigFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentProductConfigIdPrefix = "cache:bdrp:agentProductConfig:id:" + cacheHmAgentProductConfigProductIdPrefix = "cache:bdrp:agentProductConfig:productId:" +) + +type ( + agentProductConfigModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentProductConfig) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentProductConfig, error) + FindOneByProductId(ctx context.Context, productId int64) (*AgentProductConfig, error) + Update(ctx context.Context, session sqlx.Session, data *AgentProductConfig) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentProductConfig) 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 *AgentProductConfig) 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) ([]*AgentProductConfig, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentProductConfig, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentProductConfig, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentProductConfig, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentProductConfig, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentProductConfigModel struct { + sqlc.CachedConn + table string + } + + AgentProductConfig struct { + Id int64 `db:"id"` + ProductId int64 `db:"product_id"` // 产品ID + CostPrice float64 `db:"cost_price"` // 成本价 + PriceRangeMin float64 `db:"price_range_min"` // 定价区间最低 + PriceRangeMax float64 `db:"price_range_max"` // 定价区间最高 + PricingStandard float64 `db:"pricing_standard"` // 定价标准 + OverpricingRatio float64 `db:"overpricing_ratio"` // 超定价标准收费比例 + 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"` // 版本号 + } +) + +func newAgentProductConfigModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentProductConfigModel { + return &defaultAgentProductConfigModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_product_config`", + } +} + +func (m *defaultAgentProductConfigModel) Insert(ctx context.Context, session sqlx.Session, data *AgentProductConfig) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentProductConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, data.Id) + hmAgentProductConfigProductIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigProductIdPrefix, data.ProductId) + 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, agentProductConfigRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.ProductId, data.CostPrice, data.PriceRangeMin, data.PriceRangeMax, data.PricingStandard, data.OverpricingRatio, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.ProductId, data.CostPrice, data.PriceRangeMin, data.PriceRangeMax, data.PricingStandard, data.OverpricingRatio, data.DeleteTime, data.DelState, data.Version) + }, hmAgentProductConfigIdKey, hmAgentProductConfigProductIdKey) +} + +func (m *defaultAgentProductConfigModel) FindOne(ctx context.Context, id int64) (*AgentProductConfig, error) { + hmAgentProductConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, id) + var resp AgentProductConfig + err := m.QueryRowCtx(ctx, &resp, hmAgentProductConfigIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentProductConfigRows, 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 *defaultAgentProductConfigModel) FindOneByProductId(ctx context.Context, productId int64) (*AgentProductConfig, error) { + hmAgentProductConfigProductIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigProductIdPrefix, productId) + var resp AgentProductConfig + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentProductConfigProductIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `product_id` = ? and del_state = ? limit 1", agentProductConfigRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, productId, 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 *defaultAgentProductConfigModel) Update(ctx context.Context, session sqlx.Session, newData *AgentProductConfig) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentProductConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, data.Id) + hmAgentProductConfigProductIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigProductIdPrefix, data.ProductId) + 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, agentProductConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.CostPrice, newData.PriceRangeMin, newData.PriceRangeMax, newData.PricingStandard, newData.OverpricingRatio, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.CostPrice, newData.PriceRangeMin, newData.PriceRangeMax, newData.PricingStandard, newData.OverpricingRatio, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentProductConfigIdKey, hmAgentProductConfigProductIdKey) +} + +func (m *defaultAgentProductConfigModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentProductConfig) 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 + } + hmAgentProductConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, data.Id) + hmAgentProductConfigProductIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigProductIdPrefix, data.ProductId) + 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, agentProductConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.CostPrice, newData.PriceRangeMin, newData.PriceRangeMax, newData.PricingStandard, newData.OverpricingRatio, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.CostPrice, newData.PriceRangeMin, newData.PriceRangeMax, newData.PricingStandard, newData.OverpricingRatio, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentProductConfigIdKey, hmAgentProductConfigProductIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentProductConfigModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentProductConfig) 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 "), "AgentProductConfigModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentProductConfigModel) 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 *defaultAgentProductConfigModel) 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 *defaultAgentProductConfigModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentProductConfig, error) { + + builder = builder.Columns(agentProductConfigRows) + + 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 []*AgentProductConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentProductConfigModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentProductConfig, error) { + + builder = builder.Columns(agentProductConfigRows) + + 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 []*AgentProductConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentProductConfigModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentProductConfig, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentProductConfigRows) + + 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 []*AgentProductConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentProductConfigModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentProductConfig, error) { + + builder = builder.Columns(agentProductConfigRows) + + 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 []*AgentProductConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentProductConfigModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentProductConfig, error) { + + builder = builder.Columns(agentProductConfigRows) + + 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 []*AgentProductConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentProductConfigModel) 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 *defaultAgentProductConfigModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentProductConfigModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentProductConfigIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, id) + hmAgentProductConfigProductIdKey := fmt.Sprintf("%s%v", cacheHmAgentProductConfigProductIdPrefix, data.ProductId) + _, 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) + }, hmAgentProductConfigIdKey, hmAgentProductConfigProductIdKey) + return err +} +func (m *defaultAgentProductConfigModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentProductConfigIdPrefix, primary) +} +func (m *defaultAgentProductConfigModel) 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", agentProductConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentProductConfigModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentRealNameModel.go b/app/main/model/agentRealNameModel.go new file mode 100644 index 0000000..50bca1f --- /dev/null +++ b/app/main/model/agentRealNameModel.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 _ AgentRealNameModel = (*customAgentRealNameModel)(nil) + +type ( + // AgentRealNameModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentRealNameModel. + AgentRealNameModel interface { + agentRealNameModel + } + + customAgentRealNameModel struct { + *defaultAgentRealNameModel + } +) + +// NewAgentRealNameModel returns a model for the database table. +func NewAgentRealNameModel(conn sqlx.SqlConn, c cache.CacheConf) AgentRealNameModel { + return &customAgentRealNameModel{ + defaultAgentRealNameModel: newAgentRealNameModel(conn, c), + } +} diff --git a/app/main/model/agentRealNameModel_gen.go b/app/main/model/agentRealNameModel_gen.go new file mode 100644 index 0000000..945b5ce --- /dev/null +++ b/app/main/model/agentRealNameModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentRealNameFieldNames = builder.RawFieldNames(&AgentRealName{}) + agentRealNameRows = strings.Join(agentRealNameFieldNames, ",") + agentRealNameRowsExpectAutoSet = strings.Join(stringx.Remove(agentRealNameFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentRealNameRowsWithPlaceHolder = strings.Join(stringx.Remove(agentRealNameFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentRealNameIdPrefix = "cache:bdrp:agentRealName:id:" + cacheHmAgentRealNameAgentIdPrefix = "cache:bdrp:agentRealName:agentId:" +) + +type ( + agentRealNameModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentRealName) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentRealName, error) + FindOneByAgentId(ctx context.Context, agentId int64) (*AgentRealName, error) + Update(ctx context.Context, session sqlx.Session, data *AgentRealName) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentRealName) 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 *AgentRealName) 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) ([]*AgentRealName, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRealName, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRealName, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentRealName, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentRealName, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentRealNameModel struct { + sqlc.CachedConn + table string + } + + AgentRealName struct { + Id int64 `db:"id"` // 主键ID + AgentId int64 `db:"agent_id"` // 代理ID + Name string `db:"name"` // 实名姓名 + IdCard string `db:"id_card"` // 身份证号 + Status string `db:"status"` // 认证状态(认证中、通过、拒绝) + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + ApproveTime sql.NullTime `db:"approve_time"` // 认证通过时间 + RejectTime sql.NullTime `db:"reject_time"` // 认证拒绝时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + } +) + +func newAgentRealNameModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentRealNameModel { + return &defaultAgentRealNameModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_real_name`", + } +} + +func (m *defaultAgentRealNameModel) Insert(ctx context.Context, session sqlx.Session, data *AgentRealName) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentRealNameAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameAgentIdPrefix, data.AgentId) + hmAgentRealNameIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, 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, agentRealNameRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.Name, data.IdCard, data.Status, data.DelState, data.Version, data.ApproveTime, data.RejectTime, data.DeleteTime) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.Name, data.IdCard, data.Status, data.DelState, data.Version, data.ApproveTime, data.RejectTime, data.DeleteTime) + }, hmAgentRealNameAgentIdKey, hmAgentRealNameIdKey) +} + +func (m *defaultAgentRealNameModel) FindOne(ctx context.Context, id int64) (*AgentRealName, error) { + hmAgentRealNameIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, id) + var resp AgentRealName + err := m.QueryRowCtx(ctx, &resp, hmAgentRealNameIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentRealNameRows, 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 *defaultAgentRealNameModel) FindOneByAgentId(ctx context.Context, agentId int64) (*AgentRealName, error) { + hmAgentRealNameAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameAgentIdPrefix, agentId) + var resp AgentRealName + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentRealNameAgentIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `agent_id` = ? and del_state = ? limit 1", agentRealNameRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, agentId, 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 *defaultAgentRealNameModel) Update(ctx context.Context, session sqlx.Session, newData *AgentRealName) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentRealNameAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameAgentIdPrefix, data.AgentId) + hmAgentRealNameIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, 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, agentRealNameRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.Name, newData.IdCard, newData.Status, newData.DelState, newData.Version, newData.ApproveTime, newData.RejectTime, newData.DeleteTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.Name, newData.IdCard, newData.Status, newData.DelState, newData.Version, newData.ApproveTime, newData.RejectTime, newData.DeleteTime, newData.Id) + }, hmAgentRealNameAgentIdKey, hmAgentRealNameIdKey) +} + +func (m *defaultAgentRealNameModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentRealName) 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 + } + hmAgentRealNameAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameAgentIdPrefix, data.AgentId) + hmAgentRealNameIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, 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, agentRealNameRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.Name, newData.IdCard, newData.Status, newData.DelState, newData.Version, newData.ApproveTime, newData.RejectTime, newData.DeleteTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.Name, newData.IdCard, newData.Status, newData.DelState, newData.Version, newData.ApproveTime, newData.RejectTime, newData.DeleteTime, newData.Id, oldVersion) + }, hmAgentRealNameAgentIdKey, hmAgentRealNameIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentRealNameModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentRealName) 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 "), "AgentRealNameModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentRealNameModel) 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 *defaultAgentRealNameModel) 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 *defaultAgentRealNameModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentRealName, error) { + + builder = builder.Columns(agentRealNameRows) + + 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 []*AgentRealName + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRealNameModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRealName, error) { + + builder = builder.Columns(agentRealNameRows) + + 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 []*AgentRealName + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRealNameModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRealName, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentRealNameRows) + + 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 []*AgentRealName + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentRealNameModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentRealName, error) { + + builder = builder.Columns(agentRealNameRows) + + 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 []*AgentRealName + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRealNameModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentRealName, error) { + + builder = builder.Columns(agentRealNameRows) + + 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 []*AgentRealName + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRealNameModel) 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 *defaultAgentRealNameModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentRealNameModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentRealNameAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameAgentIdPrefix, data.AgentId) + hmAgentRealNameIdKey := fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, 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) + }, hmAgentRealNameAgentIdKey, hmAgentRealNameIdKey) + return err +} +func (m *defaultAgentRealNameModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentRealNameIdPrefix, primary) +} +func (m *defaultAgentRealNameModel) 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", agentRealNameRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentRealNameModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentRewardsModel.go b/app/main/model/agentRewardsModel.go new file mode 100644 index 0000000..a1e937b --- /dev/null +++ b/app/main/model/agentRewardsModel.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 _ AgentRewardsModel = (*customAgentRewardsModel)(nil) + +type ( + // AgentRewardsModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentRewardsModel. + AgentRewardsModel interface { + agentRewardsModel + } + + customAgentRewardsModel struct { + *defaultAgentRewardsModel + } +) + +// NewAgentRewardsModel returns a model for the database table. +func NewAgentRewardsModel(conn sqlx.SqlConn, c cache.CacheConf) AgentRewardsModel { + return &customAgentRewardsModel{ + defaultAgentRewardsModel: newAgentRewardsModel(conn, c), + } +} diff --git a/app/main/model/agentRewardsModel_gen.go b/app/main/model/agentRewardsModel_gen.go new file mode 100644 index 0000000..67c8aec --- /dev/null +++ b/app/main/model/agentRewardsModel_gen.go @@ -0,0 +1,371 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentRewardsFieldNames = builder.RawFieldNames(&AgentRewards{}) + agentRewardsRows = strings.Join(agentRewardsFieldNames, ",") + agentRewardsRowsExpectAutoSet = strings.Join(stringx.Remove(agentRewardsFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentRewardsRowsWithPlaceHolder = strings.Join(stringx.Remove(agentRewardsFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentRewardsIdPrefix = "cache:bdrp:agentRewards:id:" +) + +type ( + agentRewardsModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentRewards, error) + Update(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentRewards) 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 *AgentRewards) 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) ([]*AgentRewards, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRewards, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRewards, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentRewards, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentRewards, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentRewardsModel struct { + sqlc.CachedConn + table string + } + + AgentRewards struct { + Id int64 `db:"id"` + AgentId int64 `db:"agent_id"` + RelationAgentId sql.NullInt64 `db:"relation_agent_id"` // 关联代理ID + Amount float64 `db:"amount"` + Type string `db:"type"` + 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"` // 版本号 + } +) + +func newAgentRewardsModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentRewardsModel { + return &defaultAgentRewardsModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_rewards`", + } +} + +func (m *defaultAgentRewardsModel) Insert(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, 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, agentRewardsRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version) + }, hmAgentRewardsIdKey) +} + +func (m *defaultAgentRewardsModel) FindOne(ctx context.Context, id int64) (*AgentRewards, error) { + hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, id) + var resp AgentRewards + err := m.QueryRowCtx(ctx, &resp, hmAgentRewardsIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentRewardsRows, 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 *defaultAgentRewardsModel) Update(ctx context.Context, session sqlx.Session, data *AgentRewards) (sql.Result, error) { + hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, 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, agentRewardsRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id) + }, hmAgentRewardsIdKey) +} + +func (m *defaultAgentRewardsModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentRewards) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, 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, agentRewardsRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.RelationAgentId, data.Amount, data.Type, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, hmAgentRewardsIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentRewardsModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentRewards) 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 "), "AgentRewardsModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentRewardsModel) 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 *defaultAgentRewardsModel) 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 *defaultAgentRewardsModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentRewards, error) { + + builder = builder.Columns(agentRewardsRows) + + 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 []*AgentRewards + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRewardsModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRewards, error) { + + builder = builder.Columns(agentRewardsRows) + + 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 []*AgentRewards + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRewardsModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentRewards, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentRewardsRows) + + 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 []*AgentRewards + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentRewardsModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentRewards, error) { + + builder = builder.Columns(agentRewardsRows) + + 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 []*AgentRewards + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRewardsModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentRewards, error) { + + builder = builder.Columns(agentRewardsRows) + + 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 []*AgentRewards + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentRewardsModel) 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 *defaultAgentRewardsModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentRewardsModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + hmAgentRewardsIdKey := fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, 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) + }, hmAgentRewardsIdKey) + return err +} +func (m *defaultAgentRewardsModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentRewardsIdPrefix, primary) +} +func (m *defaultAgentRewardsModel) 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", agentRewardsRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentRewardsModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentWalletModel.go b/app/main/model/agentWalletModel.go new file mode 100644 index 0000000..1583c4d --- /dev/null +++ b/app/main/model/agentWalletModel.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 _ AgentWalletModel = (*customAgentWalletModel)(nil) + +type ( + // AgentWalletModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentWalletModel. + AgentWalletModel interface { + agentWalletModel + } + + customAgentWalletModel struct { + *defaultAgentWalletModel + } +) + +// NewAgentWalletModel returns a model for the database table. +func NewAgentWalletModel(conn sqlx.SqlConn, c cache.CacheConf) AgentWalletModel { + return &customAgentWalletModel{ + defaultAgentWalletModel: newAgentWalletModel(conn, c), + } +} diff --git a/app/main/model/agentWalletModel_gen.go b/app/main/model/agentWalletModel_gen.go new file mode 100644 index 0000000..7bd86e7 --- /dev/null +++ b/app/main/model/agentWalletModel_gen.go @@ -0,0 +1,411 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentWalletFieldNames = builder.RawFieldNames(&AgentWallet{}) + agentWalletRows = strings.Join(agentWalletFieldNames, ",") + agentWalletRowsExpectAutoSet = strings.Join(stringx.Remove(agentWalletFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentWalletRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWalletFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentWalletIdPrefix = "cache:bdrp:agentWallet:id:" + cacheHmAgentWalletAgentIdPrefix = "cache:bdrp:agentWallet:agentId:" +) + +type ( + agentWalletModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentWallet) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentWallet, error) + FindOneByAgentId(ctx context.Context, agentId int64) (*AgentWallet, error) + Update(ctx context.Context, session sqlx.Session, data *AgentWallet) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWallet) 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 *AgentWallet) 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) ([]*AgentWallet, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWallet, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWallet, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWallet, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWallet, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentWalletModel struct { + sqlc.CachedConn + table string + } + + AgentWallet struct { + Id int64 `db:"id"` + AgentId int64 `db:"agent_id"` + Balance float64 `db:"balance"` + FrozenBalance float64 `db:"frozen_balance"` + TotalEarnings float64 `db:"total_earnings"` + WithdrawnAmount float64 `db:"withdrawn_amount"` + 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"` // 版本号 + } +) + +func newAgentWalletModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentWalletModel { + return &defaultAgentWalletModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_wallet`", + } +} + +func (m *defaultAgentWalletModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWallet) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentWalletAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletAgentIdPrefix, data.AgentId) + hmAgentWalletIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, 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, agentWalletRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.Balance, data.FrozenBalance, data.TotalEarnings, data.WithdrawnAmount, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.Balance, data.FrozenBalance, data.TotalEarnings, data.WithdrawnAmount, data.DeleteTime, data.DelState, data.Version) + }, hmAgentWalletAgentIdKey, hmAgentWalletIdKey) +} + +func (m *defaultAgentWalletModel) FindOne(ctx context.Context, id int64) (*AgentWallet, error) { + hmAgentWalletIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, id) + var resp AgentWallet + err := m.QueryRowCtx(ctx, &resp, hmAgentWalletIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWalletRows, 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 *defaultAgentWalletModel) FindOneByAgentId(ctx context.Context, agentId int64) (*AgentWallet, error) { + hmAgentWalletAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletAgentIdPrefix, agentId) + var resp AgentWallet + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentWalletAgentIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `agent_id` = ? and del_state = ? limit 1", agentWalletRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, agentId, 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 *defaultAgentWalletModel) Update(ctx context.Context, session sqlx.Session, newData *AgentWallet) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentWalletAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletAgentIdPrefix, data.AgentId) + hmAgentWalletIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, 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, agentWalletRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.Balance, newData.FrozenBalance, newData.TotalEarnings, newData.WithdrawnAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.Balance, newData.FrozenBalance, newData.TotalEarnings, newData.WithdrawnAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmAgentWalletAgentIdKey, hmAgentWalletIdKey) +} + +func (m *defaultAgentWalletModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentWallet) 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 + } + hmAgentWalletAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletAgentIdPrefix, data.AgentId) + hmAgentWalletIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, 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, agentWalletRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AgentId, newData.Balance, newData.FrozenBalance, newData.TotalEarnings, newData.WithdrawnAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.Balance, newData.FrozenBalance, newData.TotalEarnings, newData.WithdrawnAmount, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmAgentWalletAgentIdKey, hmAgentWalletIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentWalletModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentWallet) 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 "), "AgentWalletModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentWalletModel) 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 *defaultAgentWalletModel) 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 *defaultAgentWalletModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentWallet, error) { + + builder = builder.Columns(agentWalletRows) + + 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 []*AgentWallet + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWallet, error) { + + builder = builder.Columns(agentWalletRows) + + 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 []*AgentWallet + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWallet, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentWalletRows) + + 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 []*AgentWallet + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentWalletModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWallet, error) { + + builder = builder.Columns(agentWalletRows) + + 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 []*AgentWallet + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWallet, error) { + + builder = builder.Columns(agentWalletRows) + + 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 []*AgentWallet + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletModel) 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 *defaultAgentWalletModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentWalletModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentWalletAgentIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletAgentIdPrefix, data.AgentId) + hmAgentWalletIdKey := fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, 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) + }, hmAgentWalletAgentIdKey, hmAgentWalletIdKey) + return err +} +func (m *defaultAgentWalletModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentWalletIdPrefix, primary) +} +func (m *defaultAgentWalletModel) 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", agentWalletRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentWalletModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentWalletTransactionModel.go b/app/main/model/agentWalletTransactionModel.go new file mode 100644 index 0000000..55eba0b --- /dev/null +++ b/app/main/model/agentWalletTransactionModel.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 _ AgentWalletTransactionModel = (*customAgentWalletTransactionModel)(nil) + +type ( + // AgentWalletTransactionModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentWalletTransactionModel. + AgentWalletTransactionModel interface { + agentWalletTransactionModel + } + + customAgentWalletTransactionModel struct { + *defaultAgentWalletTransactionModel + } +) + +// NewAgentWalletTransactionModel returns a model for the database table. +func NewAgentWalletTransactionModel(conn sqlx.SqlConn, c cache.CacheConf) AgentWalletTransactionModel { + return &customAgentWalletTransactionModel{ + defaultAgentWalletTransactionModel: newAgentWalletTransactionModel(conn, c), + } +} diff --git a/app/main/model/agentWalletTransactionModel_gen.go b/app/main/model/agentWalletTransactionModel_gen.go new file mode 100644 index 0000000..f134c70 --- /dev/null +++ b/app/main/model/agentWalletTransactionModel_gen.go @@ -0,0 +1,377 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentWalletTransactionFieldNames = builder.RawFieldNames(&AgentWalletTransaction{}) + agentWalletTransactionRows = strings.Join(agentWalletTransactionFieldNames, ",") + agentWalletTransactionRowsExpectAutoSet = strings.Join(stringx.Remove(agentWalletTransactionFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentWalletTransactionRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWalletTransactionFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentWalletTransactionIdPrefix = "cache:bdrp:agentWalletTransaction:id:" +) + +type ( + agentWalletTransactionModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentWalletTransaction, error) + Update(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) 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 *AgentWalletTransaction) 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) ([]*AgentWalletTransaction, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWalletTransaction, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWalletTransaction, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWalletTransaction, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWalletTransaction, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentWalletTransactionModel struct { + sqlc.CachedConn + table string + } + + AgentWalletTransaction struct { + Id int64 `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"` // 版本号 + AgentId int64 `db:"agent_id"` // 代理ID + TransactionType string `db:"transaction_type"` // 交易类型:commission(佣金收入)、withdraw(提现)、freeze(冻结)、unfreeze(解冻)、reward(奖励)、refund(退款)、adjust(调整)等 + Amount float64 `db:"amount"` // 变动金额(正数为增加,负数为减少) + BalanceBefore float64 `db:"balance_before"` // 变动前余额 + BalanceAfter float64 `db:"balance_after"` // 变动后余额 + FrozenBalanceBefore float64 `db:"frozen_balance_before"` // 变动前冻结余额 + FrozenBalanceAfter float64 `db:"frozen_balance_after"` // 变动后冻结余额 + TransactionId sql.NullString `db:"transaction_id"` // 关联交易ID(订单号、提现申请号等) + RelatedUserId sql.NullInt64 `db:"related_user_id"` // 关联用户ID(如佣金来源用户) + Remark sql.NullString `db:"remark"` // 备注说明 + } +) + +func newAgentWalletTransactionModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentWalletTransactionModel { + return &defaultAgentWalletTransactionModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_wallet_transaction`", + } +} + +func (m *defaultAgentWalletTransactionModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentWalletTransactionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, 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, agentWalletTransactionRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark) + }, bdrpAgentWalletTransactionIdKey) +} + +func (m *defaultAgentWalletTransactionModel) FindOne(ctx context.Context, id int64) (*AgentWalletTransaction, error) { + bdrpAgentWalletTransactionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, id) + var resp AgentWalletTransaction + err := m.QueryRowCtx(ctx, &resp, bdrpAgentWalletTransactionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWalletTransactionRows, 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 *defaultAgentWalletTransactionModel) Update(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) (sql.Result, error) { + bdrpAgentWalletTransactionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, 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, agentWalletTransactionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark, data.Id) + }, bdrpAgentWalletTransactionIdKey) +} + +func (m *defaultAgentWalletTransactionModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + bdrpAgentWalletTransactionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, 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, agentWalletTransactionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.AgentId, data.TransactionType, data.Amount, data.BalanceBefore, data.BalanceAfter, data.FrozenBalanceBefore, data.FrozenBalanceAfter, data.TransactionId, data.RelatedUserId, data.Remark, data.Id, oldVersion) + }, bdrpAgentWalletTransactionIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentWalletTransactionModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentWalletTransaction) 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 "), "AgentWalletTransactionModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentWalletTransactionModel) 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 *defaultAgentWalletTransactionModel) 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 *defaultAgentWalletTransactionModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentWalletTransaction, error) { + + builder = builder.Columns(agentWalletTransactionRows) + + 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 []*AgentWalletTransaction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletTransactionModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWalletTransaction, error) { + + builder = builder.Columns(agentWalletTransactionRows) + + 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 []*AgentWalletTransaction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletTransactionModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWalletTransaction, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentWalletTransactionRows) + + 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 []*AgentWalletTransaction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentWalletTransactionModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWalletTransaction, error) { + + builder = builder.Columns(agentWalletTransactionRows) + + 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 []*AgentWalletTransaction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletTransactionModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWalletTransaction, error) { + + builder = builder.Columns(agentWalletTransactionRows) + + 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 []*AgentWalletTransaction + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWalletTransactionModel) 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 *defaultAgentWalletTransactionModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentWalletTransactionModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + bdrpAgentWalletTransactionIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, 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) + }, bdrpAgentWalletTransactionIdKey) + return err +} +func (m *defaultAgentWalletTransactionModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentWalletTransactionIdPrefix, primary) +} +func (m *defaultAgentWalletTransactionModel) 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", agentWalletTransactionRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentWalletTransactionModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentWithdrawalModel.go b/app/main/model/agentWithdrawalModel.go new file mode 100644 index 0000000..56f5e50 --- /dev/null +++ b/app/main/model/agentWithdrawalModel.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 _ AgentWithdrawalModel = (*customAgentWithdrawalModel)(nil) + +type ( + // AgentWithdrawalModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentWithdrawalModel. + AgentWithdrawalModel interface { + agentWithdrawalModel + } + + customAgentWithdrawalModel struct { + *defaultAgentWithdrawalModel + } +) + +// NewAgentWithdrawalModel returns a model for the database table. +func NewAgentWithdrawalModel(conn sqlx.SqlConn, c cache.CacheConf) AgentWithdrawalModel { + return &customAgentWithdrawalModel{ + defaultAgentWithdrawalModel: newAgentWithdrawalModel(conn, c), + } +} diff --git a/app/main/model/agentWithdrawalModel_gen.go b/app/main/model/agentWithdrawalModel_gen.go new file mode 100644 index 0000000..369386b --- /dev/null +++ b/app/main/model/agentWithdrawalModel_gen.go @@ -0,0 +1,417 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + "bdrp-server/common/globalkey" +) + +var ( + agentWithdrawalFieldNames = builder.RawFieldNames(&AgentWithdrawal{}) + agentWithdrawalRows = strings.Join(agentWithdrawalFieldNames, ",") + agentWithdrawalRowsExpectAutoSet = strings.Join(stringx.Remove(agentWithdrawalFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentWithdrawalRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWithdrawalFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpAgentWithdrawalIdPrefix = "cache:bdrp:agentWithdrawal:id:" + cacheBdrpAgentWithdrawalWithdrawNoPrefix = "cache:bdrp:agentWithdrawal:withdrawNo:" +) + +type ( + agentWithdrawalModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentWithdrawal, error) + FindOneByWithdrawNo(ctx context.Context, withdrawNo string) (*AgentWithdrawal, error) + Update(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) 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 *AgentWithdrawal) 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) ([]*AgentWithdrawal, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawal, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawal, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawal, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawal, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentWithdrawalModel struct { + sqlc.CachedConn + table string + } + + AgentWithdrawal struct { + Id int64 `db:"id"` + AgentId int64 `db:"agent_id"` // 代理ID + WithdrawType int64 `db:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡 + WithdrawNo string `db:"withdraw_no"` // 提现单号 + Amount float64 `db:"amount"` // 提现金额 + ActualAmount float64 `db:"actual_amount"` // 实际到账金额(扣税后) + TaxAmount float64 `db:"tax_amount"` // 扣税金额 + Status int64 `db:"status"` // 状态:1-申请中,2-成功,3-失败 + PayeeAccount string `db:"payeeAccount"` // 收款人账号 + BankCardNo sql.NullString `db:"bank_card_no"` // 银行卡号 + BankName sql.NullString `db:"bank_name"` // 开户支行 + PayeeName sql.NullString `db:"payee_name"` // 收款人姓名 + 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"` // 版本号 + } +) + +func newAgentWithdrawalModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentWithdrawalModel { + return &defaultAgentWithdrawalModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_withdrawal`", + } +} + +func (m *defaultAgentWithdrawalModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, data.Id) + bdrpAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalWithdrawNoPrefix, 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) + if session != nil { + return session.ExecCtx(ctx, query, data.AgentId, data.WithdrawType, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Remark, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AgentId, data.WithdrawType, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Remark, data.DeleteTime, data.DelState, data.Version) + }, bdrpAgentWithdrawalIdKey, bdrpAgentWithdrawalWithdrawNoKey) +} + +func (m *defaultAgentWithdrawalModel) FindOne(ctx context.Context, id int64) (*AgentWithdrawal, error) { + bdrpAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, id) + var resp AgentWithdrawal + err := m.QueryRowCtx(ctx, &resp, bdrpAgentWithdrawalIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWithdrawalRows, 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 *defaultAgentWithdrawalModel) FindOneByWithdrawNo(ctx context.Context, withdrawNo string) (*AgentWithdrawal, error) { + bdrpAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalWithdrawNoPrefix, withdrawNo) + var resp AgentWithdrawal + err := m.QueryRowIndexCtx(ctx, &resp, bdrpAgentWithdrawalWithdrawNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `withdraw_no` = ? and del_state = ? limit 1", agentWithdrawalRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, withdrawNo, 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 *defaultAgentWithdrawalModel) Update(ctx context.Context, session sqlx.Session, newData *AgentWithdrawal) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, data.Id) + bdrpAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo) + 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.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, bdrpAgentWithdrawalIdKey, bdrpAgentWithdrawalWithdrawNoKey) +} + +func (m *defaultAgentWithdrawalModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentWithdrawal) 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 + } + bdrpAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, data.Id) + bdrpAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo) + 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.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, bdrpAgentWithdrawalIdKey, bdrpAgentWithdrawalWithdrawNoKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentWithdrawalModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) 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 "), "AgentWithdrawalModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentWithdrawalModel) 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 *defaultAgentWithdrawalModel) 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 *defaultAgentWithdrawalModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentWithdrawal, error) { + + builder = builder.Columns(agentWithdrawalRows) + + 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 []*AgentWithdrawal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawal, error) { + + builder = builder.Columns(agentWithdrawalRows) + + 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 []*AgentWithdrawal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawal, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentWithdrawalRows) + + 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 []*AgentWithdrawal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentWithdrawalModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawal, error) { + + builder = builder.Columns(agentWithdrawalRows) + + 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 []*AgentWithdrawal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawal, error) { + + builder = builder.Columns(agentWithdrawalRows) + + 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 []*AgentWithdrawal + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalModel) 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 *defaultAgentWithdrawalModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentWithdrawalModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, id) + bdrpAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo) + _, 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) + }, bdrpAgentWithdrawalIdKey, bdrpAgentWithdrawalWithdrawNoKey) + return err +} +func (m *defaultAgentWithdrawalModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpAgentWithdrawalIdPrefix, primary) +} +func (m *defaultAgentWithdrawalModel) 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", agentWithdrawalRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentWithdrawalModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentWithdrawalTaxExemptionModel.go b/app/main/model/agentWithdrawalTaxExemptionModel.go new file mode 100644 index 0000000..8709870 --- /dev/null +++ b/app/main/model/agentWithdrawalTaxExemptionModel.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 _ AgentWithdrawalTaxExemptionModel = (*customAgentWithdrawalTaxExemptionModel)(nil) + +type ( + // AgentWithdrawalTaxExemptionModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentWithdrawalTaxExemptionModel. + AgentWithdrawalTaxExemptionModel interface { + agentWithdrawalTaxExemptionModel + } + + customAgentWithdrawalTaxExemptionModel struct { + *defaultAgentWithdrawalTaxExemptionModel + } +) + +// NewAgentWithdrawalTaxExemptionModel returns a model for the database table. +func NewAgentWithdrawalTaxExemptionModel(conn sqlx.SqlConn, c cache.CacheConf) AgentWithdrawalTaxExemptionModel { + return &customAgentWithdrawalTaxExemptionModel{ + defaultAgentWithdrawalTaxExemptionModel: newAgentWithdrawalTaxExemptionModel(conn, c), + } +} diff --git a/app/main/model/agentWithdrawalTaxExemptionModel_gen.go b/app/main/model/agentWithdrawalTaxExemptionModel_gen.go new file mode 100644 index 0000000..f2c1ef1 --- /dev/null +++ b/app/main/model/agentWithdrawalTaxExemptionModel_gen.go @@ -0,0 +1,411 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentWithdrawalTaxExemptionFieldNames = builder.RawFieldNames(&AgentWithdrawalTaxExemption{}) + agentWithdrawalTaxExemptionRows = strings.Join(agentWithdrawalTaxExemptionFieldNames, ",") + agentWithdrawalTaxExemptionRowsExpectAutoSet = strings.Join(stringx.Remove(agentWithdrawalTaxExemptionFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentWithdrawalTaxExemptionRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWithdrawalTaxExemptionFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentWithdrawalTaxExemptionIdPrefix = "cache:bdrp:agentWithdrawalTaxExemption:id:" + cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix = "cache:bdrp:agentWithdrawalTaxExemption:agentId:yearMonth:" +) + +type ( + agentWithdrawalTaxExemptionModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTaxExemption) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentWithdrawalTaxExemption, error) + FindOneByAgentIdYearMonth(ctx context.Context, agentId int64, yearMonth int64) (*AgentWithdrawalTaxExemption, error) + Update(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTaxExemption) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTaxExemption) 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 *AgentWithdrawalTaxExemption) 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) ([]*AgentWithdrawalTaxExemption, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTaxExemption, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTaxExemption, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawalTaxExemption, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawalTaxExemption, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentWithdrawalTaxExemptionModel struct { + sqlc.CachedConn + table string + } + + AgentWithdrawalTaxExemption struct { + Id int64 `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"` // 版本号 + YearMonth int64 `db:"year_month"` // 年月标识,格式:202401 + TotalExemptionAmount float64 `db:"total_exemption_amount"` // 月度免税总额度 + UsedExemptionAmount float64 `db:"used_exemption_amount"` // 已使用免税额度 + RemainingExemptionAmount float64 `db:"remaining_exemption_amount"` // 剩余免税额度 + AgentId int64 `db:"agent_id"` // 关联到代理用户表的id + } +) + +func newAgentWithdrawalTaxExemptionModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentWithdrawalTaxExemptionModel { + return &defaultAgentWithdrawalTaxExemptionModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_withdrawal_tax_exemption`", + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTaxExemption) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix, data.AgentId, data.YearMonth) + hmAgentWithdrawalTaxExemptionIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, 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, agentWithdrawalTaxExemptionRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.YearMonth, data.TotalExemptionAmount, data.UsedExemptionAmount, data.RemainingExemptionAmount, data.AgentId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.YearMonth, data.TotalExemptionAmount, data.UsedExemptionAmount, data.RemainingExemptionAmount, data.AgentId) + }, hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey, hmAgentWithdrawalTaxExemptionIdKey) +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) FindOne(ctx context.Context, id int64) (*AgentWithdrawalTaxExemption, error) { + hmAgentWithdrawalTaxExemptionIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, id) + var resp AgentWithdrawalTaxExemption + err := m.QueryRowCtx(ctx, &resp, hmAgentWithdrawalTaxExemptionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWithdrawalTaxExemptionRows, 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 *defaultAgentWithdrawalTaxExemptionModel) FindOneByAgentIdYearMonth(ctx context.Context, agentId int64, yearMonth int64) (*AgentWithdrawalTaxExemption, error) { + hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix, agentId, yearMonth) + var resp AgentWithdrawalTaxExemption + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `agent_id` = ? and `year_month` = ? and del_state = ? limit 1", agentWithdrawalTaxExemptionRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, agentId, yearMonth, 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 *defaultAgentWithdrawalTaxExemptionModel) Update(ctx context.Context, session sqlx.Session, newData *AgentWithdrawalTaxExemption) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix, data.AgentId, data.YearMonth) + hmAgentWithdrawalTaxExemptionIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, 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, agentWithdrawalTaxExemptionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.YearMonth, newData.TotalExemptionAmount, newData.UsedExemptionAmount, newData.RemainingExemptionAmount, newData.AgentId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.YearMonth, newData.TotalExemptionAmount, newData.UsedExemptionAmount, newData.RemainingExemptionAmount, newData.AgentId, newData.Id) + }, hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey, hmAgentWithdrawalTaxExemptionIdKey) +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentWithdrawalTaxExemption) 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 + } + hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix, data.AgentId, data.YearMonth) + hmAgentWithdrawalTaxExemptionIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, 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, agentWithdrawalTaxExemptionRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.YearMonth, newData.TotalExemptionAmount, newData.UsedExemptionAmount, newData.RemainingExemptionAmount, newData.AgentId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.YearMonth, newData.TotalExemptionAmount, newData.UsedExemptionAmount, newData.RemainingExemptionAmount, newData.AgentId, newData.Id, oldVersion) + }, hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey, hmAgentWithdrawalTaxExemptionIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTaxExemption) 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 "), "AgentWithdrawalTaxExemptionModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) 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 *defaultAgentWithdrawalTaxExemptionModel) 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 *defaultAgentWithdrawalTaxExemptionModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentWithdrawalTaxExemption, error) { + + builder = builder.Columns(agentWithdrawalTaxExemptionRows) + + 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 []*AgentWithdrawalTaxExemption + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTaxExemption, error) { + + builder = builder.Columns(agentWithdrawalTaxExemptionRows) + + 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 []*AgentWithdrawalTaxExemption + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTaxExemption, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentWithdrawalTaxExemptionRows) + + 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 []*AgentWithdrawalTaxExemption + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawalTaxExemption, error) { + + builder = builder.Columns(agentWithdrawalTaxExemptionRows) + + 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 []*AgentWithdrawalTaxExemption + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawalTaxExemption, error) { + + builder = builder.Columns(agentWithdrawalTaxExemptionRows) + + 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 []*AgentWithdrawalTaxExemption + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) 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 *defaultAgentWithdrawalTaxExemptionModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentWithdrawalTaxExemptionModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey := fmt.Sprintf("%s%v:%v", cacheHmAgentWithdrawalTaxExemptionAgentIdYearMonthPrefix, data.AgentId, data.YearMonth) + hmAgentWithdrawalTaxExemptionIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, 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) + }, hmAgentWithdrawalTaxExemptionAgentIdYearMonthKey, hmAgentWithdrawalTaxExemptionIdKey) + return err +} +func (m *defaultAgentWithdrawalTaxExemptionModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxExemptionIdPrefix, primary) +} +func (m *defaultAgentWithdrawalTaxExemptionModel) 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", agentWithdrawalTaxExemptionRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentWithdrawalTaxExemptionModel) tableName() string { + return m.table +} diff --git a/app/main/model/agentWithdrawalTaxModel.go b/app/main/model/agentWithdrawalTaxModel.go new file mode 100644 index 0000000..1b9ea2d --- /dev/null +++ b/app/main/model/agentWithdrawalTaxModel.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 _ AgentWithdrawalTaxModel = (*customAgentWithdrawalTaxModel)(nil) + +type ( + // AgentWithdrawalTaxModel is an interface to be customized, add more methods here, + // and implement the added methods in customAgentWithdrawalTaxModel. + AgentWithdrawalTaxModel interface { + agentWithdrawalTaxModel + } + + customAgentWithdrawalTaxModel struct { + *defaultAgentWithdrawalTaxModel + } +) + +// NewAgentWithdrawalTaxModel returns a model for the database table. +func NewAgentWithdrawalTaxModel(conn sqlx.SqlConn, c cache.CacheConf) AgentWithdrawalTaxModel { + return &customAgentWithdrawalTaxModel{ + defaultAgentWithdrawalTaxModel: newAgentWithdrawalTaxModel(conn, c), + } +} diff --git a/app/main/model/agentWithdrawalTaxModel_gen.go b/app/main/model/agentWithdrawalTaxModel_gen.go new file mode 100644 index 0000000..ff17d60 --- /dev/null +++ b/app/main/model/agentWithdrawalTaxModel_gen.go @@ -0,0 +1,419 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + agentWithdrawalTaxFieldNames = builder.RawFieldNames(&AgentWithdrawalTax{}) + agentWithdrawalTaxRows = strings.Join(agentWithdrawalTaxFieldNames, ",") + agentWithdrawalTaxRowsExpectAutoSet = strings.Join(stringx.Remove(agentWithdrawalTaxFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + agentWithdrawalTaxRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWithdrawalTaxFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAgentWithdrawalTaxIdPrefix = "cache:bdrp:agentWithdrawalTax:id:" + cacheHmAgentWithdrawalTaxWithdrawalIdPrefix = "cache:bdrp:agentWithdrawalTax:withdrawalId:" +) + +type ( + agentWithdrawalTaxModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTax) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AgentWithdrawalTax, error) + FindOneByWithdrawalId(ctx context.Context, withdrawalId int64) (*AgentWithdrawalTax, error) + Update(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTax) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTax) 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 *AgentWithdrawalTax) 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) ([]*AgentWithdrawalTax, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTax, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTax, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawalTax, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawalTax, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAgentWithdrawalTaxModel struct { + sqlc.CachedConn + table string + } + + AgentWithdrawalTax struct { + Id int64 `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"` // 版本号 + WithdrawalAmount float64 `db:"withdrawal_amount"` // 提现金额 + ExemptionAmount float64 `db:"exemption_amount"` // 免税金额 + TaxableAmount float64 `db:"taxable_amount"` // 应税金额 + TaxRate float64 `db:"tax_rate"` // 税率,如:0.2000表示20% + TaxAmount float64 `db:"tax_amount"` // 应缴税费 + ActualAmount float64 `db:"actual_amount"` // 实际到账金额 + YearMonth int64 `db:"year_month"` // 所属年月,格式:202401 + TaxStatus int64 `db:"tax_status"` // 扣税状态:0-待扣税,1-已扣税,2-免税,3-扣税失败 + TaxTime sql.NullTime `db:"tax_time"` // 扣税时间 + Remark sql.NullString `db:"remark"` // 备注信息 + AgentId int64 `db:"agent_id"` // 关联到代理用户表的id + WithdrawalId int64 `db:"withdrawal_id"` // 关联提现记录表的id + ExemptionRecordId int64 `db:"exemption_record_id"` // 关联到免税额度记录的id + } +) + +func newAgentWithdrawalTaxModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgentWithdrawalTaxModel { + return &defaultAgentWithdrawalTaxModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`agent_withdrawal_tax`", + } +} + +func (m *defaultAgentWithdrawalTaxModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTax) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAgentWithdrawalTaxIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, data.Id) + hmAgentWithdrawalTaxWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxWithdrawalIdPrefix, data.WithdrawalId) + 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, agentWithdrawalTaxRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.WithdrawalAmount, data.ExemptionAmount, data.TaxableAmount, data.TaxRate, data.TaxAmount, data.ActualAmount, data.YearMonth, data.TaxStatus, data.TaxTime, data.Remark, data.AgentId, data.WithdrawalId, data.ExemptionRecordId) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.WithdrawalAmount, data.ExemptionAmount, data.TaxableAmount, data.TaxRate, data.TaxAmount, data.ActualAmount, data.YearMonth, data.TaxStatus, data.TaxTime, data.Remark, data.AgentId, data.WithdrawalId, data.ExemptionRecordId) + }, hmAgentWithdrawalTaxIdKey, hmAgentWithdrawalTaxWithdrawalIdKey) +} + +func (m *defaultAgentWithdrawalTaxModel) FindOne(ctx context.Context, id int64) (*AgentWithdrawalTax, error) { + hmAgentWithdrawalTaxIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, id) + var resp AgentWithdrawalTax + err := m.QueryRowCtx(ctx, &resp, hmAgentWithdrawalTaxIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWithdrawalTaxRows, 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 *defaultAgentWithdrawalTaxModel) FindOneByWithdrawalId(ctx context.Context, withdrawalId int64) (*AgentWithdrawalTax, error) { + hmAgentWithdrawalTaxWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxWithdrawalIdPrefix, withdrawalId) + var resp AgentWithdrawalTax + err := m.QueryRowIndexCtx(ctx, &resp, hmAgentWithdrawalTaxWithdrawalIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `withdrawal_id` = ? and del_state = ? limit 1", agentWithdrawalTaxRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, withdrawalId, 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 *defaultAgentWithdrawalTaxModel) Update(ctx context.Context, session sqlx.Session, newData *AgentWithdrawalTax) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmAgentWithdrawalTaxIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, data.Id) + hmAgentWithdrawalTaxWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxWithdrawalIdPrefix, data.WithdrawalId) + 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, agentWithdrawalTaxRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.WithdrawalAmount, newData.ExemptionAmount, newData.TaxableAmount, newData.TaxRate, newData.TaxAmount, newData.ActualAmount, newData.YearMonth, newData.TaxStatus, newData.TaxTime, newData.Remark, newData.AgentId, newData.WithdrawalId, newData.ExemptionRecordId, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.WithdrawalAmount, newData.ExemptionAmount, newData.TaxableAmount, newData.TaxRate, newData.TaxAmount, newData.ActualAmount, newData.YearMonth, newData.TaxStatus, newData.TaxTime, newData.Remark, newData.AgentId, newData.WithdrawalId, newData.ExemptionRecordId, newData.Id) + }, hmAgentWithdrawalTaxIdKey, hmAgentWithdrawalTaxWithdrawalIdKey) +} + +func (m *defaultAgentWithdrawalTaxModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentWithdrawalTax) 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 + } + hmAgentWithdrawalTaxIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, data.Id) + hmAgentWithdrawalTaxWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxWithdrawalIdPrefix, data.WithdrawalId) + 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, agentWithdrawalTaxRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.WithdrawalAmount, newData.ExemptionAmount, newData.TaxableAmount, newData.TaxRate, newData.TaxAmount, newData.ActualAmount, newData.YearMonth, newData.TaxStatus, newData.TaxTime, newData.Remark, newData.AgentId, newData.WithdrawalId, newData.ExemptionRecordId, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.WithdrawalAmount, newData.ExemptionAmount, newData.TaxableAmount, newData.TaxRate, newData.TaxAmount, newData.ActualAmount, newData.YearMonth, newData.TaxStatus, newData.TaxTime, newData.Remark, newData.AgentId, newData.WithdrawalId, newData.ExemptionRecordId, newData.Id, oldVersion) + }, hmAgentWithdrawalTaxIdKey, hmAgentWithdrawalTaxWithdrawalIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAgentWithdrawalTaxModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AgentWithdrawalTax) 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 "), "AgentWithdrawalTaxModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAgentWithdrawalTaxModel) 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 *defaultAgentWithdrawalTaxModel) 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 *defaultAgentWithdrawalTaxModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AgentWithdrawalTax, error) { + + builder = builder.Columns(agentWithdrawalTaxRows) + + 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 []*AgentWithdrawalTax + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTax, error) { + + builder = builder.Columns(agentWithdrawalTaxRows) + + 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 []*AgentWithdrawalTax + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AgentWithdrawalTax, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(agentWithdrawalTaxRows) + + 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 []*AgentWithdrawalTax + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAgentWithdrawalTaxModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AgentWithdrawalTax, error) { + + builder = builder.Columns(agentWithdrawalTaxRows) + + 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 []*AgentWithdrawalTax + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AgentWithdrawalTax, error) { + + builder = builder.Columns(agentWithdrawalTaxRows) + + 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 []*AgentWithdrawalTax + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAgentWithdrawalTaxModel) 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 *defaultAgentWithdrawalTaxModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAgentWithdrawalTaxModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmAgentWithdrawalTaxIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, id) + hmAgentWithdrawalTaxWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxWithdrawalIdPrefix, data.WithdrawalId) + _, 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) + }, hmAgentWithdrawalTaxIdKey, hmAgentWithdrawalTaxWithdrawalIdKey) + return err +} +func (m *defaultAgentWithdrawalTaxModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAgentWithdrawalTaxIdPrefix, primary) +} +func (m *defaultAgentWithdrawalTaxModel) 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", agentWithdrawalTaxRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAgentWithdrawalTaxModel) tableName() string { + return m.table +} diff --git a/app/main/model/authorizationDocumentModel.go b/app/main/model/authorizationDocumentModel.go new file mode 100644 index 0000000..267470b --- /dev/null +++ b/app/main/model/authorizationDocumentModel.go @@ -0,0 +1,39 @@ +package model + +import ( + "context" + + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AuthorizationDocumentModel = (*customAuthorizationDocumentModel)(nil) + +type ( + // AuthorizationDocumentModel is an interface to be customized, add more methods here, + // and implement the added methods in customAuthorizationDocumentModel. + AuthorizationDocumentModel interface { + authorizationDocumentModel + FindByOrderId(ctx context.Context, orderId int64) ([]*AuthorizationDocument, error) + } + + customAuthorizationDocumentModel struct { + *defaultAuthorizationDocumentModel + } +) + +// NewAuthorizationDocumentModel returns a model for the database table. +func NewAuthorizationDocumentModel(conn sqlx.SqlConn, c cache.CacheConf) AuthorizationDocumentModel { + return &customAuthorizationDocumentModel{ + defaultAuthorizationDocumentModel: newAuthorizationDocumentModel(conn, c), + } +} + +// FindByOrderId 根据订单ID查询授权书列表 +func (m *customAuthorizationDocumentModel) FindByOrderId(ctx context.Context, orderId int64) ([]*AuthorizationDocument, error) { + query := `SELECT * FROM authorization_document WHERE order_id = ? AND del_state = 0 ORDER BY create_time DESC` + + var authDocs []*AuthorizationDocument + err := m.QueryRowsNoCacheCtx(ctx, &authDocs, query, orderId) + return authDocs, err +} diff --git a/app/main/model/authorizationDocumentModel_gen.go b/app/main/model/authorizationDocumentModel_gen.go new file mode 100644 index 0000000..0ebf02f --- /dev/null +++ b/app/main/model/authorizationDocumentModel_gen.go @@ -0,0 +1,377 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + authorizationDocumentFieldNames = builder.RawFieldNames(&AuthorizationDocument{}) + authorizationDocumentRows = strings.Join(authorizationDocumentFieldNames, ",") + authorizationDocumentRowsExpectAutoSet = strings.Join(stringx.Remove(authorizationDocumentFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + authorizationDocumentRowsWithPlaceHolder = strings.Join(stringx.Remove(authorizationDocumentFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmAuthorizationDocumentIdPrefix = "cache:bdrp:authorizationDocument:id:" +) + +type ( + authorizationDocumentModel interface { + Insert(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AuthorizationDocument, error) + Update(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) 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 *AuthorizationDocument) 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) ([]*AuthorizationDocument, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AuthorizationDocument, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AuthorizationDocument, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AuthorizationDocument, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AuthorizationDocument, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultAuthorizationDocumentModel struct { + sqlc.CachedConn + table string + } + + AuthorizationDocument struct { + Id int64 `db:"id"` // 主键ID + UserId int64 `db:"user_id"` // 用户ID + OrderId int64 `db:"order_id"` // 订单ID + QueryId int64 `db:"query_id"` // 查询ID + FileName string `db:"file_name"` // 文件名 + FilePath string `db:"file_path"` // 文件路径 + FileUrl string `db:"file_url"` // 文件访问URL + FileSize int64 `db:"file_size"` // 文件大小(字节) + FileType string `db:"file_type"` // 文件类型 + Status string `db:"status"` // 状态(active/expired/deleted) + ExpireTime sql.NullTime `db:"expire_time"` // 过期时间(永久保留设为NULL) + 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"` // 版本号 + } +) + +func newAuthorizationDocumentModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAuthorizationDocumentModel { + return &defaultAuthorizationDocumentModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`authorization_document`", + } +} + +func (m *defaultAuthorizationDocumentModel) Insert(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmAuthorizationDocumentIdKey := fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, 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, authorizationDocumentRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version) + }, hmAuthorizationDocumentIdKey) +} + +func (m *defaultAuthorizationDocumentModel) FindOne(ctx context.Context, id int64) (*AuthorizationDocument, error) { + hmAuthorizationDocumentIdKey := fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, id) + var resp AuthorizationDocument + err := m.QueryRowCtx(ctx, &resp, hmAuthorizationDocumentIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", authorizationDocumentRows, 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 *defaultAuthorizationDocumentModel) Update(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) (sql.Result, error) { + hmAuthorizationDocumentIdKey := fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, 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, authorizationDocumentRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version, data.Id) + } + return conn.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version, data.Id) + }, hmAuthorizationDocumentIdKey) +} + +func (m *defaultAuthorizationDocumentModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + hmAuthorizationDocumentIdKey := fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, 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, authorizationDocumentRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.UserId, data.OrderId, data.QueryId, data.FileName, data.FilePath, data.FileUrl, data.FileSize, data.FileType, data.Status, data.ExpireTime, data.DeleteTime, data.DelState, data.Version, data.Id, oldVersion) + }, hmAuthorizationDocumentIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultAuthorizationDocumentModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *AuthorizationDocument) 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 "), "AuthorizationDocumentModel delete err : %+v", err) + } + return nil +} + +func (m *defaultAuthorizationDocumentModel) 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 *defaultAuthorizationDocumentModel) 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 *defaultAuthorizationDocumentModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*AuthorizationDocument, error) { + + builder = builder.Columns(authorizationDocumentRows) + + 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 []*AuthorizationDocument + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAuthorizationDocumentModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AuthorizationDocument, error) { + + builder = builder.Columns(authorizationDocumentRows) + + 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 []*AuthorizationDocument + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAuthorizationDocumentModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*AuthorizationDocument, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(authorizationDocumentRows) + + 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 []*AuthorizationDocument + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultAuthorizationDocumentModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*AuthorizationDocument, error) { + + builder = builder.Columns(authorizationDocumentRows) + + 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 []*AuthorizationDocument + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAuthorizationDocumentModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*AuthorizationDocument, error) { + + builder = builder.Columns(authorizationDocumentRows) + + 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 []*AuthorizationDocument + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultAuthorizationDocumentModel) 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 *defaultAuthorizationDocumentModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultAuthorizationDocumentModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + hmAuthorizationDocumentIdKey := fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, 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) + }, hmAuthorizationDocumentIdKey) + return err +} +func (m *defaultAuthorizationDocumentModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmAuthorizationDocumentIdPrefix, primary) +} +func (m *defaultAuthorizationDocumentModel) 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", authorizationDocumentRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultAuthorizationDocumentModel) tableName() string { + return m.table +} diff --git a/app/main/model/exampleModel.go b/app/main/model/exampleModel.go new file mode 100644 index 0000000..d041ea0 --- /dev/null +++ b/app/main/model/exampleModel.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 _ ExampleModel = (*customExampleModel)(nil) + +type ( + // ExampleModel is an interface to be customized, add more methods here, + // and implement the added methods in customExampleModel. + ExampleModel interface { + exampleModel + } + + customExampleModel struct { + *defaultExampleModel + } +) + +// NewExampleModel returns a model for the database table. +func NewExampleModel(conn sqlx.SqlConn, c cache.CacheConf) ExampleModel { + return &customExampleModel{ + defaultExampleModel: newExampleModel(conn, c), + } +} diff --git a/app/main/model/exampleModel_gen.go b/app/main/model/exampleModel_gen.go new file mode 100644 index 0000000..599eeaa --- /dev/null +++ b/app/main/model/exampleModel_gen.go @@ -0,0 +1,435 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + exampleFieldNames = builder.RawFieldNames(&Example{}) + exampleRows = strings.Join(exampleFieldNames, ",") + exampleRowsExpectAutoSet = strings.Join(stringx.Remove(exampleFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + exampleRowsWithPlaceHolder = strings.Join(stringx.Remove(exampleFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmExampleIdPrefix = "cache:bdrp:example:id:" + cacheHmExampleApiIdPrefix = "cache:bdrp:example:apiId:" + cacheHmExampleFeatureIdPrefix = "cache:bdrp:example:featureId:" +) + +type ( + exampleModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Example, error) + FindOneByApiId(ctx context.Context, apiId string) (*Example, error) + FindOneByFeatureId(ctx context.Context, featureId int64) (*Example, error) + Update(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Example) 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 *Example) 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) ([]*Example, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Example, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Example, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultExampleModel struct { + sqlc.CachedConn + table string + } + + Example struct { + Id int64 `db:"id"` // 主键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标识 + FeatureId int64 `db:"feature_id"` // 关联feature表的ID + Content string `db:"content"` // 内容 + } +) + +func newExampleModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultExampleModel { + return &defaultExampleModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`example`", + } +} + +func (m *defaultExampleModel) Insert(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmExampleApiIdKey := fmt.Sprintf("%s%v", cacheHmExampleApiIdPrefix, data.ApiId) + hmExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheHmExampleFeatureIdPrefix, data.FeatureId) + hmExampleIdKey := fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, 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, exampleRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.FeatureId, data.Content) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.FeatureId, data.Content) + }, hmExampleApiIdKey, hmExampleFeatureIdKey, hmExampleIdKey) +} + +func (m *defaultExampleModel) FindOne(ctx context.Context, id int64) (*Example, error) { + hmExampleIdKey := fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, id) + var resp Example + err := m.QueryRowCtx(ctx, &resp, hmExampleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", exampleRows, 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 *defaultExampleModel) FindOneByApiId(ctx context.Context, apiId string) (*Example, error) { + hmExampleApiIdKey := fmt.Sprintf("%s%v", cacheHmExampleApiIdPrefix, apiId) + var resp Example + err := m.QueryRowIndexCtx(ctx, &resp, hmExampleApiIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `api_id` = ? and del_state = ? limit 1", exampleRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, apiId, 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 *defaultExampleModel) FindOneByFeatureId(ctx context.Context, featureId int64) (*Example, error) { + hmExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheHmExampleFeatureIdPrefix, featureId) + var resp Example + err := m.QueryRowIndexCtx(ctx, &resp, hmExampleFeatureIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `feature_id` = ? and del_state = ? limit 1", exampleRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, featureId, 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 *defaultExampleModel) Update(ctx context.Context, session sqlx.Session, newData *Example) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmExampleApiIdKey := fmt.Sprintf("%s%v", cacheHmExampleApiIdPrefix, data.ApiId) + hmExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheHmExampleFeatureIdPrefix, data.FeatureId) + hmExampleIdKey := fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, 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, exampleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id) + }, hmExampleApiIdKey, hmExampleFeatureIdKey, hmExampleIdKey) +} + +func (m *defaultExampleModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Example) 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 + } + hmExampleApiIdKey := fmt.Sprintf("%s%v", cacheHmExampleApiIdPrefix, data.ApiId) + hmExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheHmExampleFeatureIdPrefix, data.FeatureId) + hmExampleIdKey := fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, 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, exampleRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id, oldVersion) + }, hmExampleApiIdKey, hmExampleFeatureIdKey, hmExampleIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultExampleModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Example) 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 "), "ExampleModel delete err : %+v", err) + } + return nil +} + +func (m *defaultExampleModel) 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 *defaultExampleModel) 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 *defaultExampleModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Example, error) { + + builder = builder.Columns(exampleRows) + + 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 []*Example + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultExampleModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, error) { + + builder = builder.Columns(exampleRows) + + 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 []*Example + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultExampleModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(exampleRows) + + 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 []*Example + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultExampleModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Example, error) { + + builder = builder.Columns(exampleRows) + + 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 []*Example + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultExampleModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Example, error) { + + builder = builder.Columns(exampleRows) + + 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 []*Example + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultExampleModel) 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 *defaultExampleModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultExampleModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmExampleApiIdKey := fmt.Sprintf("%s%v", cacheHmExampleApiIdPrefix, data.ApiId) + hmExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheHmExampleFeatureIdPrefix, data.FeatureId) + hmExampleIdKey := fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, 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) + }, hmExampleApiIdKey, hmExampleFeatureIdKey, hmExampleIdKey) + return err +} +func (m *defaultExampleModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmExampleIdPrefix, primary) +} +func (m *defaultExampleModel) 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", exampleRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultExampleModel) tableName() string { + return m.table +} diff --git a/app/main/model/featureModel.go b/app/main/model/featureModel.go new file mode 100644 index 0000000..b27565e --- /dev/null +++ b/app/main/model/featureModel.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 _ FeatureModel = (*customFeatureModel)(nil) + +type ( + // FeatureModel is an interface to be customized, add more methods here, + // and implement the added methods in customFeatureModel. + FeatureModel interface { + featureModel + } + + customFeatureModel struct { + *defaultFeatureModel + } +) + +// NewFeatureModel returns a model for the database table. +func NewFeatureModel(conn sqlx.SqlConn, c cache.CacheConf) FeatureModel { + return &customFeatureModel{ + defaultFeatureModel: newFeatureModel(conn, c), + } +} diff --git a/app/main/model/featureModel_gen.go b/app/main/model/featureModel_gen.go new file mode 100644 index 0000000..1859f5b --- /dev/null +++ b/app/main/model/featureModel_gen.go @@ -0,0 +1,409 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + featureFieldNames = builder.RawFieldNames(&Feature{}) + featureRows = strings.Join(featureFieldNames, ",") + featureRowsExpectAutoSet = strings.Join(stringx.Remove(featureFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + featureRowsWithPlaceHolder = strings.Join(stringx.Remove(featureFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpFeatureIdPrefix = "cache:bdrp:feature:id:" + cacheBdrpFeatureApiIdPrefix = "cache:bdrp:feature:apiId:" +) + +type ( + featureModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Feature) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Feature, error) + FindOneByApiId(ctx context.Context, apiId string) (*Feature, error) + Update(ctx context.Context, session sqlx.Session, data *Feature) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Feature) 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 *Feature) 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) ([]*Feature, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Feature, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Feature, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Feature, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Feature, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultFeatureModel struct { + sqlc.CachedConn + table string + } + + Feature struct { + Id int64 `db:"id"` // 主键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"` // 描述 + CostPrice float64 `db:"cost_price"` // 成本价 + } +) + +func newFeatureModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultFeatureModel { + return &defaultFeatureModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`feature`", + } +} + +func (m *defaultFeatureModel) Insert(ctx context.Context, session sqlx.Session, data *Feature) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpFeatureApiIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureApiIdPrefix, data.ApiId) + bdrpFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, 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) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.CostPrice) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.Name, data.CostPrice) + }, bdrpFeatureApiIdKey, bdrpFeatureIdKey) +} + +func (m *defaultFeatureModel) FindOne(ctx context.Context, id int64) (*Feature, error) { + bdrpFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, id) + var resp Feature + err := m.QueryRowCtx(ctx, &resp, bdrpFeatureIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", featureRows, 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 *defaultFeatureModel) FindOneByApiId(ctx context.Context, apiId string) (*Feature, error) { + bdrpFeatureApiIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureApiIdPrefix, apiId) + var resp Feature + err := m.QueryRowIndexCtx(ctx, &resp, bdrpFeatureApiIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `api_id` = ? and del_state = ? limit 1", featureRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, apiId, 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 *defaultFeatureModel) Update(ctx context.Context, session sqlx.Session, newData *Feature) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpFeatureApiIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureApiIdPrefix, data.ApiId) + bdrpFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, 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, featureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.CostPrice, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.CostPrice, newData.Id) + }, bdrpFeatureApiIdKey, bdrpFeatureIdKey) +} + +func (m *defaultFeatureModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Feature) 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 + } + bdrpFeatureApiIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureApiIdPrefix, data.ApiId) + bdrpFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, 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, featureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.CostPrice, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.Name, newData.CostPrice, newData.Id, oldVersion) + }, bdrpFeatureApiIdKey, bdrpFeatureIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultFeatureModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Feature) 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 "), "FeatureModel delete err : %+v", err) + } + return nil +} + +func (m *defaultFeatureModel) 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 *defaultFeatureModel) 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 *defaultFeatureModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Feature, error) { + + builder = builder.Columns(featureRows) + + 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 []*Feature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultFeatureModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Feature, error) { + + builder = builder.Columns(featureRows) + + 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 []*Feature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultFeatureModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Feature, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(featureRows) + + 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 []*Feature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultFeatureModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Feature, error) { + + builder = builder.Columns(featureRows) + + 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 []*Feature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultFeatureModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Feature, error) { + + builder = builder.Columns(featureRows) + + 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 []*Feature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultFeatureModel) 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 *defaultFeatureModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultFeatureModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpFeatureApiIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureApiIdPrefix, data.ApiId) + bdrpFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, 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) + }, bdrpFeatureApiIdKey, bdrpFeatureIdKey) + return err +} +func (m *defaultFeatureModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpFeatureIdPrefix, primary) +} +func (m *defaultFeatureModel) 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", featureRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultFeatureModel) tableName() string { + return m.table +} diff --git a/app/main/model/globalNotificationsModel.go b/app/main/model/globalNotificationsModel.go new file mode 100644 index 0000000..059bdff --- /dev/null +++ b/app/main/model/globalNotificationsModel.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 _ GlobalNotificationsModel = (*customGlobalNotificationsModel)(nil) + +type ( + // GlobalNotificationsModel is an interface to be customized, add more methods here, + // and implement the added methods in customGlobalNotificationsModel. + GlobalNotificationsModel interface { + globalNotificationsModel + } + + customGlobalNotificationsModel struct { + *defaultGlobalNotificationsModel + } +) + +// NewGlobalNotificationsModel returns a model for the database table. +func NewGlobalNotificationsModel(conn sqlx.SqlConn, c cache.CacheConf) GlobalNotificationsModel { + return &customGlobalNotificationsModel{ + defaultGlobalNotificationsModel: newGlobalNotificationsModel(conn, c), + } +} diff --git a/app/main/model/globalNotificationsModel_gen.go b/app/main/model/globalNotificationsModel_gen.go new file mode 100644 index 0000000..fe3b418 --- /dev/null +++ b/app/main/model/globalNotificationsModel_gen.go @@ -0,0 +1,375 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + globalNotificationsFieldNames = builder.RawFieldNames(&GlobalNotifications{}) + globalNotificationsRows = strings.Join(globalNotificationsFieldNames, ",") + globalNotificationsRowsExpectAutoSet = strings.Join(stringx.Remove(globalNotificationsFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + globalNotificationsRowsWithPlaceHolder = strings.Join(stringx.Remove(globalNotificationsFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmGlobalNotificationsIdPrefix = "cache:bdrp:globalNotifications:id:" +) + +type ( + globalNotificationsModel interface { + Insert(ctx context.Context, session sqlx.Session, data *GlobalNotifications) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*GlobalNotifications, error) + Update(ctx context.Context, session sqlx.Session, data *GlobalNotifications) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *GlobalNotifications) 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 *GlobalNotifications) 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) ([]*GlobalNotifications, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*GlobalNotifications, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*GlobalNotifications, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*GlobalNotifications, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*GlobalNotifications, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultGlobalNotificationsModel struct { + sqlc.CachedConn + table string + } + + GlobalNotifications struct { + Id int64 `db:"id"` + Title string `db:"title"` + Content string `db:"content"` + NotificationPage string `db:"notification_page"` + StartDate sql.NullTime `db:"start_date"` + EndDate sql.NullTime `db:"end_date"` + StartTime string `db:"start_time"` + EndTime string `db:"end_time"` + CreateTime time.Time `db:"create_time"` + UpdateTime time.Time `db:"update_time"` + Status int64 `db:"status"` + DelState int64 `db:"del_state"` + Version int64 `db:"version"` // 版本号 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + } +) + +func newGlobalNotificationsModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultGlobalNotificationsModel { + return &defaultGlobalNotificationsModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`global_notifications`", + } +} + +func (m *defaultGlobalNotificationsModel) Insert(ctx context.Context, session sqlx.Session, data *GlobalNotifications) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmGlobalNotificationsIdKey := fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, 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, globalNotificationsRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime) + } + return conn.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime) + }, hmGlobalNotificationsIdKey) +} + +func (m *defaultGlobalNotificationsModel) FindOne(ctx context.Context, id int64) (*GlobalNotifications, error) { + hmGlobalNotificationsIdKey := fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, id) + var resp GlobalNotifications + err := m.QueryRowCtx(ctx, &resp, hmGlobalNotificationsIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", globalNotificationsRows, 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 *defaultGlobalNotificationsModel) Update(ctx context.Context, session sqlx.Session, data *GlobalNotifications) (sql.Result, error) { + hmGlobalNotificationsIdKey := fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, 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, globalNotificationsRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id) + } + return conn.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id) + }, hmGlobalNotificationsIdKey) +} + +func (m *defaultGlobalNotificationsModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *GlobalNotifications) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + hmGlobalNotificationsIdKey := fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, 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, globalNotificationsRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.Title, data.Content, data.NotificationPage, data.StartDate, data.EndDate, data.StartTime, data.EndTime, data.Status, data.DelState, data.Version, data.DeleteTime, data.Id, oldVersion) + }, hmGlobalNotificationsIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultGlobalNotificationsModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *GlobalNotifications) 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 "), "GlobalNotificationsModel delete err : %+v", err) + } + return nil +} + +func (m *defaultGlobalNotificationsModel) 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 *defaultGlobalNotificationsModel) 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 *defaultGlobalNotificationsModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*GlobalNotifications, error) { + + builder = builder.Columns(globalNotificationsRows) + + 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 []*GlobalNotifications + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultGlobalNotificationsModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*GlobalNotifications, error) { + + builder = builder.Columns(globalNotificationsRows) + + 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 []*GlobalNotifications + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultGlobalNotificationsModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*GlobalNotifications, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(globalNotificationsRows) + + 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 []*GlobalNotifications + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultGlobalNotificationsModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*GlobalNotifications, error) { + + builder = builder.Columns(globalNotificationsRows) + + 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 []*GlobalNotifications + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultGlobalNotificationsModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*GlobalNotifications, error) { + + builder = builder.Columns(globalNotificationsRows) + + 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 []*GlobalNotifications + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultGlobalNotificationsModel) 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 *defaultGlobalNotificationsModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultGlobalNotificationsModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + hmGlobalNotificationsIdKey := fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, 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) + }, hmGlobalNotificationsIdKey) + return err +} +func (m *defaultGlobalNotificationsModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmGlobalNotificationsIdPrefix, primary) +} +func (m *defaultGlobalNotificationsModel) 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", globalNotificationsRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultGlobalNotificationsModel) tableName() string { + return m.table +} diff --git a/app/main/model/orderModel.go b/app/main/model/orderModel.go new file mode 100644 index 0000000..6bf505c --- /dev/null +++ b/app/main/model/orderModel.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 _ OrderModel = (*customOrderModel)(nil) + +type ( + // OrderModel is an interface to be customized, add more methods here, + // and implement the added methods in customOrderModel. + OrderModel interface { + orderModel + } + + customOrderModel struct { + *defaultOrderModel + } +) + +// NewOrderModel returns a model for the database table. +func NewOrderModel(conn sqlx.SqlConn, c cache.CacheConf) OrderModel { + return &customOrderModel{ + defaultOrderModel: newOrderModel(conn, c), + } +} diff --git a/app/main/model/orderModel_gen.go b/app/main/model/orderModel_gen.go new file mode 100644 index 0000000..27bd0c9 --- /dev/null +++ b/app/main/model/orderModel_gen.go @@ -0,0 +1,417 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + "bdrp-server/common/globalkey" +) + +var ( + orderFieldNames = builder.RawFieldNames(&Order{}) + orderRows = strings.Join(orderFieldNames, ",") + orderRowsExpectAutoSet = strings.Join(stringx.Remove(orderFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + orderRowsWithPlaceHolder = strings.Join(stringx.Remove(orderFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpOrderIdPrefix = "cache:bdrp:order:id:" + cacheBdrpOrderOrderNoPrefix = "cache:bdrp:order:orderNo:" +) + +type ( + orderModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Order) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Order, error) + FindOneByOrderNo(ctx context.Context, orderNo string) (*Order, error) + Update(ctx context.Context, session sqlx.Session, data *Order) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Order) 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 *Order) 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) ([]*Order, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Order, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Order, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Order, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Order, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultOrderModel struct { + sqlc.CachedConn + table string + } + + Order struct { + Id int64 `db:"id"` // 主键ID + OrderNo string `db:"order_no"` // 自生成的订单号 + UserId int64 `db:"user_id"` // 用户ID + ProductId int64 `db:"product_id"` // 产品ID(软关联到产品表) + PaymentPlatform string `db:"payment_platform"` // 支付平台(支付宝、微信、苹果内购、其他) + PaymentScene string `db:"payment_scene"` // 支付场景(App、H5、微信小程序、公众号) + PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号 + Amount float64 `db:"amount"` // 支付金额 + Status string `db:"status"` // 支付状态 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + PayTime sql.NullTime `db:"pay_time"` // 支付时间 + RefundTime sql.NullTime `db:"refund_time"` // 退款时间 + CloseTime sql.NullTime `db:"close_time"` // 订单关闭时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + SalesCost float64 `db:"sales_cost"` // 销售成本 + } +) + +func newOrderModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultOrderModel { + return &defaultOrderModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`order`", + } +} + +func (m *defaultOrderModel) Insert(ctx context.Context, session sqlx.Session, data *Order) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpOrderIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, data.Id) + bdrpOrderOrderNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderOrderNoPrefix, 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, orderRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost) + } + return conn.ExecCtx(ctx, query, data.OrderNo, data.UserId, data.ProductId, data.PaymentPlatform, data.PaymentScene, data.PlatformOrderId, data.Amount, data.Status, data.DelState, data.Version, data.PayTime, data.RefundTime, data.CloseTime, data.DeleteTime, data.SalesCost) + }, bdrpOrderIdKey, bdrpOrderOrderNoKey) +} + +func (m *defaultOrderModel) FindOne(ctx context.Context, id int64) (*Order, error) { + bdrpOrderIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, id) + var resp Order + err := m.QueryRowCtx(ctx, &resp, bdrpOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", orderRows, 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 *defaultOrderModel) FindOneByOrderNo(ctx context.Context, orderNo string) (*Order, error) { + bdrpOrderOrderNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderOrderNoPrefix, orderNo) + var resp Order + err := m.QueryRowIndexCtx(ctx, &resp, bdrpOrderOrderNoKey, 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", orderRows, 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 *defaultOrderModel) Update(ctx context.Context, session sqlx.Session, newData *Order) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpOrderIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, data.Id) + bdrpOrderOrderNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderOrderNoPrefix, 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, orderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id) + }, bdrpOrderIdKey, bdrpOrderOrderNoKey) +} + +func (m *defaultOrderModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Order) 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 + } + bdrpOrderIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, data.Id) + bdrpOrderOrderNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderOrderNoPrefix, 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, orderRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.OrderNo, newData.UserId, newData.ProductId, newData.PaymentPlatform, newData.PaymentScene, newData.PlatformOrderId, newData.Amount, newData.Status, newData.DelState, newData.Version, newData.PayTime, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.SalesCost, newData.Id, oldVersion) + }, bdrpOrderIdKey, bdrpOrderOrderNoKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultOrderModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Order) 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 "), "OrderModel delete err : %+v", err) + } + return nil +} + +func (m *defaultOrderModel) 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 *defaultOrderModel) 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 *defaultOrderModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Order, error) { + + builder = builder.Columns(orderRows) + + 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 []*Order + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Order, error) { + + builder = builder.Columns(orderRows) + + 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 []*Order + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Order, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(orderRows) + + 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 []*Order + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultOrderModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Order, error) { + + builder = builder.Columns(orderRows) + + 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 []*Order + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Order, error) { + + builder = builder.Columns(orderRows) + + 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 []*Order + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderModel) 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 *defaultOrderModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultOrderModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpOrderIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, id) + bdrpOrderOrderNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderOrderNoPrefix, 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) + }, bdrpOrderIdKey, bdrpOrderOrderNoKey) + return err +} +func (m *defaultOrderModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpOrderIdPrefix, primary) +} +func (m *defaultOrderModel) 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", orderRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultOrderModel) tableName() string { + return m.table +} diff --git a/app/main/model/orderRefundModel.go b/app/main/model/orderRefundModel.go new file mode 100644 index 0000000..92c3174 --- /dev/null +++ b/app/main/model/orderRefundModel.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 _ OrderRefundModel = (*customOrderRefundModel)(nil) + +type ( + // OrderRefundModel is an interface to be customized, add more methods here, + // and implement the added methods in customOrderRefundModel. + OrderRefundModel interface { + orderRefundModel + } + + customOrderRefundModel struct { + *defaultOrderRefundModel + } +) + +// NewOrderRefundModel returns a model for the database table. +func NewOrderRefundModel(conn sqlx.SqlConn, c cache.CacheConf) OrderRefundModel { + return &customOrderRefundModel{ + defaultOrderRefundModel: newOrderRefundModel(conn, c), + } +} diff --git a/app/main/model/orderRefundModel_gen.go b/app/main/model/orderRefundModel_gen.go new file mode 100644 index 0000000..faa860c --- /dev/null +++ b/app/main/model/orderRefundModel_gen.go @@ -0,0 +1,441 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" + "bdrp-server/common/globalkey" +) + +var ( + orderRefundFieldNames = builder.RawFieldNames(&OrderRefund{}) + orderRefundRows = strings.Join(orderRefundFieldNames, ",") + orderRefundRowsExpectAutoSet = strings.Join(stringx.Remove(orderRefundFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + orderRefundRowsWithPlaceHolder = strings.Join(stringx.Remove(orderRefundFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpOrderRefundIdPrefix = "cache:bdrp:orderRefund:id:" + cacheBdrpOrderRefundPlatformRefundIdPrefix = "cache:bdrp:orderRefund:platformRefundId:" + cacheBdrpOrderRefundRefundNoPrefix = "cache:bdrp:orderRefund:refundNo:" +) + +type ( + orderRefundModel interface { + Insert(ctx context.Context, session sqlx.Session, data *OrderRefund) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*OrderRefund, error) + FindOneByPlatformRefundId(ctx context.Context, platformRefundId sql.NullString) (*OrderRefund, error) + FindOneByRefundNo(ctx context.Context, refundNo string) (*OrderRefund, error) + Update(ctx context.Context, session sqlx.Session, data *OrderRefund) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *OrderRefund) 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 *OrderRefund) 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) ([]*OrderRefund, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*OrderRefund, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*OrderRefund, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*OrderRefund, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*OrderRefund, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultOrderRefundModel struct { + sqlc.CachedConn + table string + } + + OrderRefund struct { + Id int64 `db:"id"` // 主键ID + RefundNo string `db:"refund_no"` // 退款单号 + OrderId int64 `db:"order_id"` // 关联的订单ID + UserId int64 `db:"user_id"` // 用户ID + ProductId int64 `db:"product_id"` // 产品ID + PlatformRefundId sql.NullString `db:"platform_refund_id"` // 支付平台退款单号 + RefundAmount float64 `db:"refund_amount"` // 退款金额 + RefundReason sql.NullString `db:"refund_reason"` // 退款原因 + Status string `db:"status"` // 退款状态 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + RefundTime sql.NullTime `db:"refund_time"` // 退款成功时间 + CloseTime sql.NullTime `db:"close_time"` // 退款关闭时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + } +) + +func newOrderRefundModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultOrderRefundModel { + return &defaultOrderRefundModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`order_refund`", + } +} + +func (m *defaultOrderRefundModel) Insert(ctx context.Context, session sqlx.Session, data *OrderRefund) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpOrderRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, data.Id) + bdrpOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId) + bdrpOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundRefundNoPrefix, data.RefundNo) + 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, orderRefundRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime) + } + return conn.ExecCtx(ctx, query, data.RefundNo, data.OrderId, data.UserId, data.ProductId, data.PlatformRefundId, data.RefundAmount, data.RefundReason, data.Status, data.DelState, data.Version, data.RefundTime, data.CloseTime, data.DeleteTime) + }, bdrpOrderRefundIdKey, bdrpOrderRefundPlatformRefundIdKey, bdrpOrderRefundRefundNoKey) +} + +func (m *defaultOrderRefundModel) FindOne(ctx context.Context, id int64) (*OrderRefund, error) { + bdrpOrderRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, id) + var resp OrderRefund + err := m.QueryRowCtx(ctx, &resp, bdrpOrderRefundIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", orderRefundRows, 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 *defaultOrderRefundModel) FindOneByPlatformRefundId(ctx context.Context, platformRefundId sql.NullString) (*OrderRefund, error) { + bdrpOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundPlatformRefundIdPrefix, platformRefundId) + var resp OrderRefund + err := m.QueryRowIndexCtx(ctx, &resp, bdrpOrderRefundPlatformRefundIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `platform_refund_id` = ? and del_state = ? limit 1", orderRefundRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, platformRefundId, 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 *defaultOrderRefundModel) FindOneByRefundNo(ctx context.Context, refundNo string) (*OrderRefund, error) { + bdrpOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundRefundNoPrefix, refundNo) + var resp OrderRefund + err := m.QueryRowIndexCtx(ctx, &resp, bdrpOrderRefundRefundNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `refund_no` = ? and del_state = ? limit 1", orderRefundRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, refundNo, 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 *defaultOrderRefundModel) Update(ctx context.Context, session sqlx.Session, newData *OrderRefund) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpOrderRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, data.Id) + bdrpOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId) + bdrpOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundRefundNoPrefix, data.RefundNo) + 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, orderRefundRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id) + }, bdrpOrderRefundIdKey, bdrpOrderRefundPlatformRefundIdKey, bdrpOrderRefundRefundNoKey) +} + +func (m *defaultOrderRefundModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *OrderRefund) 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 + } + bdrpOrderRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, data.Id) + bdrpOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId) + bdrpOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundRefundNoPrefix, data.RefundNo) + 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, orderRefundRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.RefundNo, newData.OrderId, newData.UserId, newData.ProductId, newData.PlatformRefundId, newData.RefundAmount, newData.RefundReason, newData.Status, newData.DelState, newData.Version, newData.RefundTime, newData.CloseTime, newData.DeleteTime, newData.Id, oldVersion) + }, bdrpOrderRefundIdKey, bdrpOrderRefundPlatformRefundIdKey, bdrpOrderRefundRefundNoKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultOrderRefundModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *OrderRefund) 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 "), "OrderRefundModel delete err : %+v", err) + } + return nil +} + +func (m *defaultOrderRefundModel) 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 *defaultOrderRefundModel) 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 *defaultOrderRefundModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*OrderRefund, error) { + + builder = builder.Columns(orderRefundRows) + + 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 []*OrderRefund + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderRefundModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*OrderRefund, error) { + + builder = builder.Columns(orderRefundRows) + + 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 []*OrderRefund + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderRefundModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*OrderRefund, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(orderRefundRows) + + 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 []*OrderRefund + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultOrderRefundModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*OrderRefund, error) { + + builder = builder.Columns(orderRefundRows) + + 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 []*OrderRefund + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderRefundModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*OrderRefund, error) { + + builder = builder.Columns(orderRefundRows) + + 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 []*OrderRefund + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultOrderRefundModel) 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 *defaultOrderRefundModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultOrderRefundModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpOrderRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, id) + bdrpOrderRefundPlatformRefundIdKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundPlatformRefundIdPrefix, data.PlatformRefundId) + bdrpOrderRefundRefundNoKey := fmt.Sprintf("%s%v", cacheBdrpOrderRefundRefundNoPrefix, data.RefundNo) + _, 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) + }, bdrpOrderRefundIdKey, bdrpOrderRefundPlatformRefundIdKey, bdrpOrderRefundRefundNoKey) + return err +} +func (m *defaultOrderRefundModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpOrderRefundIdPrefix, primary) +} +func (m *defaultOrderRefundModel) 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", orderRefundRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultOrderRefundModel) tableName() string { + return m.table +} diff --git a/app/main/model/productFeatureModel.go b/app/main/model/productFeatureModel.go new file mode 100644 index 0000000..5b77422 --- /dev/null +++ b/app/main/model/productFeatureModel.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 _ ProductFeatureModel = (*customProductFeatureModel)(nil) + +type ( + // ProductFeatureModel is an interface to be customized, add more methods here, + // and implement the added methods in customProductFeatureModel. + ProductFeatureModel interface { + productFeatureModel + } + + customProductFeatureModel struct { + *defaultProductFeatureModel + } +) + +// NewProductFeatureModel returns a model for the database table. +func NewProductFeatureModel(conn sqlx.SqlConn, c cache.CacheConf) ProductFeatureModel { + return &customProductFeatureModel{ + defaultProductFeatureModel: newProductFeatureModel(conn, c), + } +} diff --git a/app/main/model/productFeatureModel_gen.go b/app/main/model/productFeatureModel_gen.go new file mode 100644 index 0000000..998af4f --- /dev/null +++ b/app/main/model/productFeatureModel_gen.go @@ -0,0 +1,411 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + productFeatureFieldNames = builder.RawFieldNames(&ProductFeature{}) + productFeatureRows = strings.Join(productFeatureFieldNames, ",") + productFeatureRowsExpectAutoSet = strings.Join(stringx.Remove(productFeatureFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + productFeatureRowsWithPlaceHolder = strings.Join(stringx.Remove(productFeatureFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpProductFeatureIdPrefix = "cache:bdrp:productFeature:id:" + cacheBdrpProductFeatureProductIdFeatureIdPrefix = "cache:bdrp:productFeature:productId:featureId:" +) + +type ( + productFeatureModel interface { + Insert(ctx context.Context, session sqlx.Session, data *ProductFeature) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*ProductFeature, error) + FindOneByProductIdFeatureId(ctx context.Context, productId int64, featureId int64) (*ProductFeature, error) + Update(ctx context.Context, session sqlx.Session, data *ProductFeature) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *ProductFeature) 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 *ProductFeature) 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) ([]*ProductFeature, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ProductFeature, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ProductFeature, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ProductFeature, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ProductFeature, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultProductFeatureModel struct { + sqlc.CachedConn + table string + } + + ProductFeature struct { + Id int64 `db:"id"` // 主键ID + ProductId int64 `db:"product_id"` // 产品ID + FeatureId int64 `db:"feature_id"` // 功能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"` // 版本号 + Sort int64 `db:"sort"` + IsImportant int64 `db:"is_important"` + Enable int64 `db:"enable"` + } +) + +func newProductFeatureModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultProductFeatureModel { + return &defaultProductFeatureModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`product_feature`", + } +} + +func (m *defaultProductFeatureModel) Insert(ctx context.Context, session sqlx.Session, data *ProductFeature) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpProductFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, data.Id) + bdrpProductFeatureProductIdFeatureIdKey := fmt.Sprintf("%s%v:%v", cacheBdrpProductFeatureProductIdFeatureIdPrefix, data.ProductId, data.FeatureId) + 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, productFeatureRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.ProductId, data.FeatureId, data.DeleteTime, data.DelState, data.Version, data.Sort, data.IsImportant, data.Enable) + } + return conn.ExecCtx(ctx, query, data.ProductId, data.FeatureId, data.DeleteTime, data.DelState, data.Version, data.Sort, data.IsImportant, data.Enable) + }, bdrpProductFeatureIdKey, bdrpProductFeatureProductIdFeatureIdKey) +} + +func (m *defaultProductFeatureModel) FindOne(ctx context.Context, id int64) (*ProductFeature, error) { + bdrpProductFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, id) + var resp ProductFeature + err := m.QueryRowCtx(ctx, &resp, bdrpProductFeatureIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", productFeatureRows, 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 *defaultProductFeatureModel) FindOneByProductIdFeatureId(ctx context.Context, productId int64, featureId int64) (*ProductFeature, error) { + bdrpProductFeatureProductIdFeatureIdKey := fmt.Sprintf("%s%v:%v", cacheBdrpProductFeatureProductIdFeatureIdPrefix, productId, featureId) + var resp ProductFeature + err := m.QueryRowIndexCtx(ctx, &resp, bdrpProductFeatureProductIdFeatureIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `product_id` = ? and `feature_id` = ? and del_state = ? limit 1", productFeatureRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, productId, featureId, 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 *defaultProductFeatureModel) Update(ctx context.Context, session sqlx.Session, newData *ProductFeature) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpProductFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, data.Id) + bdrpProductFeatureProductIdFeatureIdKey := fmt.Sprintf("%s%v:%v", cacheBdrpProductFeatureProductIdFeatureIdPrefix, data.ProductId, data.FeatureId) + 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, productFeatureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.FeatureId, newData.DeleteTime, newData.DelState, newData.Version, newData.Sort, newData.IsImportant, newData.Enable, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.FeatureId, newData.DeleteTime, newData.DelState, newData.Version, newData.Sort, newData.IsImportant, newData.Enable, newData.Id) + }, bdrpProductFeatureIdKey, bdrpProductFeatureProductIdFeatureIdKey) +} + +func (m *defaultProductFeatureModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *ProductFeature) 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 + } + bdrpProductFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, data.Id) + bdrpProductFeatureProductIdFeatureIdKey := fmt.Sprintf("%s%v:%v", cacheBdrpProductFeatureProductIdFeatureIdPrefix, data.ProductId, data.FeatureId) + 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, productFeatureRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.ProductId, newData.FeatureId, newData.DeleteTime, newData.DelState, newData.Version, newData.Sort, newData.IsImportant, newData.Enable, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.ProductId, newData.FeatureId, newData.DeleteTime, newData.DelState, newData.Version, newData.Sort, newData.IsImportant, newData.Enable, newData.Id, oldVersion) + }, bdrpProductFeatureIdKey, bdrpProductFeatureProductIdFeatureIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultProductFeatureModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *ProductFeature) 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 "), "ProductFeatureModel delete err : %+v", err) + } + return nil +} + +func (m *defaultProductFeatureModel) 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 *defaultProductFeatureModel) 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 *defaultProductFeatureModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*ProductFeature, error) { + + builder = builder.Columns(productFeatureRows) + + 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 []*ProductFeature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductFeatureModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ProductFeature, error) { + + builder = builder.Columns(productFeatureRows) + + 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 []*ProductFeature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductFeatureModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*ProductFeature, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(productFeatureRows) + + 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 []*ProductFeature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultProductFeatureModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*ProductFeature, error) { + + builder = builder.Columns(productFeatureRows) + + 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 []*ProductFeature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductFeatureModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*ProductFeature, error) { + + builder = builder.Columns(productFeatureRows) + + 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 []*ProductFeature + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductFeatureModel) 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 *defaultProductFeatureModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultProductFeatureModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpProductFeatureIdKey := fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, id) + bdrpProductFeatureProductIdFeatureIdKey := fmt.Sprintf("%s%v:%v", cacheBdrpProductFeatureProductIdFeatureIdPrefix, data.ProductId, data.FeatureId) + _, 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) + }, bdrpProductFeatureIdKey, bdrpProductFeatureProductIdFeatureIdKey) + return err +} +func (m *defaultProductFeatureModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpProductFeatureIdPrefix, primary) +} +func (m *defaultProductFeatureModel) 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", productFeatureRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultProductFeatureModel) tableName() string { + return m.table +} diff --git a/app/main/model/productModel.go b/app/main/model/productModel.go new file mode 100644 index 0000000..df18c7a --- /dev/null +++ b/app/main/model/productModel.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 _ ProductModel = (*customProductModel)(nil) + +type ( + // ProductModel is an interface to be customized, add more methods here, + // and implement the added methods in customProductModel. + ProductModel interface { + productModel + } + + customProductModel struct { + *defaultProductModel + } +) + +// NewProductModel returns a model for the database table. +func NewProductModel(conn sqlx.SqlConn, c cache.CacheConf) ProductModel { + return &customProductModel{ + defaultProductModel: newProductModel(conn, c), + } +} diff --git a/app/main/model/productModel_gen.go b/app/main/model/productModel_gen.go new file mode 100644 index 0000000..061a6ce --- /dev/null +++ b/app/main/model/productModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + productFieldNames = builder.RawFieldNames(&Product{}) + productRows = strings.Join(productFieldNames, ",") + productRowsExpectAutoSet = strings.Join(stringx.Remove(productFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + productRowsWithPlaceHolder = strings.Join(stringx.Remove(productFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmProductIdPrefix = "cache:bdrp:product:id:" + cacheHmProductProductEnPrefix = "cache:bdrp:product:productEn:" +) + +type ( + productModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Product) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Product, error) + FindOneByProductEn(ctx context.Context, productEn string) (*Product, error) + Update(ctx context.Context, session sqlx.Session, data *Product) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Product) 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 *Product) 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) ([]*Product, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Product, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Product, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Product, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Product, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultProductModel struct { + sqlc.CachedConn + table string + } + + Product struct { + Id int64 `db:"id"` // 主键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"` // 版本号 + ProductName string `db:"product_name"` // 服务名 + ProductEn string `db:"product_en"` // 英文名 + Description string `db:"description"` // 描述 + Notes sql.NullString `db:"notes"` // 备注 + CostPrice float64 `db:"cost_price"` // 成本 + SellPrice float64 `db:"sell_price"` // 售价 + } +) + +func newProductModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultProductModel { + return &defaultProductModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`product`", + } +} + +func (m *defaultProductModel) Insert(ctx context.Context, session sqlx.Session, data *Product) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmProductIdKey := fmt.Sprintf("%s%v", cacheHmProductIdPrefix, data.Id) + hmProductProductEnKey := fmt.Sprintf("%s%v", cacheHmProductProductEnPrefix, data.ProductEn) + 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, productRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ProductName, data.ProductEn, data.Description, data.Notes, data.CostPrice, data.SellPrice) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ProductName, data.ProductEn, data.Description, data.Notes, data.CostPrice, data.SellPrice) + }, hmProductIdKey, hmProductProductEnKey) +} + +func (m *defaultProductModel) FindOne(ctx context.Context, id int64) (*Product, error) { + hmProductIdKey := fmt.Sprintf("%s%v", cacheHmProductIdPrefix, id) + var resp Product + err := m.QueryRowCtx(ctx, &resp, hmProductIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", productRows, 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 *defaultProductModel) FindOneByProductEn(ctx context.Context, productEn string) (*Product, error) { + hmProductProductEnKey := fmt.Sprintf("%s%v", cacheHmProductProductEnPrefix, productEn) + var resp Product + err := m.QueryRowIndexCtx(ctx, &resp, hmProductProductEnKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `product_en` = ? and del_state = ? limit 1", productRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, productEn, 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 *defaultProductModel) Update(ctx context.Context, session sqlx.Session, newData *Product) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmProductIdKey := fmt.Sprintf("%s%v", cacheHmProductIdPrefix, data.Id) + hmProductProductEnKey := fmt.Sprintf("%s%v", cacheHmProductProductEnPrefix, data.ProductEn) + 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, productRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ProductName, newData.ProductEn, newData.Description, newData.Notes, newData.CostPrice, newData.SellPrice, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ProductName, newData.ProductEn, newData.Description, newData.Notes, newData.CostPrice, newData.SellPrice, newData.Id) + }, hmProductIdKey, hmProductProductEnKey) +} + +func (m *defaultProductModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Product) 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 + } + hmProductIdKey := fmt.Sprintf("%s%v", cacheHmProductIdPrefix, data.Id) + hmProductProductEnKey := fmt.Sprintf("%s%v", cacheHmProductProductEnPrefix, data.ProductEn) + 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, productRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ProductName, newData.ProductEn, newData.Description, newData.Notes, newData.CostPrice, newData.SellPrice, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ProductName, newData.ProductEn, newData.Description, newData.Notes, newData.CostPrice, newData.SellPrice, newData.Id, oldVersion) + }, hmProductIdKey, hmProductProductEnKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultProductModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Product) 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 "), "ProductModel delete err : %+v", err) + } + return nil +} + +func (m *defaultProductModel) 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 *defaultProductModel) 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 *defaultProductModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Product, error) { + + builder = builder.Columns(productRows) + + 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 []*Product + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Product, error) { + + builder = builder.Columns(productRows) + + 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 []*Product + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Product, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(productRows) + + 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 []*Product + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultProductModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Product, error) { + + builder = builder.Columns(productRows) + + 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 []*Product + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Product, error) { + + builder = builder.Columns(productRows) + + 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 []*Product + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultProductModel) 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 *defaultProductModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultProductModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmProductIdKey := fmt.Sprintf("%s%v", cacheHmProductIdPrefix, id) + hmProductProductEnKey := fmt.Sprintf("%s%v", cacheHmProductProductEnPrefix, data.ProductEn) + _, 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) + }, hmProductIdKey, hmProductProductEnKey) + return err +} +func (m *defaultProductModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmProductIdPrefix, primary) +} +func (m *defaultProductModel) 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", productRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultProductModel) tableName() string { + return m.table +} diff --git a/app/main/model/queryCleanupConfigModel.go b/app/main/model/queryCleanupConfigModel.go new file mode 100644 index 0000000..1c69371 --- /dev/null +++ b/app/main/model/queryCleanupConfigModel.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 _ QueryCleanupConfigModel = (*customQueryCleanupConfigModel)(nil) + +type ( + // QueryCleanupConfigModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryCleanupConfigModel. + QueryCleanupConfigModel interface { + queryCleanupConfigModel + } + + customQueryCleanupConfigModel struct { + *defaultQueryCleanupConfigModel + } +) + +// NewQueryCleanupConfigModel returns a model for the database table. +func NewQueryCleanupConfigModel(conn sqlx.SqlConn, c cache.CacheConf) QueryCleanupConfigModel { + return &customQueryCleanupConfigModel{ + defaultQueryCleanupConfigModel: newQueryCleanupConfigModel(conn, c), + } +} diff --git a/app/main/model/queryCleanupConfigModel_gen.go b/app/main/model/queryCleanupConfigModel_gen.go new file mode 100644 index 0000000..3e4e184 --- /dev/null +++ b/app/main/model/queryCleanupConfigModel_gen.go @@ -0,0 +1,410 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + queryCleanupConfigFieldNames = builder.RawFieldNames(&QueryCleanupConfig{}) + queryCleanupConfigRows = strings.Join(queryCleanupConfigFieldNames, ",") + queryCleanupConfigRowsExpectAutoSet = strings.Join(stringx.Remove(queryCleanupConfigFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryCleanupConfigRowsWithPlaceHolder = strings.Join(stringx.Remove(queryCleanupConfigFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmQueryCleanupConfigIdPrefix = "cache:bdrp:queryCleanupConfig:id:" + cacheHmQueryCleanupConfigConfigKeyPrefix = "cache:bdrp:queryCleanupConfig:configKey:" +) + +type ( + queryCleanupConfigModel interface { + Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupConfig) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*QueryCleanupConfig, error) + FindOneByConfigKey(ctx context.Context, configKey string) (*QueryCleanupConfig, error) + Update(ctx context.Context, session sqlx.Session, data *QueryCleanupConfig) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryCleanupConfig) 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 *QueryCleanupConfig) 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) ([]*QueryCleanupConfig, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupConfig, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupConfig, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupConfig, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupConfig, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryCleanupConfigModel struct { + sqlc.CachedConn + table string + } + + QueryCleanupConfig struct { + Id int64 `db:"id"` // 主键ID + 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"` // 版本号 + ConfigKey string `db:"config_key"` // 配置键 + ConfigValue string `db:"config_value"` // 配置值 + ConfigDesc string `db:"config_desc"` // 配置说明 + Status int64 `db:"status"` // 状态:1-启用,2-禁用 + } +) + +func newQueryCleanupConfigModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryCleanupConfigModel { + return &defaultQueryCleanupConfigModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query_cleanup_config`", + } +} + +func (m *defaultQueryCleanupConfigModel) Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupConfig) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmQueryCleanupConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigConfigKeyPrefix, data.ConfigKey) + hmQueryCleanupConfigIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, 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, queryCleanupConfigRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ConfigKey, data.ConfigValue, data.ConfigDesc, data.Status) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ConfigKey, data.ConfigValue, data.ConfigDesc, data.Status) + }, hmQueryCleanupConfigConfigKeyKey, hmQueryCleanupConfigIdKey) +} + +func (m *defaultQueryCleanupConfigModel) FindOne(ctx context.Context, id int64) (*QueryCleanupConfig, error) { + hmQueryCleanupConfigIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, id) + var resp QueryCleanupConfig + err := m.QueryRowCtx(ctx, &resp, hmQueryCleanupConfigIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryCleanupConfigRows, 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 *defaultQueryCleanupConfigModel) FindOneByConfigKey(ctx context.Context, configKey string) (*QueryCleanupConfig, error) { + hmQueryCleanupConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigConfigKeyPrefix, configKey) + var resp QueryCleanupConfig + err := m.QueryRowIndexCtx(ctx, &resp, hmQueryCleanupConfigConfigKeyKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `config_key` = ? and del_state = ? limit 1", queryCleanupConfigRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, configKey, 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 *defaultQueryCleanupConfigModel) Update(ctx context.Context, session sqlx.Session, newData *QueryCleanupConfig) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmQueryCleanupConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigConfigKeyPrefix, data.ConfigKey) + hmQueryCleanupConfigIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, 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, queryCleanupConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ConfigKey, newData.ConfigValue, newData.ConfigDesc, newData.Status, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ConfigKey, newData.ConfigValue, newData.ConfigDesc, newData.Status, newData.Id) + }, hmQueryCleanupConfigConfigKeyKey, hmQueryCleanupConfigIdKey) +} + +func (m *defaultQueryCleanupConfigModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *QueryCleanupConfig) 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 + } + hmQueryCleanupConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigConfigKeyPrefix, data.ConfigKey) + hmQueryCleanupConfigIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, 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, queryCleanupConfigRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ConfigKey, newData.ConfigValue, newData.ConfigDesc, newData.Status, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ConfigKey, newData.ConfigValue, newData.ConfigDesc, newData.Status, newData.Id, oldVersion) + }, hmQueryCleanupConfigConfigKeyKey, hmQueryCleanupConfigIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryCleanupConfigModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryCleanupConfig) 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 "), "QueryCleanupConfigModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryCleanupConfigModel) 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 *defaultQueryCleanupConfigModel) 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 *defaultQueryCleanupConfigModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*QueryCleanupConfig, error) { + + builder = builder.Columns(queryCleanupConfigRows) + + 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 []*QueryCleanupConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupConfigModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupConfig, error) { + + builder = builder.Columns(queryCleanupConfigRows) + + 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 []*QueryCleanupConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupConfigModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupConfig, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryCleanupConfigRows) + + 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 []*QueryCleanupConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryCleanupConfigModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupConfig, error) { + + builder = builder.Columns(queryCleanupConfigRows) + + 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 []*QueryCleanupConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupConfigModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupConfig, error) { + + builder = builder.Columns(queryCleanupConfigRows) + + 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 []*QueryCleanupConfig + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupConfigModel) 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 *defaultQueryCleanupConfigModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryCleanupConfigModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmQueryCleanupConfigConfigKeyKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigConfigKeyPrefix, data.ConfigKey) + hmQueryCleanupConfigIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, 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) + }, hmQueryCleanupConfigConfigKeyKey, hmQueryCleanupConfigIdKey) + return err +} +func (m *defaultQueryCleanupConfigModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmQueryCleanupConfigIdPrefix, primary) +} +func (m *defaultQueryCleanupConfigModel) 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", queryCleanupConfigRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryCleanupConfigModel) tableName() string { + return m.table +} diff --git a/app/main/model/queryCleanupDetailModel.go b/app/main/model/queryCleanupDetailModel.go new file mode 100644 index 0000000..52de046 --- /dev/null +++ b/app/main/model/queryCleanupDetailModel.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 _ QueryCleanupDetailModel = (*customQueryCleanupDetailModel)(nil) + +type ( + // QueryCleanupDetailModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryCleanupDetailModel. + QueryCleanupDetailModel interface { + queryCleanupDetailModel + } + + customQueryCleanupDetailModel struct { + *defaultQueryCleanupDetailModel + } +) + +// NewQueryCleanupDetailModel returns a model for the database table. +func NewQueryCleanupDetailModel(conn sqlx.SqlConn, c cache.CacheConf) QueryCleanupDetailModel { + return &customQueryCleanupDetailModel{ + defaultQueryCleanupDetailModel: newQueryCleanupDetailModel(conn, c), + } +} diff --git a/app/main/model/queryCleanupDetailModel_gen.go b/app/main/model/queryCleanupDetailModel_gen.go new file mode 100644 index 0000000..83cc46d --- /dev/null +++ b/app/main/model/queryCleanupDetailModel_gen.go @@ -0,0 +1,374 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + queryCleanupDetailFieldNames = builder.RawFieldNames(&QueryCleanupDetail{}) + queryCleanupDetailRows = strings.Join(queryCleanupDetailFieldNames, ",") + queryCleanupDetailRowsExpectAutoSet = strings.Join(stringx.Remove(queryCleanupDetailFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryCleanupDetailRowsWithPlaceHolder = strings.Join(stringx.Remove(queryCleanupDetailFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmQueryCleanupDetailIdPrefix = "cache:bdrp:queryCleanupDetail:id:" +) + +type ( + queryCleanupDetailModel interface { + Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*QueryCleanupDetail, error) + Update(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) 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 *QueryCleanupDetail) 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) ([]*QueryCleanupDetail, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupDetail, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupDetail, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupDetail, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupDetail, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryCleanupDetailModel struct { + sqlc.CachedConn + table string + } + + QueryCleanupDetail struct { + Id int64 `db:"id"` // 主键ID + 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"` // 版本号 + CleanupLogId int64 `db:"cleanup_log_id"` // 关联的清理日志ID + QueryId int64 `db:"query_id"` // 被清理的查询记录ID + OrderId int64 `db:"order_id"` // 关联的订单ID + UserId int64 `db:"user_id"` // 关联的用户ID + ProductId int64 `db:"product_id"` // 关联的产品ID + QueryState string `db:"query_state"` // 查询状态 + CreateTimeOld time.Time `db:"create_time_old"` // 原记录创建时间 + } +) + +func newQueryCleanupDetailModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryCleanupDetailModel { + return &defaultQueryCleanupDetailModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query_cleanup_detail`", + } +} + +func (m *defaultQueryCleanupDetailModel) Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmQueryCleanupDetailIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, 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, queryCleanupDetailRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld) + }, hmQueryCleanupDetailIdKey) +} + +func (m *defaultQueryCleanupDetailModel) FindOne(ctx context.Context, id int64) (*QueryCleanupDetail, error) { + hmQueryCleanupDetailIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, id) + var resp QueryCleanupDetail + err := m.QueryRowCtx(ctx, &resp, hmQueryCleanupDetailIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryCleanupDetailRows, 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 *defaultQueryCleanupDetailModel) Update(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) (sql.Result, error) { + hmQueryCleanupDetailIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, 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, queryCleanupDetailRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld, data.Id) + }, hmQueryCleanupDetailIdKey) +} + +func (m *defaultQueryCleanupDetailModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + hmQueryCleanupDetailIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, 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, queryCleanupDetailRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupLogId, data.QueryId, data.OrderId, data.UserId, data.ProductId, data.QueryState, data.CreateTimeOld, data.Id, oldVersion) + }, hmQueryCleanupDetailIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryCleanupDetailModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryCleanupDetail) 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 "), "QueryCleanupDetailModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryCleanupDetailModel) 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 *defaultQueryCleanupDetailModel) 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 *defaultQueryCleanupDetailModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*QueryCleanupDetail, error) { + + builder = builder.Columns(queryCleanupDetailRows) + + 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 []*QueryCleanupDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupDetailModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupDetail, error) { + + builder = builder.Columns(queryCleanupDetailRows) + + 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 []*QueryCleanupDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupDetailModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupDetail, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryCleanupDetailRows) + + 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 []*QueryCleanupDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryCleanupDetailModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupDetail, error) { + + builder = builder.Columns(queryCleanupDetailRows) + + 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 []*QueryCleanupDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupDetailModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupDetail, error) { + + builder = builder.Columns(queryCleanupDetailRows) + + 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 []*QueryCleanupDetail + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupDetailModel) 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 *defaultQueryCleanupDetailModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryCleanupDetailModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + hmQueryCleanupDetailIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, 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) + }, hmQueryCleanupDetailIdKey) + return err +} +func (m *defaultQueryCleanupDetailModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmQueryCleanupDetailIdPrefix, primary) +} +func (m *defaultQueryCleanupDetailModel) 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", queryCleanupDetailRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryCleanupDetailModel) tableName() string { + return m.table +} diff --git a/app/main/model/queryCleanupLogModel.go b/app/main/model/queryCleanupLogModel.go new file mode 100644 index 0000000..b64cb69 --- /dev/null +++ b/app/main/model/queryCleanupLogModel.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 _ QueryCleanupLogModel = (*customQueryCleanupLogModel)(nil) + +type ( + // QueryCleanupLogModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryCleanupLogModel. + QueryCleanupLogModel interface { + queryCleanupLogModel + } + + customQueryCleanupLogModel struct { + *defaultQueryCleanupLogModel + } +) + +// NewQueryCleanupLogModel returns a model for the database table. +func NewQueryCleanupLogModel(conn sqlx.SqlConn, c cache.CacheConf) QueryCleanupLogModel { + return &customQueryCleanupLogModel{ + defaultQueryCleanupLogModel: newQueryCleanupLogModel(conn, c), + } +} diff --git a/app/main/model/queryCleanupLogModel_gen.go b/app/main/model/queryCleanupLogModel_gen.go new file mode 100644 index 0000000..1390265 --- /dev/null +++ b/app/main/model/queryCleanupLogModel_gen.go @@ -0,0 +1,373 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + queryCleanupLogFieldNames = builder.RawFieldNames(&QueryCleanupLog{}) + queryCleanupLogRows = strings.Join(queryCleanupLogFieldNames, ",") + queryCleanupLogRowsExpectAutoSet = strings.Join(stringx.Remove(queryCleanupLogFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryCleanupLogRowsWithPlaceHolder = strings.Join(stringx.Remove(queryCleanupLogFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmQueryCleanupLogIdPrefix = "cache:bdrp:queryCleanupLog:id:" +) + +type ( + queryCleanupLogModel interface { + Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*QueryCleanupLog, error) + Update(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) 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 *QueryCleanupLog) 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) ([]*QueryCleanupLog, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupLog, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupLog, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupLog, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupLog, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryCleanupLogModel struct { + sqlc.CachedConn + table string + } + + QueryCleanupLog struct { + Id int64 `db:"id"` // 主键ID + 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"` // 版本号 + CleanupTime time.Time `db:"cleanup_time"` // 清理执行时间 + CleanupBefore time.Time `db:"cleanup_before"` // 清理截止时间 + AffectedRows int64 `db:"affected_rows"` // 影响行数 + Status int64 `db:"status"` // 状态:1-成功,2-失败 + ErrorMsg sql.NullString `db:"error_msg"` // 错误信息 + Remark sql.NullString `db:"remark"` // 备注说明 + } +) + +func newQueryCleanupLogModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryCleanupLogModel { + return &defaultQueryCleanupLogModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query_cleanup_log`", + } +} + +func (m *defaultQueryCleanupLogModel) Insert(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmQueryCleanupLogIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, 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, queryCleanupLogRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark) + }, hmQueryCleanupLogIdKey) +} + +func (m *defaultQueryCleanupLogModel) FindOne(ctx context.Context, id int64) (*QueryCleanupLog, error) { + hmQueryCleanupLogIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, id) + var resp QueryCleanupLog + err := m.QueryRowCtx(ctx, &resp, hmQueryCleanupLogIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryCleanupLogRows, 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 *defaultQueryCleanupLogModel) Update(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) (sql.Result, error) { + hmQueryCleanupLogIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, 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, queryCleanupLogRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark, data.Id) + }, hmQueryCleanupLogIdKey) +} + +func (m *defaultQueryCleanupLogModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + hmQueryCleanupLogIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, 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, queryCleanupLogRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.CleanupTime, data.CleanupBefore, data.AffectedRows, data.Status, data.ErrorMsg, data.Remark, data.Id, oldVersion) + }, hmQueryCleanupLogIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryCleanupLogModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryCleanupLog) 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 "), "QueryCleanupLogModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryCleanupLogModel) 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 *defaultQueryCleanupLogModel) 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 *defaultQueryCleanupLogModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*QueryCleanupLog, error) { + + builder = builder.Columns(queryCleanupLogRows) + + 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 []*QueryCleanupLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupLogModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupLog, error) { + + builder = builder.Columns(queryCleanupLogRows) + + 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 []*QueryCleanupLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupLogModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryCleanupLog, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryCleanupLogRows) + + 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 []*QueryCleanupLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryCleanupLogModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryCleanupLog, error) { + + builder = builder.Columns(queryCleanupLogRows) + + 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 []*QueryCleanupLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupLogModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryCleanupLog, error) { + + builder = builder.Columns(queryCleanupLogRows) + + 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 []*QueryCleanupLog + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryCleanupLogModel) 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 *defaultQueryCleanupLogModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryCleanupLogModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + hmQueryCleanupLogIdKey := fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, 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) + }, hmQueryCleanupLogIdKey) + return err +} +func (m *defaultQueryCleanupLogModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmQueryCleanupLogIdPrefix, primary) +} +func (m *defaultQueryCleanupLogModel) 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", queryCleanupLogRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryCleanupLogModel) tableName() string { + return m.table +} diff --git a/app/main/model/queryModel.go b/app/main/model/queryModel.go new file mode 100644 index 0000000..adf9117 --- /dev/null +++ b/app/main/model/queryModel.go @@ -0,0 +1,60 @@ +package model + +import ( + "context" + "fmt" + "time" + "bdrp-server/common/globalkey" + + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ QueryModel = (*customQueryModel)(nil) + +type ( + // QueryModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryModel. + QueryModel interface { + queryModel + DeleteBefore(ctx context.Context, before time.Time) (int64, error) + } + + customQueryModel struct { + *defaultQueryModel + } +) + +// NewQueryModel returns a model for the database table. +func NewQueryModel(conn sqlx.SqlConn, c cache.CacheConf) QueryModel { + return &customQueryModel{ + defaultQueryModel: newQueryModel(conn, c), + } +} + +func (m *customQueryModel) DeleteBefore(ctx context.Context, before time.Time) (int64, error) { + var affected int64 = 0 + + // 使用事务处理批量删除 + err := m.defaultQueryModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error { + query := fmt.Sprintf("DELETE FROM %s WHERE create_time < ? AND del_state = ?", m.defaultQueryModel.table) + result, err := session.ExecCtx(ctx, query, before.Format("2006-01-02 15:04:05"), globalkey.DelStateNo) + if err != nil { + return err + } + + rows, err := result.RowsAffected() + if err != nil { + return err + } + + affected = rows + return nil + }) + + if err != nil { + return 0, err + } + + return affected, nil +} diff --git a/app/main/model/queryModel_gen.go b/app/main/model/queryModel_gen.go new file mode 100644 index 0000000..61a829e --- /dev/null +++ b/app/main/model/queryModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + queryFieldNames = builder.RawFieldNames(&Query{}) + queryRows = strings.Join(queryFieldNames, ",") + queryRowsExpectAutoSet = strings.Join(stringx.Remove(queryFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryRowsWithPlaceHolder = strings.Join(stringx.Remove(queryFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmQueryIdPrefix = "cache:bdrp:query:id:" + cacheHmQueryOrderIdPrefix = "cache:bdrp:query:orderId:" +) + +type ( + queryModel interface { + Insert(ctx context.Context, session sqlx.Session, data *Query) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Query, error) + FindOneByOrderId(ctx context.Context, orderId int64) (*Query, error) + Update(ctx context.Context, session sqlx.Session, data *Query) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Query) 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 *Query) 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) ([]*Query, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Query, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Query, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Query, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Query, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryModel struct { + sqlc.CachedConn + table string + } + + Query struct { + Id int64 `db:"id"` // 主键ID + OrderId int64 `db:"order_id"` // 订单ID(软关联到订单表) + UserId int64 `db:"user_id"` // 用户ID(直接关联到用户) + ProductId int64 `db:"product_id"` // 产品ID(直接关联到产品) + QueryParams string `db:"query_params"` // 查询params数据 + QueryData sql.NullString `db:"query_data"` // 查询结果数据 + QueryState string `db:"query_state"` // 查询状态 + DelState int64 `db:"del_state"` // 删除状态 + Version int64 `db:"version"` // 版本号 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + } +) + +func newQueryModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryModel { + return &defaultQueryModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query`", + } +} + +func (m *defaultQueryModel) Insert(ctx context.Context, session sqlx.Session, data *Query) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmQueryIdKey := fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, data.Id) + hmQueryOrderIdKey := fmt.Sprintf("%s%v", cacheHmQueryOrderIdPrefix, data.OrderId) + 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, queryRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.OrderId, data.UserId, data.ProductId, data.QueryParams, data.QueryData, data.QueryState, data.DelState, data.Version, data.DeleteTime) + } + return conn.ExecCtx(ctx, query, data.OrderId, data.UserId, data.ProductId, data.QueryParams, data.QueryData, data.QueryState, data.DelState, data.Version, data.DeleteTime) + }, hmQueryIdKey, hmQueryOrderIdKey) +} + +func (m *defaultQueryModel) FindOne(ctx context.Context, id int64) (*Query, error) { + hmQueryIdKey := fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, id) + var resp Query + err := m.QueryRowCtx(ctx, &resp, hmQueryIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryRows, 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 *defaultQueryModel) FindOneByOrderId(ctx context.Context, orderId int64) (*Query, error) { + hmQueryOrderIdKey := fmt.Sprintf("%s%v", cacheHmQueryOrderIdPrefix, orderId) + var resp Query + err := m.QueryRowIndexCtx(ctx, &resp, hmQueryOrderIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `order_id` = ? and del_state = ? limit 1", queryRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, orderId, 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 *defaultQueryModel) Update(ctx context.Context, session sqlx.Session, newData *Query) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmQueryIdKey := fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, data.Id) + hmQueryOrderIdKey := fmt.Sprintf("%s%v", cacheHmQueryOrderIdPrefix, data.OrderId) + 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, queryRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderId, newData.UserId, newData.ProductId, newData.QueryParams, newData.QueryData, newData.QueryState, newData.DelState, newData.Version, newData.DeleteTime, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.OrderId, newData.UserId, newData.ProductId, newData.QueryParams, newData.QueryData, newData.QueryState, newData.DelState, newData.Version, newData.DeleteTime, newData.Id) + }, hmQueryIdKey, hmQueryOrderIdKey) +} + +func (m *defaultQueryModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Query) 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 + } + hmQueryIdKey := fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, data.Id) + hmQueryOrderIdKey := fmt.Sprintf("%s%v", cacheHmQueryOrderIdPrefix, data.OrderId) + 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, queryRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.OrderId, newData.UserId, newData.ProductId, newData.QueryParams, newData.QueryData, newData.QueryState, newData.DelState, newData.Version, newData.DeleteTime, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.OrderId, newData.UserId, newData.ProductId, newData.QueryParams, newData.QueryData, newData.QueryState, newData.DelState, newData.Version, newData.DeleteTime, newData.Id, oldVersion) + }, hmQueryIdKey, hmQueryOrderIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Query) 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 "), "QueryModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryModel) 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 *defaultQueryModel) 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 *defaultQueryModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Query, error) { + + builder = builder.Columns(queryRows) + + 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 []*Query + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Query, error) { + + builder = builder.Columns(queryRows) + + 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 []*Query + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Query, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryRows) + + 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 []*Query + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Query, error) { + + builder = builder.Columns(queryRows) + + 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 []*Query + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Query, error) { + + builder = builder.Columns(queryRows) + + 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 []*Query + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryModel) 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 *defaultQueryModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmQueryIdKey := fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, id) + hmQueryOrderIdKey := fmt.Sprintf("%s%v", cacheHmQueryOrderIdPrefix, data.OrderId) + _, 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) + }, hmQueryIdKey, hmQueryOrderIdKey) + return err +} +func (m *defaultQueryModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmQueryIdPrefix, primary) +} +func (m *defaultQueryModel) 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", queryRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryModel) tableName() string { + return m.table +} diff --git a/app/main/model/queryUserRecordModel.go b/app/main/model/queryUserRecordModel.go new file mode 100644 index 0000000..c545a63 --- /dev/null +++ b/app/main/model/queryUserRecordModel.go @@ -0,0 +1,76 @@ +package model + +import ( + "context" + "fmt" + "time" + + "bdrp-server/common/globalkey" + + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ QueryUserRecordModel = (*customQueryUserRecordModel)(nil) + +type ( + // QueryUserRecordModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryUserRecordModel. + QueryUserRecordModel interface { + queryUserRecordModel + FindOneByQueryNo(ctx context.Context, queryNo string) (*QueryUserRecord, error) + CountByEncryptedIdCardIn72Hours(ctx context.Context, encryptedIdCard string) (int64, error) + } + + customQueryUserRecordModel struct { + *defaultQueryUserRecordModel + } +) + +// FindOneByQueryNo 根据 query_no 查询一条记录(query_no 与 order.order_no 一致) +func (m *customQueryUserRecordModel) FindOneByQueryNo(ctx context.Context, queryNo string) (*QueryUserRecord, error) { + query := fmt.Sprintf("select %s from %s where `query_no` = ? and del_state = ? limit 1", queryUserRecordRows, m.table) + var resp QueryUserRecord + err := m.QueryRowNoCacheCtx(ctx, &resp, query, queryNo, globalkey.DelStateNo) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +// CountByEncryptedIdCardIn72Hours 查询72小时内某个加密身份证号的已支付查询次数 +func (m *customQueryUserRecordModel) CountByEncryptedIdCardIn72Hours(ctx context.Context, encryptedIdCard string) (int64, error) { + // 计算72小时前的时间 + seventyTwoHoursAgo := time.Now().Add(-72 * time.Hour) + + // 关联 order 表,只统计已支付的订单 + query := fmt.Sprintf(` + select count(*) + from %s qur + inner join `+"`order`"+` o on qur.order_id = o.id + where qur.id_card = ? + and qur.create_time >= ? + and qur.del_state = ? + and qur.order_id > 0 + and o.status = 'paid' + and o.del_state = ? + `, m.table) + var count int64 + err := m.QueryRowNoCacheCtx(ctx, &count, query, encryptedIdCard, seventyTwoHoursAgo, globalkey.DelStateNo, globalkey.DelStateNo) + if err != nil { + return 0, err + } + return count, nil +} + +// NewQueryUserRecordModel returns a model for the database table. +func NewQueryUserRecordModel(conn sqlx.SqlConn, c cache.CacheConf) QueryUserRecordModel { + return &customQueryUserRecordModel{ + defaultQueryUserRecordModel: newQueryUserRecordModel(conn, c), + } +} diff --git a/app/main/model/queryUserRecordModel_gen.go b/app/main/model/queryUserRecordModel_gen.go new file mode 100644 index 0000000..49aa43c --- /dev/null +++ b/app/main/model/queryUserRecordModel_gen.go @@ -0,0 +1,376 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + queryUserRecordFieldNames = builder.RawFieldNames(&QueryUserRecord{}) + queryUserRecordRows = strings.Join(queryUserRecordFieldNames, ",") + queryUserRecordRowsExpectAutoSet = strings.Join(stringx.Remove(queryUserRecordFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryUserRecordRowsWithPlaceHolder = strings.Join(stringx.Remove(queryUserRecordFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpQueryUserRecordIdPrefix = "cache:bdrp:queryUserRecord:id:" +) + +type ( + queryUserRecordModel interface { + Insert(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*QueryUserRecord, error) + Update(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryUserRecord) 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 *QueryUserRecord) 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) ([]*QueryUserRecord, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryUserRecord, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryUserRecord, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryUserRecordModel struct { + sqlc.CachedConn + table string + } + + QueryUserRecord struct { + Id int64 `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"` // 版本号 + UserId int64 `db:"user_id"` // 用户ID + Name string `db:"name"` // 姓名密文(AES-ECB+Base64) + IdCard string `db:"id_card"` // 身份证号密文(AES-ECB+Base64) + Mobile string `db:"mobile"` // 手机号密文(AES-ECB+Base64) + Product string `db:"product"` // 产品类型,如 marriage/homeservice/riskassessment 等 + QueryNo string `db:"query_no"` // 查询单号(与 order.order_no 一致,如 Q_xxx),用户提交查询时生成 + OrderId int64 `db:"order_id"` // 订单ID,关联 order 表,用户发起支付并创建订单后写入 + PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号(支付宝/微信),支付成功后由回调写入 + AgentIdentifier sql.NullString `db:"agent_identifier"` // 代理标识,代理渠道时有值 + } +) + +func newQueryUserRecordModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryUserRecordModel { + return &defaultQueryUserRecordModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query_user_record`", + } +} + +func (m *defaultQueryUserRecordModel) Insert(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, 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, queryUserRecordRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier) + }, bdrpQueryUserRecordIdKey) +} + +func (m *defaultQueryUserRecordModel) FindOne(ctx context.Context, id int64) (*QueryUserRecord, error) { + bdrpQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, id) + var resp QueryUserRecord + err := m.QueryRowCtx(ctx, &resp, bdrpQueryUserRecordIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryUserRecordRows, 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 *defaultQueryUserRecordModel) Update(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) { + bdrpQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, 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, queryUserRecordRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id) + }, bdrpQueryUserRecordIdKey) +} + +func (m *defaultQueryUserRecordModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryUserRecord) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + bdrpQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, 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, queryUserRecordRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id, oldVersion) + }, bdrpQueryUserRecordIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryUserRecordModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryUserRecord) 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 "), "QueryUserRecordModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryUserRecordModel) 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 *defaultQueryUserRecordModel) 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 *defaultQueryUserRecordModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + 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 []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + 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 []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryUserRecordRows) + + 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 []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + 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 []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + 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 []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) 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 *defaultQueryUserRecordModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryUserRecordModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + bdrpQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, 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) + }, bdrpQueryUserRecordIdKey) + return err +} +func (m *defaultQueryUserRecordModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpQueryUserRecordIdPrefix, primary) +} +func (m *defaultQueryUserRecordModel) 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", queryUserRecordRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryUserRecordModel) tableName() string { + return m.table +} diff --git a/app/main/model/userAuthModel.go b/app/main/model/userAuthModel.go new file mode 100644 index 0000000..0812e1d --- /dev/null +++ b/app/main/model/userAuthModel.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 _ UserAuthModel = (*customUserAuthModel)(nil) + +type ( + // UserAuthModel is an interface to be customized, add more methods here, + // and implement the added methods in customUserAuthModel. + UserAuthModel interface { + userAuthModel + } + + customUserAuthModel struct { + *defaultUserAuthModel + } +) + +// NewUserAuthModel returns a model for the database table. +func NewUserAuthModel(conn sqlx.SqlConn, c cache.CacheConf) UserAuthModel { + return &customUserAuthModel{ + defaultUserAuthModel: newUserAuthModel(conn, c), + } +} diff --git a/app/main/model/userAuthModel_gen.go b/app/main/model/userAuthModel_gen.go new file mode 100644 index 0000000..d580e10 --- /dev/null +++ b/app/main/model/userAuthModel_gen.go @@ -0,0 +1,435 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + userAuthFieldNames = builder.RawFieldNames(&UserAuth{}) + userAuthRows = strings.Join(userAuthFieldNames, ",") + userAuthRowsExpectAutoSet = strings.Join(stringx.Remove(userAuthFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + userAuthRowsWithPlaceHolder = strings.Join(stringx.Remove(userAuthFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmUserAuthIdPrefix = "cache:bdrp:userAuth:id:" + cacheHmUserAuthAuthTypeAuthKeyPrefix = "cache:bdrp:userAuth:authType:authKey:" + cacheHmUserAuthUserIdAuthTypePrefix = "cache:bdrp:userAuth:userId:authType:" +) + +type ( + userAuthModel interface { + Insert(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*UserAuth, error) + FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserAuth, error) + FindOneByUserIdAuthType(ctx context.Context, userId int64, authType string) (*UserAuth, error) + Update(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *UserAuth) 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 *UserAuth) 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) ([]*UserAuth, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserAuth, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserAuth, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultUserAuthModel struct { + sqlc.CachedConn + table string + } + + UserAuth struct { + Id int64 `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"` // 版本号 + UserId int64 `db:"user_id"` + AuthKey string `db:"auth_key"` // 平台唯一id + AuthType string `db:"auth_type"` // 平台类型 + } +) + +func newUserAuthModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserAuthModel { + return &defaultUserAuthModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`user_auth`", + } +} + +func (m *defaultUserAuthModel) Insert(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmUserAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserAuthIdKey := fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, data.Id) + hmUserAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType) + 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, userAuthRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.AuthKey, data.AuthType) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.AuthKey, data.AuthType) + }, hmUserAuthAuthTypeAuthKeyKey, hmUserAuthIdKey, hmUserAuthUserIdAuthTypeKey) +} + +func (m *defaultUserAuthModel) FindOne(ctx context.Context, id int64) (*UserAuth, error) { + hmUserAuthIdKey := fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, id) + var resp UserAuth + err := m.QueryRowCtx(ctx, &resp, hmUserAuthIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userAuthRows, 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 *defaultUserAuthModel) FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserAuth, error) { + hmUserAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthAuthTypeAuthKeyPrefix, authType, authKey) + var resp UserAuth + err := m.QueryRowIndexCtx(ctx, &resp, hmUserAuthAuthTypeAuthKeyKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `auth_type` = ? and `auth_key` = ? and del_state = ? limit 1", userAuthRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, authType, authKey, 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 *defaultUserAuthModel) FindOneByUserIdAuthType(ctx context.Context, userId int64, authType string) (*UserAuth, error) { + hmUserAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthUserIdAuthTypePrefix, userId, authType) + var resp UserAuth + err := m.QueryRowIndexCtx(ctx, &resp, hmUserAuthUserIdAuthTypeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `user_id` = ? and `auth_type` = ? and del_state = ? limit 1", userAuthRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, userId, authType, 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 *defaultUserAuthModel) Update(ctx context.Context, session sqlx.Session, newData *UserAuth) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmUserAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserAuthIdKey := fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, data.Id) + hmUserAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType) + 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, userAuthRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id) + }, hmUserAuthAuthTypeAuthKeyKey, hmUserAuthIdKey, hmUserAuthUserIdAuthTypeKey) +} + +func (m *defaultUserAuthModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *UserAuth) 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 + } + hmUserAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserAuthIdKey := fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, data.Id) + hmUserAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType) + 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, userAuthRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id, oldVersion) + }, hmUserAuthAuthTypeAuthKeyKey, hmUserAuthIdKey, hmUserAuthUserIdAuthTypeKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultUserAuthModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *UserAuth) 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 "), "UserAuthModel delete err : %+v", err) + } + return nil +} + +func (m *defaultUserAuthModel) 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 *defaultUserAuthModel) 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 *defaultUserAuthModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*UserAuth, error) { + + builder = builder.Columns(userAuthRows) + + 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 []*UserAuth + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserAuthModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, error) { + + builder = builder.Columns(userAuthRows) + + 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 []*UserAuth + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserAuthModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(userAuthRows) + + 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 []*UserAuth + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultUserAuthModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserAuth, error) { + + builder = builder.Columns(userAuthRows) + + 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 []*UserAuth + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserAuthModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserAuth, error) { + + builder = builder.Columns(userAuthRows) + + 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 []*UserAuth + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserAuthModel) 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 *defaultUserAuthModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultUserAuthModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmUserAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserAuthIdKey := fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, id) + hmUserAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheHmUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType) + _, 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) + }, hmUserAuthAuthTypeAuthKeyKey, hmUserAuthIdKey, hmUserAuthUserIdAuthTypeKey) + return err +} +func (m *defaultUserAuthModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmUserAuthIdPrefix, primary) +} +func (m *defaultUserAuthModel) 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", userAuthRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultUserAuthModel) tableName() string { + return m.table +} diff --git a/app/main/model/userModel.go b/app/main/model/userModel.go new file mode 100644 index 0000000..8123712 --- /dev/null +++ b/app/main/model/userModel.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 _ UserModel = (*customUserModel)(nil) + +type ( + // UserModel is an interface to be customized, add more methods here, + // and implement the added methods in customUserModel. + UserModel interface { + userModel + } + + customUserModel struct { + *defaultUserModel + } +) + +// NewUserModel returns a model for the database table. +func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel { + return &customUserModel{ + defaultUserModel: newUserModel(conn, c), + } +} diff --git a/app/main/model/userModel_gen.go b/app/main/model/userModel_gen.go new file mode 100644 index 0000000..2654ed8 --- /dev/null +++ b/app/main/model/userModel_gen.go @@ -0,0 +1,412 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + userFieldNames = builder.RawFieldNames(&User{}) + userRows = strings.Join(userFieldNames, ",") + userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheBdrpUserIdPrefix = "cache:bdrp:user:id:" + cacheBdrpUserMobilePrefix = "cache:bdrp:user:mobile:" +) + +type ( + userModel interface { + Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*User, error) + FindOneByMobile(ctx context.Context, mobile sql.NullString) (*User, error) + Update(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *User) 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 *User) 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) ([]*User, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultUserModel struct { + sqlc.CachedConn + table string + } + + User struct { + Id int64 `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"` // 版本号 + Mobile sql.NullString `db:"mobile"` + Password sql.NullString `db:"password"` + Nickname sql.NullString `db:"nickname"` + Info string `db:"info"` + Inside int64 `db:"inside"` + Disable int64 `db:"disable"` // 0可用 1禁用 + } +) + +func newUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserModel { + return &defaultUserModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`user`", + } +} + +func (m *defaultUserModel) Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id) + bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile) + 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, userRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable) + }, bdrpUserIdKey, bdrpUserMobileKey) +} + +func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) { + bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, id) + var resp User + err := m.QueryRowCtx(ctx, &resp, bdrpUserIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userRows, 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 *defaultUserModel) FindOneByMobile(ctx context.Context, mobile sql.NullString) (*User, error) { + bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, mobile) + var resp User + err := m.QueryRowIndexCtx(ctx, &resp, bdrpUserMobileKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `mobile` = ? and del_state = ? limit 1", userRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, mobile, 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 *defaultUserModel) Update(ctx context.Context, session sqlx.Session, newData *User) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id) + bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile) + 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, userRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id) + }, bdrpUserIdKey, bdrpUserMobileKey) +} + +func (m *defaultUserModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *User) 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 + } + bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id) + bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile) + 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, userRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion) + }, bdrpUserIdKey, bdrpUserMobileKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultUserModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *User) 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 "), "UserModel delete err : %+v", err) + } + return nil +} + +func (m *defaultUserModel) 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 *defaultUserModel) 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 *defaultUserModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*User, error) { + + builder = builder.Columns(userRows) + + 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 []*User + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error) { + + builder = builder.Columns(userRows) + + 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 []*User + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(userRows) + + 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 []*User + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultUserModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error) { + + builder = builder.Columns(userRows) + + 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 []*User + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error) { + + builder = builder.Columns(userRows) + + 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 []*User + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserModel) 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 *defaultUserModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultUserModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, id) + bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile) + _, 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) + }, bdrpUserIdKey, bdrpUserMobileKey) + return err +} +func (m *defaultUserModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, primary) +} +func (m *defaultUserModel) 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", userRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultUserModel) tableName() string { + return m.table +} diff --git a/app/main/model/userTempModel.go b/app/main/model/userTempModel.go new file mode 100644 index 0000000..bcb7978 --- /dev/null +++ b/app/main/model/userTempModel.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 _ UserTempModel = (*customUserTempModel)(nil) + +type ( + // UserTempModel is an interface to be customized, add more methods here, + // and implement the added methods in customUserTempModel. + UserTempModel interface { + userTempModel + } + + customUserTempModel struct { + *defaultUserTempModel + } +) + +// NewUserTempModel returns a model for the database table. +func NewUserTempModel(conn sqlx.SqlConn, c cache.CacheConf) UserTempModel { + return &customUserTempModel{ + defaultUserTempModel: newUserTempModel(conn, c), + } +} diff --git a/app/main/model/userTempModel_gen.go b/app/main/model/userTempModel_gen.go new file mode 100644 index 0000000..4a031ea --- /dev/null +++ b/app/main/model/userTempModel_gen.go @@ -0,0 +1,408 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "bdrp-server/common/globalkey" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + userTempFieldNames = builder.RawFieldNames(&UserTemp{}) + userTempRows = strings.Join(userTempFieldNames, ",") + userTempRowsExpectAutoSet = strings.Join(stringx.Remove(userTempFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + userTempRowsWithPlaceHolder = strings.Join(stringx.Remove(userTempFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheHmUserTempIdPrefix = "cache:bdrp:userTemp:id:" + cacheHmUserTempAuthTypeAuthKeyPrefix = "cache:bdrp:userTemp:authType:authKey:" +) + +type ( + userTempModel interface { + Insert(ctx context.Context, session sqlx.Session, data *UserTemp) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*UserTemp, error) + FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserTemp, error) + Update(ctx context.Context, session sqlx.Session, data *UserTemp) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *UserTemp) 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 *UserTemp) 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) ([]*UserTemp, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserTemp, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserTemp, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserTemp, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserTemp, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultUserTempModel struct { + sqlc.CachedConn + table string + } + + UserTemp struct { + Id int64 `db:"id"` + AuthKey string `db:"auth_key"` // 平台唯一id + AuthType string `db:"auth_type"` // 平台类型 + 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"` // 版本号 + } +) + +func newUserTempModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserTempModel { + return &defaultUserTempModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`user_temp`", + } +} + +func (m *defaultUserTempModel) Insert(ctx context.Context, session sqlx.Session, data *UserTemp) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + hmUserTempAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserTempAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserTempIdKey := fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, 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, userTempRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.AuthKey, data.AuthType, data.DeleteTime, data.DelState, data.Version) + } + return conn.ExecCtx(ctx, query, data.AuthKey, data.AuthType, data.DeleteTime, data.DelState, data.Version) + }, hmUserTempAuthTypeAuthKeyKey, hmUserTempIdKey) +} + +func (m *defaultUserTempModel) FindOne(ctx context.Context, id int64) (*UserTemp, error) { + hmUserTempIdKey := fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, id) + var resp UserTemp + err := m.QueryRowCtx(ctx, &resp, hmUserTempIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userTempRows, 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 *defaultUserTempModel) FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserTemp, error) { + hmUserTempAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserTempAuthTypeAuthKeyPrefix, authType, authKey) + var resp UserTemp + err := m.QueryRowIndexCtx(ctx, &resp, hmUserTempAuthTypeAuthKeyKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where `auth_type` = ? and `auth_key` = ? and del_state = ? limit 1", userTempRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, authType, authKey, 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 *defaultUserTempModel) Update(ctx context.Context, session sqlx.Session, newData *UserTemp) (sql.Result, error) { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return nil, err + } + hmUserTempAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserTempAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserTempIdKey := fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, 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, userTempRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AuthKey, newData.AuthType, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + } + return conn.ExecCtx(ctx, query, newData.AuthKey, newData.AuthType, newData.DeleteTime, newData.DelState, newData.Version, newData.Id) + }, hmUserTempAuthTypeAuthKeyKey, hmUserTempIdKey) +} + +func (m *defaultUserTempModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *UserTemp) 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 + } + hmUserTempAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserTempAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserTempIdKey := fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, 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, userTempRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, newData.AuthKey, newData.AuthType, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, newData.AuthKey, newData.AuthType, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion) + }, hmUserTempAuthTypeAuthKeyKey, hmUserTempIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultUserTempModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *UserTemp) 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 "), "UserTempModel delete err : %+v", err) + } + return nil +} + +func (m *defaultUserTempModel) 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 *defaultUserTempModel) 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 *defaultUserTempModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*UserTemp, error) { + + builder = builder.Columns(userTempRows) + + 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 []*UserTemp + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserTempModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserTemp, error) { + + builder = builder.Columns(userTempRows) + + 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 []*UserTemp + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserTempModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserTemp, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(userTempRows) + + 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 []*UserTemp + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultUserTempModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserTemp, error) { + + builder = builder.Columns(userTempRows) + + 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 []*UserTemp + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserTempModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserTemp, error) { + + builder = builder.Columns(userTempRows) + + 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 []*UserTemp + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultUserTempModel) 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 *defaultUserTempModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultUserTempModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + hmUserTempAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheHmUserTempAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey) + hmUserTempIdKey := fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, 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) + }, hmUserTempAuthTypeAuthKeyKey, hmUserTempIdKey) + return err +} +func (m *defaultUserTempModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheHmUserTempIdPrefix, primary) +} +func (m *defaultUserTempModel) 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", userTempRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultUserTempModel) tableName() string { + return m.table +} diff --git a/app/main/model/vars.go b/app/main/model/vars.go new file mode 100644 index 0000000..174a22a --- /dev/null +++ b/app/main/model/vars.go @@ -0,0 +1,121 @@ +package model + +import ( + "errors" + + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var ErrNotFound = sqlx.ErrNotFound +var ErrNoRowsUpdate = errors.New("update db no rows change") + +// 平台 +var PlatformWxMini string = "wxmini" +var PlatformWxH5 string = "wxh5" +var PlatformApp string = "app" +var PlatformH5 string = "h5" +var PlatformAdmin string = "admin" + +// 用户授权类型 +var UserAuthTypeMobile string = "mobile" +var UserAuthTypeWxMiniOpenID string = "wxmini_openid" +var UserAuthTypeWxh5OpenID string = "wxh5_openid" +var UserAuthTypeUUID string = "uuid" + +// 代理扣除类型 +var AgentDeductionTypeCost string = "cost" +var AgentDeductionTypePricing string = "pricing" + +var AgentRewardsTypeDescendantPromotion string = "descendant_promotion" +var AgentRewardsTypeDescendantUpgradeVip string = "descendant_upgrade_vip" +var AgentRewardsTypeDescendantUpgradeSvip string = "descendant_upgrade_svip" +var AgentRewardsTypeDescendantStayActive string = "descendant_stay_active" +var AgentRewardsTypeDescendantNewActive string = "descendant_new_active" +var AgentRewardsTypeDescendantWithdraw string = "descendant_withdraw" + +var AgentLeveNameNormal string = "normal" +var AgentLeveNameVIP string = "VIP" +var AgentLeveNameSVIP string = "SVIP" + +const ( + OrderStatusPending = "pending" + OrderStatusPaid = "paid" + OrderStatusFailed = "failed" + OrderStatusRefunding = "refunding" + OrderStatusRefunded = "refunded" + OrderStatusClosed = "closed" +) +const ( + OrderRefundStatusPending = "pending" + OrderRefundStatusSuccess = "success" + OrderRefundStatusFailed = "failed" + OrderRefundStatusClosed = "closed" +) +const ( + QueryStatePending = "pending" + QueryStateFailed = "failed" + QueryStateSuccess = "success" + QueryStateProcessing = "processing" + QueryStateCleaned = "cleaned" + QueryStateRefunded = "refunded" +) + +const ( + GrantTypeFace string = "face" + AuthorizationGrantTypeSms = "sms" +) +const ( + AuthorizationStatusPending = "pending" + AuthorizationStatusSuccess = "success" + AuthorizationStatusFailed = "failed" + AuthorizationStatusExpired = "expired" + AuthorizationStatusRevoked = "revoked" + AuthorizationStatusRejected = "rejected" +) + +const ( + AuthorizationFaceStatusPending = "pending" + AuthorizationFaceStatusSuccess = "success" + AuthorizationFaceStatusFailed = "failed" +) + +const ( + AgentRealNameStatusPending = "pending" + AgentRealNameStatusApproved = "approved" + AgentRealNameStatusRejected = "rejected" +) + +// 用户身份类型 +const ( + UserTypeTemp = 0 // 临时用户 + UserTypeNormal = 1 // 正式用户 + UserTypeAdmin = 2 // 管理员 +) + +// 管理员角色编码 +const ( + AdminRoleCodeSuper = "SUPER" // 超级管理员 +) + +// 代理状态 +const ( + AgentStatusNo = 0 // 非代理 + AgentStatusYes = 1 // 是代理 +) +const ( + TaxStatusPending = 0 // 待扣税 + TaxStatusSuccess = 1 // 已扣税 + TaxStatusExempt = 2 // 免税 + TaxStatusFailed = 3 // 扣税失败 +) + +// 钱包交易类型 +const ( + WalletTransactionTypeCommission = "commission" // 佣金收入 + WalletTransactionTypeWithdraw = "withdraw" // 提现 + WalletTransactionTypeFreeze = "freeze" // 冻结 + WalletTransactionTypeUnfreeze = "unfreeze" // 解冻 + WalletTransactionTypeReward = "reward" // 奖励 + WalletTransactionTypeRefund = "refund" // 退款 + WalletTransactionTypeAdjust = "adjust" // 调整 +) diff --git a/common/ctxdata/ctxData.go b/common/ctxdata/ctxData.go new file mode 100644 index 0000000..8786bff --- /dev/null +++ b/common/ctxdata/ctxData.go @@ -0,0 +1,104 @@ +package ctxdata + +import ( + "context" + "bdrp-server/app/main/model" + jwtx "bdrp-server/common/jwt" + "encoding/json" + "errors" + "fmt" +) + +const CtxKeyJwtUserId = "userId" + +// 定义错误类型 +var ( + ErrNoInCtx = errors.New("上下文中没有相关数据") + ErrInvalidUserId = errors.New("用户ID格式无效") // 数据异常 +) + +// GetUidFromCtx 从 context 中获取用户 ID +func GetUidFromCtx(ctx context.Context) (int64, error) { + // 尝试从上下文中获取 jwtUserId + value := ctx.Value(CtxKeyJwtUserId) + if value == nil { + claims, err := GetClaimsFromCtx(ctx) + if err != nil { + return 0, err + } + return claims.UserId, nil + } + + // 根据值的类型进行不同处理 + switch v := value.(type) { + case json.Number: + // 如果是 json.Number 类型,转换为 int64 + uid, err := v.Int64() + if err != nil { + return 0, fmt.Errorf("%w: %v", ErrInvalidUserId, err) + } + return uid, nil + case int64: + // 如果已经是 int64 类型,直接返回 + return v, nil + case float64: + // 有些JSON解析器可能会将数字解析为float64 + return int64(v), nil + case int: + // 处理int类型 + return int64(v), nil + default: + // 其他类型都视为无效 + return 0, fmt.Errorf("%w: 期望类型 json.Number 或 int64, 实际类型 %T", ErrInvalidUserId, value) + } +} + +func GetClaimsFromCtx(ctx context.Context) (*jwtx.JwtClaims, error) { + value := ctx.Value(jwtx.ExtraKey) + if value == nil { + return nil, ErrNoInCtx + } + + // 首先尝试直接断言为 *jwtx.JwtClaims + if claims, ok := value.(*jwtx.JwtClaims); ok { + return claims, nil + } + + // 如果直接断言失败,尝试从 map[string]interface{} 中解析 + if claimsMap, ok := value.(map[string]interface{}); ok { + return jwtx.MapToJwtClaims(claimsMap) + } + + return nil, ErrNoInCtx +} + +// IsNoUserIdError 判断是否是未登录错误 +func IsNoUserIdError(err error) bool { + return errors.Is(err, ErrNoInCtx) +} + +// IsInvalidUserIdError 判断是否是用户ID格式错误 +func IsInvalidUserIdError(err error) bool { + return errors.Is(err, ErrInvalidUserId) +} + +// GetPlatformFromCtx 从 context 中获取平台 +func GetPlatformFromCtx(ctx context.Context) (string, error) { + platform, platformOk := ctx.Value("platform").(string) + if !platformOk { + return "", fmt.Errorf("平台不存在: %s", platform) + } + + switch platform { + case model.PlatformWxMini: + return model.PlatformWxMini, nil + case model.PlatformWxH5: + return model.PlatformWxH5, nil + case model.PlatformApp: + return model.PlatformApp, nil + case model.PlatformH5: + return model.PlatformH5, nil + default: + return "", fmt.Errorf("不支持的支付平台: %s", platform) + } +} diff --git a/common/globalkey/constantKey.go b/common/globalkey/constantKey.go new file mode 100644 index 0000000..584938d --- /dev/null +++ b/common/globalkey/constantKey.go @@ -0,0 +1,14 @@ +package globalkey + +/** +global constant key +*/ + +//软删除 +var DelStateNo int64 = 0 //未删除 +var DelStateYes int64 = 1 //已删除 + +//时间格式化模版 +var DateTimeFormatTplStandardDateTime = "Y-m-d H:i:s" +var DateTimeFormatTplStandardDate = "Y-m-d" +var DateTimeFormatTplStandardTime = "H:i:s" diff --git a/common/globalkey/redisCacheKey.go b/common/globalkey/redisCacheKey.go new file mode 100644 index 0000000..9296e19 --- /dev/null +++ b/common/globalkey/redisCacheKey.go @@ -0,0 +1,9 @@ +package globalkey + +/** +redis key except "model cache key" in here, +but "model cache key" in model +*/ + +// CacheUserTokenKey /** 用户登陆的token +const CacheUserTokenKey = "user_token:%d" diff --git a/common/interceptor/rpcserver/loggerInterceptor.go b/common/interceptor/rpcserver/loggerInterceptor.go new file mode 100644 index 0000000..c25c178 --- /dev/null +++ b/common/interceptor/rpcserver/loggerInterceptor.go @@ -0,0 +1,39 @@ +package rpcserver + +import ( + "context" + + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +/** +* @Description rpc service logger interceptor +* @Author Mikael +* @Date 2021/1/9 13:35 +* @Version 1.0 +**/ + +func LoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + + resp, err = handler(ctx, req) + if err != nil { + causeErr := errors.Cause(err) // err类型 + if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 + logx.WithContext(ctx).Errorf("【RPC-SRV-ERR】 %v", err) + + //转成grpc err + err = status.Error(codes.Code(e.GetErrCode()), e.GetErrMsg()) + } else { + logx.WithContext(ctx).Errorf("【RPC-SRV-ERR】 %v", err) + } + + } + + return resp, err +} diff --git a/common/jwt/jwtx.go b/common/jwt/jwtx.go new file mode 100644 index 0000000..1fd93c7 --- /dev/null +++ b/common/jwt/jwtx.go @@ -0,0 +1,111 @@ +package jwtx + +import ( + "encoding/json" + "errors" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +const ExtraKey = "extra" + +type JwtClaims struct { + UserId int64 `json:"userId"` + AgentId int64 `json:"agentId"` + Platform string `json:"platform"` + // 用户身份类型:0-临时用户,1-正式用户 + UserType int64 `json:"userType"` + // 是否代理:0-否,1-是 + IsAgent int64 `json:"isAgent"` +} + +// MapToJwtClaims 将 map[string]interface{} 转换为 JwtClaims 结构体 +func MapToJwtClaims(claimsMap map[string]interface{}) (*JwtClaims, error) { + // 使用JSON序列化/反序列化的方式自动转换 + jsonData, err := json.Marshal(claimsMap) + if err != nil { + return nil, errors.New("序列化claims失败") + } + + var claims JwtClaims + if err := json.Unmarshal(jsonData, &claims); err != nil { + return nil, errors.New("反序列化claims失败") + } + + return &claims, nil +} + +// GenerateJwtToken 生成JWT token +func GenerateJwtToken(claims JwtClaims, secret string, expire int64) (string, error) { + now := time.Now().Unix() + + // 将 claims 结构体转换为 map[string]interface{} + claimsBytes, err := json.Marshal(claims) + if err != nil { + return "", err + } + + var claimsMap map[string]interface{} + if err := json.Unmarshal(claimsBytes, &claimsMap); err != nil { + return "", err + } + + jwtClaims := jwt.MapClaims{ + "exp": now + expire, + "iat": now, + "userId": claims.UserId, + ExtraKey: claimsMap, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims) + return token.SignedString([]byte(secret)) +} + +func ParseJwtToken(tokenStr string, secret string) (*JwtClaims, error) { + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }) + + if err != nil { + // 检查是否是JWT验证错误 + if validationErr, ok := err.(*jwt.ValidationError); ok { + // 如果是过期错误,返回更明确的错误信息 + if validationErr.Errors&jwt.ValidationErrorExpired != 0 { + return nil, errors.New("token已过期") + } + // 如果是签名错误,返回签名错误信息 + if validationErr.Errors&jwt.ValidationErrorSignatureInvalid != 0 { + return nil, errors.New("token签名无效") + } + // 其他验证错误 + return nil, errors.New("token验证失败") + } + return nil, errors.New("invalid JWT") + } + + if !token.Valid { + return nil, errors.New("token无效") + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, errors.New("invalid JWT claims") + } + + extraInfo, exists := claims[ExtraKey] + if !exists { + return nil, errors.New("extra not found in JWT") + } + + // 尝试直接断言为 JwtClaims 结构体 + if jwtClaims, ok := extraInfo.(JwtClaims); ok { + return &jwtClaims, nil + } + + // 尝试从 map[string]interface{} 中解析 + if claimsMap, ok := extraInfo.(map[string]interface{}); ok { + return MapToJwtClaims(claimsMap) + } + + return nil, errors.New("unsupported extra type in JWT") +} diff --git a/common/jwt/jwtx_test.go b/common/jwt/jwtx_test.go new file mode 100644 index 0000000..13740f2 --- /dev/null +++ b/common/jwt/jwtx_test.go @@ -0,0 +1,393 @@ +package jwtx + +import ( + "strings" + "testing" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +func TestGenerateJwtToken(t *testing.T) { + // 测试数据 + testClaims := JwtClaims{ + UserId: 1, + AgentId: 0, + Platform: "wxh5", + UserType: 0, + IsAgent: 0, + } + testSecret := "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" + testExpire := int64(2592000) // 1小时 + + tests := []struct { + name string + claims JwtClaims + secret string + expire int64 + wantErr bool + }{ + { + name: "正常生成token", + claims: testClaims, + secret: testSecret, + expire: testExpire, + wantErr: false, + }, + { + name: "不同用户数据", + claims: JwtClaims{ + UserId: 99999, + AgentId: 11111, + Platform: "mobile", + UserType: 0, + IsAgent: 1, + }, + secret: testSecret, + expire: testExpire, + wantErr: false, + }, + { + name: "空密钥", + claims: testClaims, + secret: "", + expire: testExpire, + wantErr: false, // 空密钥不会导致生成失败,但验证时会失败 + }, + { + name: "零过期时间", + claims: testClaims, + secret: testSecret, + expire: 0, + wantErr: false, + }, + { + name: "负数过期时间", + claims: testClaims, + secret: testSecret, + expire: -3600, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + token, err := GenerateJwtToken(tt.claims, tt.secret, tt.expire) + + if (err != nil) != tt.wantErr { + t.Errorf("GenerateJwtToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // 验证token不为空 + if token == "" { + t.Error("GenerateJwtToken() 返回的token为空") + return + } + + // 验证token格式(JWT token应该包含两个点分隔符) + parts := strings.Split(token, ".") + if len(parts) != 3 { + t.Errorf("GenerateJwtToken() 返回的token格式不正确,期望3部分,实际%d部分", len(parts)) + return + } + + // 验证token可以被解析(不验证签名,只验证格式) + parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + return []byte(tt.secret), nil + }) + + if err == nil && parsedToken != nil { + // 验证claims是否正确设置 + if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok { + // 验证userId + if userId, exists := claims["userId"]; exists { + if int64(userId.(float64)) != tt.claims.UserId { + t.Errorf("token中的userId不匹配,期望%d,实际%v", tt.claims.UserId, userId) + } + } else { + t.Error("token中缺少userId字段") + } + + // 验证extra字段存在 + if _, exists := claims[ExtraKey]; !exists { + t.Error("token中缺少extra字段") + } + + // 验证exp字段 + if exp, exists := claims["exp"]; exists { + expTime := int64(exp.(float64)) + now := time.Now().Unix() + expectedExp := now + tt.expire + // 允许5秒的时间差异 + if expTime < expectedExp-5 || expTime > expectedExp+5 { + t.Errorf("token过期时间不正确,期望约%d,实际%d", expectedExp, expTime) + } + } else { + t.Error("token中缺少exp字段") + } + + // 验证iat字段 + if _, exists := claims["iat"]; !exists { + t.Error("token中缺少iat字段") + } + } + } + + t.Logf("生成的token: %s", token) + } + }) + } +} + +func TestGenerateJwtTokenAndParse(t *testing.T) { + // 测试生成token后能够正确解析 + testClaims := JwtClaims{ + UserId: 12345, + AgentId: 67890, + Platform: "web", + UserType: 1, + IsAgent: 0, + } + testSecret := "test-secret-key" + testExpire := int64(3600) + + // 生成token + token, err := GenerateJwtToken(testClaims, testSecret, testExpire) + if err != nil { + t.Fatalf("GenerateJwtToken() failed: %v", err) + } + + // 解析token + parsedClaims, err := ParseJwtToken(token, testSecret) + if err != nil { + t.Fatalf("ParseJwtToken() failed: %v", err) + } + + // 验证解析出的claims与原始claims一致 + if parsedClaims.UserId != testClaims.UserId { + t.Errorf("UserId不匹配,期望%d,实际%d", testClaims.UserId, parsedClaims.UserId) + } + if parsedClaims.AgentId != testClaims.AgentId { + t.Errorf("AgentId不匹配,期望%d,实际%d", testClaims.AgentId, parsedClaims.AgentId) + } + if parsedClaims.Platform != testClaims.Platform { + t.Errorf("Platform不匹配,期望%s,实际%s", testClaims.Platform, parsedClaims.Platform) + } + if parsedClaims.UserType != testClaims.UserType { + t.Errorf("UserType不匹配,期望%d,实际%d", testClaims.UserType, parsedClaims.UserType) + } + if parsedClaims.IsAgent != testClaims.IsAgent { + t.Errorf("IsAgent不匹配,期望%d,实际%d", testClaims.IsAgent, parsedClaims.IsAgent) + } + + t.Logf("测试通过: 生成token并成功解析,claims数据一致") +} + +func BenchmarkGenerateJwtToken(t *testing.B) { + // 性能测试 + testClaims := JwtClaims{ + UserId: 12345, + AgentId: 67890, + Platform: "web", + UserType: 1, + IsAgent: 0, + } + testSecret := "test-secret-key" + testExpire := int64(3600) + + t.ResetTimer() + for i := 0; i < t.N; i++ { + _, err := GenerateJwtToken(testClaims, testSecret, testExpire) + if err != nil { + t.Fatalf("GenerateJwtToken() failed: %v", err) + } + } +} + +func TestParseJwtToken(t *testing.T) { + // 使用你修改的测试数据 + testClaims := JwtClaims{ + UserId: 6, + AgentId: 0, + Platform: "wxh5", + UserType: 0, + IsAgent: 0, + } + testSecret := "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" + testExpire := int64(2592000) // 30天 + + // 先生成一个token用于测试 + token, err := GenerateJwtToken(testClaims, testSecret, testExpire) + if err != nil { + t.Fatalf("生成token失败: %v", err) + } + + t.Logf("生成的测试token: %s", token) + + tests := []struct { + name string + token string + secret string + wantErr bool + wantClaims *JwtClaims + }{ + { + name: "正常解析token", + token: token, + secret: testSecret, + wantErr: false, + wantClaims: &testClaims, + }, + { + name: "错误的密钥", + token: token, + secret: "wrong-secret", + wantErr: true, + wantClaims: nil, + }, + { + name: "空token", + token: "", + secret: testSecret, + wantErr: true, + wantClaims: nil, + }, + { + name: "无效token格式", + token: "invalid.token.format", + secret: testSecret, + wantErr: true, + wantClaims: nil, + }, + { + name: "缺少点分隔符的token", + token: "invalidtoken", + secret: testSecret, + wantErr: true, + wantClaims: nil, + }, + { + name: "自定义token", + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTI5MDA5MTQsImV4dHJhIjp7ImFnZW50SWQiOjAsImlzQWdlbnQiOjAsInBsYXRmb3JtIjoid3hoNSIsInVzZXJJZCI6NiwidXNlclR5cGUiOjF9LCJpYXQiOjE3NTAzMDg5MTQsInVzZXJJZCI6Nn0.GPKgLOaALOIa1ft7Hipuo4YKFf5guYt0rz2MCDCSdCQ", + secret: testSecret, + wantErr: false, + wantClaims: &testClaims, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + claims, err := ParseJwtToken(tt.token, tt.secret) + + if (err != nil) != tt.wantErr { + t.Errorf("ParseJwtToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr && tt.wantClaims != nil { + if claims == nil { + t.Error("ParseJwtToken() 返回的claims为nil") + return + } + + // 验证各个字段 + if claims.UserId != tt.wantClaims.UserId { + t.Errorf("UserId不匹配,期望%d,实际%d", tt.wantClaims.UserId, claims.UserId) + } + if claims.AgentId != tt.wantClaims.AgentId { + t.Errorf("AgentId不匹配,期望%d,实际%d", tt.wantClaims.AgentId, claims.AgentId) + } + if claims.Platform != tt.wantClaims.Platform { + t.Errorf("Platform不匹配,期望%s,实际%s", tt.wantClaims.Platform, claims.Platform) + } + if claims.UserType != tt.wantClaims.UserType { + t.Errorf("UserType不匹配,期望%d,实际%d", tt.wantClaims.UserType, claims.UserType) + } + if claims.IsAgent != tt.wantClaims.IsAgent { + t.Errorf("IsAgent不匹配,期望%d,实际%d", tt.wantClaims.IsAgent, claims.IsAgent) + } + + t.Logf("解析成功的claims: UserId=%d, AgentId=%d, Platform=%s, UserType=%d, IsAgent=%d", + claims.UserId, claims.AgentId, claims.Platform, claims.UserType, claims.IsAgent) + } + }) + } +} + +// TestParseCustomJwtToken 测试解析自定义token - 你可以在这里传入你自己的token +func TestParseCustomJwtToken(t *testing.T) { + // 在这里修改你想要测试的token和secret + customToken := "" // 在这里粘贴你的token + customSecret := "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" // 你的密钥 + + // 如果没有提供自定义token,跳过测试 + if customToken == "" { + t.Skip("跳过自定义token测试,请在代码中设置customToken值") + return + } + + t.Logf("解析自定义token: %s", customToken) + + claims, err := ParseJwtToken(customToken, customSecret) + if err != nil { + t.Fatalf("解析自定义token失败: %v", err) + } + + t.Logf("解析结果:") + t.Logf(" UserId: %d", claims.UserId) + t.Logf(" AgentId: %d", claims.AgentId) + t.Logf(" Platform: %s", claims.Platform) + t.Logf(" UserType: %d", claims.UserType) + t.Logf(" IsAgent: %d", claims.IsAgent) +} + +// TestGenerateAndParseWithRealData 生成一个真实的token并解析 +func TestGenerateAndParseWithRealData(t *testing.T) { + // 使用真实数据生成token + realClaims := JwtClaims{ + UserId: 1, + AgentId: 0, + Platform: "wxh5", + UserType: 0, + IsAgent: 0, + } + realSecret := "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" + realExpire := int64(2592000) // 30天 + + // 生成token + token, err := GenerateJwtToken(realClaims, realSecret, realExpire) + if err != nil { + t.Fatalf("生成token失败: %v", err) + } + + t.Logf("=== 生成的完整token ===") + t.Logf("Token: %s", token) + t.Logf("========================") + + // 解析token + parsedClaims, err := ParseJwtToken(token, realSecret) + if err != nil { + t.Fatalf("解析token失败: %v", err) + } + + t.Logf("=== 解析结果 ===") + t.Logf("UserId: %d", parsedClaims.UserId) + t.Logf("AgentId: %d", parsedClaims.AgentId) + t.Logf("Platform: %s", parsedClaims.Platform) + t.Logf("UserType: %d", parsedClaims.UserType) + t.Logf("IsAgent: %d", parsedClaims.IsAgent) + t.Logf("================") + + // 验证数据一致性 + if parsedClaims.UserId != realClaims.UserId || + parsedClaims.AgentId != realClaims.AgentId || + parsedClaims.Platform != realClaims.Platform || + parsedClaims.UserType != realClaims.UserType || + parsedClaims.IsAgent != realClaims.IsAgent { + t.Error("解析出的claims与原始数据不一致") + } else { + t.Log("✅ 数据一致性验证通过") + } +} diff --git a/common/kqueue/message.go b/common/kqueue/message.go new file mode 100644 index 0000000..e1df433 --- /dev/null +++ b/common/kqueue/message.go @@ -0,0 +1,8 @@ +//KqMessage +package kqueue + +//第三方支付回调更改支付状态通知 +type ThirdPaymentUpdatePayStatusNotifyMessage struct { + PayStatus int64 `json:"payStatus"` + OrderSn string `json:"orderSn"` +} diff --git a/common/middleware/commonJwtAuthMiddleware.go b/common/middleware/commonJwtAuthMiddleware.go new file mode 100644 index 0000000..db0dae7 --- /dev/null +++ b/common/middleware/commonJwtAuthMiddleware.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "github.com/zeromicro/go-zero/rest/handler" + "net/http" +) + +// CommonJwtAuthMiddleware : with jwt on the verification, no jwt on the verification +type CommonJwtAuthMiddleware struct { + secret string +} + +func NewCommonJwtAuthMiddleware(secret string) *CommonJwtAuthMiddleware { + return &CommonJwtAuthMiddleware{ + secret: secret, + } +} + +func (m *CommonJwtAuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if len(r.Header.Get("Authorization")) > 0 { + //has jwt Authorization + authHandler := handler.Authorize(m.secret) + authHandler(next).ServeHTTP(w, r) + return + } else { + //no jwt Authorization + next(w, r) + } + } +} diff --git a/common/result/httpResult.go b/common/result/httpResult.go new file mode 100644 index 0000000..787dc8c --- /dev/null +++ b/common/result/httpResult.go @@ -0,0 +1,89 @@ +package result + +import ( + "fmt" + "net/http" + + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest/httpx" + "google.golang.org/grpc/status" +) + +// http返回 +func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) { + + if err == nil { + httpx.WriteJson(w, http.StatusOK, Success(resp)) + } else { + //错误返回 + errcode := xerr.SERVER_COMMON_ERROR + errmsg := "服务器开小差啦,稍后再来试一试" + + causeErr := errors.Cause(err) // err类型 + if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 + //自定义CodeError + errcode = e.GetErrCode() + errmsg = e.GetErrMsg() + } else { + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 + grpcCode := uint32(gstatus.Code()) + if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 + errcode = grpcCode + errmsg = gstatus.Message() + } + } + } + + logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err) + + httpx.WriteJson(w, http.StatusOK, Error(errcode, errmsg)) + } +} + +// 授权的http方法 +func AuthHttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) { + + if err == nil { + //成功返回 + r := Success(resp) + httpx.WriteJson(w, http.StatusOK, r) + } else { + //错误返回 + errcode := xerr.SERVER_COMMON_ERROR + errmsg := "服务器开小差啦,稍后再来试一试" + + causeErr := errors.Cause(err) // err类型 + if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 + //自定义CodeError + errcode = e.GetErrCode() + errmsg = e.GetErrMsg() + } else { + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 + grpcCode := uint32(gstatus.Code()) + if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 + errcode = grpcCode + errmsg = gstatus.Message() + } + } + } + + logx.WithContext(r.Context()).Errorf("【GATEWAY-ERR】 : %+v ", err) + + httpx.WriteJson(w, http.StatusUnauthorized, Error(errcode, errmsg)) + } +} + +// http 参数错误返回 +func ParamErrorResult(r *http.Request, w http.ResponseWriter, err error) { + errMsg := fmt.Sprintf("%s,%s", xerr.MapErrMsg(xerr.REUQEST_PARAM_ERROR), err.Error()) + httpx.WriteJson(w, http.StatusOK, Error(xerr.REUQEST_PARAM_ERROR, errMsg)) +} + +// http 参数校验失败返回 +func ParamValidateErrorResult(r *http.Request, w http.ResponseWriter, err error) { + //errMsg := fmt.Sprintf("%s,%s", xerr.MapErrMsg(xerr.REUQEST_PARAM_ERROR), err.Error()) + httpx.WriteJson(w, http.StatusOK, Error(xerr.PARAM_VERIFICATION_ERROR, err.Error())) +} diff --git a/common/result/jobResult.go b/common/result/jobResult.go new file mode 100644 index 0000000..d7b5097 --- /dev/null +++ b/common/result/jobResult.go @@ -0,0 +1,44 @@ +package result + +import ( + "context" + + "bdrp-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/status" +) + +// job返回 +func JobResult(ctx context.Context, resp interface{}, err error) { + if err == nil { + // 成功返回 ,只有dev环境下才会打印info,线上不显示 + if resp != nil { + logx.Infof("resp: %+v", resp) + } + return + } else { + errCode := xerr.SERVER_COMMON_ERROR + errMsg := "服务器开小差啦,稍后再来试一试" + + // 错误返回 + causeErr := errors.Cause(err) // err类型 + if e, ok := causeErr.(*xerr.CodeError); ok { // 自定义错误类型 + // 自定义CodeError + errCode = e.GetErrCode() + errMsg = e.GetErrMsg() + } else { + if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 + grpcCode := uint32(gstatus.Code()) + if xerr.IsCodeErr(grpcCode) { // 区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 + errCode = grpcCode + errMsg = gstatus.Message() + } + } + } + + logx.WithContext(ctx).Errorf("【JOB-ERR】 : %+v ,errCode:%d , errMsg:%s ", err, errCode, errMsg) + return + } +} diff --git a/common/result/responseBean.go b/common/result/responseBean.go new file mode 100644 index 0000000..d29e1e0 --- /dev/null +++ b/common/result/responseBean.go @@ -0,0 +1,21 @@ +package result + +type ResponseSuccessBean struct { + Code uint32 `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} +type NullJson struct{} + +func Success(data interface{}) *ResponseSuccessBean { + return &ResponseSuccessBean{200, "OK", data} +} + +type ResponseErrorBean struct { + Code uint32 `json:"code"` + Msg string `json:"msg"` +} + +func Error(errCode uint32, errMsg string) *ResponseErrorBean { + return &ResponseErrorBean{errCode, errMsg} +} diff --git a/common/tool/coinconvert.go b/common/tool/coinconvert.go new file mode 100644 index 0000000..f04d461 --- /dev/null +++ b/common/tool/coinconvert.go @@ -0,0 +1,19 @@ +package tool + +import "github.com/shopspring/decimal" + +var oneHundredDecimal decimal.Decimal = decimal.NewFromInt(100) + +//分转元 +func Fen2Yuan(fen int64) float64 { + y, _ := decimal.NewFromInt(fen).Div(oneHundredDecimal).Truncate(2).Float64() + return y +} + +//元转分 +func Yuan2Fen(yuan float64) int64 { + + f, _ := decimal.NewFromFloat(yuan).Mul(oneHundredDecimal).Truncate(0).Float64() + return int64(f) + +} diff --git a/common/tool/encryption.go b/common/tool/encryption.go new file mode 100644 index 0000000..b94f562 --- /dev/null +++ b/common/tool/encryption.go @@ -0,0 +1,23 @@ +package tool + +import ( + "crypto/md5" + "fmt" + "io" +) + +/** 加密方式 **/ + +func Md5ByString(str string) string { + m := md5.New() + _, err := io.WriteString(m, str) + if err != nil { + panic(err) + } + arr := m.Sum(nil) + return fmt.Sprintf("%x", arr) +} + +func Md5ByBytes(b []byte) string { + return fmt.Sprintf("%x", md5.Sum(b)) +} diff --git a/common/tool/krand.go b/common/tool/krand.go new file mode 100644 index 0000000..fb5b869 --- /dev/null +++ b/common/tool/krand.go @@ -0,0 +1,28 @@ +package tool + +import ( + "math/rand" + "time" +) + +const ( + KC_RAND_KIND_NUM = 0 // 纯数字 + KC_RAND_KIND_LOWER = 1 // 小写字母 + KC_RAND_KIND_UPPER = 2 // 大写字母 + KC_RAND_KIND_ALL = 3 // 数字、大小写字母 +) + +// 随机字符串 +func Krand(size int, kind int) string { + ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) + is_all := kind > 2 || kind < 0 + rand.Seed(time.Now().UnixNano()) + for i := 0; i < size; i++ { + if is_all { // random ikind + ikind = rand.Intn(3) + } + scope, base := kinds[ikind][0], kinds[ikind][1] + result[i] = uint8(base + rand.Intn(scope)) + } + return string(result) +} diff --git a/common/tool/krand_test.go b/common/tool/krand_test.go new file mode 100644 index 0000000..c5c0356 --- /dev/null +++ b/common/tool/krand_test.go @@ -0,0 +1,8 @@ +package tool + +import "testing" + +func TestMd5ByString(t *testing.T) { + s := Md5ByString("AAA") + t.Log(s) +} diff --git a/common/tool/placeholders.go b/common/tool/placeholders.go new file mode 100644 index 0000000..53e28d1 --- /dev/null +++ b/common/tool/placeholders.go @@ -0,0 +1,15 @@ +package tool + +import "strings" + +//替换 +func InPlaceholders(n int) string { + var b strings.Builder + for i := 0; i < n-1; i++ { + b.WriteString("?,") + } + if n > 0 { + b.WriteString("?") + } + return b.String() +} diff --git a/common/uniqueid/sn.go b/common/uniqueid/sn.go new file mode 100644 index 0000000..0899f11 --- /dev/null +++ b/common/uniqueid/sn.go @@ -0,0 +1,20 @@ +package uniqueid + +import ( + "bdrp-server/common/tool" + "fmt" + "time" +) + +// 生成sn单号 +type SnPrefix string + +const ( + SN_PREFIX_HOMESTAY_ORDER SnPrefix = "HSO" //民宿订单前缀 bdrp-server_order/homestay_order + SN_PREFIX_THIRD_PAYMENT SnPrefix = "PMT" //第三方支付流水记录前缀 bdrp-server_payment/third_payment +) + +// 生成单号 +func GenSn(snPrefix SnPrefix) string { + return fmt.Sprintf("%s%s%s", snPrefix, time.Now().Format("20060102150405"), tool.Krand(8, tool.KC_RAND_KIND_NUM)) +} diff --git a/common/uniqueid/sn_test.go b/common/uniqueid/sn_test.go new file mode 100644 index 0000000..6c12b9f --- /dev/null +++ b/common/uniqueid/sn_test.go @@ -0,0 +1,7 @@ +package uniqueid + +import "testing" + +func TestGenSn(t *testing.T) { + GenSn(SN_PREFIX_HOMESTAY_ORDER) +} diff --git a/common/uniqueid/uniqueid.go b/common/uniqueid/uniqueid.go new file mode 100644 index 0000000..ff4c474 --- /dev/null +++ b/common/uniqueid/uniqueid.go @@ -0,0 +1,23 @@ +package uniqueid + +import ( + "github.com/sony/sonyflake" + "github.com/zeromicro/go-zero/core/logx" +) + +var flake *sonyflake.Sonyflake + +func init() { + flake = sonyflake.NewSonyflake(sonyflake.Settings{}) +} + +func GenId() int64 { + + id, err := flake.NextID() + if err != nil { + logx.Severef("flake NextID failed with %s \n", err) + panic(err) + } + + return int64(id) +} diff --git a/common/wxminisub/tpl.go b/common/wxminisub/tpl.go new file mode 100644 index 0000000..d05af52 --- /dev/null +++ b/common/wxminisub/tpl.go @@ -0,0 +1,7 @@ +package wxminisub + +//订单支付成功 +const OrderPaySuccessTemplateID = "QIJPmfxaNqYzSjOlXGk1T6Xfw94JwbSPuOd3u_hi3WE" + +//支付成功入驻通知 +const OrderPaySuccessLiveKnowTemplateID = "kmm-maRr6v_9eMxEPpj-5clJ2YW_EFpd8-ngyYk63e4" diff --git a/common/xerr/errCode.go b/common/xerr/errCode.go new file mode 100644 index 0000000..396df56 --- /dev/null +++ b/common/xerr/errCode.go @@ -0,0 +1,24 @@ +package xerr + +// 成功返回 +const OK uint32 = 200 + +/**(前3位代表业务,后三位代表具体功能)**/ + +// 全局错误码 +const SERVER_COMMON_ERROR uint32 = 100001 +const REUQEST_PARAM_ERROR uint32 = 100002 +const TOKEN_EXPIRE_ERROR uint32 = 100003 +const TOKEN_GENERATE_ERROR uint32 = 100004 +const DB_ERROR uint32 = 100005 +const DB_UPDATE_AFFECTED_ZERO_ERROR uint32 = 100006 +const PARAM_VERIFICATION_ERROR uint32 = 100007 +const CUSTOM_ERROR uint32 = 100008 +const USER_NOT_FOUND uint32 = 100009 +const USER_NEED_BIND_MOBILE uint32 = 100010 +const USER_DISABLED uint32 = 100011 // 账号已被封禁 + +const LOGIN_FAILED uint32 = 200001 +const LOGIC_QUERY_WAIT uint32 = 200002 +const LOGIC_QUERY_ERROR uint32 = 200003 +const LOGIC_QUERY_NOT_FOUND uint32 = 200004 diff --git a/common/xerr/errMsg.go b/common/xerr/errMsg.go new file mode 100644 index 0000000..87cd63a --- /dev/null +++ b/common/xerr/errMsg.go @@ -0,0 +1,33 @@ +package xerr + +var message map[uint32]string + +func init() { + message = make(map[uint32]string) + message[OK] = "SUCCESS" + message[SERVER_COMMON_ERROR] = "系统正在升级,请稍后再试" + message[REUQEST_PARAM_ERROR] = "参数错误" + message[TOKEN_EXPIRE_ERROR] = "token失效,请重新登陆" + message[TOKEN_GENERATE_ERROR] = "生成token失败" + message[DB_ERROR] = "系统维护升级中,请稍后再试" + message[DB_UPDATE_AFFECTED_ZERO_ERROR] = "更新数据影响行数为0" + message[USER_NOT_FOUND] = "用户不存在" + message[USER_NEED_BIND_MOBILE] = "请先绑定手机号" + message[USER_DISABLED] = "账号已被封禁" +} + +func MapErrMsg(errcode uint32) string { + if msg, ok := message[errcode]; ok { + return msg + } else { + return "系统正在升级,请稍后再试" + } +} + +func IsCodeErr(errcode uint32) bool { + if _, ok := message[errcode]; ok { + return true + } else { + return false + } +} diff --git a/common/xerr/errors.go b/common/xerr/errors.go new file mode 100644 index 0000000..897e37e --- /dev/null +++ b/common/xerr/errors.go @@ -0,0 +1,39 @@ +package xerr + +import ( + "fmt" +) + +/** +常用通用固定错误 +*/ + +type CodeError struct { + errCode uint32 + errMsg string +} + +// 返回给前端的错误码 +func (e *CodeError) GetErrCode() uint32 { + return e.errCode +} + +// 返回给前端显示端错误信息 +func (e *CodeError) GetErrMsg() string { + return e.errMsg +} + +func (e *CodeError) Error() string { + return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg) +} + +func NewErrCodeMsg(errCode uint32, errMsg string) *CodeError { + return &CodeError{errCode: errCode, errMsg: errMsg} +} +func NewErrCode(errCode uint32) *CodeError { + return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode)} +} + +func NewErrMsg(errMsg string) *CodeError { + return &CodeError{errCode: CUSTOM_ERROR, errMsg: errMsg} +} diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 new file mode 100644 index 0000000..1659cbe --- /dev/null +++ b/deploy/script/gen_models.ps1 @@ -0,0 +1,64 @@ +# 设置输出编码为UTF-8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +# 数据库连接信息 - 修改了URL格式 +$DB_URL = "bdrp:d7X7E1KSxrZC@(127.0.0.1:21001)/bdrp" +$OUTPUT_DIR = "./model" +$TEMPLATE_DIR = "../template" + +# 表名列表 +$tables = @( + # "agent" + # "agent_active_stat", + # "agent_audit", + # "agent_closure", + # "agent_commission" + # "agent_wallet_transaction" + # "agent_commission_deduction" + # "agent_link", + # "agent_membership_config", + # "agent_membership_recharge_order" + # "agent_membership_user_config", + # "agent_order", + # "agent_platform_deduction" + # "agent_product_config", + # "agent_rewards", + # "agent_wallet", + # "agent_real_name" + # "agent_withdrawal" + # "agent_withdrawal_tax" + # "agent_withdrawal_tax_exemption" + # "feature" + # "global_notifications" + # "order" + # "order_refund" + # "product", + # "product_feature" + # "query", + # "query_user_record" # 查询用户记录表:姓名、身份证、手机号、支付订单号等 + # "query_cleanup_log" + # "query_cleanup_detail" + # "query_cleanup_config" + "user" + # "user_auth" + # "user_temp" + # "example" + # "admin_user" + # "admin_user_role" + # "admin_api", + # "admin_menu" + # "admin_role", + # "admin_role_api", + # "admin_role_menu", + # "admin_dict_data" + # "admin_dict_type" + # "admin_promotion_link" + # "admin_promotion_link_stats_total" + # "admin_promotion_link_stats_history" + # "admin_promotion_order" + +) + +# 为每个表生成模型 +foreach ($table in $tables) { + goctl model mysql datasource -url="bdrp:d7X7E1KSxrZC@tcp(127.0.0.1:21001)/bdrp" -table="$table" -dir="./model" --home="../template" -cache=true --style=goZero +} diff --git a/deploy/script/model/vars.go b/deploy/script/model/vars.go new file mode 100644 index 0000000..81462b5 --- /dev/null +++ b/deploy/script/model/vars.go @@ -0,0 +1,9 @@ +package model + +import ( + "errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var ErrNotFound = sqlx.ErrNotFound +var ErrNoRowsUpdate = errors.New("update db no rows change") \ No newline at end of file diff --git a/deploy/sql/docker-init/01_init.sql b/deploy/sql/docker-init/01_init.sql new file mode 100644 index 0000000..2bf4eca --- /dev/null +++ b/deploy/sql/docker-init/01_init.sql @@ -0,0 +1,5717 @@ +-- 导入脚本按 UTF-8 解析,避免中文注释/数据乱码(需文件本身以 UTF-8 保存) +SET NAMES utf8mb4 COLLATE utf8mb4_general_ci; + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; + +START TRANSACTION; + +SET time_zone = "+00:00"; + +USE bdrp; +-- + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_api` +-- + +CREATE TABLE `admin_api` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `api_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '接口名称', + `api_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '接口编码', + `method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求方法:GET、POST等', + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '接口URL', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '接口描述' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '接口表'; + +-- +-- 转存表中的数据 `admin_api` +-- + +INSERT INTO + `admin_api` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_name`, + `api_code`, + `method`, + `url`, + `status`, + `description` + ) +VALUES ( + 1, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-commission-deduction-列表', + 'get__api_v1_admin_agent_agent-commission-deduction_list', + 'GET', + '/api/v1/admin/agent/agent-commission-deduction/list', + 1, + '查询agent-commission-deduction-列表' + ), + ( + 2, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-commission-列表', + 'get__api_v1_admin_agent_agent-commission_list', + 'GET', + '/api/v1/admin/agent/agent-commission/list', + 1, + '查询agent-commission-列表' + ), + ( + 3, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-link-列表', + 'get__api_v1_admin_agent_agent-link_list', + 'GET', + '/api/v1/admin/agent/agent-link/list', + 1, + '查询agent-link-列表' + ), + ( + 4, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-membership-config-列表', + 'get__api_v1_admin_agent_agent-membership-config_list', + 'GET', + '/api/v1/admin/agent/agent-membership-config/list', + 1, + '查询agent-membership-config-列表' + ), + ( + 5, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-membership-config-更新', + 'post__api_v1_admin_agent_agent-membership-config_update', + 'POST', + '/api/v1/admin/agent/agent-membership-config/update', + 1, + '创建agent-membership-config-更新' + ), + ( + 6, + '2025-09-30 17:52:12', + '2025-09-30 17:52:12', + NULL, + 0, + 0, + 'agent-membership-recharge-order-列表', + 'get__api_v1_admin_agent_agent-membership-recharge-order_list', + 'GET', + '/api/v1/admin/agent/agent-membership-recharge-order/list', + 1, + '查询agent-membership-recharge-order-列表' + ), + ( + 7, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'agent-platform-deduction-列表', + 'get__api_v1_admin_agent_agent-platform-deduction_list', + 'GET', + '/api/v1/admin/agent/agent-platform-deduction/list', + 1, + '查询agent-platform-deduction-列表' + ), + ( + 8, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'agent-production-config-列表', + 'get__api_v1_admin_agent_agent-production-config_list', + 'GET', + '/api/v1/admin/agent/agent-production-config/list', + 1, + '查询agent-production-config-列表' + ), + ( + 9, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'agent-production-config-更新', + 'post__api_v1_admin_agent_agent-production-config_update', + 'POST', + '/api/v1/admin/agent/agent-production-config/update', + 1, + '创建agent-production-config-更新' + ), + ( + 10, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'agent-reward-列表', + 'get__api_v1_admin_agent_agent-reward_list', + 'GET', + '/api/v1/admin/agent/agent-reward/list', + 1, + '查询agent-reward-列表' + ), + ( + 11, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'agent-withdrawal-列表', + 'get__api_v1_admin_agent_agent-withdrawal_list', + 'GET', + '/api/v1/admin/agent/agent-withdrawal/list', + 1, + '查询agent-withdrawal-列表' + ), + ( + 12, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '代理管理-列表', + 'get__api_v1_admin_agent_list', + 'GET', + '/api/v1/admin/agent/list', + 1, + '查询代理管理-列表' + ), + ( + 13, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-batch-update-status', + 'put__api_v1_admin_api_batch-update-status', + 'PUT', + '/api/v1/admin/api/batch-update-status', + 1, + '更新api-batch-update-status' + ), + ( + 14, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-创建', + 'post__api_v1_admin_api_create', + 'POST', + '/api/v1/admin/api/create', + 1, + '创建api-创建' + ), + ( + 15, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_api_delete', + 'DELETE', + '/api/v1/admin/api/delete/:id', + 1, + '删除delete-:id' + ), + ( + 16, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_api_detail', + 'GET', + '/api/v1/admin/api/detail/:id', + 1, + '查询detail-:id' + ), + ( + 17, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-列表', + 'get__api_v1_admin_api_list', + 'GET', + '/api/v1/admin/api/list', + 1, + '查询api-列表' + ), + ( + 18, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_api_update', + 'PUT', + '/api/v1/admin/api/update/:id', + 1, + '更新update-:id' + ), + ( + 19, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '功能管理-config-example', + 'post__api_v1_admin_feature_config-example', + 'POST', + '/api/v1/admin/feature/config-example', + 1, + '创建功能管理-config-example' + ), + ( + 20, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '功能管理-创建', + 'post__api_v1_admin_feature_create', + 'POST', + '/api/v1/admin/feature/create', + 1, + '创建功能管理-创建' + ), + ( + 21, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_feature_delete', + 'DELETE', + '/api/v1/admin/feature/delete/:id', + 1, + '删除delete-:id' + ), + ( + 22, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_feature_detail', + 'GET', + '/api/v1/admin/feature/detail/:id', + 1, + '查询detail-:id' + ), + ( + 23, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'example-:feature_id', + 'get__api_v1_admin_feature_example', + 'GET', + '/api/v1/admin/feature/example/:feature_id', + 1, + '查询example-:feature_id' + ), + ( + 24, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '功能管理-列表', + 'get__api_v1_admin_feature_list', + 'GET', + '/api/v1/admin/feature/list', + 1, + '查询功能管理-列表' + ), + ( + 25, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_feature_update', + 'PUT', + '/api/v1/admin/feature/update/:id', + 1, + '更新update-:id' + ), + ( + 26, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '菜单管理-all', + 'get__api_v1_admin_menu_all', + 'GET', + '/api/v1/admin/menu/all', + 1, + '查询菜单管理-all' + ), + ( + 27, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '菜单管理-创建', + 'post__api_v1_admin_menu_create', + 'POST', + '/api/v1/admin/menu/create', + 1, + '创建菜单管理-创建' + ), + ( + 28, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_menu_delete', + 'DELETE', + '/api/v1/admin/menu/delete/:id', + 1, + '删除delete-:id' + ), + ( + 29, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_menu_detail', + 'GET', + '/api/v1/admin/menu/detail/:id', + 1, + '查询detail-:id' + ), + ( + 30, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '菜单管理-列表', + 'get__api_v1_admin_menu_list', + 'GET', + '/api/v1/admin/menu/list', + 1, + '查询菜单管理-列表' + ), + ( + 31, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_menu_update', + 'PUT', + '/api/v1/admin/menu/update/:id', + 1, + '更新update-:id' + ), + ( + 32, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '通知管理-创建', + 'post__api_v1_admin_notification_create', + 'POST', + '/api/v1/admin/notification/create', + 1, + '创建通知管理-创建' + ), + ( + 33, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_notification_delete', + 'DELETE', + '/api/v1/admin/notification/delete/:id', + 1, + '删除delete-:id' + ), + ( + 34, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_notification_detail', + 'GET', + '/api/v1/admin/notification/detail/:id', + 1, + '查询detail-:id' + ), + ( + 35, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '通知管理-列表', + 'get__api_v1_admin_notification_list', + 'GET', + '/api/v1/admin/notification/list', + 1, + '查询通知管理-列表' + ), + ( + 36, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_notification_update', + 'PUT', + '/api/v1/admin/notification/update/:id', + 1, + '更新update-:id' + ), + ( + 37, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '订单管理-创建', + 'post__api_v1_admin_order_create', + 'POST', + '/api/v1/admin/order/create', + 1, + '创建订单管理-创建' + ), + ( + 38, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_order_delete', + 'DELETE', + '/api/v1/admin/order/delete/:id', + 1, + '删除delete-:id' + ), + ( + 39, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_order_detail', + 'GET', + '/api/v1/admin/order/detail/:id', + 1, + '查询detail-:id' + ), + ( + 40, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '订单管理-列表', + 'get__api_v1_admin_order_list', + 'GET', + '/api/v1/admin/order/list', + 1, + '查询订单管理-列表' + ), + ( + 41, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'refund-:id', + 'post__api_v1_admin_order_refund', + 'POST', + '/api/v1/admin/order/refund/:id', + 1, + '创建refund-:id' + ), + ( + 42, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_order_update', + 'PUT', + '/api/v1/admin/order/update/:id', + 1, + '更新update-:id' + ), + ( + 43, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '平台用户-创建', + 'post__api_v1_admin_platform_user_create', + 'POST', + '/api/v1/admin/platform_user/create', + 1, + '创建平台用户-创建' + ), + ( + 44, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_platform_user_delete', + 'DELETE', + '/api/v1/admin/platform_user/delete/:id', + 1, + '删除delete-:id' + ), + ( + 45, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_platform_user_detail', + 'GET', + '/api/v1/admin/platform_user/detail/:id', + 1, + '查询detail-:id' + ), + ( + 46, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '平台用户-列表', + 'get__api_v1_admin_platform_user_list', + 'GET', + '/api/v1/admin/platform_user/list', + 1, + '查询平台用户-列表' + ), + ( + 47, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_platform_user_update', + 'PUT', + '/api/v1/admin/platform_user/update/:id', + 1, + '更新update-:id' + ), + ( + 48, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '产品管理-创建', + 'post__api_v1_admin_product_create', + 'POST', + '/api/v1/admin/product/create', + 1, + '创建产品管理-创建' + ), + ( + 49, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_product_delete', + 'DELETE', + '/api/v1/admin/product/delete/:id', + 1, + '删除delete-:id' + ), + ( + 50, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_product_detail', + 'GET', + '/api/v1/admin/product/detail/:id', + 1, + '查询detail-:id' + ), + ( + 51, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'list-:product_id', + 'get__api_v1_admin_product_feature_list', + 'GET', + '/api/v1/admin/product/feature/list/:product_id', + 1, + '查询list-:product_id' + ), + ( + 52, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:product_id', + 'put__api_v1_admin_product_feature_update', + 'PUT', + '/api/v1/admin/product/feature/update/:product_id', + 1, + '更新update-:product_id' + ), + ( + 53, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '产品管理-列表', + 'get__api_v1_admin_product_list', + 'GET', + '/api/v1/admin/product/list', + 1, + '查询产品管理-列表' + ), + ( + 54, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_product_update', + 'PUT', + '/api/v1/admin/product/update/:id', + 1, + '更新update-:id' + ), + ( + 55, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'link-创建', + 'post__api_v1_admin_promotion_link_create', + 'POST', + '/api/v1/admin/promotion/link/create', + 1, + '创建link-创建' + ), + ( + 56, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_promotion_link_delete', + 'DELETE', + '/api/v1/admin/promotion/link/delete/:id', + 1, + '删除delete-:id' + ), + ( + 57, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_promotion_link_detail', + 'GET', + '/api/v1/admin/promotion/link/detail/:id', + 1, + '查询detail-:id' + ), + ( + 58, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'link-列表', + 'get__api_v1_admin_promotion_link_list', + 'GET', + '/api/v1/admin/promotion/link/list', + 1, + '查询link-列表' + ), + ( + 59, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_promotion_link_update', + 'PUT', + '/api/v1/admin/promotion/link/update/:id', + 1, + '更新update-:id' + ), + ( + 60, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'record-:path', + 'get__api_v1_admin_promotion_link_record', + 'GET', + '/api/v1/admin/promotion/link/record/:path', + 1, + '查询record-:path' + ), + ( + 61, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'stats-history', + 'get__api_v1_admin_promotion_stats_history', + 'GET', + '/api/v1/admin/promotion/stats/history', + 1, + '查询stats-history' + ), + ( + 62, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'stats-total', + 'get__api_v1_admin_promotion_stats_total', + 'GET', + '/api/v1/admin/promotion/stats/total', + 1, + '查询stats-total' + ), + ( + 63, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'cleanup-配置', + 'put__api_v1_admin_query_cleanup_config', + 'PUT', + '/api/v1/admin/query/cleanup/config', + 1, + '更新cleanup-配置' + ), + ( + 64, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'cleanup-configs', + 'get__api_v1_admin_query_cleanup_configs', + 'GET', + '/api/v1/admin/query/cleanup/configs', + 1, + '查询cleanup-configs' + ), + ( + 65, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'details-:log_id', + 'get__api_v1_admin_query_cleanup_details', + 'GET', + '/api/v1/admin/query/cleanup/details/:log_id', + 1, + '查询details-:log_id' + ), + ( + 66, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'cleanup-logs', + 'get__api_v1_admin_query_cleanup_logs', + 'GET', + '/api/v1/admin/query/cleanup/logs', + 1, + '查询cleanup-logs' + ), + ( + 67, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:order_id', + 'get__api_v1_admin_query_detail', + 'GET', + '/api/v1/admin/query/detail/:order_id', + 1, + '查询detail-:order_id' + ), + ( + 68, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '角色管理-创建', + 'post__api_v1_admin_role_create', + 'POST', + '/api/v1/admin/role/create', + 1, + '创建角色管理-创建' + ), + ( + 69, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_role_delete', + 'DELETE', + '/api/v1/admin/role/delete/:id', + 1, + '删除delete-:id' + ), + ( + 70, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_role_detail', + 'GET', + '/api/v1/admin/role/detail/:id', + 1, + '查询detail-:id' + ), + ( + 71, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '角色管理-列表', + 'get__api_v1_admin_role_list', + 'GET', + '/api/v1/admin/role/list', + 1, + '查询角色管理-列表' + ), + ( + 72, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_role_update', + 'PUT', + '/api/v1/admin/role/update/:id', + 1, + '更新update-:id' + ), + ( + 73, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-all', + 'get__api_v1_admin_api_all', + 'GET', + '/api/v1/admin/api/all', + 1, + '查询api-all' + ), + ( + 74, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-列表', + 'get__api_v1_admin_role_api_list', + 'GET', + '/api/v1/admin/role/:role_id/api/list', + 1, + '查询api-列表' + ), + ( + 75, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-assign', + 'post__api_v1_admin_role_api_assign', + 'POST', + '/api/v1/admin/role/api/assign', + 1, + '创建api-assign' + ), + ( + 76, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-remove', + 'post__api_v1_admin_role_api_remove', + 'POST', + '/api/v1/admin/role/api/remove', + 1, + '创建api-remove' + ), + ( + 77, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'api-更新', + 'put__api_v1_admin_role_api_update', + 'PUT', + '/api/v1/admin/role/api/update', + 1, + '更新api-更新' + ), + ( + 78, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '用户管理-创建', + 'post__api_v1_admin_user_create', + 'POST', + '/api/v1/admin/user/create', + 1, + '创建用户管理-创建' + ), + ( + 79, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'delete-:id', + 'delete__api_v1_admin_user_delete', + 'DELETE', + '/api/v1/admin/user/delete/:id', + 1, + '删除delete-:id' + ), + ( + 80, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'detail-:id', + 'get__api_v1_admin_user_detail', + 'GET', + '/api/v1/admin/user/detail/:id', + 1, + '查询detail-:id' + ), + ( + 81, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '用户管理-info', + 'get__api_v1_admin_user_info', + 'GET', + '/api/v1/admin/user/info', + 1, + '查询用户管理-info' + ), + ( + 82, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + '用户管理-列表', + 'get__api_v1_admin_user_list', + 'GET', + '/api/v1/admin/user/list', + 1, + '查询用户管理-列表' + ), + ( + 83, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'reset-password-:id', + 'put__api_v1_admin_user_reset-password', + 'PUT', + '/api/v1/admin/user/reset-password/:id', + 1, + '更新reset-password-:id' + ), + ( + 84, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'update-:id', + 'put__api_v1_admin_user_update', + 'PUT', + '/api/v1/admin/user/update/:id', + 1, + '更新update-:id' + ), + ( + 85, + '2025-09-30 17:52:13', + '2025-09-30 17:52:13', + NULL, + 0, + 0, + 'health-check', + 'get__api_v1_health_check', + 'GET', + '/api/v1/health/check', + 1, + '查询health-check' + ), + ( + 86, + '2025-10-24 15:42:28', + '2025-10-24 15:42:28', + NULL, + 0, + 0, + 'retry-agent-process-:id', + 'post__api_v1_admin_order_retry-agent-process', + 'POST', + '/api/v1/admin/order/retry-agent-process/:id', + 1, + '创建retry-agent-process-:id' + ), + ( + 87, + '2025-10-24 16:36:51', + '2025-10-24 16:36:51', + NULL, + 0, + 0, + 'wechat-getSignature', + 'post__api_v1_wechat_getSignature', + 'POST', + '/api/v1/wechat/getSignature', + 1, + '创建wechat-getSignature' + ), + ( + 88, + '2025-12-24 19:19:09', + '2025-12-24 19:19:09', + NULL, + 0, + 0, + 'bank-card-review', + 'post__api_v1_admin_agent_agent-withdrawal_bank-card_review', + 'POST', + '/api/v1/admin/agent/agent-withdrawal/bank-card/review', + 1, + '创建bank-card-review' + ), + ( + 89, + '2025-12-27 14:07:21', + '2025-12-27 14:07:21', + NULL, + 0, + 0, + 'agent-withdrawal-statistics', + 'get__api_v1_admin_agent_agent-withdrawal_statistics', + 'GET', + '/api/v1/admin/agent/agent-withdrawal/statistics', + 1, + '查询agent-withdrawal-statistics' + ), + ( + 90, + '2025-12-27 14:07:21', + '2025-12-27 14:07:21', + NULL, + 0, + 0, + '订单管理-refund-statistics', + 'get__api_v1_admin_order_refund-statistics', + 'GET', + '/api/v1/admin/order/refund-statistics', + 1, + '查询订单管理-refund-statistics' + ), + ( + 91, + '2025-12-27 14:07:21', + '2025-12-27 14:07:21', + NULL, + 0, + 0, + '订单管理-revenue-statistics', + 'get__api_v1_admin_order_revenue-statistics', + 'GET', + '/api/v1/admin/order/revenue-statistics', + 1, + '查询订单管理-revenue-statistics' + ), + ( + 92, + '2025-12-27 16:08:33', + '2025-12-27 16:08:33', + NULL, + 0, + 0, + 'agent-order-statistics', + 'get__api_v1_admin_agent_agent-order_statistics', + 'GET', + '/api/v1/admin/agent/agent-order/statistics', + 1, + '查询agent-order-statistics' + ), + ( + 93, + '2025-12-27 16:08:33', + '2025-12-27 16:08:33', + NULL, + 0, + 0, + '代理管理-statistics', + 'get__api_v1_admin_agent_statistics', + 'GET', + '/api/v1/admin/agent/statistics', + 1, + '查询代理管理-statistics' + ), + ( + 94, + '2025-12-29 16:31:23', + '2025-12-29 16:31:23', + NULL, + 0, + 0, + 'agent-link-product-statistics', + 'get__api_v1_admin_agent_agent-link_product-statistics', + 'GET', + '/api/v1/admin/agent/agent-link/product-statistics', + 1, + '查询agent-link-product-statistics' + ), + ( + 95, + '2025-12-29 16:31:23', + '2025-12-29 16:31:23', + NULL, + 0, + 0, + '订单管理-source-statistics', + 'get__api_v1_admin_order_source-statistics', + 'GET', + '/api/v1/admin/order/source-statistics', + 1, + '查询订单管理-source-statistics' + ), + ( + 96, + '2025-12-29 16:31:23', + '2025-12-29 16:31:23', + NULL, + 0, + 0, + '订单管理-statistics', + 'get__api_v1_admin_order_statistics', + 'GET', + '/api/v1/admin/order/statistics', + 1, + '查询订单管理-statistics' + ), + ( + 97, + '2025-12-31 12:42:23', + '2025-12-31 12:42:23', + NULL, + 0, + 0, + 'agent-commission-batch-unfreeze', + 'post__api_v1_admin_agent_agent-commission_batch-unfreeze', + 'POST', + '/api/v1/admin/agent/agent-commission/batch-unfreeze', + 1, + '创建agent-commission-batch-unfreeze' + ), + ( + 98, + '2025-12-31 12:42:23', + '2025-12-31 12:42:23', + NULL, + 0, + 0, + 'agent-commission-update-status', + 'post__api_v1_admin_agent_agent-commission_update-status', + 'POST', + '/api/v1/admin/agent/agent-commission/update-status', + 1, + '创建agent-commission-update-status' + ), + ( + 99, + '2026-01-03 18:11:08', + '2026-01-03 18:11:08', + NULL, + 0, + 0, + 'wallet-:agent_id', + 'get__api_v1_admin_agent_wallet', + 'GET', + '/api/v1/admin/agent/wallet/:agent_id', + 1, + '查询wallet-:agent_id' + ), + ( + 100, + '2026-01-04 16:14:19', + '2026-01-04 16:14:19', + NULL, + 0, + 0, + 'wallet-update-balance', + 'post__api_v1_admin_agent_wallet_update-balance', + 'POST', + '/api/v1/admin/agent/wallet/update-balance', + 1, + '创建wallet-update-balance' + ), + ( + 101, + '2026-01-05 15:08:27', + '2026-01-05 15:08:27', + NULL, + 0, + 0, + '代理管理-system-config', + 'get__api_v1_admin_agent_system-config', + 'GET', + '/api/v1/admin/agent/system-config', + 1, + '查询代理管理-system-config' + ), + ( + 102, + '2026-01-05 15:08:27', + '2026-01-05 15:08:27', + NULL, + 0, + 0, + '代理管理-system-config', + 'post__api_v1_admin_agent_system-config', + 'POST', + '/api/v1/admin/agent/system-config', + 1, + '创建代理管理-system-config' + ), + ( + 103, + '2026-01-07 17:08:33', + '2026-01-07 17:08:33', + NULL, + 0, + 0, + 'wallet-transaction-列表', + 'get__api_v1_admin_agent_wallet-transaction_list', + 'GET', + '/api/v1/admin/agent/wallet-transaction/list', + 1, + '查询wallet-transaction-列表' + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_dict_data` +-- + +CREATE TABLE `admin_dict_data` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `dict_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型编码', + `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典标签', + `dict_value` tinyint NOT NULL COMMENT '字典键值', + `dict_sort` int NOT NULL DEFAULT '0' COMMENT '字典排序', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表'; + +-- +-- 转存表中的数据 `admin_dict_data` +-- + +INSERT INTO + `admin_dict_data` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `dict_type`, + `dict_label`, + `dict_value`, + `dict_sort`, + `status`, + `remark` + ) +VALUES ( + 1, + '2025-04-29 13:28:38', + '2025-04-29 13:28:59', + NULL, + 0, + 0, + 'admin_menu_type', + 'catalog', + 0, + 1, + 1, + '目录' + ), + ( + 2, + '2025-04-29 13:28:38', + '2025-04-29 13:29:03', + NULL, + 0, + 0, + 'admin_menu_type', + 'menu', + 1, + 2, + 1, + '菜单' + ), + ( + 3, + '2025-04-29 13:28:38', + '2025-04-29 13:29:05', + NULL, + 0, + 0, + 'admin_menu_type', + 'action', + 2, + 3, + 1, + '按钮' + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_dict_type` +-- + +CREATE TABLE `admin_dict_type` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `dict_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型编码', + `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型名称', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表'; + +-- +-- 转存表中的数据 `admin_dict_type` +-- + +INSERT INTO + `admin_dict_type` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `dict_type`, + `dict_name`, + `status`, + `remark` + ) +VALUES ( + 1, + '2025-04-29 13:28:38', + '2025-04-29 13:29:09', + NULL, + 0, + 0, + 'admin_menu_type', + '菜单类型', + 1, + '系统菜单类型' + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_menu` +-- + +CREATE TABLE `admin_menu` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `pid` bigint NOT NULL DEFAULT '0' COMMENT '父菜单ID', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '路由名称', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '路由路径', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '组件路径', + `redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '重定向路径', + `meta` json NOT NULL COMMENT '路由元数据配置', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态: 0-禁用, 1-启用', + `type` tinyint NOT NULL DEFAULT '0', + `sort` int NOT NULL DEFAULT '0' COMMENT '排序号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表'; + +-- +-- 转存表中的数据 `admin_menu` +-- + +INSERT INTO + `admin_menu` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `pid`, + `name`, + `path`, + `component`, + `redirect`, + `meta`, + `status`, + `type`, + `sort` + ) +VALUES ( + 1, + '2025-04-27 20:19:01', + '2025-04-28 18:13:14', + NULL, + 0, + 0, + 0, + 'Dashboard', + '/dashboard', + '', + NULL, + '{\"icon\": \"lucide:layout-dashboard\", \"order\": -1, \"title\": \"page.dashboard.title\"}', + 1, + 0, + 0 + ), + ( + 2, + '2025-04-27 20:19:01', + '2025-04-28 18:13:12', + NULL, + 0, + 0, + 1, + 'Analytics', + '/analytics', + '/dashboard/analytics/index', + NULL, + '{\"icon\": \"lucide:area-chart\", \"title\": \"page.dashboard.analytics\", \"affixTab\": true}', + 1, + 0, + 0 + ), + ( + 3, + '2025-04-27 20:19:01', + '2026-03-30 14:07:05', + '2026-03-30 14:07:06', + 1, + 1, + 1, + 'Workspace', + '/workspace', + '/dashboard/workspace/index', + NULL, + '{\"icon\": \"carbon:workspace\", \"title\": \"page.dashboard.workspace\"}', + 1, + 0, + 0 + ), + ( + 4, + '2025-04-27 20:19:01', + '2026-03-30 14:07:46', + '2026-03-30 14:07:46', + 1, + 1, + 0, + 'Demos', + '/demos', + '', + NULL, + '{\"icon\": \"ic:baseline-view-in-ar\", \"order\": 1000, \"title\": \"demos.title\", \"keepAlive\": true}', + 1, + 0, + 0 + ), + ( + 5, + '2025-04-27 20:19:01', + '2026-03-30 14:07:44', + '2026-03-30 14:07:45', + 1, + 1, + 4, + 'AntDesignDemos', + '/demos/ant-design', + '/demos/antd/index', + NULL, + '{\"title\": \"demos.antd\"}', + 1, + 0, + 0 + ), + ( + 6, + '2025-04-27 20:19:01', + '2025-06-06 17:48:31', + NULL, + 0, + 0, + 24, + 'OrderManage', + '/order', + '/order/order/index', + NULL, + '{\"icon\": \"lucide:shopping-cart\", \"order\": 1000, \"title\": \"订单管理\"}', + 1, + 1, + 0 + ), + ( + 7, + '2025-04-27 20:19:01', + '2026-03-30 14:07:55', + '2026-03-30 14:07:55', + 1, + 1, + 0, + 'VbenProject', + '/vben-admin', + '', + NULL, + '{\"icon\": \"carbon:apps\", \"order\": 9998, \"title\": \"demos.vben.title\", \"badgeType\": \"dot\"}', + 1, + 0, + 0 + ), + ( + 8, + '2025-04-27 20:19:01', + '2026-03-30 14:07:53', + '2026-03-30 14:07:53', + 1, + 1, + 7, + 'VbenDocument', + '/vben-admin/document', + 'IFrameView', + NULL, + '{\"icon\": \"lucide:book-open-text\", \"link\": \"https://doc.vben.admin\", \"title\": \"demos.vben.document\"}', + 1, + 0, + 0 + ), + ( + 9, + '2025-04-27 20:19:01', + '2026-03-30 14:08:17', + '2026-03-30 14:08:17', + 1, + 1, + 0, + 'VbenAbout', + '/vben-admin/about', + '/_core/about/index', + NULL, + '{\"icon\": \"lucide:copyright\", \"order\": 9999, \"title\": \"demos.vben.about\"}', + 1, + 0, + 0 + ), + ( + 10, + '2025-04-28 18:12:21', + '2025-04-28 18:12:21', + NULL, + 0, + 0, + 0, + 'System', + '/system', + '', + NULL, + '{\"icon\": \"ion:settings-outline\", \"order\": 9997, \"title\": \"system.title\"}', + 1, + 0, + 9997 + ), + ( + 11, + '2025-04-28 18:12:21', + '2025-04-28 18:12:21', + NULL, + 0, + 0, + 10, + 'SystemRole', + '/system/role', + '/system/role/list', + NULL, + '{\"icon\": \"mdi:account-group\", \"title\": \"system.role.title\"}', + 1, + 1, + 1 + ), + ( + 12, + '2025-04-28 18:12:21', + '2025-04-28 18:12:21', + NULL, + 0, + 0, + 10, + 'SystemMenu', + '/system/menu', + '/system/menu/list', + NULL, + '{\"icon\": \"mdi:menu\", \"title\": \"system.menu.title\"}', + 1, + 1, + 2 + ), + ( + 13, + '2025-04-28 18:12:21', + '2025-05-29 15:23:31', + NULL, + 0, + 0, + 10, + 'SystemDept', + '/system/dept', + '/system/dept/list', + NULL, + '{\"icon\": \"charm:organisation\", \"title\": \"system.dept.title\"}', + 0, + 1, + 0 + ), + ( + 14, + '2025-04-29 21:40:22', + '2025-04-29 21:40:22', + NULL, + 0, + 0, + 10, + 'SystemUser', + '/system/user', + '/system/user/list', + NULL, + '{\"icon\": \"carbon:user-filled\", \"title\": \"用户管理\"}', + 1, + 1, + 0 + ), + ( + 15, + '2025-05-08 16:12:22', + '2026-03-30 14:10:07', + '2026-03-30 14:10:07', + 1, + 1, + 0, + 'Promotion', + '/promotion', + '', + NULL, + '{\"icon\": \"carbon:carbon\", \"title\": \"推广\"}', + 1, + 0, + 0 + ), + ( + 16, + '2025-05-08 16:20:19', + '2026-03-30 14:10:03', + '2026-03-30 14:10:03', + 1, + 1, + 15, + 'PromotionLink', + '/promotion/link', + '/promotion/link/index', + NULL, + '{\"icon\": \"carbon:link\", \"title\": \"推广链接管理\"}', + 1, + 1, + 0 + ), + ( + 17, + '2025-05-08 16:21:41', + '2026-03-30 14:10:05', + '2026-03-30 14:10:05', + 1, + 1, + 15, + 'PromotionAnalytics', + '/promotion/analytics', + '/promotion/analytics/index', + NULL, + '{\"icon\": \"carbon:analytics\", \"title\": \"推广数据分析\"}', + 1, + 1, + 0 + ), + ( + 18, + '2025-05-13 20:29:52', + '2025-05-13 20:30:59', + NULL, + 0, + 0, + 0, + 'platformUser', + '/platform-user', + '/platform-user/list', + NULL, + '{\"icon\": \"carbon:user-profile\", \"title\": \"平台用户管理\", \"activeIcon\": \"\"}', + 1, + 1, + 0 + ), + ( + 19, + '2025-05-14 01:05:54', + '2025-05-14 01:05:54', + NULL, + 0, + 0, + 0, + 'notification', + '/notification', + '/notification/list', + NULL, + '{\"icon\": \"carbon:notification\", \"title\": \"通知管理\"}', + 1, + 1, + 0 + ), + ( + 20, + '2025-05-15 22:56:11', + '2025-05-15 22:56:11', + NULL, + 0, + 0, + 0, + 'productManage', + '/product-manage', + '', + NULL, + '{\"icon\": \"carbon:box\", \"title\": \"产品管理\"}', + 1, + 0, + 0 + ), + ( + 21, + '2025-05-15 23:14:04', + '2025-05-15 23:14:04', + NULL, + 0, + 0, + 20, + 'productList', + '/product-manage/product', + '/product-manage/product/list', + NULL, + '{\"icon\": \"carbon:store\", \"title\": \"产品列表\"}', + 1, + 1, + 0 + ), + ( + 22, + '2025-05-16 00:15:01', + '2025-05-16 00:15:01', + NULL, + 0, + 0, + 20, + 'feature', + '/product-manage/feature', + '/product-manage/feature/list', + NULL, + '{\"icon\": \"carbon:web-services-container\", \"title\": \"模块列表\"}', + 1, + 1, + 0 + ), + ( + 23, + '2025-05-29 16:04:53', + '2025-06-06 17:48:36', + NULL, + 0, + 0, + 24, + 'OrderQueryDetail', + '/order/query/:id', + '/order/query/query-details', + NULL, + '{\"icon\": \"carbon:report\", \"title\": \"查询结果\", \"hideInMenu\": true}', + 1, + 1, + 0 + ), + ( + 24, + '2025-05-29 16:15:36', + '2025-05-29 16:15:36', + NULL, + 0, + 0, + 0, + 'Order', + '/order', + '', + NULL, + '{\"icon\": \"lucide:shopping-cart\", \"title\": \"订单管理\"}', + 1, + 0, + 0 + ), + ( + 25, + '2025-06-06 18:24:53', + '2025-06-06 18:26:18', + NULL, + 0, + 0, + 24, + 'queryCleanup', + '/order/query/query-cleanup-manage', + '/order/query-cleanup/query-cleanup-log', + NULL, + '{\"icon\": \"carbon:clean\", \"title\": \"查询数据管理\", \"activeIcon\": \"\"}', + 1, + 1, + 0 + ), + ( + 26, + '2025-06-06 21:43:48', + '2025-06-06 21:58:06', + NULL, + 0, + 0, + 0, + 'AgentManage', + '/agent-manage', + '', + NULL, + '{\"icon\": \"carbon:airline-manage-gates\", \"title\": \"代理系统\"}', + 1, + 0, + 0 + ), + ( + 27, + '2025-06-06 21:44:27', + '2025-06-07 11:38:40', + NULL, + 0, + 0, + 26, + 'AgentList', + '/agent-manage/agent-list', + '/agent/agent-list/list', + NULL, + '{\"icon\": \"carbon:list\", \"title\": \"代理列表\"}', + 1, + 1, + 0 + ), + ( + 28, + '2025-06-07 11:39:25', + '2025-06-07 11:39:33', + NULL, + 0, + 0, + 26, + 'AgentLinks', + '/agent-manage/agent-links', + '/agent/agent-links/list', + NULL, + '{\"icon\": \"carbon:link\", \"title\": \"推广链接\"}', + 1, + 1, + 0 + ), + ( + 29, + '2025-06-07 11:40:47', + '2025-06-08 12:25:17', + NULL, + 0, + 0, + 26, + 'AgentCommission', + '/agent-manage/agent-commission', + '/agent/agent-commission/list', + NULL, + '{\"icon\": \"carbon:money\", \"title\": \"佣金记录\"}', + 1, + 1, + 0 + ), + ( + 30, + '2025-06-07 12:14:15', + '2025-06-07 12:14:15', + NULL, + 0, + 0, + 26, + 'AgentReward', + '/agent-manage/agent-reward', + '/agent/agent-reward/list', + NULL, + '{\"icon\": \"carbon:store\", \"title\": \"平台奖励\"}', + 1, + 1, + 0 + ), + ( + 31, + '2025-06-07 12:15:12', + '2025-06-07 12:15:12', + NULL, + 0, + 0, + 26, + 'AgentWithdrawal', + '/agent-manage/withdrawal', + '/agent/agent-withdrawal/list', + NULL, + '{\"icon\": \"carbon:piggy-bank\", \"title\": \"提现记录\"}', + 1, + 1, + 0 + ), + ( + 32, + '2025-06-07 13:44:27', + '2025-06-07 13:44:27', + NULL, + 0, + 0, + 26, + 'AgentCommissionDeduction', + '/agent-manage/agent-commission-deduction', + '/agent/agent-commission-deduction/list', + NULL, + '{\"icon\": \"carbon:collapse-categories\", \"title\": \"上级抽佣记录\"}', + 1, + 1, + 0 + ), + ( + 33, + '2025-06-07 16:28:52', + '2025-06-07 16:28:52', + NULL, + 0, + 0, + 26, + 'AgentPlatformDeduction', + '/agent-manage/agent-platform-deduction', + '/agent/agent-platform-deduction/list', + NULL, + '{\"icon\": \"carbon:ibm-openshift-container-platform-on-vpc-for-regulated-industries\", \"title\": \"平台抽佣记录\"}', + 1, + 1, + 0 + ), + ( + 34, + '2025-06-07 16:46:18', + '2025-06-07 17:09:55', + NULL, + 0, + 0, + 26, + 'AgentProductConfig', + '/agent-manage/agent-product-config', + '/agent/agent-product-config/list', + NULL, + '{\"icon\": \"carbon:cloud-satellite-config\", \"title\": \"代理产品配置\"}', + 1, + 1, + 0 + ), + ( + 35, + '2025-06-07 21:17:02', + '2025-06-08 11:40:11', + NULL, + 0, + 0, + 26, + 'AgentMembershipRechargeOrder', + '/agent-manage/agent-membership-recharge-order', + '/agent/agent-membership-recharge-order/list', + NULL, + '{\"icon\": \"carbon:feature-membership\", \"title\": \"会员充值订单\"}', + 1, + 1, + 0 + ), + ( + 36, + '2025-06-07 23:40:35', + '2025-06-07 23:40:35', + NULL, + 0, + 0, + 26, + 'AgentMembershipConfig', + '/agent-manage/agent-membership-config', + '/agent/agent-membership-config/list', + NULL, + '{\"icon\": \"carbon:document-configuration\", \"title\": \"代理会员配置\"}', + 1, + 1, + 0 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_role` +-- + +CREATE TABLE `admin_role` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称', + `role_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色编码', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色描述', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `sort` int NOT NULL DEFAULT '0' COMMENT '排序' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表'; + +-- +-- 转存表中的数据 `admin_role` +-- + +INSERT INTO + `admin_role` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `role_name`, + `role_code`, + `description`, + `status`, + `sort` + ) +VALUES ( + 1, + '2025-04-27 20:19:01', + '2025-04-29 19:22:43', + NULL, + 0, + 0, + '超级管理员', + 'SUPER', + '系统超级管理员,拥有所有权限', + 1, + 0 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_role_api` +-- + +CREATE TABLE `admin_role_api` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `role_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到角色表的id', + `api_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到接口表的id' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色接口关联表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_role_menu` +-- + +CREATE TABLE `admin_role_menu` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `role_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到角色表的id', + `menu_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到菜单表的id' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色菜单关联表'; + +-- +-- 转存表中的数据 `admin_role_menu` +-- + +INSERT INTO + `admin_role_menu` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `role_id`, + `menu_id` + ) +VALUES ( + 17, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 1 + ), + ( + 22, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 6 + ), + ( + 26, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 10 + ), + ( + 27, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 11 + ), + ( + 28, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 12 + ), + ( + 29, + '2025-04-29 19:13:18', + '2025-04-29 19:13:18', + NULL, + 0, + 0, + 1, + 13 + ), + ( + 35, + '2025-04-29 21:41:12', + '2025-04-29 21:41:12', + NULL, + 0, + 0, + 1, + 14 + ), + ( + 42, + '2025-05-13 20:31:07', + '2025-05-13 20:31:07', + NULL, + 0, + 0, + 1, + 18 + ), + ( + 59, + '2025-05-14 01:06:56', + '2025-05-14 01:06:56', + NULL, + 0, + 0, + 1, + 19 + ), + ( + 60, + '2025-05-15 22:56:20', + '2025-05-15 22:56:20', + NULL, + 0, + 0, + 1, + 20 + ), + ( + 61, + '2025-05-15 23:14:14', + '2025-05-15 23:14:14', + NULL, + 0, + 0, + 1, + 21 + ), + ( + 62, + '2025-05-16 00:15:11', + '2025-05-16 00:15:11', + NULL, + 0, + 0, + 1, + 22 + ), + ( + 63, + '2025-05-29 16:05:13', + '2025-05-29 16:05:13', + NULL, + 0, + 0, + 1, + 23 + ), + ( + 64, + '2025-05-29 16:26:42', + '2025-05-29 16:26:42', + NULL, + 0, + 0, + 1, + 24 + ), + ( + 65, + '2025-06-06 18:25:27', + '2025-06-06 18:25:27', + NULL, + 0, + 0, + 1, + 25 + ), + ( + 66, + '2025-06-06 21:45:00', + '2025-06-06 21:45:00', + NULL, + 0, + 0, + 1, + 26 + ), + ( + 67, + '2025-06-06 21:45:00', + '2025-06-06 21:45:00', + NULL, + 0, + 0, + 1, + 27 + ), + ( + 68, + '2025-06-07 11:40:56', + '2025-06-07 11:40:56', + NULL, + 0, + 0, + 1, + 28 + ), + ( + 69, + '2025-06-07 11:59:38', + '2025-06-07 11:59:38', + NULL, + 0, + 0, + 1, + 29 + ), + ( + 70, + '2025-06-07 12:15:23', + '2025-06-07 12:15:23', + NULL, + 0, + 0, + 1, + 30 + ), + ( + 71, + '2025-06-07 12:15:23', + '2025-06-07 12:15:23', + NULL, + 0, + 0, + 1, + 31 + ), + ( + 72, + '2025-06-07 13:44:41', + '2025-06-07 13:44:41', + NULL, + 0, + 0, + 1, + 32 + ), + ( + 73, + '2025-06-07 16:28:59', + '2025-06-07 16:28:59', + NULL, + 0, + 0, + 1, + 33 + ), + ( + 74, + '2025-06-07 16:46:23', + '2025-06-07 16:46:23', + NULL, + 0, + 0, + 1, + 34 + ), + ( + 75, + '2025-06-07 21:17:17', + '2025-06-07 21:17:17', + NULL, + 0, + 0, + 1, + 35 + ), + ( + 76, + '2025-06-07 23:40:41', + '2025-06-07 23:40:41', + NULL, + 0, + 0, + 1, + 36 + ), + ( + 77, + '2025-06-08 18:26:24', + '2025-06-08 18:26:24', + NULL, + 0, + 0, + 1, + 2 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_user` +-- + +CREATE TABLE `admin_user` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码', + `real_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '真实姓名', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '管理员用户表'; + +-- +-- 转存表中的数据 `admin_user` +-- + +INSERT INTO + `admin_user` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `username`, + `password`, + `real_name`, + `status` + ) +VALUES ( + 1, + '2025-04-27 20:19:01', + '2026-04-01 15:31:55', + NULL, + 0, + 0, + 'admin', + '$2a$10$yrsN3Vs6zpe83yHkdEid5u/IlZVZZ4Xo05lXWa6d1yPAva4KfZc2W', + '', + 1 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `admin_user_role` +-- + +CREATE TABLE `admin_user_role` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `user_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到用户表的id', + `role_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到角色表的id' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户角色关联表'; + +-- +-- 转存表中的数据 `admin_user_role` +-- + +INSERT INTO + `admin_user_role` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `user_id`, + `role_id` + ) +VALUES ( + 1, + '2025-04-27 20:19:01', + '2025-04-27 20:19:01', + NULL, + 0, + 0, + 1, + 1 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent` +-- + +CREATE TABLE `agent` ( + `id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `level_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'normal' COMMENT '代理等级', + `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `mobile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `wechat_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `membership_expiry_time` datetime DEFAULT NULL COMMENT '会员过期时间', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_active_stat` +-- + +CREATE TABLE `agent_active_stat` ( + `id` bigint NOT NULL COMMENT '主键ID', + `agent_id` bigint NOT NULL COMMENT '代理ID', + `stat_date` date NOT NULL COMMENT '统计日期', + `active_number` int NOT NULL DEFAULT '0' COMMENT '下级活跃数', + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理活跃统计表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_audit` +-- + +CREATE TABLE `agent_audit` ( + `id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `mobile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `wechat_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `status` tinyint NOT NULL DEFAULT '0', + `audit_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `audit_time` datetime DEFAULT NULL, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理审核表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_closure` +-- + +CREATE TABLE `agent_closure` ( + `id` bigint NOT NULL, + `ancestor_id` bigint NOT NULL, + `descendant_id` bigint NOT NULL, + `depth` int NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理闭包表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_commission` +-- + +CREATE TABLE `agent_commission` ( + `id` bigint NOT NULL, + `agent_id` bigint NOT NULL, + `order_id` bigint NOT NULL, + `amount` decimal(10, 2) NOT NULL DEFAULT '0.00', + `refunded_amount` decimal(18, 2) NOT NULL DEFAULT '0.00' COMMENT '已退款佣金金额', + `product_id` bigint NOT NULL COMMENT '产品ID', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-已结算,1-冻结中,2-已退款', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理佣金表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_commission_deduction` +-- + +CREATE TABLE `agent_commission_deduction` ( + `id` int NOT NULL, + `agent_id` bigint NOT NULL, + `deducted_agent_id` bigint NOT NULL COMMENT '被抽佣代理ID', + `amount` decimal(10, 2) NOT NULL DEFAULT '0.00', + `product_id` bigint NOT NULL COMMENT '产品ID', + `order_id` bigint DEFAULT NULL COMMENT '关联订单ID', + `type` enum('cost', 'pricing') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'cost', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理佣金扣除表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_link` +-- + +CREATE TABLE `agent_link` ( + `id` bigint NOT NULL, + `product_id` bigint NOT NULL, + `price` decimal(10, 2) NOT NULL, + `user_id` bigint NOT NULL, + `agent_id` bigint NOT NULL, + `link_identifier` varchar(255) NOT NULL, + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL, + `del_state` tinyint DEFAULT '0', + `version` bigint DEFAULT '0' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_membership_config` +-- + +CREATE TABLE `agent_membership_config` ( + `id` bigint NOT NULL, + `level_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '会员级别名称,如 S级,S+级', + `price` decimal(10, 2) DEFAULT NULL COMMENT '会员年费', + `report_commission` decimal(5, 2) DEFAULT NULL COMMENT '直推报告收益', + `lower_activity_reward` decimal(10, 2) DEFAULT NULL COMMENT '下级活跃奖励金额', + `new_activity_reward` decimal(10, 2) DEFAULT NULL COMMENT '新增活跃奖励金额', + `lower_standard_count` int DEFAULT NULL COMMENT '活跃下级达标个数', + `new_lower_standard_count` int DEFAULT NULL COMMENT '新增活跃下级达标个数', + `lower_withdraw_reward_ratio` decimal(5, 2) DEFAULT NULL COMMENT '下级提现奖励比例', + `lower_convert_vip_reward` decimal(10, 2) DEFAULT NULL COMMENT '下级转化VIP奖励', + `lower_convert_svip_reward` decimal(10, 2) DEFAULT NULL COMMENT '下级转化SVIP奖励', + `exemption_amount` decimal(10, 2) DEFAULT NULL COMMENT '免审核金额', + `price_increase_max` decimal(10, 2) DEFAULT NULL COMMENT '提价最高金额', + `price_ratio` decimal(5, 2) DEFAULT NULL COMMENT '提价区间收取比例', + `price_increase_amount` decimal(10, 2) DEFAULT NULL COMMENT '在原本成本上加价的金额', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL, + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员级别配置表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `agent_membership_config` +-- + +INSERT INTO + `agent_membership_config` ( + `id`, + `level_name`, + `price`, + `report_commission`, + `lower_activity_reward`, + `new_activity_reward`, + `lower_standard_count`, + `new_lower_standard_count`, + `lower_withdraw_reward_ratio`, + `lower_convert_vip_reward`, + `lower_convert_svip_reward`, + `exemption_amount`, + `price_increase_max`, + `price_ratio`, + `price_increase_amount`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version` + ) +VALUES ( + 1, + 'VIP', + 398.00, + 1.20, + NULL, + NULL, + NULL, + NULL, + NULL, + 199.00, + 259.00, + 1500.00, + 5.00, + 0.40, + 6.00, + '2025-02-25 21:31:13', + '2025-10-24 16:45:32', + NULL, + 0, + 0 + ), + ( + 2, + 'SVIP', + 598.00, + 1.50, + 50.00, + 50.00, + 100, + 20, + 0.01, + 259.00, + 399.00, + 3000.00, + 10.00, + 0.60, + 10.00, + '2025-02-25 21:31:13', + '2025-10-24 16:45:27', + NULL, + 0, + 0 + ), + ( + 3, + 'normal', + 0.00, + 0.00, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 500.00, + NULL, + NULL, + NULL, + '2025-02-25 21:31:13', + '2026-02-28 11:19:41', + NULL, + 0, + 0 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_membership_recharge_order` +-- + +CREATE TABLE `agent_membership_recharge_order` ( + `id` bigint NOT NULL, + `user_id` bigint NOT NULL COMMENT '用户ID', + `agent_id` bigint NOT NULL COMMENT '代理ID', + `level_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '会员级别,如 VIP,SVIP,normal', + `amount` decimal(10, 2) NOT NULL COMMENT '充值金额', + `payment_method` enum( + 'alipay', + 'wechat', + 'appleiap', + 'other' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '支付方式:支付宝,微信,苹果支付,其他', + `order_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '交易号', + `platform_order_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付平台订单号', + `status` enum( + 'pending', + 'paid', + 'failed', + 'refunded', + 'closed' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'pending', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员充值记录表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_membership_user_config` +-- + +CREATE TABLE `agent_membership_user_config` ( + `id` bigint NOT NULL, + `user_id` bigint NOT NULL COMMENT '用户ID,标识代理用户', + `agent_id` bigint NOT NULL COMMENT '代理ID', + `product_id` bigint NOT NULL COMMENT '产品ID', + `price_range_from` decimal(10, 2) NOT NULL COMMENT '定价区间最低', + `price_range_to` decimal(10, 2) NOT NULL COMMENT '定价区间最高', + `price_ratio` decimal(5, 2) NOT NULL COMMENT '定价区间收取比例', + `price_increase_amount` decimal(10, 2) NOT NULL COMMENT '在原本成本上加价的金额', + `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 '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员代理配置表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_order` +-- + +CREATE TABLE `agent_order` ( + `id` bigint NOT NULL, + `order_id` bigint NOT NULL, + `agent_id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL, + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_platform_deduction` +-- + +CREATE TABLE `agent_platform_deduction` ( + `id` int NOT NULL, + `order_id` bigint NOT NULL DEFAULT '0' COMMENT '订单ID', + `agent_id` bigint NOT NULL COMMENT '被抽佣代理ID', + `amount` decimal(10, 2) NOT NULL DEFAULT '0.00', + `type` enum('cost', 'pricing') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'cost', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理平台扣除表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_product_config` +-- + +CREATE TABLE `agent_product_config` ( + `id` bigint NOT NULL, + `product_id` bigint NOT NULL COMMENT '产品ID', + `cost_price` decimal(10, 2) NOT NULL COMMENT '成本价', + `price_range_min` decimal(10, 2) NOT NULL COMMENT '定价区间最低', + `price_range_max` decimal(10, 2) NOT NULL COMMENT '定价区间最高', + `pricing_standard` decimal(10, 2) NOT NULL COMMENT '定价标准', + `overpricing_ratio` decimal(5, 2) NOT NULL COMMENT '超定价标准收费比例', + `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 '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理产品配置表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `agent_product_config` +-- + +INSERT INTO + `agent_product_config` ( + `id`, + `product_id`, + `cost_price`, + `price_range_min`, + `price_range_max`, + `pricing_standard`, + `overpricing_ratio`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version` + ) +VALUES ( + 1, + 1, + 12.80, + 12.80, + 300.00, + 59.80, + 0.10, + '2025-02-26 00:04:13', + '2026-03-01 16:38:53', + NULL, + 0, + 0 + ), + ( + 2, + 2, + 12.80, + 12.80, + 300.00, + 59.80, + 0.10, + '2025-02-26 00:04:13', + '2026-02-28 11:23:22', + NULL, + 0, + 0 + ), + ( + 3, + 3, + 8.80, + 8.80, + 200.00, + 39.80, + 0.10, + '2025-02-26 00:04:13', + '2026-02-28 11:24:13', + NULL, + 0, + 0 + ), + ( + 4, + 4, + 8.80, + 8.80, + 300.00, + 49.80, + 0.10, + '2025-02-26 00:04:13', + '2026-02-28 11:18:51', + NULL, + 0, + 0 + ), + ( + 5, + 5, + 11.89, + 11.89, + 300.00, + 59.80, + 0.10, + '2025-02-26 00:04:13', + '2026-02-28 11:23:37', + NULL, + 0, + 0 + ), + ( + 6, + 6, + 8.80, + 8.80, + 200.00, + 39.80, + 0.10, + '2025-02-26 00:04:13', + '2026-02-28 11:25:21', + NULL, + 0, + 0 + ), + ( + 7, + 7, + 6.80, + 10.80, + 200.00, + 49.80, + 0.08, + '2025-02-26 00:04:13', + '2026-02-28 11:23:47', + NULL, + 0, + 0 + ), + ( + 8, + 27, + 6.80, + 6.80, + 200.00, + 49.80, + 0.08, + '2025-02-26 00:04:13', + '2026-02-28 11:23:54', + NULL, + 0, + 0 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_real_name` +-- + +CREATE TABLE `agent_real_name` ( + `id` bigint NOT NULL COMMENT '主键ID', + `agent_id` bigint NOT NULL COMMENT '代理ID', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '实名姓名', + `id_card` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '身份证号', + `status` enum( + 'pending', + 'approved', + 'rejected' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'pending' COMMENT '认证状态(认证中、通过、拒绝)', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `approve_time` datetime DEFAULT NULL COMMENT '认证通过时间', + `reject_time` datetime DEFAULT NULL COMMENT '认证拒绝时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理实名认证表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_rewards` +-- + +CREATE TABLE `agent_rewards` ( + `id` int NOT NULL, + `agent_id` bigint NOT NULL, + `relation_agent_id` bigint DEFAULT NULL COMMENT '关联代理ID', + `amount` decimal(10, 2) NOT NULL DEFAULT '0.00', + `type` enum( + 'descendant_promotion', + 'descendant_upgrade_vip', + 'descendant_upgrade_svip', + 'descendant_stay_active', + 'descendant_new_active', + 'descendant_withdraw' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理奖励表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_wallet` +-- + +CREATE TABLE `agent_wallet` ( + `id` bigint NOT NULL, + `agent_id` bigint NOT NULL, + `balance` decimal(10, 2) NOT NULL DEFAULT '0.00', + `frozen_balance` decimal(10, 2) NOT NULL DEFAULT '0.00', + `total_earnings` decimal(10, 2) NOT NULL DEFAULT '0.00', + `withdrawn_amount` decimal(10, 2) NOT NULL DEFAULT '0.00', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理钱包表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_wallet_transaction` +-- + +CREATE TABLE `agent_wallet_transaction` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `agent_id` bigint NOT NULL COMMENT '代理ID', + `transaction_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '交易类型:commission(佣金收入)、withdraw(提现)、freeze(冻结)、unfreeze(解冻)、reward(奖励)、refund(退款)、adjust(调整)等', + `amount` decimal(10, 2) NOT NULL COMMENT '变动金额(正数为增加,负数为减少)', + `balance_before` decimal(10, 2) NOT NULL COMMENT '变动前余额', + `balance_after` decimal(10, 2) NOT NULL COMMENT '变动后余额', + `frozen_balance_before` decimal(10, 2) NOT NULL COMMENT '变动前冻结余额', + `frozen_balance_after` decimal(10, 2) NOT NULL COMMENT '变动后冻结余额', + `transaction_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '关联交易ID(订单号、提现申请号等)', + `related_user_id` bigint DEFAULT NULL COMMENT '关联用户ID(如佣金来源用户)', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注说明' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理钱包流水记录表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_withdrawal` +-- + +CREATE TABLE `agent_withdrawal` ( + `id` bigint NOT NULL, + `agent_id` bigint NOT NULL COMMENT '代理ID', + `withdraw_type` tinyint NOT NULL DEFAULT '1' COMMENT '提现类型:1-支付宝,2-银行卡', + `withdraw_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '提现单号', + `amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '提现金额', + `actual_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '实际到账金额(扣税后)', + `tax_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '扣税金额', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-申请中,2-成功,3-失败', + `payeeAccount` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '收款人账号', + `bank_card_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '银行卡号', + `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '开户支行', + `payee_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收款人姓名', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `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 '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理提现记录表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_withdrawal_tax` +-- + +CREATE TABLE `agent_withdrawal_tax` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `withdrawal_amount` decimal(10, 2) NOT NULL COMMENT '提现金额', + `exemption_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '免税金额', + `taxable_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '应税金额', + `tax_rate` decimal(5, 4) NOT NULL DEFAULT '0.2000' COMMENT '税率,如:0.2000表示20%', + `tax_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '应缴税费', + `actual_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '实际到账金额', + `year_month` int NOT NULL COMMENT '所属年月,格式:202401', + `tax_status` tinyint NOT NULL DEFAULT '0' COMMENT '扣税状态:0-待扣税,1-已扣税,2-免税,3-扣税失败', + `tax_time` datetime DEFAULT NULL COMMENT '扣税时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注信息', + `agent_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到代理用户表的id', + `withdrawal_id` bigint NOT NULL DEFAULT '0' COMMENT '关联提现记录表的id', + `exemption_record_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到免税额度记录的id' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理提现扣税表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `agent_withdrawal_tax_exemption` +-- + +CREATE TABLE `agent_withdrawal_tax_exemption` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `year_month` int NOT NULL COMMENT '年月标识,格式:202401', + `total_exemption_amount` decimal(10, 2) NOT NULL DEFAULT '800.00' COMMENT '月度免税总额度', + `used_exemption_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '已使用免税额度', + `remaining_exemption_amount` decimal(10, 2) NOT NULL DEFAULT '800.00' COMMENT '剩余免税额度', + `agent_id` bigint NOT NULL DEFAULT '0' COMMENT '关联到代理用户表的id' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代理提现免税额度表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `authorization_document` +-- + +CREATE TABLE `authorization_document` ( + `id` bigint NOT NULL COMMENT '主键ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `query_id` bigint NOT NULL COMMENT '查询ID', + `file_name` varchar(255) NOT NULL COMMENT '文件名', + `file_path` varchar(500) NOT NULL COMMENT '文件路径', + `file_url` varchar(500) NOT NULL COMMENT '文件访问URL', + `file_size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小(字节)', + `file_type` varchar(50) NOT NULL COMMENT '文件类型', + `status` varchar(20) NOT NULL DEFAULT 'active' COMMENT '状态(active/expired/deleted)', + `expire_time` datetime DEFAULT NULL COMMENT '过期时间(永久保留设为NULL)', + `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(1) NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '授权文档表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `example` +-- + +CREATE TABLE `example` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `api_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'API标识', + `feature_id` bigint NOT NULL COMMENT '关联feature表的ID', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '示例表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `example` +-- + +INSERT INTO + `example` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_id`, + `feature_id`, + `content` + ) +VALUES ( + 35, + '2025-09-15 22:45:09', + '2025-09-21 22:31:28', + NULL, + 0, + 0, + 'DWBG8B4D', + 67, + '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' + ), + ( + 36, + '2025-09-15 22:45:09', + '2025-09-21 22:31:42', + NULL, + 0, + 0, + 'JRZQ4B6C', + 69, + 'qIw0D0X6DGRI0kC8c+RxEKlAlQKDcg5F9iAB1BZRVrjgnFcOfs9dsGK4nqPzy2wM85V2TptNfzRGXKWS9ZpoyXka/tjfnS0Tj0R8L8baccdJzrl2xElu8znK6fHk4BbCTXUI2qlWkFQ7SvBeg1GZNwtxHY1+zAYDkjuEiehhyepHU9RmJegxU5GFCz8sNDeVMbG/OLkaWYkR6pZCmxUR7lecw4VmWXmbiUtztjCAQ6YrfjjNEgIOFhq4rYX+y5eWjiSP1OIae3FZ8bgc+8fVHuGm2O+K8PaZeZATjwfPAY7HdvzGC9gqidQb0gL3oaGId3XQOG5ogyYTj1IzSpAdVV58d5YYKn5dpn7RH0wz1wc4pwO0NTFaa8gTWRzVSTztlyLZWWuUjOYyzMzsIsjysLa82il2mn6a4qkc/oRenf/bm7uH6simzl5PiEMe2WK8sQuhh3lk2dDtrWFsrNEXan3tb6d65PgRr8dO6aQWIElRxZrN/WUXchGK15p4rA7hxUm4XzDgTGmZ5hh3MFd26qE5RsMYOsIK9u0VibV4m4mXtebxXZTQ68krjz94NXnAc+p7u1NjcchdN/L+py0aZ/Xf11ci3yULrRLlDBvOitFlxa+rwkuRoYZsdi24W6QqOxOA7RFx1sKBemspkEyuSO0sWxMjitVVYi4DrqdcqCfDdHZwli84ms5uiaMapOip' + ), + ( + 37, + '2025-09-17 18:20:31', + '2025-09-30 18:00:11', + NULL, + 0, + 0, + 'JRZQ09J8', + 70, + 'fnzxyJ/OiwJaWhuRCxHPJFQod4xr6oFpkujFLNWXxiMTDXOp1EoQF0C4i8FVEbmuXdsNapKkYh8wHZhcR21R7TfTb5HaRKL6Q53/RxeWJwHZSwVzTbbOjcXNBII9ijB2swwuTKOfI1FTxaPYBfBwcpUz7Vksu1xX30G1J1YYy73DlMNMbhPW5wBtelAvryHWvcAm7N8dXUdmPM+QCNZQQg==' + ); + +INSERT INTO + `example` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_id`, + `feature_id`, + `content` + ) +VALUES ( + 38, + '2025-09-17 20:32:33', + '2025-09-30 18:56:25', + NULL, + 0, + 0, + 'DWBG6A2C', + 68, + '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' + ), + ( + 39, + '2025-09-18 20:32:51', + '2025-09-21 22:31:53', + NULL, + 0, + 0, + 'JRZQ5E9F', + 71, + '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' + ); + +INSERT INTO + `example` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_id`, + `feature_id`, + `content` + ) +VALUES ( + 45, + '2025-09-21 02:45:12', + '2025-09-30 18:52:58', + NULL, + 0, + 0, + 'QYGL3F8E', + 59, + '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' + ); + +INSERT INTO + `example` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_id`, + `feature_id`, + `content` + ) +VALUES ( + 46, + '2025-10-18 14:45:51', + '2025-10-18 14:45:51', + NULL, + 0, + 0, + 'IVYZ81NC', + 72, + 'NqH/2/mBEFq1BuA/qGgg+1cLcfloIiiwKeBSDunSEMStn7Cpq45C+xb+GtdJS2G6m4zOTiocxdfb3dQvfpC5H7yxEOyzaG/HpBxIXjrJHEVeQ9z5dJ+bgZNlgQrvBD/N4WuvL6UIFLlo70aIxWjdX/65kATr4qVYwPa3tZQR1rZgvHpU3RD30EV2+RDIvY0kcwyjjtr0b2gHBZiJrtYfxlFJsXe/BR6ns2b0TSSGo0QImJAlT8idq/dh5hVA3qMGUUw738nz7SFVbzjpghbIv+y41vOuUobcac0P/RoWhizUCPqGwtrPco4kDnuhBLS9tSbS/qSf0h8XTJlq84kGAVWNlK/fK9RCODGl39agI5t1d6JczX6T/xpjppEaKm9xi1hB+dNFLWUWkjS1jHzMqA==' + ), + ( + 47, + '2025-10-28 22:03:30', + '2025-10-28 22:06:42', + NULL, + 0, + 0, + 'QCXG7A2B', + 77, + '7NLhryd2y3AZhisMRHa0Sr/skszIb7qmT7Jbdo8vxNA=' + ), + ( + 48, + '2025-10-28 22:06:57', + '2025-10-28 22:06:57', + NULL, + 0, + 0, + 'QCXG9P1C', + 79, + 'bGfP5JrgFYAO735YdC8AvuwQgeDhRK+JXji7QeM1ITN5z/x0HkDd0OKH4FimlNBssh7AALWdkHXJAa41uBzJMUtxdh1Dn0i5UUfdAtlZ+YEccl/AZjMLvj+Rd47I/ssWiyrpGeu/WbhDHcJir5CqjoOh/c5sH/oECYFpw7o1PWK8rw8rRsqFpcuxp9LCl1Fa+wqkz3XmXOe/fVL1IoQs4tkkzzyJwYiyB08ddK8ZAStJuRnT0wOfgVCkSu9vB6fEL5vDahJ09NEidk/0m0s/IkB7IySvsCWtKA0zha7RvyztNdr5BIgwDLiewToACY+q9kfxzx9/nqmA4VIwQwLjX0gRqCE9VZfMFY7jFfxtMbEggNBIIlVNWNRXIgkDZcHEt02/zF1jwN+9pGMh2C/iNGgIIb3Dy/KhHrpkcMGGD2Y4I+f48V4Xh6JO87A0zjhuDaYQS7qUOafHBzL979kgSouCKAZ/NZp0VW61BqehS8wm1MWeWFww1PLaFZZxNzZPz2Jt4vdlutDOjFWJtjWjKZaLpABWcwzYcDV3C3VE0EWgFZ8e5yqFThP234ABrF1GoVcwcsYNbz3kFAuujHrCo3NXvMlcWyMb0vnI1LGaao0=' + ), + ( + 49, + '2025-10-28 22:09:59', + '2025-10-28 22:09:59', + NULL, + 0, + 0, + 'IVYZ7F3A', + 78, + 'nbft302GLyaG+PiCfirKy3w/dRz0rkZN71OI5iZ1yl0np8+maB+1pJNZXb/W4GIMjh5xu+GOcB5ZsiPOgqKXvMxf3GB6OLzWHQeRoJAewuYLuIS/goHWeny+X6FY6lcV7T6m4GO/qVeSQhmnFarGLXM1LB3deYzPGXZoP1VhgmXlD6u1tWhXgLCF1ljpgkJmoagPO6t+G1QOG4BLrpWPcRSKc0Tg005EsqZh211aSBzbhIGAaZjfO/RjtLs5WYP/lBUz69firMgSjEpBTZxN5mq2COlswdlPVbLYolilvCTz7LMuRviSPFn55XFiJ/uPJwNG06l4Z9IZGcPNtDnd9hm0PqCS5V8PNkAgJxP4hwakOJYV09iztTSEujaP/x8JI6XTcvhZYS6JgfbFKItMOgFy6cXgSHMfQzVWld4QloJh3TFjCNp9PCG5m+lO/AwQRYoYYdZ0ipu0gtc+JcT1qNXsH6s8zPfLLHaleyVrrZUmyA71g5dqeY+TuK7p58Vh4C5kSlWA+sMjALPHUxfSrvR2xZimY/e9k7o0xwGmz9mYJybu2EFE29ZiJnk03efvLF2CELuTY52Hyl1gj55l/Q==' + ), + ( + 50, + '2025-11-06 22:38:52', + '2025-11-06 22:38:52', + NULL, + 0, + 0, + 'FLXGDEA9', + 73, + 'tJuhZye3TjV1LnQ+YXtEtq63SG+NvnGI7KC32NTBMSqhQ/8fEEw2YNF0la7b0SkW3nqfrKTxB9J9uM22ghpjx998D2a8VwQvxniSFocpmnaOBGTgjgSPVcqZMGzOn/2TJWww+G9cxsEtMwsvCmsv0RMCGOfV/zyxaCLk2MWMmyZVkxPOJt+BWhM4+SgUawHY' + ), + ( + 51, + '2025-11-10 16:21:59', + '2025-11-10 16:23:43', + NULL, + 0, + 0, + 'IVYZ3P9M', + 80, + 'BHK9tIb7axlDP6zKha9jowNss+XkG8CYLSckuCu3T/1hD38w1w+SMh+zpR4un7Dj9tPDCoqd0jRtJBvmft6vRg69/gKCLskwovUXZ+jXXxODrwwb5DTBs7B9uRAj+OUm/P8LazWT1MdsxSGlx3xNGvew/kwU3mjOZx97z+zhnBbehC/6QflKveapN/4pDCHDltVaC+cObX1D7oRy/HtWito+gNYOIbT/HpDFveLuiSiKGVatXol17HS3LZUWLAo1XX+fi8O7y4sOMRPhXrbMA8rSPQbICuxH+OpFXvWprW9uvCaNCWl3qlupz/B+2MGCCG5rf/0KdOOi7/zbFPys+C82VN1Mu5UhhGb8YEBPfNlb/Um7axe/SPadXz2BRw1o+Ql3qgkSliA5IRUeHz7MWNSHZ3c8S0be9VBEyJOTLdUS5WBNhI2M8fGwXLJqchcA4TN3We0la4IPRdfQQG65lmJgNFEtD0bxyloNAhqRKm+gv6n+pW7Y+ejnKH7u7gMD8YBGkwsT1vrJaLiTj0UnCKv8fBJvogyTCiEV+1BcI4myhuwsdQgrG3/0Ztftob9YlbuEcREXPMW3DOMMOmmgn/DAetfMdsqCW0A449rhDRVkgHfMdlQ5EMXXxPb6ioZb1Mos2zdHKf6UKwT99siMpT5dG9bXFpB5bpXIJzL7IU3Z0kUs4B1YbUIF3e4wJ+VxHFmBMxFp2TOUdpvP9DaVcGwtGhL1s00qfN6vyDsClpZ7Hl347h9XBVDvNMNwBSQ4A+/y836fGqv0AGx8jWd4xVDtJhnUlq+6upNcM7wzHEN+aX0vVMsy4q2Mz1hdV06FIZCFkuS+AwxwdXivbkFl9G9vTGOcVv72oM9xEjYljqKq8tYJWMwh70K300qFaoyTU3jOLqvul+vmhASExp3NONJMDDzwJqpQyTQIH0pYo6WLdq3rEC9F5giaqWjnAyRuAKX+N8kMDYKDKRIJr4t6ZGcy47vCJIAa6C7exoi2QiYaXtZIrJ22PaGZYkWVG4RyT1O5GHPW9wsBi+pErur+oGaOfYSfBEGSmR+sKrbWE1rH0L3M/Mr6lDjsgWkvdObZzeeLClIB52QfaHxPcjMNO6Wg33i3USO6PMhY9szTgN9lXlYyY/fxp65EjPgLndjqFdkLGNr4xPR4e7lbzNFRIA==' + ), + ( + 52, + '2025-12-24 19:37:49', + '2025-12-24 19:37:49', + NULL, + 0, + 0, + 'FLXG7E8F', + 81, + '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' + ), + ( + 53, + '2026-02-01 17:23:58', + '2026-02-01 17:23:58', + NULL, + 0, + 0, + 'JRZQ8B3C', + 82, + 'JuhZ2tslSZ8pzhbTL9sR4mWpJc0JldHOSHqrCp6tXiY3tiQMvguSl75lqTr0Vf8JMaVIMlHuDhFK5kULJm8GR7SOsUMgxFM+XfBtvbXko6cJiqrzDNvjkYNnNKNXr4LyNakRFX9szmquAqz5o6/KRegjGD9lE/ZGt9BVAWxXKl4nFriYdHmnuqPxms3vCJfoRidQ1k8bCSWfMHmVG/nsXw==' + ), + ( + 54, + '2026-03-01 16:09:55', + '2026-03-01 16:09:55', + NULL, + 0, + 0, + 'IVYZ0S0D', + 83, + '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' + ), + ( + 55, + '2026-03-01 16:10:17', + '2026-03-01 16:10:17', + NULL, + 0, + 0, + 'IVYZ6M8P', + 84, + 'qqcWBqlep8YSZ0J8KivbH1nU/e83qZTpLRz2ka5uEHgcWkjqyq9BYD5msilcwORF/znDGUnpGsDePVSPge4ncivfwkGYDMcKEbNLXeVBRmgvvSzXiGKXrPRQxn2UOewKFLzc6A2WLsN9pVQiRJjgOsI/bPW8EBEI6FB1c+EAp/VZhRtw+Z1zZ6pqOwk1ogOJ1RSisjpDDoXhTwZ95UkdJnH5KsxArpfqG9SlCQVwK7J1dfW141zTElLNzOHaQo3MAAxYBati33mOk6gaxQ1LGYwH7tbx5Aa5nuu40RuyOcE=' + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `feature` +-- + +CREATE TABLE `feature` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `api_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'API标识', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述', + `cost_price` decimal(10, 2) DEFAULT '0.00' COMMENT '成本价' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '功能表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `feature` +-- + +INSERT INTO + `feature` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `api_id`, + `name`, + `cost_price` + ) +VALUES ( + 59, + '2025-04-21 14:54:53', + '2026-04-01 15:29:44', + NULL, + 0, + 1, + 'QYGL3F8E', + '人企关系加强版', + 10.00 + ), + ( + 67, + '2025-09-21 20:01:50', + '2026-04-01 15:29:41', + NULL, + 0, + 3, + 'DWBG8B4D', + '谛听多维报告', + 10.00 + ), + ( + 68, + '2025-09-21 20:01:50', + '2026-04-01 15:29:38', + NULL, + 0, + 2, + 'DWBG6A2C', + '司南报告服务', + 10.00 + ), + ( + 69, + '2025-09-21 20:02:26', + '2026-04-01 15:29:35', + NULL, + 0, + 3, + 'JRZQ4B6C', + '借贷表现', + 10.00 + ), + ( + 70, + '2025-09-21 20:02:26', + '2026-04-01 15:29:24', + NULL, + 0, + 2, + 'JRZQ09J8', + '收入评估', + 10.00 + ), + ( + 71, + '2025-09-21 20:02:26', + '2026-04-01 15:29:21', + NULL, + 0, + 2, + 'JRZQ5E9F', + '借选指数评估', + 10.00 + ), + ( + 72, + '2025-10-18 14:44:47', + '2026-04-01 15:29:18', + NULL, + 0, + 2, + 'IVYZ81NC', + '婚姻状况', + 10.00 + ), + ( + 73, + '2025-10-23 15:48:22', + '2026-04-01 15:29:15', + NULL, + 0, + 2, + 'FLXGDEA9', + '本人不良', + 10.00 + ), + ( + 74, + '2025-10-23 15:49:02', + '2026-04-01 15:29:11', + NULL, + 0, + 2, + 'IVYZ6G7H', + '婚姻状态查询补证版', + 10.00 + ), + ( + 75, + '2025-10-23 15:49:38', + '2026-04-01 15:29:09', + NULL, + 0, + 2, + 'IVYZ3A7F', + '学历信息查询', + 10.00 + ), + ( + 76, + '2025-10-23 15:49:49', + '2026-04-01 15:29:05', + NULL, + 0, + 2, + 'YYSY7D3E', + '携号转网查询', + 10.00 + ), + ( + 77, + '2025-10-28 21:25:16', + '2026-04-01 15:29:01', + NULL, + 0, + 3, + 'QCXG7A2B', + '名下车辆', + 10.00 + ), + ( + 78, + '2025-10-28 21:28:54', + '2026-04-01 15:28:58', + NULL, + 0, + 4, + 'IVYZ7F3A', + '学历信息', + 10.00 + ), + ( + 79, + '2025-10-28 22:05:42', + '2026-04-01 15:28:56', + NULL, + 0, + 3, + 'QCXG9P1C', + '名下车辆', + 10.00 + ), + ( + 80, + '2025-11-10 16:19:41', + '2026-04-01 15:28:30', + NULL, + 0, + 3, + 'IVYZ3P9M', + '学历信息', + 10.00 + ), + ( + 81, + '2025-12-24 19:34:24', + '2026-04-01 15:28:27', + NULL, + 0, + 4, + 'FLXG7E8F', + '司法涉诉', + 10.00 + ), + ( + 82, + '2026-02-01 17:23:31', + '2026-04-01 15:28:24', + NULL, + 0, + 1, + 'JRZQ8B3C', + '个人消费等级', + 10.00 + ), + ( + 83, + '2026-03-01 16:03:13', + '2026-04-01 15:28:21', + NULL, + 0, + 1, + 'IVYZ0S0D', + '劳动仲裁信息', + 10.00 + ), + ( + 84, + '2026-03-01 16:09:44', + '2026-04-01 15:28:18', + NULL, + 0, + 1, + 'IVYZ6M8P', + '职业资格证书', + 10.00 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `global_notifications` +-- + +CREATE TABLE `global_notifications` ( + `id` int NOT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `notification_page` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `start_date` date DEFAULT NULL, + `end_date` date DEFAULT NULL, + `start_time` time DEFAULT '00:00:00', + `end_time` time DEFAULT '02:30:00', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `status` tinyint NOT NULL DEFAULT '1', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `order` +-- + +CREATE TABLE `order` ( + `id` bigint NOT NULL COMMENT '主键ID', + `order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '自生成的订单号', + `user_id` bigint NOT NULL COMMENT '用户ID', + `product_id` bigint NOT NULL COMMENT '产品ID(软关联到产品表)', + `payment_platform` enum( + 'alipay', + 'wechat', + 'appleiap', + 'other' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '支付平台(支付宝、微信、苹果内购、其他)', + `payment_scene` enum( + 'app', + 'h5', + 'mini_program', + 'public_account' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '支付场景(App、H5、微信小程序、公众号)', + `platform_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付平台订单号', + `amount` decimal(10, 2) NOT NULL COMMENT '支付金额', + `status` enum( + 'pending', + 'paid', + 'failed', + 'refunded', + 'closed', + 'refunding' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'pending' COMMENT '支付状态', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `pay_time` datetime DEFAULT NULL COMMENT '支付时间', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + `close_time` datetime DEFAULT NULL COMMENT '订单关闭时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `sales_cost` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '销售成本' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '订单表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `order_refund` +-- + +CREATE TABLE `order_refund` ( + `id` bigint NOT NULL COMMENT '主键ID', + `refund_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '退款单号', + `order_id` bigint NOT NULL COMMENT '关联的订单ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `product_id` bigint NOT NULL COMMENT '产品ID', + `platform_refund_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付平台退款单号', + `refund_amount` decimal(10, 2) NOT NULL COMMENT '退款金额', + `refund_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '退款原因', + `status` enum( + 'pending', + 'success', + 'failed', + 'closed' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'pending' COMMENT '退款状态', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `refund_time` datetime DEFAULT NULL COMMENT '退款成功时间', + `close_time` datetime DEFAULT NULL COMMENT '退款关闭时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '退款记录表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `product` +-- + +CREATE TABLE `product` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `product_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '服务名', + `product_en` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '英文名', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '描述', + `notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '备注', + `cost_price` decimal(10, 2) NOT NULL DEFAULT '1.00' COMMENT '成本', + `sell_price` decimal(10, 2) NOT NULL DEFAULT '1.00' COMMENT '售价' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '产品表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `product` +-- + +INSERT INTO + `product` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `product_name`, + `product_en`, + `description`, + `notes`, + `cost_price`, + `sell_price` + ) +VALUES ( + 1, + '2024-11-04 16:25:19', + '2026-03-01 16:33:55', + NULL, + 0, + 0, + '入职风险', + 'backgroundcheck', + '

【职场X光探测器】——司法级人才风险防御系统 当您向候选人发放offer时,是否透视过这些暗雷? ⚡ 光鲜简历背后承接的竞业协议与商业壁垒前科 ⚡ 高管候选人关联的境外敏感行业投资版图 ⚡ 核心技术岗员工的学术论文抄袭黑历史 入职风险报告依托大数据技术连通国家权威数据库,为企业构建多维防御体系,让人力资源决策穿透信息迷雾。 法律声明: 本产品不提供背景调查服务,不涉及个人信息采集

', + NULL, + 12.92, + 99.90 + ), + ( + 2, + '2024-11-04 16:25:19', + '2026-02-14 17:33:48', + NULL, + 0, + 0, + '小微企业', + 'companyinfo', + '

【企业风险CT机】——司法级全息商业决策系统 当您评估一个企业的价值时,账面上的数据可能只是真相的冰山一角。企业风险报告依托大数据技术直连权威数据库,通过司法穿透、财务验真、关联网络三重扫描,为企业主构建「决策安全舱」,让投资并购不再盲人摸象。

', + NULL, + 2.40, + 88.80 + ), + ( + 3, + '2024-11-04 16:25:19', + '2025-12-26 15:36:01', + NULL, + 0, + 0, + '家政风险', + 'homeservice', + '

【家庭安全警报器】——家政人员风险防火墙 当您把家门钥匙交给陌生人的瞬间,是否担忧过这些隐患? ⚠️ 自称「金牌月嫂」却遭前雇主控诉偷窃金饰 ⚠️ 持假健康证上岗的护工将传染病带入母婴室 ⚠️ 被法院列为老赖的保洁员卷走客户预付工资 家政风险报告依托大数据技术深度打通司法权威数据库,用司法级风控手段为您的家庭筑起三重防护网。

', + NULL, + 2.40, + 39.80 + ), + ( + 4, + '2024-11-04 16:25:19', + '2026-02-14 17:34:06', + NULL, + 0, + 0, + '婚恋风险', + 'marriage', + '

【婚姻照妖镜】——婚恋防骗预警系统 面对杀猪盘、重婚骗局等新型婚恋陷阱,仅查婚姻状态还不够!天远数据婚恋风险报告,深度融合全国司法大数据与民政婚姻档案,7x24小时扫描对方隐藏的「黑历史」与「风险关系链」,为您的感情与财产筑起法律屏障火墙。

', + NULL, + 5.00, + 49.80 + ), + ( + 5, + '2024-11-04 16:25:19', + '2026-02-14 18:08:27', + NULL, + 0, + 0, + '贷前风险', + 'preloanbackgroundcheck', + '

【信贷安全盾牌】——贷前风险诊断系统 当您审批贷一笔贷款时,未知的借款人司法炸弹可能正在侵蚀资产安全。贷前风险报告依托大数据技术深度融合司法等权威数据库,为金融机构构建多维防御网,实现风险层层递进从「经验判断」到「司法等级」的质升级。 法律声明: 本产品仅提供个人信用大数据检测,不提供放款和修复征信服务,不查央行征信,无任何爬虫数据

', + NULL, + 5.58, + 88.80 + ), + ( + 6, + '2024-11-04 16:25:19', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + '租赁风险', + 'rentalinfo', + '

【租赁避雷针】——租客信用防火墙 当您交出门钥押金时,是否想过对方可能是「失信老赖」? ⚠️ 表面光鲜的租客实为被法院执行的职业「租金能欠者」 ⚠️ 号称「房东直租」的二房东竟有非法集资前科 ⚠️ 隐瞒的群租集底让租客面临被清退风险无门 天远数据租赁风险报告依托大数据技术连通权威数据库,为房东构建多维防御体系,精准扫描高危租客,让资产安全从源头可控。

', + NULL, + 0.00, + 39.80 + ), + ( + 7, + '2024-11-04 16:25:19', + '2026-02-01 17:25:02', + NULL, + 0, + 0, + '个人风险', + 'riskassessment', + '

【人生风险雷达】——您的智能风险导航仪 在瞬息万变的时代,未知风险可能潜伏在每一次投资、职业跃迁或社交关系中。天远数据个人风险报告,通过AI大数据技术为您的信用画像、生活轨迹与决策路径进行360度扫描,像贴身导航仪一样提前预警风险盲区,助您掌控人生方向盘。

', + NULL, + 3.60, + 49.80 + ), + ( + 27, + '2025-09-21 20:03:32', + '2026-02-14 18:08:06', + NULL, + 0, + 0, + '个人大数据', + 'personalData', + '

【人生风险雷达】——您的智能风险导航仪 在瞬息万变的时代,未知风险可能潜伏在每一次投资、职业跃迁或社交关系中。天远数据个人风险报告,通过AI大数据技术为您的信用画像、生活轨迹与决策路径进行360度扫描,像贴身导航仪一样提前预警风险盲区,助您掌控人生方向盘。

', + NULL, + 3.60, + 49.80 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `product_feature` +-- + +CREATE TABLE `product_feature` ( + `id` bigint NOT NULL COMMENT '主键ID', + `product_id` bigint NOT NULL COMMENT '产品ID', + `feature_id` bigint NOT NULL COMMENT '功能ID', + `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 '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `sort` int NOT NULL DEFAULT '0', + `is_important` tinyint(1) NOT NULL DEFAULT '0', + `enable` tinyint(1) NOT NULL DEFAULT '1' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '产品与功能关联表' ROW_FORMAT = DYNAMIC; + +-- +-- 转存表中的数据 `product_feature` +-- + +INSERT INTO + `product_feature` ( + `id`, + `product_id`, + `feature_id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `sort`, + `is_important`, + `enable` + ) +VALUES ( + 6, + 2, + 59, + '2024-11-20 01:28:38', + '2025-09-21 20:27:25', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 50, + 26, + 1, + '2024-12-13 11:35:04', + '2025-06-19 15:41:14', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 52, + 24, + 6, + '2024-12-13 11:35:04', + '2024-12-13 11:35:04', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 53, + 23, + 7, + '2024-12-13 11:35:04', + '2024-12-13 11:35:04', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 55, + 8, + 37, + '2024-12-13 22:53:21', + '2024-12-24 22:17:35', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 56, + 25, + 8, + '2024-12-16 10:40:26', + '2024-12-16 10:40:26', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 57, + 9, + 38, + '2024-12-24 22:20:19', + '2024-12-24 22:20:19', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 58, + 10, + 39, + '2024-12-24 22:20:40', + '2024-12-24 22:20:40', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 59, + 11, + 40, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 60, + 12, + 41, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 61, + 13, + 42, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 62, + 14, + 43, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 63, + 15, + 44, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 64, + 16, + 45, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 65, + 17, + 46, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 66, + 18, + 47, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 67, + 19, + 48, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 68, + 20, + 49, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 69, + 21, + 50, + '2024-12-24 22:22:17', + '2024-12-24 22:22:17', + NULL, + 0, + 0, + 0, + 0, + 1 + ), + ( + 99, + 27, + 67, + '2025-09-21 20:03:49', + '2026-02-01 17:26:27', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 102, + 5, + 67, + '2025-09-21 20:26:51', + '2025-09-21 20:26:51', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 103, + 2, + 68, + '2025-09-21 20:27:25', + '2025-09-21 20:27:25', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 108, + 1, + 72, + '2025-10-18 15:11:20', + '2025-10-28 21:23:07', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 109, + 4, + 72, + '2025-10-18 15:11:32', + '2025-10-18 15:11:32', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 111, + 7, + 67, + '2025-10-28 21:34:33', + '2026-02-01 17:26:30', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 113, + 4, + 68, + '2025-10-28 21:35:43', + '2025-10-28 21:35:43', + NULL, + 0, + 0, + 3, + 0, + 1 + ), + ( + 114, + 3, + 68, + '2025-10-28 21:36:07', + '2025-10-28 21:36:07', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 117, + 1, + 68, + '2025-10-28 21:37:03', + '2026-03-01 16:33:55', + NULL, + 0, + 0, + 6, + 0, + 1 + ), + ( + 119, + 5, + 79, + '2025-10-28 22:06:20', + '2025-10-28 22:06:20', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 120, + 4, + 79, + '2025-10-28 22:06:31', + '2025-10-28 22:06:31', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 128, + 1, + 80, + '2025-11-10 16:21:16', + '2025-11-10 16:21:16', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 129, + 5, + 71, + '2025-11-10 17:06:07', + '2026-02-04 17:40:11', + NULL, + 0, + 0, + 3, + 0, + 1 + ), + ( + 138, + 27, + 82, + '2026-02-01 17:24:58', + '2026-02-01 17:26:27', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 139, + 7, + 82, + '2026-02-01 17:25:02', + '2026-02-01 17:26:30', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 140, + 5, + 82, + '2026-02-04 17:40:11', + '2026-02-14 18:08:27', + NULL, + 0, + 0, + 4, + 0, + 1 + ), + ( + 141, + 1, + 82, + '2026-02-04 17:40:28', + '2026-03-01 16:33:55', + NULL, + 0, + 0, + 5, + 0, + 1 + ), + ( + 142, + 1, + 83, + '2026-03-01 16:33:55', + '2026-03-01 16:33:55', + NULL, + 0, + 0, + 3, + 0, + 1 + ), + ( + 143, + 1, + 84, + '2026-03-01 16:33:55', + '2026-03-01 16:33:55', + NULL, + 0, + 0, + 4, + 0, + 1 + ), + ( + 144, + 6, + 82, + '2026-04-01 15:27:57', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + 1, + 0, + 1 + ), + ( + 145, + 6, + 70, + '2026-04-01 15:27:57', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + 4, + 0, + 1 + ), + ( + 146, + 6, + 69, + '2026-04-01 15:27:57', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + 5, + 0, + 1 + ), + ( + 147, + 6, + 73, + '2026-04-01 15:27:57', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + 2, + 0, + 1 + ), + ( + 148, + 6, + 71, + '2026-04-01 15:27:57', + '2026-04-01 15:27:57', + NULL, + 0, + 0, + 3, + 0, + 1 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `query` +-- + +CREATE TABLE `query` ( + `id` bigint NOT NULL COMMENT '主键ID', + `order_id` bigint NOT NULL COMMENT '订单ID(软关联到订单表)', + `user_id` bigint NOT NULL COMMENT '用户ID(直接关联到用户)', + `product_id` bigint NOT NULL COMMENT '产品ID(直接关联到产品)', + `query_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '查询params数据', + `query_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '查询结果数据', + `query_state` enum( + 'pending', + 'success', + 'failed' + ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'pending' COMMENT '查询状态', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `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 '删除时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '查询结果表,存储关联订单的查询数据' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `query_cleanup_config` +-- + +CREATE TABLE `query_cleanup_config` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '版本号', + `config_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置键', + `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置值', + `config_desc` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置说明', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-启用,2-禁用' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '查询数据清理配置表'; + +-- +-- 转存表中的数据 `query_cleanup_config` +-- + +INSERT INTO + `query_cleanup_config` ( + `id`, + `create_time`, + `update_time`, + `delete_time`, + `del_state`, + `version`, + `config_key`, + `config_value`, + `config_desc`, + `status` + ) +VALUES ( + 1, + '2025-06-06 16:27:50', + '2025-06-06 19:58:39', + NULL, + 0, + 0, + 'cleanup_cron', + '0 3 * * *', + '清理任务执行时间(cron表达式)', + 1 + ), + ( + 2, + '2025-06-06 16:27:50', + '2025-06-06 16:28:35', + NULL, + 0, + 0, + 'retention_days', + '15', + '数据保留天数', + 1 + ), + ( + 3, + '2025-06-06 16:27:50', + '2025-06-06 19:58:23', + NULL, + 0, + 0, + 'batch_size', + '100', + '每次清理的批次大小', + 1 + ), + ( + 4, + '2025-06-06 16:27:50', + '2025-06-06 19:58:23', + NULL, + 0, + 0, + 'enable_cleanup', + '1', + '是否启用清理:1-启用,2-禁用', + 1 + ); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `query_cleanup_detail` +-- + +CREATE TABLE `query_cleanup_detail` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '版本号', + `cleanup_log_id` bigint NOT NULL COMMENT '关联的清理日志ID', + `query_id` bigint NOT NULL COMMENT '被清理的查询记录ID', + `order_id` bigint NOT NULL COMMENT '关联的订单ID', + `user_id` bigint NOT NULL COMMENT '关联的用户ID', + `product_id` bigint NOT NULL COMMENT '关联的产品ID', + `query_state` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '查询状态', + `create_time_old` datetime NOT NULL COMMENT '原记录创建时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '查询数据清理明细表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `query_cleanup_log` +-- + +CREATE TABLE `query_cleanup_log` ( + `id` bigint NOT NULL COMMENT '主键ID', + `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 '版本号', + `cleanup_time` datetime NOT NULL COMMENT '清理执行时间', + `cleanup_before` datetime NOT NULL COMMENT '清理截止时间', + `affected_rows` int NOT NULL DEFAULT '0' COMMENT '影响行数', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-成功,2-失败', + `error_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '错误信息', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注说明' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '查询数据清理日志表'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `query_user_record` +-- + +CREATE TABLE `query_user_record` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户ID', + `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '姓名密文(AES-ECB+Base64)', + `id_card` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '身份证号密文(AES-ECB+Base64)', + `mobile` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号密文(AES-ECB+Base64)', + `product` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '产品类型,如 marriage/homeservice/riskassessment 等', + `query_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '查询单号(与 order.order_no 一致,如 Q_xxx),用户提交查询时生成', + `order_id` bigint NOT NULL DEFAULT '0' COMMENT '订单ID,关联 order 表,用户发起支付并创建订单后写入', + `platform_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付平台订单号(支付宝/微信),支付成功后由回调写入', + `agent_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '代理标识,代理渠道时有值' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '查询用户记录表:姓名、身份证、手机号、支付订单号等,用于通过查询信息追溯订单'; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `user` +-- + +CREATE TABLE `user` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `mobile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `inside` tinyint NOT NULL DEFAULT '0', + `disable` tinyint NOT NULL DEFAULT '0' COMMENT '0可用 1禁用' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `user_auth` +-- + +CREATE TABLE `user_auth` ( + `id` bigint NOT NULL, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + `user_id` bigint NOT NULL DEFAULT '0', + `auth_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '平台唯一id', + `auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '平台类型' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户授权表' ROW_FORMAT = DYNAMIC; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `user_temp` +-- + +CREATE TABLE `user_temp` ( + `id` bigint NOT NULL, + `auth_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id', + `auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '临时用户表'; + +-- +-- 转储表的索引 +-- + +-- +-- 表的索引 `admin_api` +-- +ALTER TABLE `admin_api` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_api_code` (`api_code`), +ADD KEY `idx_url_method` (`url`, `method`) COMMENT '优化接口查询'; + +-- +-- 表的索引 `admin_dict_data` +-- +ALTER TABLE `admin_dict_data` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_type_value` (`dict_type`, `dict_value`), +ADD UNIQUE KEY `uk_type_label` (`dict_type`, `dict_label`), +ADD KEY `idx_dict_type` (`dict_type`); + +-- +-- 表的索引 `admin_dict_type` +-- +ALTER TABLE `admin_dict_type` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_dict_type` (`dict_type`); + +-- +-- 表的索引 `admin_menu` +-- +ALTER TABLE `admin_menu` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_name_path` (`name`, `path`), +ADD KEY `idx_pid` (`pid`) USING BTREE COMMENT '优化层级查询'; + +-- +-- 表的索引 `admin_role` +-- +ALTER TABLE `admin_role` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_role_code` (`role_code`); + +-- +-- 表的索引 `admin_role_api` +-- +ALTER TABLE `admin_role_api` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_role_api` (`role_id`, `api_id`), +ADD KEY `idx_role_id` (`role_id`) COMMENT '优化角色查询', +ADD KEY `idx_api_id` (`api_id`) COMMENT '优化接口查询'; + +-- +-- 表的索引 `admin_role_menu` +-- +ALTER TABLE `admin_role_menu` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`), +ADD KEY `idx_role_id` (`role_id`) COMMENT '优化角色查询', +ADD KEY `idx_menu_id` (`menu_id`) COMMENT '优化菜单查询'; + +-- +-- 表的索引 `admin_user` +-- +ALTER TABLE `admin_user` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_username` (`username`), +ADD UNIQUE KEY `uk_real_name` (`real_name`); + +-- +-- 表的索引 `admin_user_role` +-- +ALTER TABLE `admin_user_role` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_user_role` (`user_id`, `role_id`), +ADD KEY `idx_user_id` (`user_id`) COMMENT '优化用户查询', +ADD KEY `idx_role_id` (`role_id`) COMMENT '优化角色查询'; + +-- +-- 表的索引 `agent` +-- +ALTER TABLE `agent` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `uniq_user_id` (`user_id`) USING BTREE, +ADD UNIQUE KEY `uniq_mobile` (`mobile`) USING BTREE, +ADD KEY `idx_user_id` (`user_id`) USING BTREE; + +-- +-- 表的索引 `agent_active_stat` +-- +ALTER TABLE `agent_active_stat` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_agent_date` (`agent_id`, `stat_date`) USING BTREE, +ADD KEY `idx_stat_date` (`stat_date`) USING BTREE; + +-- +-- 表的索引 `agent_audit` +-- +ALTER TABLE `agent_audit` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `uniq_user_id` (`user_id`) USING BTREE; + +-- +-- 表的索引 `agent_closure` +-- +ALTER TABLE `agent_closure` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_descendant_depth` (`descendant_id`, `depth`), +ADD UNIQUE KEY `unique_ancestor_descendant` ( + `ancestor_id`, + `descendant_id` +) USING BTREE; + +-- +-- 表的索引 `agent_commission` +-- +ALTER TABLE `agent_commission` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD KEY `idx_agent_id` (`agent_id`) USING BTREE, +ADD KEY `idx_order_id` (`order_id`) USING BTREE; + +-- +-- 表的索引 `agent_commission_deduction` +-- +ALTER TABLE `agent_commission_deduction` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD KEY `idx_agent_id` (`agent_id`) USING BTREE, +ADD KEY `idx_deducted_agent_id` (`deducted_agent_id`) USING BTREE, +ADD KEY `idx_order_type` (`order_id`, `type`); + +-- +-- 表的索引 `agent_link` +-- +ALTER TABLE `agent_link` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uniq_link_identifier` (`link_identifier`), +ADD KEY `idx_product_id` (`product_id`), +ADD KEY `idx_user_id` (`user_id`), +ADD KEY `idx_agent_id` (`agent_id`); + +-- +-- 表的索引 `agent_membership_config` +-- +ALTER TABLE `agent_membership_config` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uniq_level_name` (`level_name`) USING BTREE; + +-- +-- 表的索引 `agent_membership_recharge_order` +-- +ALTER TABLE `agent_membership_recharge_order` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_order_no` (`order_no`), +ADD UNIQUE KEY `unique_platform_order_id` (`platform_order_id`); + +-- +-- 表的索引 `agent_membership_user_config` +-- +ALTER TABLE `agent_membership_user_config` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uniq_agent_id_product_id` (`agent_id`, `product_id`) USING BTREE, +ADD KEY `idx_user_id` (`user_id`) USING BTREE, +ADD KEY `idx_agent_id` (`agent_id`) USING BTREE; + +-- +-- 表的索引 `agent_order` +-- +ALTER TABLE `agent_order` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_order_id` (`order_id`), +ADD KEY `idx_order_id` (`order_id`), +ADD KEY `idx_agent_id` (`agent_id`); + +-- +-- 表的索引 `agent_platform_deduction` +-- +ALTER TABLE `agent_platform_deduction` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD KEY `idx_agent_id` (`agent_id`) USING BTREE, +ADD KEY `idx_order_id` (`order_id`); + +-- +-- 表的索引 `agent_product_config` +-- +ALTER TABLE `agent_product_config` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uniq_product_id` (`product_id`) USING BTREE; + +-- +-- 表的索引 `agent_real_name` +-- +ALTER TABLE `agent_real_name` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_agent_id` (`agent_id`), +ADD KEY `idx_status` (`status`), +ADD KEY `idx_id_card` (`id_card`) USING BTREE; + +-- +-- 表的索引 `agent_rewards` +-- +ALTER TABLE `agent_rewards` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD KEY `idx_agent_id` (`agent_id`) USING BTREE, +ADD KEY `idx_relation_agent_id` (`relation_agent_id`) USING BTREE; + +-- +-- 表的索引 `agent_wallet` +-- +ALTER TABLE `agent_wallet` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `uniq_agent_id` (`agent_id`) USING BTREE; + +-- +-- 表的索引 `agent_wallet_transaction` +-- +ALTER TABLE `agent_wallet_transaction` +ADD PRIMARY KEY (`id`), +ADD KEY `idx_agent_id` (`agent_id`) COMMENT '代理查询流水', +ADD KEY `idx_transaction_id` (`transaction_id`) COMMENT '根据交易ID查询', +ADD KEY `idx_create_time` (`create_time`) COMMENT '按时间查询', +ADD KEY `idx_agent_id_create_time` (`agent_id`, `create_time`) COMMENT '分页查询代理流水'; + +-- +-- 表的索引 `agent_withdrawal` +-- +ALTER TABLE `agent_withdrawal` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_withdraw_no` (`withdraw_no`) USING BTREE, +ADD KEY `idx_agent_status` (`agent_id`, `status`) USING BTREE, +ADD KEY `idx_withdraw_type` (`withdraw_type`); + +-- +-- 表的索引 `agent_withdrawal_tax` +-- +ALTER TABLE `agent_withdrawal_tax` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_withdrawal_id` (`withdrawal_id`) COMMENT '提现记录ID唯一', +ADD KEY `idx_agent_id` (`agent_id`) COMMENT '优化代理查询', +ADD KEY `idx_year_month` (`year_month`) COMMENT '优化按月查询', +ADD KEY `idx_tax_status` (`tax_status`) COMMENT '优化状态查询', +ADD KEY `idx_exemption_record_id` (`exemption_record_id`) COMMENT '优化免税记录关联查询', +ADD KEY `idx_create_time` (`create_time`) COMMENT '优化时间查询'; + +-- +-- 表的索引 `agent_withdrawal_tax_exemption` +-- +ALTER TABLE `agent_withdrawal_tax_exemption` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `uk_agent_year_month` (`agent_id`, `year_month`) COMMENT '同一代理同一月份只能有一条记录', +ADD KEY `idx_agent_id` (`agent_id`) COMMENT '优化代理查询', +ADD KEY `idx_year_month` (`year_month`) COMMENT '优化按月查询'; + +-- +-- 表的索引 `authorization_document` +-- +ALTER TABLE `authorization_document` +ADD PRIMARY KEY (`id`), +ADD KEY `idx_user_id` (`user_id`), +ADD KEY `idx_order_id` (`order_id`), +ADD KEY `idx_query_id` (`query_id`), +ADD KEY `idx_status` (`status`), +ADD KEY `idx_del_state` (`del_state`), +ADD KEY `idx_create_time` (`create_time`); + +-- +-- 表的索引 `example` +-- +ALTER TABLE `example` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_api_id` (`api_id`) USING BTREE, +ADD UNIQUE KEY `unique_feature_id` (`feature_id`) USING BTREE; + +-- +-- 表的索引 `feature` +-- +ALTER TABLE `feature` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_api_id` (`api_id`) USING BTREE; + +-- +-- 表的索引 `global_notifications` +-- +ALTER TABLE `global_notifications` +ADD PRIMARY KEY (`id`) USING BTREE; + +-- +-- 表的索引 `order` +-- +ALTER TABLE `order` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_order_no` (`order_no`) USING BTREE, +ADD KEY `idx_user_id` (`user_id`) USING BTREE, +ADD KEY `idx_product_id` (`product_id`) USING BTREE, +ADD KEY `idx_payment_platform` (`payment_platform`) USING BTREE, +ADD KEY `idx_payment_scene` (`payment_scene`) USING BTREE; + +-- +-- 表的索引 `order_refund` +-- +ALTER TABLE `order_refund` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_refund_no` (`refund_no`), +ADD UNIQUE KEY `unique_platform_refund_id` (`platform_refund_id`), +ADD KEY `idx_user_id` (`user_id`), +ADD KEY `idx_product_id` (`product_id`), +ADD KEY `idx_status` (`status`), +ADD KEY `idx_order_id` (`order_id`); + +-- +-- 表的索引 `product` +-- +ALTER TABLE `product` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_product_en` (`product_en`) USING BTREE; + +-- +-- 表的索引 `product_feature` +-- +ALTER TABLE `product_feature` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_product_feature` (`product_id`, `feature_id`) USING BTREE; + +-- +-- 表的索引 `query` +-- +ALTER TABLE `query` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_order_id` (`order_id`) USING BTREE, +ADD KEY `idx_user_id` (`user_id`) USING BTREE, +ADD KEY `idx_product_id` (`product_id`) USING BTREE; + +-- +-- 表的索引 `query_cleanup_config` +-- +ALTER TABLE `query_cleanup_config` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_config_key` (`config_key`) COMMENT '配置键唯一索引'; + +-- +-- 表的索引 `query_cleanup_detail` +-- +ALTER TABLE `query_cleanup_detail` +ADD PRIMARY KEY (`id`), +ADD KEY `idx_cleanup_log_id` (`cleanup_log_id`) COMMENT '优化按清理日志查询', +ADD KEY `idx_query_id` (`query_id`) COMMENT '优化按查询ID查询', +ADD KEY `idx_order_id` (`order_id`) COMMENT '优化按订单ID查询', +ADD KEY `idx_user_id` (`user_id`) COMMENT '优化按用户ID查询'; + +-- +-- 表的索引 `query_cleanup_log` +-- +ALTER TABLE `query_cleanup_log` +ADD PRIMARY KEY (`id`), +ADD KEY `idx_cleanup_time` (`cleanup_time`) COMMENT '优化按清理时间查询', +ADD KEY `idx_cleanup_before` (`cleanup_before`) COMMENT '优化按清理截止时间查询', +ADD KEY `idx_status` (`status`) COMMENT '优化按状态查询'; + +-- +-- 表的索引 `query_user_record` +-- +ALTER TABLE `query_user_record` +ADD PRIMARY KEY (`id`), +ADD KEY `idx_user_id` (`user_id`), +ADD KEY `idx_id_card` (`id_card`), +ADD KEY `idx_mobile` (`mobile`), +ADD KEY `idx_query_no` (`query_no`), +ADD KEY `idx_order_id` (`order_id`), +ADD KEY `idx_platform_order_id` (`platform_order_id`), +ADD KEY `idx_create_time` (`create_time`); + +-- +-- 表的索引 `user` +-- +ALTER TABLE `user` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_mobile` (`mobile`) USING BTREE; + +-- +-- 表的索引 `user_auth` +-- +ALTER TABLE `user_auth` +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE KEY `unique_type_key` (`auth_type`, `auth_key`) USING BTREE, +ADD UNIQUE KEY `unique_userId_key` (`user_id`, `auth_type`) USING BTREE; + +-- +-- 表的索引 `user_temp` +-- +ALTER TABLE `user_temp` +ADD PRIMARY KEY (`id`), +ADD UNIQUE KEY `unique_type_key` (`auth_type`, `auth_key`) USING BTREE; + +-- +-- 在导出的表使用AUTO_INCREMENT +-- + +-- +-- 使用表AUTO_INCREMENT `admin_api` +-- +ALTER TABLE `admin_api` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 104; + +-- +-- 使用表AUTO_INCREMENT `admin_dict_data` +-- +ALTER TABLE `admin_dict_data` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 4; + +-- +-- 使用表AUTO_INCREMENT `admin_dict_type` +-- +ALTER TABLE `admin_dict_type` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 2; + +-- +-- 使用表AUTO_INCREMENT `admin_menu` +-- +ALTER TABLE `admin_menu` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 37; + +-- +-- 使用表AUTO_INCREMENT `admin_role` +-- +ALTER TABLE `admin_role` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 3; + +-- +-- 使用表AUTO_INCREMENT `admin_role_api` +-- +ALTER TABLE `admin_role_api` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `admin_role_menu` +-- +ALTER TABLE `admin_role_menu` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 78; + +-- +-- 使用表AUTO_INCREMENT `admin_user` +-- +ALTER TABLE `admin_user` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 3; + +-- +-- 使用表AUTO_INCREMENT `admin_user_role` +-- +ALTER TABLE `admin_user_role` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 3; + +-- +-- 使用表AUTO_INCREMENT `agent` +-- +ALTER TABLE `agent` MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_active_stat` +-- +ALTER TABLE `agent_active_stat` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `agent_audit` +-- +ALTER TABLE `agent_audit` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_closure` +-- +ALTER TABLE `agent_closure` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_commission` +-- +ALTER TABLE `agent_commission` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_commission_deduction` +-- +ALTER TABLE `agent_commission_deduction` +MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_link` +-- +ALTER TABLE `agent_link` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_membership_config` +-- +ALTER TABLE `agent_membership_config` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 4; + +-- +-- 使用表AUTO_INCREMENT `agent_membership_recharge_order` +-- +ALTER TABLE `agent_membership_recharge_order` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_membership_user_config` +-- +ALTER TABLE `agent_membership_user_config` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_order` +-- +ALTER TABLE `agent_order` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_platform_deduction` +-- +ALTER TABLE `agent_platform_deduction` +MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_product_config` +-- +ALTER TABLE `agent_product_config` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT, +AUTO_INCREMENT = 9; + +-- +-- 使用表AUTO_INCREMENT `agent_real_name` +-- +ALTER TABLE `agent_real_name` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `agent_rewards` +-- +ALTER TABLE `agent_rewards` +MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_wallet` +-- +ALTER TABLE `agent_wallet` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_wallet_transaction` +-- +ALTER TABLE `agent_wallet_transaction` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_withdrawal` +-- +ALTER TABLE `agent_withdrawal` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_withdrawal_tax` +-- +ALTER TABLE `agent_withdrawal_tax` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `agent_withdrawal_tax_exemption` +-- +ALTER TABLE `agent_withdrawal_tax_exemption` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `authorization_document` +-- +ALTER TABLE `authorization_document` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `example` +-- +ALTER TABLE `example` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', +AUTO_INCREMENT = 56; + +-- +-- 使用表AUTO_INCREMENT `feature` +-- +ALTER TABLE `feature` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', +AUTO_INCREMENT = 85; + +-- +-- 使用表AUTO_INCREMENT `global_notifications` +-- +ALTER TABLE `global_notifications` +MODIFY `id` int NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `order` +-- +ALTER TABLE `order` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `order_refund` +-- +ALTER TABLE `order_refund` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `product` +-- +ALTER TABLE `product` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', +AUTO_INCREMENT = 28; + +-- +-- 使用表AUTO_INCREMENT `product_feature` +-- +ALTER TABLE `product_feature` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', +AUTO_INCREMENT = 149; + +-- +-- 使用表AUTO_INCREMENT `query` +-- +ALTER TABLE `query` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `query_cleanup_config` +-- +ALTER TABLE `query_cleanup_config` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', +AUTO_INCREMENT = 9; + +-- +-- 使用表AUTO_INCREMENT `query_cleanup_detail` +-- +ALTER TABLE `query_cleanup_detail` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `query_cleanup_log` +-- +ALTER TABLE `query_cleanup_log` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID'; + +-- +-- 使用表AUTO_INCREMENT `query_user_record` +-- +ALTER TABLE `query_user_record` +MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `user` +-- +ALTER TABLE `user` MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `user_auth` +-- +ALTER TABLE `user_auth` MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `user_temp` +-- +ALTER TABLE `user_temp` MODIFY `id` bigint NOT NULL AUTO_INCREMENT; + +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */ +; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */ +; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */ +; \ No newline at end of file diff --git a/deploy/sql/template.sql b/deploy/sql/template.sql new file mode 100644 index 0000000..1e732f4 --- /dev/null +++ b/deploy/sql/template.sql @@ -0,0 +1,20 @@ +CREATE TABLE `表名` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + + /* 业务字段开始 */ + `字段1` 数据类型 [约束条件] [DEFAULT 默认值] [COMMENT '字段说明'], + `字段2` 数据类型 [约束条件] [DEFAULT 默认值] [COMMENT '字段说明'], + /* 关联字段 - 软关联 */ + `关联表id` bigint [NOT NULL] [DEFAULT '0'] COMMENT '关联到XX表的id', + /* 业务字段结束 */ + + PRIMARY KEY (`id`), + /* 索引定义 */ + UNIQUE KEY `索引名称` (`字段名`), + KEY `idx_关联字段` (`关联表id`) COMMENT '优化关联查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='表说明'; \ No newline at end of file diff --git a/deploy/template/api/config.tpl b/deploy/template/api/config.tpl new file mode 100644 index 0000000..55127ef --- /dev/null +++ b/deploy/template/api/config.tpl @@ -0,0 +1,9 @@ +package config + +import {{.authImport}} + +type Config struct { + rest.RestConf + {{.auth}} + {{.jwtTrans}} +} diff --git a/deploy/template/api/context.tpl b/deploy/template/api/context.tpl new file mode 100644 index 0000000..c15c1e4 --- /dev/null +++ b/deploy/template/api/context.tpl @@ -0,0 +1,17 @@ +package svc + +import ( + {{.configImport}} +) + +type ServiceContext struct { + Config {{.config}} + {{.middleware}} +} + +func NewServiceContext(c {{.config}}) *ServiceContext { + return &ServiceContext{ + Config: c, + {{.middlewareAssignment}} + } +} diff --git a/deploy/template/api/etc.tpl b/deploy/template/api/etc.tpl new file mode 100644 index 0000000..ed55cf1 --- /dev/null +++ b/deploy/template/api/etc.tpl @@ -0,0 +1,3 @@ +Name: {{.serviceName}} +Host: {{.host}} +Port: {{.port}} diff --git a/deploy/template/api/handler.tpl b/deploy/template/api/handler.tpl new file mode 100644 index 0000000..ac2ee8a --- /dev/null +++ b/deploy/template/api/handler.tpl @@ -0,0 +1,27 @@ +package {{.PkgName}} + +import ( + "net/http" + + "bdrp-server/common/result" + "bdrp-server/pkg/lzkit/validator" + "github.com/zeromicro/go-zero/rest/httpx" + {{.ImportPackages}} +) + +func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + {{if .HasRequest}}var req types.{{.RequestType}} + 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 + } + {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx) + {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}}) + result.HttpResult(r, w, {{if .HasResp}}resp{{else}}nil{{end}}, err) + } +} diff --git a/deploy/template/api/logic.tpl b/deploy/template/api/logic.tpl new file mode 100644 index 0000000..7c04323 --- /dev/null +++ b/deploy/template/api/logic.tpl @@ -0,0 +1,25 @@ +package {{.pkgName}} + +import ( + {{.imports}} +) + +type {{.logic}} struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} { + return &{{.logic}}{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} { + // todo: add your logic here and delete this line + + {{.returnString}} +} diff --git a/deploy/template/api/main.tpl b/deploy/template/api/main.tpl new file mode 100644 index 0000000..0350414 --- /dev/null +++ b/deploy/template/api/main.tpl @@ -0,0 +1,27 @@ +package main + +import ( + "flag" + "fmt" + + {{.importPackages}} + "bdrp-server/common/middleware" +) + +var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + ctx := svc.NewServiceContext(c) + server := rest.MustNewServer(c.RestConf) + defer server.Stop() + + handler.RegisterHandlers(server, ctx) + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/deploy/template/api/middleware.tpl b/deploy/template/api/middleware.tpl new file mode 100644 index 0000000..af714e0 --- /dev/null +++ b/deploy/template/api/middleware.tpl @@ -0,0 +1,20 @@ + +package middleware + +import "net/http" + +type {{.name}} struct { +} + +func New{{.name}}() *{{.name}} { + return &{{.name}}{} +} + +func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO generate middleware implement function, delete after code implementation + + // Passthrough to next handler if need + next(w, r) + } +} diff --git a/deploy/template/api/route-addition.tpl b/deploy/template/api/route-addition.tpl new file mode 100644 index 0000000..bb8a5df --- /dev/null +++ b/deploy/template/api/route-addition.tpl @@ -0,0 +1,4 @@ + + server.AddRoutes( + {{.routes}} {{.jwt}}{{.signature}} {{.prefix}} {{.timeout}} {{.maxBytes}} + ) diff --git a/deploy/template/api/routes.tpl b/deploy/template/api/routes.tpl new file mode 100644 index 0000000..f13fb11 --- /dev/null +++ b/deploy/template/api/routes.tpl @@ -0,0 +1,13 @@ +// Code generated by goctl. DO NOT EDIT. +package handler + +import ( + "net/http"{{if .hasTimeout}} + "time"{{end}} + + {{.importPackages}} +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + {{.routesAdditions}} +} diff --git a/deploy/template/api/template.tpl b/deploy/template/api/template.tpl new file mode 100644 index 0000000..2176441 --- /dev/null +++ b/deploy/template/api/template.tpl @@ -0,0 +1,24 @@ +syntax = "v1" + +info ( + title: // TODO: add title + desc: // TODO: add description + author: "{{.gitUser}}" + email: "{{.gitEmail}}" +) + +type request { + // TODO: add members here and delete this comment +} + +type response { + // TODO: add members here and delete this comment +} + +service {{.serviceName}} { + @handler GetUser // TODO: set handler name and delete this comment + get /users/id/:userId(request) returns(response) + + @handler CreateUser // TODO: set handler name and delete this comment + post /users/create(request) +} diff --git a/deploy/template/api/types.tpl b/deploy/template/api/types.tpl new file mode 100644 index 0000000..735ec2d --- /dev/null +++ b/deploy/template/api/types.tpl @@ -0,0 +1,6 @@ +// Code generated by goctl. DO NOT EDIT. +package types{{if .containsTime}} +import ( + "time" +){{end}} +{{.types}} diff --git a/deploy/template/docker/docker.tpl b/deploy/template/docker/docker.tpl new file mode 100644 index 0000000..d1b5ff4 --- /dev/null +++ b/deploy/template/docker/docker.tpl @@ -0,0 +1,33 @@ +FROM golang:{{.Version}}alpine AS builder + +LABEL stage=gobuilder + +ENV CGO_ENABLED 0 +{{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories +{{end}}{{if .HasTimezone}} +RUN apk update --no-cache && apk add --no-cache tzdata +{{end}} +WORKDIR /build + +ADD go.mod . +ADD go.sum . +RUN go mod download +COPY . . +{{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc +{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoMainFrom}} + + +FROM {{.BaseImage}} + +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}} +ENV TZ {{.Timezone}} +{{end}} +WORKDIR /app +COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}} +COPY --from=builder /app/etc /app/etc{{end}} +{{if .HasPort}} +EXPOSE {{.Port}} +{{end}} +CMD ["./{{.ExeFile}}"{{.Argument}}] diff --git a/deploy/template/gateway/etc.tpl b/deploy/template/gateway/etc.tpl new file mode 100644 index 0000000..0a70f1a --- /dev/null +++ b/deploy/template/gateway/etc.tpl @@ -0,0 +1,18 @@ +Name: gateway-example # gateway name +Host: localhost # gateway host +Port: 8888 # gateway port +Upstreams: # upstreams + - Grpc: # grpc upstream + Target: 0.0.0.0:8080 # grpc target,the direct grpc server address,for only one node +# Endpoints: [0.0.0.0:8080,192.168.120.1:8080] # grpc endpoints, the grpc server address list, for multiple nodes +# Etcd: # etcd config, if you want to use etcd to discover the grpc server address +# Hosts: [127.0.0.1:2378,127.0.0.1:2379] # etcd hosts +# Key: greet.grpc # the discovery key + # protoset mode + ProtoSets: + - hello.pb + # Mappings can also be written in proto options +# Mappings: # routes mapping +# - Method: get +# Path: /ping +# RpcPath: hello.Hello/Ping diff --git a/deploy/template/gateway/main.tpl b/deploy/template/gateway/main.tpl new file mode 100644 index 0000000..6273451 --- /dev/null +++ b/deploy/template/gateway/main.tpl @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/gateway" +) + +var configFile = flag.String("f", "etc/gateway.yaml", "config file") + +func main() { + flag.Parse() + + var c gateway.GatewayConf + conf.MustLoad(*configFile, &c) + gw := gateway.MustNewServer(c) + defer gw.Stop() + gw.Start() +} diff --git a/deploy/template/kube/deployment.tpl b/deploy/template/kube/deployment.tpl new file mode 100644 index 0000000..14145df --- /dev/null +++ b/deploy/template/kube/deployment.tpl @@ -0,0 +1,117 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.Name}} + namespace: {{.Namespace}} + labels: + app: {{.Name}} +spec: + replicas: {{.Replicas}} + revisionHistoryLimit: {{.Revisions}} + selector: + matchLabels: + app: {{.Name}} + template: + metadata: + labels: + app: {{.Name}} + spec:{{if .ServiceAccount}} + serviceAccountName: {{.ServiceAccount}}{{end}} + containers: + - name: {{.Name}} + image: {{.Image}} + {{if .ImagePullPolicy}}imagePullPolicy: {{.ImagePullPolicy}} + {{end}}ports: + - containerPort: {{.Port}} + readinessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: {{.Port}} + initialDelaySeconds: 15 + periodSeconds: 20 + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + volumeMounts: + - name: timezone + mountPath: /etc/localtime + {{if .Secret}}imagePullSecrets: + - name: {{.Secret}} + {{end}}volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai + +--- + +apiVersion: v1 +kind: Service +metadata: + name: {{.Name}}-svc + namespace: {{.Namespace}} +spec: + ports: + {{if .UseNodePort}}- nodePort: {{.NodePort}} + port: {{.Port}} + protocol: TCP + targetPort: {{.TargetPort}} + type: NodePort{{else}}- port: {{.Port}} + targetPort: {{.TargetPort}}{{end}} + selector: + app: {{.Name}} + +--- + +apiVersion: autoscaling/v2beta2 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-c + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-c +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 + +--- + +apiVersion: autoscaling/v2beta2 +kind: HorizontalPodAutoscaler +metadata: + name: {{.Name}}-hpa-m + namespace: {{.Namespace}} + labels: + app: {{.Name}}-hpa-m +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.Name}} + minReplicas: {{.MinReplicas}} + maxReplicas: {{.MaxReplicas}} + metrics: + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/deploy/template/kube/job.tpl b/deploy/template/kube/job.tpl new file mode 100644 index 0000000..0da72ed --- /dev/null +++ b/deploy/template/kube/job.tpl @@ -0,0 +1,37 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{.Name}} + namespace: {{.Namespace}} +spec: + successfulJobsHistoryLimit: {{.SuccessfulJobsHistoryLimit}} + schedule: "{{.Schedule}}" + jobTemplate: + spec: + template: + spec:{{if .ServiceAccount}} + serviceAccountName: {{.ServiceAccount}}{{end}} + {{end}}containers: + - name: {{.Name}} + image: # todo image url + resources: + requests: + cpu: {{.RequestCpu}}m + memory: {{.RequestMem}}Mi + limits: + cpu: {{.LimitCpu}}m + memory: {{.LimitMem}}Mi + command: + - ./{{.ServiceName}} + - -f + - ./{{.Name}}.yaml + volumeMounts: + - name: timezone + mountPath: /etc/localtime + imagePullSecrets: + - name: # registry secret, if no, remove this + restartPolicy: OnFailure + volumes: + - name: timezone + hostPath: + path: /usr/share/zoneinfo/Asia/Shanghai diff --git a/deploy/template/model/delete.tpl b/deploy/template/model/delete.tpl new file mode 100644 index 0000000..e22869a --- /dev/null +++ b/deploy/template/model/delete.tpl @@ -0,0 +1,21 @@ +func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, session sqlx.Session, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error { + {{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, {{.lowerStartCamelPrimaryKey}}) + if err!=nil{ + return err + } + + {{end}} {{.keys}} + _, err {{if .containsIndexCache}}={{else}}:={{end}} m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table) + if session!=nil{ + return session.ExecCtx(ctx,query, {{.lowerStartCamelPrimaryKey}}) + } + return conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}) + }, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table) + if session!=nil{ + _,err:= session.ExecCtx(ctx,query, {{.lowerStartCamelPrimaryKey}}) + return err + } + _,err:=m.conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}){{end}} + return err +} \ No newline at end of file diff --git a/deploy/template/model/err.tpl b/deploy/template/model/err.tpl new file mode 100644 index 0000000..cbc9f82 --- /dev/null +++ b/deploy/template/model/err.tpl @@ -0,0 +1,9 @@ +package {{.pkg}} + +import ( + "errors" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var ErrNotFound = sqlx.ErrNotFound +var ErrNoRowsUpdate = errors.New("update db no rows change") \ No newline at end of file diff --git a/deploy/template/model/field.tpl b/deploy/template/model/field.tpl new file mode 100644 index 0000000..6b4ed38 --- /dev/null +++ b/deploy/template/model/field.tpl @@ -0,0 +1 @@ +{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}} \ No newline at end of file diff --git a/deploy/template/model/find-one-by-field-extra-method.tpl b/deploy/template/model/find-one-by-field-extra-method.tpl new file mode 100644 index 0000000..af15538 --- /dev/null +++ b/deploy/template/model/find-one-by-field-extra-method.tpl @@ -0,0 +1,7 @@ +func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary) +} +func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { + query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table ) + return conn.QueryRowCtx(ctx, v, query, primary,globalkey.DelStateNo) +} diff --git a/deploy/template/model/find-one-by-field.tpl b/deploy/template/model/find-one-by-field.tpl new file mode 100644 index 0000000..e6fd828 --- /dev/null +++ b/deploy/template/model/find-one-by-field.tpl @@ -0,0 +1,32 @@ + +func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) { +{{if .withCache}}{{.cacheKey}} + var resp {{.upperStartCamelObject}} + err := m.QueryRowIndexCtx(ctx, &resp, {{.cacheKeyVariable}}, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := fmt.Sprintf("select %s from %s where {{.originalField}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}},globalkey.DelStateNo); err != nil { + return nil, err + } + return resp.{{.upperStartCamelPrimaryKey}}, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +}{{else}}var resp {{.upperStartCamelObject}} + query := fmt.Sprintf("select %s from %s where {{.originalField}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table ) + err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}},globalkey.DelStateNo) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +}{{end}} + diff --git a/deploy/template/model/find-one.tpl b/deploy/template/model/find-one.tpl new file mode 100644 index 0000000..72d5ecf --- /dev/null +++ b/deploy/template/model/find-one.tpl @@ -0,0 +1,26 @@ +func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) { + {{if .withCache}}{{.cacheKey}} + var resp {{.upperStartCamelObject}} + err := m.QueryRowCtx(ctx, &resp, {{.cacheKeyVariable}}, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) + return conn.QueryRowCtx(ctx, v, query, {{.lowerStartCamelPrimaryKey}},globalkey.DelStateNo) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + }{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) + var resp {{.upperStartCamelObject}} + err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelPrimaryKey}},globalkey.DelStateNo) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + }{{end}} +} diff --git a/deploy/template/model/import-no-cache.tpl b/deploy/template/model/import-no-cache.tpl new file mode 100644 index 0000000..689bc05 --- /dev/null +++ b/deploy/template/model/import-no-cache.tpl @@ -0,0 +1,16 @@ +import ( + "context" + "database/sql" + "fmt" + "strings" + + {{if .time}}"time"{{end}} + + "bdrp-server/common/globalkey" + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) diff --git a/deploy/template/model/import.tpl b/deploy/template/model/import.tpl new file mode 100644 index 0000000..9264702 --- /dev/null +++ b/deploy/template/model/import.tpl @@ -0,0 +1,17 @@ +import ( + "context" + "database/sql" + "fmt" + "strings" + + {{if .time}}"time"{{end}} + + "bdrp-server/common/globalkey" + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) diff --git a/deploy/template/model/insert.tpl b/deploy/template/model/insert.tpl new file mode 100644 index 0000000..d8060e8 --- /dev/null +++ b/deploy/template/model/insert.tpl @@ -0,0 +1,17 @@ + +func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result,error) { + data.DelState = globalkey.DelStateNo + {{if .withCache}}{{.keys}} + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) + if session != nil{ + return session.ExecCtx(ctx,query,{{.expressionValues}}) + } + return conn.ExecCtx(ctx, query, {{.expressionValues}}) + }, {{.keyValues}}){{else}} + query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) + if session != nil{ + return session.ExecCtx(ctx,query,{{.expressionValues}}) + } + return m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}} +} diff --git a/deploy/template/model/interface-delete.tpl b/deploy/template/model/interface-delete.tpl new file mode 100644 index 0000000..a7f11a7 --- /dev/null +++ b/deploy/template/model/interface-delete.tpl @@ -0,0 +1 @@ +Delete(ctx context.Context,session sqlx.Session, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error \ No newline at end of file diff --git a/deploy/template/model/interface-find-one-by-field.tpl b/deploy/template/model/interface-find-one-by-field.tpl new file mode 100644 index 0000000..9615aa3 --- /dev/null +++ b/deploy/template/model/interface-find-one-by-field.tpl @@ -0,0 +1 @@ +FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) \ No newline at end of file diff --git a/deploy/template/model/interface-find-one.tpl b/deploy/template/model/interface-find-one.tpl new file mode 100644 index 0000000..a7a5440 --- /dev/null +++ b/deploy/template/model/interface-find-one.tpl @@ -0,0 +1 @@ +FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) \ No newline at end of file diff --git a/deploy/template/model/interface-insert.tpl b/deploy/template/model/interface-insert.tpl new file mode 100644 index 0000000..6705d2b --- /dev/null +++ b/deploy/template/model/interface-insert.tpl @@ -0,0 +1 @@ +Insert(ctx context.Context, session sqlx.Session,data *{{.upperStartCamelObject}}) (sql.Result,error) \ No newline at end of file diff --git a/deploy/template/model/interface-update.tpl b/deploy/template/model/interface-update.tpl new file mode 100644 index 0000000..32a4214 --- /dev/null +++ b/deploy/template/model/interface-update.tpl @@ -0,0 +1,12 @@ +Update(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result, error) +UpdateWithVersion(ctx context.Context,session sqlx.Session,data *{{.upperStartCamelObject}}) 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 *{{.upperStartCamelObject}}) 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) ([]*{{.upperStartCamelObject}},error) +FindPageListByPage(ctx context.Context,rowBuilder squirrel.SelectBuilder,page ,pageSize int64,orderBy string) ([]*{{.upperStartCamelObject}},error) +FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*{{.upperStartCamelObject}}, int64, error) +FindPageListByIdDESC(ctx context.Context,rowBuilder squirrel.SelectBuilder ,preMinId ,pageSize int64) ([]*{{.upperStartCamelObject}},error) +FindPageListByIdASC(ctx context.Context,rowBuilder squirrel.SelectBuilder,preMaxId ,pageSize int64) ([]*{{.upperStartCamelObject}},error) \ No newline at end of file diff --git a/deploy/template/model/model-gen.tpl b/deploy/template/model/model-gen.tpl new file mode 100644 index 0000000..8da9d0b --- /dev/null +++ b/deploy/template/model/model-gen.tpl @@ -0,0 +1,13 @@ +// Code generated by goctl. DO NOT EDIT! + +package {{.pkg}} +{{.imports}} +{{.vars}} +{{.types}} +{{.new}} +{{.insert}} +{{.find}} +{{.update}} +{{.delete}} +{{.extraMethod}} +{{.tableName}} \ No newline at end of file diff --git a/deploy/template/model/model-new.tpl b/deploy/template/model/model-new.tpl new file mode 100644 index 0000000..5e9d15f --- /dev/null +++ b/deploy/template/model/model-new.tpl @@ -0,0 +1,7 @@ + +func new{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *default{{.upperStartCamelObject}}Model { + return &default{{.upperStartCamelObject}}Model{ + {{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}}, + table: {{.table}}, + } +} diff --git a/deploy/template/model/model.tpl b/deploy/template/model/model.tpl new file mode 100644 index 0000000..0ac2918 --- /dev/null +++ b/deploy/template/model/model.tpl @@ -0,0 +1,37 @@ +package {{.pkg}} +{{if .withCache}} +import ( + + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" + +) +{{else}} +import ( + + "github.com/zeromicro/go-zero/core/stores/sqlx" + +) + +{{end}} +var _ {{.upperStartCamelObject}}Model = (*custom{{.upperStartCamelObject}}Model)(nil) + +type ( + // {{.upperStartCamelObject}}Model is an interface to be customized, add more methods here, + // and implement the added methods in custom{{.upperStartCamelObject}}Model. + {{.upperStartCamelObject}}Model interface { + {{.lowerStartCamelObject}}Model + +} + + custom{{.upperStartCamelObject}}Model struct { + *default{{.upperStartCamelObject}}Model + } +) + +// New{{.upperStartCamelObject}}Model returns a model for the database table. +func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) {{.upperStartCamelObject}}Model { + return &custom{{.upperStartCamelObject}}Model{ + default{{.upperStartCamelObject}}Model: new{{.upperStartCamelObject}}Model(conn{{if .withCache}}, c{{end}}), + } +} diff --git a/deploy/template/model/table-name.tpl b/deploy/template/model/table-name.tpl new file mode 100644 index 0000000..69733fa --- /dev/null +++ b/deploy/template/model/table-name.tpl @@ -0,0 +1,4 @@ + +func (m *default{{.upperStartCamelObject}}Model) tableName() string { + return m.table +} diff --git a/deploy/template/model/tag.tpl b/deploy/template/model/tag.tpl new file mode 100644 index 0000000..8e1ddf0 --- /dev/null +++ b/deploy/template/model/tag.tpl @@ -0,0 +1 @@ +`db:"{{.field}}"` \ No newline at end of file diff --git a/deploy/template/model/types.tpl b/deploy/template/model/types.tpl new file mode 100644 index 0000000..d551a4f --- /dev/null +++ b/deploy/template/model/types.tpl @@ -0,0 +1,15 @@ + +type ( + {{.lowerStartCamelObject}}Model interface{ + {{.method}} + } + + default{{.upperStartCamelObject}}Model struct { + {{if .withCache}}sqlc.CachedConn{{else}}conn sqlx.SqlConn{{end}} + table string + } + + {{.upperStartCamelObject}} struct { + {{.fields}} + } +) diff --git a/deploy/template/model/update.tpl b/deploy/template/model/update.tpl new file mode 100644 index 0000000..b01e30f --- /dev/null +++ b/deploy/template/model/update.tpl @@ -0,0 +1,286 @@ + +func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context,session sqlx.Session, {{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) (sql.Result,error) { + {{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, newData.{{.upperStartCamelPrimaryKey}}) + if err!=nil{ + return nil,err + } + {{end}}{{.keys}} + return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) + if session != nil{ + return session.ExecCtx(ctx,query, {{.expressionValues}}) + } + return conn.ExecCtx(ctx, query, {{.expressionValues}}) + }, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) + if session != nil{ + return session.ExecCtx(ctx,query, {{.expressionValues}}) + } + return m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}} +} + +func (m *default{{.upperStartCamelObject}}Model) UpdateWithVersion(ctx context.Context,session sqlx.Session,{{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) error { + + {{if .containsIndexCache}} + oldVersion := newData.Version + newData.Version += 1 + {{else}} + oldVersion := data.Version + data.Version += 1 + {{end}} + + var sqlResult sql.Result + var err error + + {{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, newData.{{.upperStartCamelPrimaryKey}}) + if err!=nil{ + return err + } + {{end}}{{.keys}} + 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 {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and version = ? ", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) + if session != nil{ + return session.ExecCtx(ctx,query, {{.expressionValues}},oldVersion) + } + return conn.ExecCtx(ctx,query, {{.expressionValues}},oldVersion) + }, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and version = ? ", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) + if session != nil{ + sqlResult,err = session.ExecCtx(ctx,query, {{.expressionValues}},oldVersion) + }else{ + sqlResult,err = m.conn.ExecCtx(ctx,query, {{.expressionValues}},oldVersion) + } + {{end}} + if err != nil { + return err + } + updateCount , err := sqlResult.RowsAffected() + if err != nil{ + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *default{{.upperStartCamelObject}}Model) DeleteSoft(ctx context.Context,session sqlx.Session,data *{{.upperStartCamelObject}}) 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 "),"{{.upperStartCamelObject}}Model delete err : %+v",err) + } + return nil +} + +func (m *default{{.upperStartCamelObject}}Model) 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 + {{if .withCache}}err = m.QueryRowNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) 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 + {{if .withCache}}err = m.QueryRowNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) FindAll(ctx context.Context,builder squirrel.SelectBuilder,orderBy string) ([]*{{.upperStartCamelObject}},error) { + + builder = builder.Columns({{.lowerStartCamelObject}}Rows) + + 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 []*{{.upperStartCamelObject}} + {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) FindPageListByPage(ctx context.Context,builder squirrel.SelectBuilder,page ,pageSize int64,orderBy string) ([]*{{.upperStartCamelObject}},error) { + + builder = builder.Columns({{.lowerStartCamelObject}}Rows) + + 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 []*{{.upperStartCamelObject}} + {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + + +func (m *default{{.upperStartCamelObject}}Model) FindPageListByPageWithTotal(ctx context.Context,builder squirrel.SelectBuilder,page ,pageSize int64,orderBy string) ([]*{{.upperStartCamelObject}},int64,error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns({{.lowerStartCamelObject}}Rows) + + 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 []*{{.upperStartCamelObject}} + {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp,total, nil + default: + return nil,total, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) FindPageListByIdDESC(ctx context.Context,builder squirrel.SelectBuilder ,preMinId ,pageSize int64) ([]*{{.upperStartCamelObject}},error) { + + builder = builder.Columns({{.lowerStartCamelObject}}Rows) + + 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 []*{{.upperStartCamelObject}} + {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) FindPageListByIdASC(ctx context.Context,builder squirrel.SelectBuilder,preMaxId ,pageSize int64) ([]*{{.upperStartCamelObject}},error) { + + builder = builder.Columns({{.lowerStartCamelObject}}Rows) + + 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 []*{{.upperStartCamelObject}} + {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} + err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) + {{end}} + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *default{{.upperStartCamelObject}}Model) Trans(ctx context.Context,fn func(ctx context.Context,session sqlx.Session) error) error { + {{if .withCache}} + return m.TransactCtx(ctx,func(ctx context.Context,session sqlx.Session) error { + return fn(ctx,session) + }) + {{else}} + return m.conn.TransactCtx(ctx,func(ctx context.Context,session sqlx.Session) error { + return fn(ctx,session) + }) + {{end}} +} + +func(m *default{{.upperStartCamelObject}}Model) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} \ No newline at end of file diff --git a/deploy/template/model/var.tpl b/deploy/template/model/var.tpl new file mode 100644 index 0000000..a10ca33 --- /dev/null +++ b/deploy/template/model/var.tpl @@ -0,0 +1,9 @@ + +var ( + {{.lowerStartCamelObject}}FieldNames = builder.RawFieldNames(&{{.upperStartCamelObject}}{}{{if .postgreSql}},true{{end}}) + {{.lowerStartCamelObject}}Rows = strings.Join({{.lowerStartCamelObject}}FieldNames, ",") + {{.lowerStartCamelObject}}RowsExpectAutoSet = {{if .postgreSql}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ","){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "`create_time`", "`update_time`"), ","){{end}} + {{.lowerStartCamelObject}}RowsWithPlaceHolder = {{if .postgreSql}}builder.PostgreSqlJoin(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "create_time", "update_time")){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "`create_time`", "`update_time`"), "=?,") + "=?"{{end}} + + {{if .withCache}}{{.cacheKeys}}{{end}} +) diff --git a/deploy/template/mongo/err.tpl b/deploy/template/mongo/err.tpl new file mode 100644 index 0000000..27d9244 --- /dev/null +++ b/deploy/template/mongo/err.tpl @@ -0,0 +1,12 @@ +package model + +import ( + "errors" + + "github.com/zeromicro/go-zero/core/stores/mon" +) + +var ( + ErrNotFound = mon.ErrNotFound + ErrInvalidObjectId = errors.New("invalid objectId") +) diff --git a/deploy/template/mongo/model.tpl b/deploy/template/mongo/model.tpl new file mode 100644 index 0000000..287125d --- /dev/null +++ b/deploy/template/mongo/model.tpl @@ -0,0 +1,78 @@ +// Code generated by goctl. DO NOT EDIT. +package model + +import ( + "context" + "time" + + {{if .Cache}}"github.com/zeromicro/go-zero/core/stores/monc"{{else}}"github.com/zeromicro/go-zero/core/stores/mon"{{end}} + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +{{if .Cache}}var prefix{{.Type}}CacheKey = "cache:{{.lowerType}}:"{{end}} + +type {{.lowerType}}Model interface{ + Insert(ctx context.Context,data *{{.Type}}) error + FindOne(ctx context.Context,id string) (*{{.Type}}, error) + Update(ctx context.Context,data *{{.Type}}) (*mongo.UpdateResult, error) + Delete(ctx context.Context,id string) (int64, error) +} + +type default{{.Type}}Model struct { + conn {{if .Cache}}*monc.Model{{else}}*mon.Model{{end}} +} + +func newDefault{{.Type}}Model(conn {{if .Cache}}*monc.Model{{else}}*mon.Model{{end}}) *default{{.Type}}Model { + return &default{{.Type}}Model{conn: conn} +} + + +func (m *default{{.Type}}Model) Insert(ctx context.Context, data *{{.Type}}) error { + if data.ID.IsZero() { + data.ID = primitive.NewObjectID() + data.CreateAt = time.Now() + data.UpdateAt = time.Now() + } + + {{if .Cache}}key := prefix{{.Type}}CacheKey + data.ID.Hex(){{end}} + _, err := m.conn.InsertOne(ctx, {{if .Cache}}key, {{end}} data) + return err +} + +func (m *default{{.Type}}Model) FindOne(ctx context.Context, id string) (*{{.Type}}, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, ErrInvalidObjectId + } + + var data {{.Type}} + {{if .Cache}}key := prefix{{.Type}}CacheKey + id{{end}} + err = m.conn.FindOne(ctx, {{if .Cache}}key, {{end}}&data, bson.M{"_id": oid}) + switch err { + case nil: + return &data, nil + case {{if .Cache}}monc{{else}}mon{{end}}.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *default{{.Type}}Model) Update(ctx context.Context, data *{{.Type}}) (*mongo.UpdateResult, error) { + data.UpdateAt = time.Now() + {{if .Cache}}key := prefix{{.Type}}CacheKey + data.ID.Hex(){{end}} + res, err := m.conn.UpdateOne(ctx, {{if .Cache}}key, {{end}}bson.M{"_id": data.ID}, bson.M{"$set": data}) + return res, err +} + +func (m *default{{.Type}}Model) Delete(ctx context.Context, id string) (int64, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return 0, ErrInvalidObjectId + } + {{if .Cache}}key := prefix{{.Type}}CacheKey +id{{end}} + res, err := m.conn.DeleteOne(ctx, {{if .Cache}}key, {{end}}bson.M{"_id": oid}) + return res, err +} diff --git a/deploy/template/mongo/model_custom.tpl b/deploy/template/mongo/model_custom.tpl new file mode 100644 index 0000000..31fa865 --- /dev/null +++ b/deploy/template/mongo/model_custom.tpl @@ -0,0 +1,38 @@ +package model + +{{if .Cache}}import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/monc" +){{else}}import "github.com/zeromicro/go-zero/core/stores/mon"{{end}} + +{{if .Easy}} +const {{.Type}}CollectionName = "{{.snakeType}}" +{{end}} + +var _ {{.Type}}Model = (*custom{{.Type}}Model)(nil) + +type ( + // {{.Type}}Model is an interface to be customized, add more methods here, + // and implement the added methods in custom{{.Type}}Model. + {{.Type}}Model interface { + {{.lowerType}}Model + } + + custom{{.Type}}Model struct { + *default{{.Type}}Model + } +) + + +// New{{.Type}}Model returns a model for the mongo. +{{if .Easy}}func New{{.Type}}Model(url, db string{{if .Cache}}, c cache.CacheConf{{end}}) {{.Type}}Model { + conn := {{if .Cache}}monc{{else}}mon{{end}}.MustNewModel(url, db, {{.Type}}CollectionName{{if .Cache}}, c{{end}}) + return &custom{{.Type}}Model{ + default{{.Type}}Model: newDefault{{.Type}}Model(conn), + } +}{{else}}func New{{.Type}}Model(url, db, collection string{{if .Cache}}, c cache.CacheConf{{end}}) {{.Type}}Model { + conn := {{if .Cache}}monc{{else}}mon{{end}}.MustNewModel(url, db, collection{{if .Cache}}, c{{end}}) + return &custom{{.Type}}Model{ + default{{.Type}}Model: newDefault{{.Type}}Model(conn), + } +}{{end}} diff --git a/deploy/template/mongo/model_types.tpl b/deploy/template/mongo/model_types.tpl new file mode 100644 index 0000000..8da006f --- /dev/null +++ b/deploy/template/mongo/model_types.tpl @@ -0,0 +1,14 @@ +package model + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type {{.Type}} struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + // TODO: Fill your own fields + UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` + CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` +} diff --git a/deploy/template/newapi/newtemplate.tpl b/deploy/template/newapi/newtemplate.tpl new file mode 100644 index 0000000..28be510 --- /dev/null +++ b/deploy/template/newapi/newtemplate.tpl @@ -0,0 +1,12 @@ +type Request { + Name string `path:"name,options=you|me"` +} + +type Response { + Message string `json:"message"` +} + +service {{.name}}-api { + @handler {{.handler}}Handler + get /from/:name(Request) returns (Response) +} diff --git a/deploy/template/rpc/call.tpl b/deploy/template/rpc/call.tpl new file mode 100644 index 0000000..27b4879 --- /dev/null +++ b/deploy/template/rpc/call.tpl @@ -0,0 +1,33 @@ +{{.head}} + +package {{.filePackage}} + +import ( + "context" + + {{.pbPackage}} + {{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}} + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + {{.alias}} + + {{.serviceName}} interface { + {{.interface}} + } + + default{{.serviceName}} struct { + cli zrpc.Client + } +) + +func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} { + return &default{{.serviceName}}{ + cli: cli, + } +} + +{{.functions}} diff --git a/deploy/template/rpc/config.tpl b/deploy/template/rpc/config.tpl new file mode 100644 index 0000000..c1f85b9 --- /dev/null +++ b/deploy/template/rpc/config.tpl @@ -0,0 +1,7 @@ +package config + +import "github.com/zeromicro/go-zero/zrpc" + +type Config struct { + zrpc.RpcServerConf +} diff --git a/deploy/template/rpc/etc.tpl b/deploy/template/rpc/etc.tpl new file mode 100644 index 0000000..6cd4bdd --- /dev/null +++ b/deploy/template/rpc/etc.tpl @@ -0,0 +1,6 @@ +Name: {{.serviceName}}.rpc +ListenOn: 0.0.0.0:8080 +Etcd: + Hosts: + - 127.0.0.1:2379 + Key: {{.serviceName}}.rpc diff --git a/deploy/template/rpc/logic-func.tpl b/deploy/template/rpc/logic-func.tpl new file mode 100644 index 0000000..e9410d4 --- /dev/null +++ b/deploy/template/rpc/logic-func.tpl @@ -0,0 +1,6 @@ +{{if .hasComment}}{{.comment}}{{end}} +func (l *{{.logicName}}) {{.method}} ({{if .hasReq}}in {{.request}}{{if .stream}},stream {{.streamBody}}{{end}}{{else}}stream {{.streamBody}}{{end}}) ({{if .hasReply}}{{.response}},{{end}} error) { + // todo: add your logic here and delete this line + + return {{if .hasReply}}&{{.responseType}}{},{{end}} nil +} diff --git a/deploy/template/rpc/logic.tpl b/deploy/template/rpc/logic.tpl new file mode 100644 index 0000000..b8d81f0 --- /dev/null +++ b/deploy/template/rpc/logic.tpl @@ -0,0 +1,24 @@ +package {{.packageName}} + +import ( + "context" + + {{.imports}} + + "github.com/zeromicro/go-zero/core/logx" +) + +type {{.logicName}} struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} { + return &{{.logicName}}{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} +{{.functions}} diff --git a/deploy/template/rpc/main.tpl b/deploy/template/rpc/main.tpl new file mode 100644 index 0000000..efb35e0 --- /dev/null +++ b/deploy/template/rpc/main.tpl @@ -0,0 +1,41 @@ +package main + +import ( + "flag" + "fmt" + + {{.imports}} + "bdrp-server/common/interceptor/rpcserver" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/core/service" + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + ctx := svc.NewServiceContext(c) + + s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { +{{range .serviceNames}} {{.Pkg}}.Register{{.Service}}Server(grpcServer, {{.ServerPkg}}.New{{.Service}}Server(ctx)) +{{end}} + if c.Mode == service.DevMode || c.Mode == service.TestMode { + reflection.Register(grpcServer) + } + }) + + //rpc log + s.AddUnaryInterceptors(rpcserver.LoggerInterceptor) + + defer s.Stop() + + fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) + s.Start() +} diff --git a/deploy/template/rpc/server-func.tpl b/deploy/template/rpc/server-func.tpl new file mode 100644 index 0000000..d771b43 --- /dev/null +++ b/deploy/template/rpc/server-func.tpl @@ -0,0 +1,6 @@ + +{{if .hasComment}}{{.comment}}{{end}} +func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{if .hasReq}} in {{.request}}{{end}}{{else}}{{if .hasReq}} in {{.request}},{{end}}stream {{.streamBody}}{{end}}) ({{if .notStream}}{{.response}},{{end}}error) { + l := {{.logicPkg}}.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx) + return l.{{.method}}({{if .hasReq}}in{{if .stream}} ,stream{{end}}{{else}}{{if .stream}}stream{{end}}{{end}}) +} diff --git a/deploy/template/rpc/server.tpl b/deploy/template/rpc/server.tpl new file mode 100644 index 0000000..84a2f9c --- /dev/null +++ b/deploy/template/rpc/server.tpl @@ -0,0 +1,22 @@ +{{.head}} + +package server + +import ( + {{if .notStream}}"context"{{end}} + + {{.imports}} +) + +type {{.server}}Server struct { + svcCtx *svc.ServiceContext + {{.unimplementedServer}} +} + +func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server { + return &{{.server}}Server{ + svcCtx: svcCtx, + } +} + +{{.funcs}} diff --git a/deploy/template/rpc/svc.tpl b/deploy/template/rpc/svc.tpl new file mode 100644 index 0000000..cf2b47a --- /dev/null +++ b/deploy/template/rpc/svc.tpl @@ -0,0 +1,13 @@ +package svc + +import {{.imports}} + +type ServiceContext struct { + Config config.Config +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config:c, + } +} diff --git a/deploy/template/rpc/template.tpl b/deploy/template/rpc/template.tpl new file mode 100644 index 0000000..76daa94 --- /dev/null +++ b/deploy/template/rpc/template.tpl @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package {{.package}}; +option go_package="./{{.package}}"; + +message Request { + string ping = 1; +} + +message Response { + string pong = 1; +} + +service {{.serviceName}} { + rpc Ping(Request) returns(Response); +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f44b2cd --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,83 @@ +services: + mysql: + image: mysql:8.0.34 + container_name: bdrp_mysql + environment: + # 时区上海 - Time zone Shanghai (Change if needed) + TZ: Asia/Shanghai + # root 密码 - root password + MYSQL_ROOT_PASSWORD: yfg87gyuYiy1 + MYSQL_DATABASE: bdrp + MYSQL_USER: bdrp + MYSQL_PASSWORD: d7X7E1KSxrZC + ports: + - "21001:3306" + volumes: + # 数据挂载 - Data mounting + - ./data/mysql/data:/var/lib/mysql + # 仅首次空库时执行 /docker-entrypoint-initdb.d 下脚本 + - ./deploy/sql/docker-init:/docker-entrypoint-initdb.d:ro + # 日志 + command: + # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配) + # Modify the Mysql 8.0 default password strategy to the original strategy (MySQL8.0 to change its default strategy will cause the password to be unable to match) + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 + restart: always + networks: + - bdrp_net + + redis: + image: redis:7.4.0 + container_name: bdrp_redis + ports: + - "21002:6379" + environment: + # 时区上海 - Time zone Shanghai (Change if needed) + TZ: Asia/Shanghai + volumes: + # 数据文件 - data files + - ./data/redis/data:/data:rw + command: "redis-server --requirepass 3m3WsgyCKWqz --appendonly yes" + privileged: true + restart: always + networks: + - bdrp_net + + asynqmon: + image: hibiken/asynqmon:latest + container_name: bdrp_asynqmon + ports: + - "21003:8080" + environment: + - TZ=Asia/Shanghai + command: + - "--redis-addr=bdrp_redis:6379" + - "--redis-password=3m3WsgyCKWqz" + restart: always + networks: + - bdrp_net + depends_on: + - redis + + phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: bdrp_phpmyadmin + restart: unless-stopped + environment: + PMA_HOST: bdrp_mysql + PMA_PORT: 3306 + PMA_USER: bdrp + PMA_PASSWORD: d7X7E1KSxrZC + ports: + - "21004:80" + depends_on: + - mysql + networks: + - bdrp_net +networks: + bdrp_net: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f71048b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,91 @@ +version: "3" + +services: + mysql: + image: mysql:8.0.34 + container_name: bdrp_mysql + environment: + # 时区上海 - Time zone Shanghai (Change if needed) + TZ: Asia/Shanghai + # root 密码 - root password + MYSQL_ROOT_PASSWORD: yfg87gyuYiy1 + MYSQL_DATABASE: bdrp + MYSQL_USER: bdrp + MYSQL_PASSWORD: d7X7E1KSxrZC + ports: + - "21001:3306" + volumes: + # 数据挂载 - Data mounting + - ./data/mysql/data:/var/lib/mysql + # 仅首次空库时执行 /docker-entrypoint-initdb.d 下脚本 + - ./deploy/sql/docker-init:/docker-entrypoint-initdb.d:ro + # 日志 + command: + # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配) + # Modify the Mysql 8.0 default password strategy to the original strategy (MySQL8.0 to change its default strategy will cause the password to be unable to match) + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 + privileged: true + restart: always + networks: + - bdrp_net + - 1panel-network + + redis: + image: redis:7.4.0 + container_name: bdrp_redis + ports: + - "21002:6379" + environment: + # 时区上海 - Time zone Shanghai (Change if needed) + TZ: Asia/Shanghai + volumes: + # 数据文件 - data files + - ./data/redis/data:/data:rw + command: "redis-server --requirepass 3m3WsgyCKWqz --appendonly yes" + privileged: true + restart: always + networks: + - bdrp_net + + asynqmon: + image: hibiken/asynqmon:latest + container_name: bdrp_asynqmon + ports: + - "21003:8080" + environment: + - TZ=Asia/Shanghai + command: + - "--redis-addr=bdrp_redis:6379" + - "--redis-password=3m3WsgyCKWqz" + restart: always + networks: + - bdrp_net + depends_on: + - redis + + main: + container_name: bdrp_main + build: + context: . + dockerfile: app/main/api/Dockerfile + ports: + - "21004:8888" + volumes: + - ./data/authorization_docs:/app/data/authorization_docs:rw + environment: + - TZ=Asia/Shanghai + - ENV=production + depends_on: + - mysql + - redis + networks: + - bdrp_net + restart: always + +networks: + bdrp_net: + driver: bridge diff --git a/gen_api.ps1 b/gen_api.ps1 new file mode 100644 index 0000000..d571f8e --- /dev/null +++ b/gen_api.ps1 @@ -0,0 +1,2 @@ +# API生成脚本 +goctl api go --api ./app/main/api/desc/main.api --dir ./app/main/api --home ./deploy/template \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..03c749d --- /dev/null +++ b/go.mod @@ -0,0 +1,117 @@ +module bdrp-server + +go 1.23.0 + +toolchain go1.23.4 + +require ( + github.com/Masterminds/squirrel v1.5.4 + github.com/alibabacloud-go/captcha-20230305 v1.1.3 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 + github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6 + github.com/alibabacloud-go/tea v1.3.13 + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 + github.com/bytedance/sonic v1.13.0 + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/fogleman/gg v1.3.0 + github.com/go-playground/validator/v10 v10.22.1 + github.com/golang-jwt/jwt/v4 v4.5.2 + github.com/google/uuid v1.6.0 + github.com/hibiken/asynq v0.25.0 + github.com/jinzhu/copier v0.4.0 + github.com/jung-kurt/gofpdf v1.16.2 + github.com/pkg/errors v0.9.1 + github.com/redis/go-redis/v9 v9.17.2 + github.com/samber/lo v1.50.0 + github.com/shopspring/decimal v1.4.0 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/smartwalle/alipay/v3 v3.2.23 + github.com/sony/sonyflake v1.2.0 + github.com/stretchr/testify v1.11.1 + github.com/tidwall/gjson v1.18.0 + github.com/wechatpay-apiv3/wechatpay-go v0.2.20 + github.com/zeromicro/go-zero v1.9.4 + golang.org/x/crypto v0.33.0 + google.golang.org/grpc v1.67.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect + github.com/alibabacloud-go/openapi-util v0.1.0 // indirect + github.com/alibabacloud-go/tea-utils v1.3.1 // indirect + github.com/aliyun/credentials-go v1.4.5 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic/loader v0.2.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.9.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/grafana/pyroscope-go v1.2.7 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openzipkin/zipkin-go v0.4.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/smartwalle/ncrypto v1.0.4 // indirect + github.com/smartwalle/ngx v1.0.9 // indirect + github.com/smartwalle/nsign v1.0.9 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/image v0.28.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..91cf61a --- /dev/null +++ b/go.sum @@ -0,0 +1,494 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/captcha-20230305 v1.1.3 h1:0Aobw12m3x28aeDMPjwjXsfF8MuLvRjlQ4Hhoy5hFOY= +github.com/alibabacloud-go/captcha-20230305 v1.1.3/go.mod h1:ydzBIN2OiM7eeQPpAFyBrv1H5TY1MtUP2rQig44C4UQ= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6 h1:UTl97mt2qfavxveqCkaVg4tKaZUPzA9RKbFIRaIdtdg= +github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6/go.mod h1:UWpcGrWwTbES9QW7OQ7xDffukMJ/l7lzioixIz8+lgY= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= +github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk= +github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.13.0 h1:R+aSALdYjFT39PoytNFIxV8W7rb/ZxRpdQd+1TFZ2F0= +github.com/bytedance/sonic v1.13.0/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o= +github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo= +github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= +github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hibiken/asynq v0.25.0 h1:VCPyRRrrjFChsTSI8x5OCPu51MlEz6Rk+1p0kHKnZug= +github.com/hibiken/asynq v0.25.0/go.mod h1:DYQ1etBEl2Y+uSkqFElGYbk3M0ujLVwCfWE+TlvxtEk= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= +github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= +github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= +github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE= +github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE= +github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= +github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk= +github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw= +github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= +github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E= +github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ= +github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zeromicro/go-zero v1.9.4 h1:aRLFoISqAYijABtkbliQC5SsI5TbizJpQvoHc9xup8k= +github.com/zeromicro/go-zero v1.9.4/go.mod h1:a17JOTch25SWxBcUgJZYps60hygK3pIYdw7nGwlcS38= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= +go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/pkg/captcha/aliyun.go b/pkg/captcha/aliyun.go new file mode 100644 index 0000000..07d8c44 --- /dev/null +++ b/pkg/captcha/aliyun.go @@ -0,0 +1,81 @@ +package captcha + +import ( + "context" + "net/http" + "os" + "strings" + + "bdrp-server/common/xerr" + + captcha20230305 "github.com/alibabacloud-go/captcha-20230305/client" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/pkg/errors" +) + +// contextKey 用于在 context 中存储 *http.Request,供 VerifyWithRequest 判断微信等环境 +type contextKey struct{} + +// HTTPRequestContextKey 为 context 中 http.Request 的 key,handler 可将 r 注入后传入 logic +var HTTPRequestContextKey = &contextKey{} + +// isWeChatClient 根据 User-Agent 判断是否为微信内置浏览器/小程序环境(此类环境可不传 captchaVerifyParam,默认通过) +func isWeChatClient(r *http.Request) bool { + if r == nil { + return false + } + ua := strings.ToLower(r.Header.Get("User-Agent")) + return strings.Contains(ua, "micromessenger") || strings.Contains(ua, "miniprogram") +} + +type Config struct { + AccessKeyID string + AccessKeySecret string + EndpointURL string + SceneID string +} + +// VerifyWithRequest 在 Verify 基础上支持按请求头判断:微信/小程序环境下可不传 captchaVerifyParam,默认通过。 +// 若 ctx 中带有 HTTPRequestContextKey 的 *http.Request,且 User-Agent 为微信环境,则不再校验 captchaVerifyParam。 +func VerifyWithRequest(cfg Config, captchaVerifyParam string, ctx context.Context) error { + if ctx != nil { + if req, ok := ctx.Value(HTTPRequestContextKey).(*http.Request); ok && isWeChatClient(req) { + return nil + } + } + return Verify(cfg, captchaVerifyParam) +} + +func Verify(cfg Config, captchaVerifyParam string) error { + if os.Getenv("ENV") == "development" { + return nil + } + + if captchaVerifyParam == "" { + return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败,captchaVerifyParam 不能为空"), "empty captchaVerifyParam") + } + + clientCfg := &openapi.Config{ + AccessKeyId: tea.String(cfg.AccessKeyID), + AccessKeySecret: tea.String(cfg.AccessKeySecret), + } + clientCfg.Endpoint = tea.String(cfg.EndpointURL) + client, err := captcha20230305.NewClient(clientCfg) + if err != nil { + return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "new client error: %+v", err) + } + + req := &captcha20230305.VerifyIntelligentCaptchaRequest{ + SceneId: tea.String(cfg.SceneID), + CaptchaVerifyParam: tea.String(captchaVerifyParam), + } + resp, err := client.VerifyIntelligentCaptcha(req) + if err != nil { + return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "verify request error: %+v", err) + } + if tea.BoolValue(resp.Body.Result.VerifyResult) { + return nil + } + return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "verify result false") +} diff --git a/pkg/lzkit/crypto/README.md b/pkg/lzkit/crypto/README.md new file mode 100644 index 0000000..a7f8c03 --- /dev/null +++ b/pkg/lzkit/crypto/README.md @@ -0,0 +1,235 @@ +# AES 加密工具包 + +本包提供了多种加密方式,特别是用于处理敏感个人信息(如手机号、身份证号等)的加密和解密功能。 + +## 主要功能 + +- **AES-CBC 模式加密/解密** - 标准加密模式,适用于一般数据加密 +- **AES-ECB 模式加密/解密** - 确定性加密模式,适用于数据库字段加密和查询 +- **专门针对个人敏感信息的加密/解密方法** +- **密钥生成和管理工具** + +## 安全性说明 + +- **AES-CBC 模式**:使用随机 IV,相同明文每次加密结果不同,安全性较高 +- **AES-ECB 模式**:确定性加密,相同明文每次加密结果相同,便于数据库查询,但安全性较低 + +> **⚠️ 警告**:ECB 模式仅适用于短文本(如手机号、身份证号)的确定性加密,不建议用于加密大段文本或高安全需求场景。 + +## 使用示例 + +### 1. 加密手机号 + +使用 AES-ECB 模式加密手机号,保证确定性(相同手机号总是产生相同密文): + +```go +import ( + "fmt" + "bdrp-server/pkg/lzkit/crypto" +) + +func encryptMobileExample() { + // 您的密钥(需安全保存,建议存储在配置中) + key := []byte("1234567890abcdef") // 16字节AES-128密钥 + + // 加密手机号 + mobile := "13800138000" + encryptedMobile, err := crypto.EncryptMobile(mobile, key) + if err != nil { + panic(err) + } + + fmt.Println("加密后的手机号:", encryptedMobile) + + // 解密手机号 + decryptedMobile, err := crypto.DecryptMobile(encryptedMobile, key) + if err != nil { + panic(err) + } + + fmt.Println("解密后的手机号:", decryptedMobile) +} +``` + +### 2. 在数据库中存储和查询加密手机号 + +```go +// 加密并存储手机号 +func saveUser(db *sqlx.DB, mobile string, key []byte) (int64, error) { + encryptedMobile, err := crypto.EncryptMobile(mobile, key) + if err != nil { + return 0, err + } + + var id int64 + err = db.QueryRow( + "INSERT INTO users (mobile, create_time) VALUES (?, NOW()) RETURNING id", + encryptedMobile, + ).Scan(&id) + + return id, err +} + +// 根据手机号查询用户 +func findUserByMobile(db *sqlx.DB, mobile string, key []byte) (*User, error) { + encryptedMobile, err := crypto.EncryptMobile(mobile, key) + if err != nil { + return nil, err + } + + var user User + err = db.QueryRow( + "SELECT id, mobile, create_time FROM users WHERE mobile = ?", + encryptedMobile, + ).Scan(&user.ID, &user.EncryptedMobile, &user.CreateTime) + + if err != nil { + return nil, err + } + + // 解密手机号用于显示 + user.Mobile, _ = crypto.DecryptMobile(user.EncryptedMobile, key) + + return &user, nil +} +``` + +### 3. 加密身份证号 + +```go +func encryptIDCardExample() { + key := []byte("1234567890abcdef") + + idCard := "440101199001011234" + encryptedIDCard, err := crypto.EncryptIDCard(idCard, key) + if err != nil { + panic(err) + } + + fmt.Println("加密后的身份证号:", encryptedIDCard) + + // 解密身份证号 + decryptedIDCard, err := crypto.DecryptIDCard(encryptedIDCard, key) + if err != nil { + panic(err) + } + + fmt.Println("解密后的身份证号:", decryptedIDCard) +} +``` + +### 4. 密钥管理 + +```go +func keyManagementExample() { + // 生成随机密钥 + key, err := crypto.GenerateAESKey(16) // AES-128 + if err != nil { + panic(err) + } + fmt.Printf("生成的密钥(十六进制): %x\n", key) + + // 从密码派生密钥(便于记忆) + password := "my-secure-password" + derivedKey, err := crypto.DeriveKeyFromPassword(password, 16) + if err != nil { + panic(err) + } + fmt.Printf("从密码派生的密钥: %x\n", derivedKey) +} +``` + +### 5. 使用十六进制输出(适用于 URL 参数) + +```go +func hexEncodingExample() { + key := []byte("1234567890abcdef") + mobile := "13800138000" + + // 使用十六进制编码(适合URL参数) + encryptedHex, err := crypto.EncryptMobileHex(mobile, key) + if err != nil { + panic(err) + } + + fmt.Println("十六进制编码的加密手机号:", encryptedHex) + + // 解密十六进制编码的手机号 + decryptedMobile, err := crypto.DecryptMobileHex(encryptedHex, key) + if err != nil { + panic(err) + } + + fmt.Println("解密后的手机号:", decryptedMobile) +} +``` + +## 在 Go-Zero 项目中使用 + +在 Go-Zero 项目中,建议将加密密钥放在配置文件中: + +1. 在配置文件中添加密钥配置: + +```yaml +# etc/main.yaml +Name: main-api +Host: 0.0.0.0 +Port: 8888 + +Encrypt: + MobileKey: "1234567890abcdef" # 16字节AES-128密钥 + IDCardKey: "1234567890abcdef1234567890abcdef" # 32字节AES-256密钥 +``` + +2. 在配置结构中定义: + +```go +type Config struct { + rest.RestConf + Encrypt struct { + MobileKey string + IDCardKey string + } +} +``` + +3. 在服务上下文中使用: + +```go +type ServiceContext struct { + Config config.Config + UserModel model.UserModel + MobileKey []byte + IDCardKey []byte +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config: c, + UserModel: model.NewUserModel(sqlx.NewMysql(c.DB.DataSource), c.Cache), + MobileKey: []byte(c.Encrypt.MobileKey), + IDCardKey: []byte(c.Encrypt.IDCardKey), + } +} +``` + +4. 在 Logic 中使用: + +```go +func (l *RegisterLogic) Register(req *types.RegisterReq) (*types.RegisterResp, error) { + // 加密手机号用于存储 + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, l.svcCtx.MobileKey) + if err != nil { + return nil, errors.New("手机号加密失败") + } + + // 保存到数据库 + user := &model.User{ + Mobile: encryptedMobile, + // 其他字段... + } + + result, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user) + // 其余逻辑... +} +``` diff --git a/pkg/lzkit/crypto/README_bcrypt_test.md b/pkg/lzkit/crypto/README_bcrypt_test.md new file mode 100644 index 0000000..841fde9 --- /dev/null +++ b/pkg/lzkit/crypto/README_bcrypt_test.md @@ -0,0 +1,100 @@ +# bcrypt 密码加密测试文档 + +## 功能概述 + +为 `pkg/lzkit/crypto/bcrypt.go` 中的 `PasswordHash` 和 `PasswordVerify` 函数创建了完整的测试套件。 + +## 测试文件 + +- **文件位置**: `pkg/lzkit/crypto/bcrypt_test.go` +- **测试函数**: + - `TestPasswordHash` - 测试密码加密功能 + - `TestPasswordVerify` - 测试密码验证功能 + - `TestGeneratePasswords` - 生成常用密码的hash值 + - `BenchmarkPasswordHash` - 性能测试 + - `BenchmarkPasswordVerify` - 验证性能测试 + +## 测试覆盖 + +### 1. 密码加密测试 (`TestPasswordHash`) +- ✅ 默认cost加密 (cost=10) +- ✅ 自定义cost加密 (cost=12) +- ✅ 空密码处理 +- ✅ 复杂密码处理 +- ✅ 密码验证功能 +- ✅ 错误密码验证 + +### 2. 密码验证测试 (`TestPasswordVerify`) +- ✅ 正确密码验证 +- ✅ 错误密码验证 + +### 3. 密码生成测试 (`TestGeneratePasswords`) +生成10个常用密码的hash值,包括: +- `123456` +- `admin123` +- `password` +- `root` +- `test123` +- `MyP@ssw0rd!2024` +- `admin@123` +- `123456789` +- `qwerty` +- `abc123` + +## 性能测试结果 + +### Cost=10 性能 +- **执行时间**: ~41.7ms/op +- **内存分配**: 5314 B/op, 11 allocs/op + +### Cost=12 性能 +- **执行时间**: ~164.1ms/op +- **内存分配**: 5691 B/op, 12 allocs/op + +## 使用方法 + +### 运行所有测试 +```bash +cd pkg/lzkit/crypto +go test -v +``` + +### 运行密码相关测试 +```bash +go test -run "TestPassword|TestGenerate" -v +``` + +### 运行性能测试 +```bash +go test -bench=BenchmarkPassword -benchmem -run="^$" +``` + +### 生成密码hash +```bash +go test -run TestGeneratePasswords -v +``` + +## 示例输出 + +``` +=== 生成密码Hash值 === +1. 密码: 123456 Hash: $2a$10$AXcpNL9y5RYLiObTLFq4KOWKtlV3jEUuCd6fuzmSW2yYsSELJ23D. +2. 密码: admin123 Hash: $2a$10$5PUD/kpFGJ.09Gi.VGzu2.sCp9ZEshEcCaP4tKPNMgbvOaY8Hq7Sy +3. 密码: password Hash: $2a$10$Tjl5JY13eyGE4tPUdbco0OToz2iN6UY3Dm/QTYUpZx3b5QAPH4Aq6 +... +=== 密码生成完成 === +``` + +## 注意事项 + +1. **Cost参数**: 默认使用cost=10,可根据安全需求调整 +2. **性能考虑**: Cost越高越安全,但性能消耗越大 +3. **Hash唯一性**: 每次生成的hash都不同,但验证结果一致 +4. **安全性**: 使用bcrypt算法,适合生产环境使用 + +## 测试状态 + +✅ 所有bcrypt相关测试通过 +✅ 性能测试完成 +✅ 密码生成功能正常 +✅ 验证功能正常 diff --git a/pkg/lzkit/crypto/bcrypt.go b/pkg/lzkit/crypto/bcrypt.go new file mode 100644 index 0000000..7ed4512 --- /dev/null +++ b/pkg/lzkit/crypto/bcrypt.go @@ -0,0 +1,28 @@ +package crypto + +import ( + "golang.org/x/crypto/bcrypt" +) + +// PasswordHash 使用bcrypt对密码进行加密 +// cost参数确定加密的复杂度,默认为10,越高越安全但性能消耗越大 +func PasswordHash(password string, cost ...int) (string, error) { + defaultCost := 10 + if len(cost) > 0 && cost[0] > 0 { + defaultCost = cost[0] + } + + bytes, err := bcrypt.GenerateFromPassword([]byte(password), defaultCost) + if err != nil { + return "", err + } + + return string(bytes), nil +} + +// PasswordVerify 验证密码是否匹配 +// password是用户输入的明文密码,hash是存储的加密密码 +func PasswordVerify(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/pkg/lzkit/crypto/bcrypt_test.go b/pkg/lzkit/crypto/bcrypt_test.go new file mode 100644 index 0000000..3d7ea11 --- /dev/null +++ b/pkg/lzkit/crypto/bcrypt_test.go @@ -0,0 +1,193 @@ +package crypto + +import ( + "fmt" + "testing" +) + +// TestPasswordHash 测试密码加密功能 +func TestPasswordHash(t *testing.T) { + testCases := []struct { + name string + password string + cost int + wantErr bool + }{ + { + name: "默认cost加密", + password: "123456", + cost: 0, // 使用默认cost + wantErr: false, + }, + { + name: "自定义cost加密", + password: "admin123", + cost: 12, + wantErr: false, + }, + { + name: "空密码", + password: "", + cost: 10, + wantErr: false, + }, + { + name: "复杂密码", + password: "MyP@ssw0rd!2024", + cost: 11, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var hash string + var err error + + if tc.cost > 0 { + hash, err = PasswordHash(tc.password, tc.cost) + } else { + hash, err = PasswordHash(tc.password) + } + + if tc.wantErr { + if err == nil { + t.Errorf("PasswordHash() 期望出错,但没有错误") + } + return + } + + if err != nil { + t.Errorf("PasswordHash() 出现错误 = %v", err) + return + } + + if hash == "" { + t.Errorf("PasswordHash() 返回空字符串") + return + } + + // 验证生成的hash长度合理(bcrypt hash通常是60字符) + if len(hash) < 50 { + t.Errorf("PasswordHash() 返回的hash太短 = %d", len(hash)) + } + + // 验证密码验证功能 + if !PasswordVerify(tc.password, hash) { + t.Errorf("PasswordVerify() 验证失败,密码不匹配") + } + + // 验证错误密码 + if PasswordVerify("wrongpassword", hash) { + t.Errorf("PasswordVerify() 应该验证失败,但验证成功了") + } + }) + } +} + +// TestPasswordVerify 测试密码验证功能 +func TestPasswordVerify(t *testing.T) { + // 先生成一个已知的hash用于测试 + testPassword := "123456" + testHash, err := PasswordHash(testPassword, 10) + if err != nil { + t.Fatalf("生成测试hash失败: %v", err) + } + + testCases := []struct { + name string + password string + hash string + want bool + }{ + { + name: "正确密码", + password: testPassword, + hash: testHash, + want: true, + }, + { + name: "错误密码", + password: "wrongpassword", + hash: testHash, + want: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := PasswordVerify(tc.password, tc.hash) + if got != tc.want { + t.Errorf("PasswordVerify() = %v, want %v", got, tc.want) + } + }) + } +} + +// TestGeneratePasswords 生成常用密码的hash值(用于实际使用) +func TestGeneratePasswords(t *testing.T) { + // 常用密码列表 + passwords := []string{ + "123456", + "admin123", + "password", + "root", + "test123", + "MyP@ssw0rd!2024", + "admin123.", + "123456789", + "qwerty", + "abc123", + } + + fmt.Println("\n=== 生成密码Hash值 ===") + for i, password := range passwords { + hash, err := PasswordHash(password) + if err != nil { + t.Errorf("生成密码 %s 的hash失败: %v", password, err) + continue + } + + fmt.Printf("%d. 密码: %-20s Hash: %s\n", i+1, password, hash) + + // 验证生成的hash是否正确 + if !PasswordVerify(password, hash) { + t.Errorf("验证密码 %s 失败", password) + } + } + fmt.Println("=== 密码生成完成 ===") +} + +// BenchmarkPasswordHash 性能测试 +func BenchmarkPasswordHash(b *testing.B) { + password := "testpassword123" + + b.Run("Cost10", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := PasswordHash(password, 10) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("Cost12", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := PasswordHash(password, 12) + if err != nil { + b.Fatal(err) + } + } + }) +} + +// BenchmarkPasswordVerify 验证性能测试 +func BenchmarkPasswordVerify(b *testing.B) { + password := "testpassword123" + hash, _ := PasswordHash(password, 10) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + PasswordVerify(password, hash) + } +} diff --git a/pkg/lzkit/crypto/crypto.go b/pkg/lzkit/crypto/crypto.go new file mode 100644 index 0000000..65c840d --- /dev/null +++ b/pkg/lzkit/crypto/crypto.go @@ -0,0 +1,105 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + "io" +) + +// PKCS7填充 +func PKCS7Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +// 去除PKCS7填充 +func PKCS7UnPadding(origData []byte) ([]byte, error) { + length := len(origData) + if length == 0 { + return nil, errors.New("input data error") + } + unpadding := int(origData[length-1]) + if unpadding > length { + return nil, errors.New("unpadding size is invalid") + } + + // 检查填充字节是否一致 + for i := 0; i < unpadding; i++ { + if origData[length-1-i] != byte(unpadding) { + return nil, errors.New("invalid padding") + } + } + + return origData[:(length - unpadding)], nil +} + +// AES CBC模式加密,Base64传入传出 +func AesEncrypt(plainText, key []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + blockSize := block.BlockSize() + plainText = PKCS7Padding(plainText, blockSize) + + cipherText := make([]byte, blockSize+len(plainText)) + iv := cipherText[:blockSize] // 使用前blockSize字节作为IV + _, err = io.ReadFull(rand.Reader, iv) + if err != nil { + return "", err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(cipherText[blockSize:], plainText) + + return base64.StdEncoding.EncodeToString(cipherText), nil +} + +// AES CBC模式解密,Base64传入传出 +func AesDecrypt(cipherTextBase64 string, key []byte) ([]byte, error) { + cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + if len(cipherText) < blockSize { + return nil, errors.New("ciphertext too short") + } + + iv := cipherText[:blockSize] + cipherText = cipherText[blockSize:] + + if len(cipherText)%blockSize != 0 { + return nil, errors.New("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(cipherText, cipherText) + + plainText, err := PKCS7UnPadding(cipherText) + if err != nil { + return nil, err + } + + return plainText, nil +} + +// Md5Encrypt 用于对传入的message进行MD5加密 +func Md5Encrypt(message string) string { + hash := md5.New() + hash.Write([]byte(message)) // 将字符串转换为字节切片并写入 + return hex.EncodeToString(hash.Sum(nil)) // 将哈希值转换为16进制字符串并返回 +} diff --git a/pkg/lzkit/crypto/crypto_url.go b/pkg/lzkit/crypto/crypto_url.go new file mode 100644 index 0000000..777db50 --- /dev/null +++ b/pkg/lzkit/crypto/crypto_url.go @@ -0,0 +1,67 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "io" +) + +// AES CBC模式加密,Base64传入传出 +func AesEncryptURL(plainText, key []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + blockSize := block.BlockSize() + plainText = PKCS7Padding(plainText, blockSize) + + cipherText := make([]byte, blockSize+len(plainText)) + iv := cipherText[:blockSize] // 使用前blockSize字节作为IV + _, err = io.ReadFull(rand.Reader, iv) + if err != nil { + return "", err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(cipherText[blockSize:], plainText) + + return base64.URLEncoding.EncodeToString(cipherText), nil +} + +// AES CBC模式解密,Base64传入传出 +func AesDecryptURL(cipherTextBase64 string, key []byte) ([]byte, error) { + cipherText, err := base64.URLEncoding.DecodeString(cipherTextBase64) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + if len(cipherText) < blockSize { + return nil, errors.New("ciphertext too short") + } + + iv := cipherText[:blockSize] + cipherText = cipherText[blockSize:] + + if len(cipherText)%blockSize != 0 { + return nil, errors.New("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(cipherText, cipherText) + + plainText, err := PKCS7UnPadding(cipherText) + if err != nil { + return nil, err + } + + return plainText, nil +} diff --git a/pkg/lzkit/crypto/ecb.go b/pkg/lzkit/crypto/ecb.go new file mode 100644 index 0000000..3fed15f --- /dev/null +++ b/pkg/lzkit/crypto/ecb.go @@ -0,0 +1,274 @@ +package crypto + +import ( + "crypto/aes" + "crypto/md5" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" +) + +// ECB模式是一种基本的加密模式,每个明文块独立加密 +// 警告:ECB模式存在安全问题,仅用于需要确定性加密的场景,如数据库字段查询 +// 不要用于加密大段文本或安全要求高的场景 + +// 验证密钥长度是否有效 (AES-128, AES-192, AES-256) +func validateAESKey(key []byte) error { + switch len(key) { + case 16, 24, 32: + return nil + default: + return errors.New("AES密钥长度必须是16、24或32字节(对应AES-128、AES-192、AES-256)") + } +} + +// AesEcbEncrypt AES-ECB模式加密,返回Base64编码的密文 +// 使用已有的ECB实现,但提供更易用的接口 +func AesEcbEncrypt(plainText, key []byte) (string, error) { + if err := validateAESKey(key); err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // 使用PKCS7填充 + plainText = PKCS7Padding(plainText, block.BlockSize()) + + // 创建密文数组 + cipherText := make([]byte, len(plainText)) + + // ECB模式加密,使用west_crypto.go中已有的实现 + mode := newECBEncrypter(block) + mode.CryptBlocks(cipherText, plainText) + + // 返回Base64编码的密文 + return base64.StdEncoding.EncodeToString(cipherText), nil +} + +// AesEcbDecrypt AES-ECB模式解密,输入Base64编码的密文 +func AesEcbDecrypt(cipherTextBase64 string, key []byte) ([]byte, error) { + if err := validateAESKey(key); err != nil { + return nil, err + } + + // Base64解码 + cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // 检查密文长度 + if len(cipherText)%block.BlockSize() != 0 { + return nil, errors.New("密文长度必须是块大小的整数倍") + } + + // 创建明文数组 + plainText := make([]byte, len(cipherText)) + + // ECB模式解密,使用west_crypto.go中已有的实现 + mode := newECBDecrypter(block) + mode.CryptBlocks(plainText, cipherText) + + // 去除PKCS7填充 + plainText, err = PKCS7UnPadding(plainText) + if err != nil { + return nil, err + } + + return plainText, nil +} + +// AesEcbEncryptHex AES-ECB模式加密,返回十六进制编码的密文 +func AesEcbEncryptHex(plainText, key []byte) (string, error) { + if err := validateAESKey(key); err != nil { + return "", err + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // 使用PKCS7填充 + plainText = PKCS7Padding(plainText, block.BlockSize()) + + // 创建密文数组 + cipherText := make([]byte, len(plainText)) + + // ECB模式加密 + mode := newECBEncrypter(block) + mode.CryptBlocks(cipherText, plainText) + + // 返回十六进制编码的密文 + return hex.EncodeToString(cipherText), nil +} + +// AesEcbDecryptHex AES-ECB模式解密,输入十六进制编码的密文 +func AesEcbDecryptHex(cipherTextHex string, key []byte) ([]byte, error) { + if err := validateAESKey(key); err != nil { + return nil, err + } + + // 十六进制解码 + cipherText, err := hex.DecodeString(cipherTextHex) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // 检查密文长度 + if len(cipherText)%block.BlockSize() != 0 { + return nil, errors.New("密文长度必须是块大小的整数倍") + } + + // 创建明文数组 + plainText := make([]byte, len(cipherText)) + + // ECB模式解密 + mode := newECBDecrypter(block) + mode.CryptBlocks(plainText, cipherText) + + // 去除PKCS7填充 + plainText, err = PKCS7UnPadding(plainText) + if err != nil { + return nil, err + } + + return plainText, nil +} + +// 以下是专门用于处理手机号等敏感数据的实用函数 + +// EncryptMobile 使用AES-ECB加密手机号,返回Base64编码 +// 该方法保证对相同手机号总是产生相同密文,便于数据库查询 +func EncryptMobile(mobile string, secretKey string) (string, error) { + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return "", decodeErr + } + if mobile == "" { + return "", errors.New("手机号不能为空") + } + return AesEcbEncrypt([]byte(mobile), key) +} + +// DecryptMobile 解密手机号 +func DecryptMobile(encryptedMobile string, secretKey string) (string, error) { + key, decodeErr := hex.DecodeString(secretKey) + if decodeErr != nil { + return "", decodeErr + } + if encryptedMobile == "" { + return "", errors.New("加密手机号不能为空") + } + + bytes, err := AesEcbDecrypt(encryptedMobile, key) + if err != nil { + return "", fmt.Errorf("解密手机号失败: %v", err) + } + + return string(bytes), nil +} + +// EncryptMobileHex 使用AES-ECB加密手机号,返回十六进制编码(适用于URL参数) +func EncryptMobileHex(mobile string, key []byte) (string, error) { + if mobile == "" { + return "", errors.New("手机号不能为空") + } + return AesEcbEncryptHex([]byte(mobile), key) +} + +// DecryptMobileHex 解密十六进制编码的手机号 +func DecryptMobileHex(encryptedMobileHex string, key []byte) (string, error) { + if encryptedMobileHex == "" { + return "", errors.New("加密手机号不能为空") + } + + bytes, err := AesEcbDecryptHex(encryptedMobileHex, key) + if err != nil { + return "", fmt.Errorf("解密手机号失败: %v", err) + } + + return string(bytes), nil +} + +// EncryptIDCard 使用AES-ECB加密身份证号 +func EncryptIDCard(idCard string, key []byte) (string, error) { + if idCard == "" { + return "", errors.New("身份证号不能为空") + } + return AesEcbEncrypt([]byte(idCard), key) +} + +// DecryptIDCard 解密身份证号 +func DecryptIDCard(encryptedIDCard string, key []byte) (string, error) { + if encryptedIDCard == "" { + return "", errors.New("加密身份证号不能为空") + } + + bytes, err := AesEcbDecrypt(encryptedIDCard, key) + if err != nil { + return "", fmt.Errorf("解密身份证号失败: %v", err) + } + + return string(bytes), nil +} + +// IsEncrypted 检查字符串是否为Base64编码的加密数据 +func IsEncrypted(data string) bool { + // 检查是否是有效的Base64编码 + _, err := base64.StdEncoding.DecodeString(data) + return err == nil && len(data) >= 20 // 至少20个字符的Base64字符串 +} + +// GenerateAESKey 生成AES密钥 +// keySize: 可选16, 24, 32字节(对应AES-128, AES-192, AES-256) +func GenerateAESKey(keySize int) ([]byte, error) { + if keySize != 16 && keySize != 24 && keySize != 32 { + return nil, errors.New("密钥长度必须是16、24或32字节") + } + + key := make([]byte, keySize) + _, err := rand.Read(key) + if err != nil { + return nil, err + } + + return key, nil +} + +// DeriveKeyFromPassword 基于密码派生固定长度的AES密钥 +func DeriveKeyFromPassword(password string, keySize int) ([]byte, error) { + if keySize != 16 && keySize != 24 && keySize != 32 { + return nil, errors.New("密钥长度必须是16、24或32字节") + } + + // 使用PBKDF2或简单的方法从密码派生密钥 + // 这里使用简单的MD5方法,实际生产环境应使用更安全的PBKDF2 + hash := md5.New() + hash.Write([]byte(password)) + key := hash.Sum(nil) // 16字节 + + // 如果需要24或32字节,继续哈希 + if keySize > 16 { + hash.Reset() + hash.Write(key) + key = append(key, hash.Sum(nil)[:keySize-16]...) + } + + return key, nil +} diff --git a/pkg/lzkit/crypto/ecb_test.go b/pkg/lzkit/crypto/ecb_test.go new file mode 100644 index 0000000..f7cb2f2 --- /dev/null +++ b/pkg/lzkit/crypto/ecb_test.go @@ -0,0 +1,186 @@ +package crypto + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "testing" +) + +func TestAesEcbMobileEncryption(t *testing.T) { + // 测试手机号加密 + mobile := "18653052547" + key := []byte("ff83609b2b24fc73196aac3d3dfb874f") // 16字节AES-128密钥 + + keyStr := hex.EncodeToString(key) + // 测试加密 + encrypted, err := EncryptMobile(mobile, keyStr) + if err != nil { + t.Fatalf("手机号加密失败: %v", err) + } + fmt.Printf("encrypted: %s\n", encrypted) + jmStr := "m9EEeW9ZBBJmi1hx1k1uIQ==" + // 测试解密 + decrypted, err := DecryptMobile(jmStr, keyStr) + if err != nil { + t.Fatalf("手机号解密失败: %v", err) + } + fmt.Printf("decrypted: %s\n", decrypted) + // 验证结果 + if decrypted != mobile { + t.Errorf("解密结果不匹配,期望: %s, 实际: %s", mobile, decrypted) + } + + // 测试相同输入产生相同输出(确定性) + encrypted2, _ := EncryptMobile(mobile, keyStr) + if encrypted != encrypted2 { + t.Errorf("AES-ECB不是确定性的,两次加密结果不同: %s vs %s", encrypted, encrypted2) + } +} + +func TestAesEcbHexEncryption(t *testing.T) { + // 测试十六进制编码加密 + idCard := "440101199001011234" + key := []byte("1234567890abcdef") // 16字节AES-128密钥 + + // 测试HEX加密 + encryptedHex, err := EncryptIDCard(idCard, key) + if err != nil { + t.Fatalf("身份证加密失败: %v", err) + } + + // 测试HEX解密 + decrypted, err := DecryptIDCard(encryptedHex, key) + if err != nil { + t.Fatalf("身份证解密失败: %v", err) + } + + // 验证结果 + if decrypted != idCard { + t.Errorf("解密结果不匹配,期望: %s, 实际: %s", idCard, decrypted) + } +} + +func TestAesEcbKeyValidation(t *testing.T) { + // 测试不同长度的密钥 + validKeys := [][]byte{ + make([]byte, 16), // AES-128 + make([]byte, 24), // AES-192 + make([]byte, 32), // AES-256 + } + + invalidKeys := [][]byte{ + make([]byte, 15), + make([]byte, 20), + make([]byte, 33), + } + + text := []byte("test text") + + // 测试有效密钥 + for _, key := range validKeys { + _, err := AesEcbEncrypt(text, key) + if err != nil { + t.Errorf("有效密钥(%d字节)校验失败: %v", len(key), err) + } + } + + // 测试无效密钥 + for _, key := range invalidKeys { + _, err := AesEcbEncrypt(text, key) + if err == nil { + t.Errorf("无效密钥(%d字节)未被检测出", len(key)) + } + } +} + +func TestIsEncrypted(t *testing.T) { + // 有效的Base64编码字符串 + validBase64 := base64.StdEncoding.EncodeToString([]byte("这是一个足够长的字符串,以通过IsEncrypted检查")) + + // 无效的字符串 + invalidStrings := []string{ + "", + "abc", + "not-base64!@#", + hex.EncodeToString([]byte("hexstring")), + } + + // 测试有效的加密数据 + if !IsEncrypted(validBase64) { + t.Errorf("有效的Base64未被识别为加密数据: %s", validBase64) + } + + // 测试无效的数据 + for _, s := range invalidStrings { + if IsEncrypted(s) { + t.Errorf("无效字符串被错误识别为加密数据: %s", s) + } + } +} + +func TestDeriveKeyFromPassword(t *testing.T) { + password := "my-secure-password" + + // 测试不同长度的派生密钥 + keySizes := []int{16, 24, 32} + + for _, size := range keySizes { + key, err := DeriveKeyFromPassword(password, size) + if err != nil { + t.Errorf("从密码派生%d字节密钥失败: %v", size, err) + continue + } + + if len(key) != size { + t.Errorf("派生的密钥长度错误,期望: %d, 实际: %d", size, len(key)) + } + + // 测试相同密码总是产生相同密钥 + key2, _ := DeriveKeyFromPassword(password, size) + if string(key) != string(key2) { + t.Errorf("从相同密码派生的密钥不一致") + } + + // 使用派生的密钥加密测试 + _, err = AesEcbEncrypt([]byte("test"), key) + if err != nil { + t.Errorf("使用派生的密钥加密失败: %v", err) + } + } + + // 测试无效的密钥大小 + _, err := DeriveKeyFromPassword(password, 18) + if err == nil { + t.Error("无效的密钥大小未被检测出") + } +} + +func TestGenerateAESKey(t *testing.T) { + // 测试生成不同长度的密钥 + keySizes := []int{16, 24, 32} + + for _, size := range keySizes { + key, err := GenerateAESKey(size) + if err != nil { + t.Errorf("生成%d字节密钥失败: %v", size, err) + continue + } + + if len(key) != size { + t.Errorf("生成的密钥长度错误,期望: %d, 实际: %d", size, len(key)) + } + + // 使用生成的密钥加密测试 + _, err = AesEcbEncrypt([]byte("test"), key) + if err != nil { + t.Errorf("使用生成的密钥加密失败: %v", err) + } + } + + // 测试无效的密钥大小 + _, err := GenerateAESKey(18) + if err == nil { + t.Error("无效的密钥大小未被检测出") + } +} diff --git a/pkg/lzkit/crypto/generate.go b/pkg/lzkit/crypto/generate.go new file mode 100644 index 0000000..2d91c6d --- /dev/null +++ b/pkg/lzkit/crypto/generate.go @@ -0,0 +1,63 @@ +package crypto + +import ( + "crypto/rand" + "encoding/hex" + "io" + mathrand "math/rand" + "strconv" + "time" +) + +// 生成AES-128密钥的函数,符合市面规范 +func GenerateSecretKey() (string, error) { + key := make([]byte, 16) // 16字节密钥 + _, err := io.ReadFull(rand.Reader, key) + if err != nil { + return "", err + } + return hex.EncodeToString(key), nil +} + +func GenerateSecretId() (string, error) { + // 创建一个字节数组,用于存储随机数据 + bytes := make([]byte, 8) // 因为每个字节表示两个16进制字符 + + // 读取随机字节到数组中 + _, err := rand.Read(bytes) + if err != nil { + return "", err + } + + // 将字节数组转换为16进制字符串 + return hex.EncodeToString(bytes), nil +} + +// GenerateTransactionID 生成16位数的交易单号 +func GenerateTransactionID() string { + length := 16 + // 获取当前时间戳 + timestamp := time.Now().UnixNano() + + // 转换为字符串 + timeStr := strconv.FormatInt(timestamp, 10) + + // 生成随机数 + mathrand.Seed(time.Now().UnixNano()) + randomPart := strconv.Itoa(mathrand.Intn(1000000)) + + // 组合时间戳和随机数 + combined := timeStr + randomPart + + // 如果长度超出指定值,则截断;如果不够,则填充随机字符 + if len(combined) >= length { + return combined[:length] + } + + // 如果长度不够,填充0 + for len(combined) < length { + combined += strconv.Itoa(mathrand.Intn(10)) // 填充随机数 + } + + return combined +} diff --git a/pkg/lzkit/crypto/west_crypto.go b/pkg/lzkit/crypto/west_crypto.go new file mode 100644 index 0000000..71d8a41 --- /dev/null +++ b/pkg/lzkit/crypto/west_crypto.go @@ -0,0 +1,150 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" +) + +const ( + KEY_SIZE = 16 // AES-128, 16 bytes +) + +// Encrypt encrypts the given data using AES encryption in ECB mode with PKCS5 padding +func WestDexEncrypt(data, secretKey string) (string, error) { + key := generateAESKey(KEY_SIZE*8, []byte(secretKey)) + ciphertext, err := aesEncrypt([]byte(data), key) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// Decrypt decrypts the given base64-encoded string using AES encryption in ECB mode with PKCS5 padding +func WestDexDecrypt(encodedData, secretKey string) ([]byte, error) { + ciphertext, err := base64.StdEncoding.DecodeString(encodedData) + if err != nil { + return nil, err + } + key := generateAESKey(KEY_SIZE*8, []byte(secretKey)) + plaintext, err := aesDecrypt(ciphertext, key) + if err != nil { + return nil, err + } + return plaintext, nil +} + +// generateAESKey generates a key for AES encryption using a SHA-1 based PRNG +func generateAESKey(length int, password []byte) []byte { + h := sha1.New() + h.Write(password) + state := h.Sum(nil) + + keyBytes := make([]byte, 0, length/8) + for len(keyBytes) < length/8 { + h := sha1.New() + h.Write(state) + state = h.Sum(nil) + keyBytes = append(keyBytes, state...) + } + + return keyBytes[:length/8] +} + +// aesEncrypt encrypts plaintext using AES in ECB mode with PKCS5 padding +func aesEncrypt(plaintext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + paddedPlaintext := pkcs5Padding(plaintext, block.BlockSize()) + ciphertext := make([]byte, len(paddedPlaintext)) + mode := newECBEncrypter(block) + mode.CryptBlocks(ciphertext, paddedPlaintext) + return ciphertext, nil +} + +// aesDecrypt decrypts ciphertext using AES in ECB mode with PKCS5 padding +func aesDecrypt(ciphertext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + plaintext := make([]byte, len(ciphertext)) + mode := newECBDecrypter(block) + mode.CryptBlocks(plaintext, ciphertext) + return pkcs5Unpadding(plaintext), nil +} + +// pkcs5Padding pads the input to a multiple of the block size using PKCS5 padding +func pkcs5Padding(src []byte, blockSize int) []byte { + padding := blockSize - len(src)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +// pkcs5Unpadding removes PKCS5 padding from the input +func pkcs5Unpadding(src []byte) []byte { + length := len(src) + unpadding := int(src[length-1]) + return src[:(length - unpadding)] +} + +// ECB mode encryption/decryption +type ecb struct { + b cipher.Block + blockSize int +} + +func newECB(b cipher.Block) *ecb { + return &ecb{ + b: b, + blockSize: b.BlockSize(), + } +} + +type ecbEncrypter ecb + +func newECBEncrypter(b cipher.Block) cipher.BlockMode { + return (*ecbEncrypter)(newECB(b)) +} + +func (x *ecbEncrypter) BlockSize() int { return x.blockSize } + +func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + x.b.Encrypt(dst, src[:x.blockSize]) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } +} + +type ecbDecrypter ecb + +func newECBDecrypter(b cipher.Block) cipher.BlockMode { + return (*ecbDecrypter)(newECB(b)) +} + +func (x *ecbDecrypter) BlockSize() int { return x.blockSize } + +func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + x.b.Decrypt(dst, src[:x.blockSize]) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } +} diff --git a/pkg/lzkit/delay/ProgressiveDelay.go b/pkg/lzkit/delay/ProgressiveDelay.go new file mode 100644 index 0000000..7d2e657 --- /dev/null +++ b/pkg/lzkit/delay/ProgressiveDelay.go @@ -0,0 +1,65 @@ +package delay + +import ( + "errors" + "fmt" + "time" +) + +// ProgressiveDelay 用于管理渐进式延迟策略 +type ProgressiveDelay struct { + initialDelay time.Duration // 初始延迟时间 + growthFactor float64 // 延迟增长因子 (例如 1.5) + maxDelay time.Duration // 最大延迟时间 + maxRetryDuration time.Duration // 最大重试时间 + currentDelay time.Duration // 当前延迟时间 + startTime time.Time // 重试开始时间 +} + +// New 创建一个新的渐进式延迟对象 +func New(initialDelay, maxDelay, maxRetryDuration time.Duration, growthFactor float64) (*ProgressiveDelay, error) { + // 参数校验 + if initialDelay <= 0 { + return nil, errors.New("initialDelay must be greater than zero") + } + if maxDelay <= 0 { + return nil, errors.New("maxDelay must be greater than zero") + } + if maxRetryDuration <= 0 { + return nil, errors.New("maxRetryDuration must be greater than zero") + } + if growthFactor <= 1.0 { + return nil, errors.New("growthFactor must be greater than 1") + } + + // 初始化并返回 + return &ProgressiveDelay{ + initialDelay: initialDelay, + maxDelay: maxDelay, + maxRetryDuration: maxRetryDuration, + growthFactor: growthFactor, + currentDelay: initialDelay, + startTime: time.Now(), + }, nil +} + +// NextDelay 计算并返回下次的延迟时间 +func (pd *ProgressiveDelay) NextDelay() (time.Duration, error) { + // 检查最大重试时间是否已过 + if time.Since(pd.startTime) > pd.maxRetryDuration { + return 0, fmt.Errorf("最大重试时间超过限制: %v", pd.maxRetryDuration) + } + + // 返回当前延迟时间 + delay := pd.currentDelay + + // 计算下一个延迟时间并更新 currentDelay + pd.currentDelay = time.Duration(float64(pd.currentDelay) * pd.growthFactor) + + // 如果下次延迟超过最大延迟时间,限制在最大值 + if pd.currentDelay > pd.maxDelay { + pd.currentDelay = pd.maxDelay + } + + return delay, nil +} diff --git a/pkg/lzkit/lzUtils/json.go b/pkg/lzkit/lzUtils/json.go new file mode 100644 index 0000000..e44c371 --- /dev/null +++ b/pkg/lzkit/lzUtils/json.go @@ -0,0 +1,35 @@ +package lzUtils + +import "github.com/bytedance/sonic" + +func RecursiveParse(data interface{}) (interface{}, error) { + switch v := data.(type) { + case string: + // 尝试解析字符串是否为嵌套 JSON + var parsed interface{} + if err := sonic.Unmarshal([]byte(v), &parsed); err == nil { + return RecursiveParse(parsed) + } + return v, nil // 普通字符串直接返回 + case map[string]interface{}: + for key, val := range v { + parsed, err := RecursiveParse(val) + if err != nil { + return nil, err + } + v[key] = parsed + } + return v, nil + case []interface{}: + for i, item := range v { + parsed, err := RecursiveParse(item) + if err != nil { + return nil, err + } + v[i] = parsed + } + return v, nil + default: + return v, nil + } +} diff --git a/pkg/lzkit/lzUtils/sqlutls.go b/pkg/lzkit/lzUtils/sqlutls.go new file mode 100644 index 0000000..66d2ce0 --- /dev/null +++ b/pkg/lzkit/lzUtils/sqlutls.go @@ -0,0 +1,72 @@ +package lzUtils + +import ( + "database/sql" + "time" +) + +// StringToNullString 将 string 转换为 sql.NullString +func StringToNullString(s string) sql.NullString { + return sql.NullString{ + String: s, + Valid: s != "", + } +} + +// NullStringToString 将 sql.NullString 转换为 string +func NullStringToString(ns sql.NullString) string { + if ns.Valid { + return ns.String + } + return "" +} + +// TimeToNullTime 将 time.Time 转换为 sql.NullTime +func TimeToNullTime(t time.Time) sql.NullTime { + return sql.NullTime{ + Time: t, + Valid: !t.IsZero(), // 仅当 t 不是零值时才设置为有效 + } +} + +// NullTimeToTime 将 sql.NullTime 转换为 time.Time +func NullTimeToTime(nt sql.NullTime) time.Time { + if nt.Valid { + return nt.Time + } + return time.Time{} // 返回零值时间 +} + +// Int64ToNullInt64 将 int64 转换为 sql.NullInt64 +func Int64ToNullInt64(i int64) sql.NullInt64 { + return sql.NullInt64{ + Int64: i, + Valid: i != 0, // 仅当 i 非零时才设置为有效 + } +} + +// NullInt64ToInt64 将 sql.NullInt64 转换为 int64 +func NullInt64ToInt64(ni sql.NullInt64) int64 { + if ni.Valid { + return ni.Int64 + } + return 0 // 返回零值 int64 +} + +// Float64ToNullFloat64 将 float64 转换为 sql.NullFloat64 +// Valid 字段在 f 非零时设置为有效(注意:NaN 会被视为有效,需结合业务场景使用) +func Float64ToNullFloat64(f float64) sql.NullFloat64 { + return sql.NullFloat64{ + Float64: f, + Valid: f != 0, + } +} + +// NullFloat64ToFloat64 将 sql.NullFloat64 转换为 float64 +// 当 Valid 为 false 时,返回 float64 零值 +func NullFloat64ToFloat64(nf sql.NullFloat64) float64 { + if nf.Valid { + return nf.Float64 + } + return 0.0 +} diff --git a/pkg/lzkit/lzUtils/time.go b/pkg/lzkit/lzUtils/time.go new file mode 100644 index 0000000..3c67d1e --- /dev/null +++ b/pkg/lzkit/lzUtils/time.go @@ -0,0 +1,26 @@ +package lzUtils + +import ( + "database/sql" + "time" +) + +// RenewMembership 延长会员有效期 +func RenewMembership(expiry sql.NullTime) sql.NullTime { + // 确定基准时间 + var baseTime time.Time + if expiry.Valid { + baseTime = expiry.Time + } else { + baseTime = time.Now() + } + + // 增加一年(自动处理闰年) + newTime := baseTime.AddDate(1, 0, 0) + + // 返回始终有效的 NullTime + return sql.NullTime{ + Time: newTime, + Valid: true, + } +} diff --git a/pkg/lzkit/lzUtils/utils.go b/pkg/lzkit/lzUtils/utils.go new file mode 100644 index 0000000..69a9aca --- /dev/null +++ b/pkg/lzkit/lzUtils/utils.go @@ -0,0 +1,15 @@ +package lzUtils + +import "fmt" + +// ToWechatAmount 将金额从元转换为微信支付 SDK 需要的分(int64 类型) +func ToWechatAmount(amount float64) int64 { + // 将金额从元转换为分,并四舍五入 + return int64(amount*100 + 0.5) +} + +// ToAlipayAmount 将金额从元转换为支付宝支付 SDK 需要的字符串格式,保留两位小数 +func ToAlipayAmount(amount float64) string { + // 格式化为字符串,保留两位小数 + return fmt.Sprintf("%.2f", amount) +} diff --git a/pkg/lzkit/md5/README.md b/pkg/lzkit/md5/README.md new file mode 100644 index 0000000..5e92541 --- /dev/null +++ b/pkg/lzkit/md5/README.md @@ -0,0 +1,106 @@ +# MD5 工具包 + +这个包提供了全面的 MD5 哈希功能,包括字符串加密、文件加密、链式操作、加盐哈希等。 + +## 主要功能 + +- 字符串和字节切片的 MD5 哈希计算 +- 文件 MD5 哈希计算(支持大文件分块处理) +- 链式 API,支持构建复杂的哈希内容 +- 带盐值的 MD5 哈希,提高安全性 +- 哈希验证功能 +- 16 位和 8 位 MD5 哈希(短版本) +- HMAC-MD5 实现,增强安全性 + +## 使用示例 + +### 基本使用 + +```go +// 计算字符串的MD5哈希 +hash := md5.EncryptString("hello world") +fmt.Println(hash) // 5eb63bbbe01eeed093cb22bb8f5acdc3 + +// 计算字节切片的MD5哈希 +bytes := []byte("hello world") +hash = md5.EncryptBytes(bytes) + +// 计算文件的MD5哈希 +fileHash, err := md5.EncryptFile("path/to/file.txt") +if err != nil { + log.Fatal(err) +} +fmt.Println(fileHash) +``` + +### 链式 API + +```go +// 创建一个新的MD5实例并添加内容 +hash := md5.New(). + Add("hello"). + Add(" "). + Add("world"). + Sum() +fmt.Println(hash) // 5eb63bbbe01eeed093cb22bb8f5acdc3 + +// 或者从字符串初始化 +hash = md5.FromString("hello"). + Add(" world"). + Sum() + +// 从字节切片初始化 +hash = md5.FromBytes([]byte("hello")). + AddBytes([]byte(" world")). + Sum() +``` + +### 安全性增强 + +```go +// 使用盐值加密(提高安全性) +hashedPassword := md5.EncryptStringWithSalt("password123", "main@example.com") + +// 使用前缀加密 +hashedValue := md5.EncryptStringWithPrefix("secret-data", "prefix-") + +// 验证带盐值的哈希 +isValid := md5.VerifyMD5WithSalt("password123", "main@example.com", hashedPassword) + +// 使用HMAC-MD5提高安全性 +hmacHash := md5.MD5HMAC("message", "secret-key") +``` + +### 短哈希值 + +```go +// 获取16位MD5(32位MD5的中间部分) +hash16 := md5.Get16("hello world") +fmt.Println(hash16) // 中间16个字符 + +// 获取8位MD5 +hash8 := md5.Get8("hello world") +fmt.Println(hash8) // 中间8个字符 +``` + +### 文件验证 + +```go +// 验证文件MD5是否匹配 +match, err := md5.VerifyFileMD5("path/to/file.txt", "expected-hash") +if err != nil { + log.Fatal(err) +} +if match { + fmt.Println("文件MD5校验通过") +} else { + fmt.Println("文件MD5校验失败") +} +``` + +## 注意事项 + +1. MD5 主要用于校验,不适合用于安全存储密码等敏感信息 +2. 如果用于密码存储,请务必使用加盐处理并考虑使用更安全的算法 +3. 处理大文件时请使用`EncryptFileChunk`以优化性能 +4. 返回的 MD5 哈希值都是 32 位的小写十六进制字符串(除非使用 Get16/Get8 函数) diff --git a/pkg/lzkit/md5/example_test.go b/pkg/lzkit/md5/example_test.go new file mode 100644 index 0000000..df62260 --- /dev/null +++ b/pkg/lzkit/md5/example_test.go @@ -0,0 +1,79 @@ +package md5_test + +import ( + "bdrp-server/pkg/lzkit/md5" + "fmt" + "log" +) + +func Example() { + // 简单的字符串MD5 + hashValue := md5.EncryptString("hello world") + fmt.Println("MD5(hello world):", hashValue) + + // 使用链式API + chainHash := md5.New(). + Add("hello"). + Add(" "). + Add("world"). + Sum() + fmt.Println("链式MD5:", chainHash) + + // 使用盐值 + saltedHash := md5.EncryptStringWithSalt("password123", "main@example.com") + fmt.Println("加盐MD5:", saltedHash) + + // 验证哈希 + isValid := md5.VerifyMD5("hello world", hashValue) + fmt.Println("验证结果:", isValid) + + // 生成短版本的MD5 + fmt.Println("16位MD5:", md5.Get16("hello world")) + fmt.Println("8位MD5:", md5.Get8("hello world")) + + // 文件MD5计算 + filePath := "example.txt" // 这只是示例,实际上这个文件可能不存在 + fileHash, err := md5.EncryptFile(filePath) + if err != nil { + // 在实际代码中执行正确的错误处理 + log.Printf("计算文件MD5出错: %v", err) + } else { + fmt.Println("文件MD5:", fileHash) + } + + // HMAC-MD5 + hmacHash := md5.MD5HMAC("重要消息", "secret-key") + fmt.Println("HMAC-MD5:", hmacHash) +} + +func ExampleEncryptString() { + hash := md5.EncryptString("HelloWorld") + fmt.Println(hash) + // Output: 68e109f0f40ca72a15e05cc22786f8e6 +} + +func ExampleMD5_Sum() { + hash := md5.New(). + Add("Hello"). + Add("World"). + Sum() + fmt.Println(hash) + // Output: 68e109f0f40ca72a15e05cc22786f8e6 +} + +func ExampleEncryptStringWithSalt() { + // 为用户密码加盐,通常使用用户唯一标识(如邮箱)作为盐值 + hash := md5.EncryptStringWithSalt("password123", "main@example.com") + fmt.Println("盐值哈希长度:", len(hash)) + fmt.Println("是否为有效哈希:", md5.VerifyMD5WithSalt("password123", "main@example.com", hash)) + // Output: + // 盐值哈希长度: 32 + // 是否为有效哈希: true +} + +func ExampleGet16() { + // 获取16位MD5,适合不需要完全防碰撞场景 + hash := md5.Get16("HelloWorld") + fmt.Println(hash) + // Output: f0f40ca72a15e05c +} diff --git a/pkg/lzkit/md5/md5.go b/pkg/lzkit/md5/md5.go new file mode 100644 index 0000000..bfbfa89 --- /dev/null +++ b/pkg/lzkit/md5/md5.go @@ -0,0 +1,206 @@ +package md5 + +import ( + "bufio" + "crypto/md5" + "encoding/hex" + "io" + "os" + "strings" +) + +// MD5结构体,可用于链式调用 +type MD5 struct { + data []byte +} + +// New 创建一个新的MD5实例 +func New() *MD5 { + return &MD5{ + data: []byte{}, + } +} + +// FromString 从字符串创建MD5 +func FromString(s string) *MD5 { + return &MD5{ + data: []byte(s), + } +} + +// FromBytes 从字节切片创建MD5 +func FromBytes(b []byte) *MD5 { + return &MD5{ + data: b, + } +} + +// Add 向MD5中添加字符串 +func (m *MD5) Add(s string) *MD5 { + m.data = append(m.data, []byte(s)...) + return m +} + +// AddBytes 向MD5中添加字节切片 +func (m *MD5) AddBytes(b []byte) *MD5 { + m.data = append(m.data, b...) + return m +} + +// Sum 计算并返回MD5哈希值(16进制字符串) +func (m *MD5) Sum() string { + hash := md5.New() + hash.Write(m.data) + return hex.EncodeToString(hash.Sum(nil)) +} + +// SumBytes 计算并返回MD5哈希值(字节切片) +func (m *MD5) SumBytes() []byte { + hash := md5.New() + hash.Write(m.data) + return hash.Sum(nil) +} + +// 直接调用的工具函数 + +// EncryptString 加密字符串 +func EncryptString(s string) string { + hash := md5.New() + hash.Write([]byte(s)) + return hex.EncodeToString(hash.Sum(nil)) +} + +// EncryptBytes 加密字节切片 +func EncryptBytes(b []byte) string { + hash := md5.New() + hash.Write(b) + return hex.EncodeToString(hash.Sum(nil)) +} + +// EncryptFile 加密文件内容 +func EncryptFile(filePath string) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// EncryptFileChunk 对大文件分块计算MD5,提高效率 +func EncryptFileChunk(filePath string, chunkSize int) (string, error) { + if chunkSize <= 0 { + chunkSize = 1024 * 1024 // 默认1MB + } + + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + hash := md5.New() + buf := make([]byte, chunkSize) + reader := bufio.NewReader(file) + + for { + n, err := reader.Read(buf) + if err != nil && err != io.EOF { + return "", err + } + if n == 0 { + break + } + hash.Write(buf[:n]) + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// EncryptStringWithSalt 使用盐值加密字符串 +func EncryptStringWithSalt(s, salt string) string { + return EncryptString(s + salt) +} + +// EncryptStringWithPrefix 使用前缀加密字符串 +func EncryptStringWithPrefix(s, prefix string) string { + return EncryptString(prefix + s) +} + +// VerifyMD5 验证字符串的MD5哈希是否匹配 +func VerifyMD5(s, hash string) bool { + return EncryptString(s) == strings.ToLower(hash) +} + +// VerifyMD5WithSalt 验证带盐值的字符串MD5哈希是否匹配 +func VerifyMD5WithSalt(s, salt, hash string) bool { + return EncryptStringWithSalt(s, salt) == strings.ToLower(hash) +} + +// VerifyFileMD5 验证文件的MD5哈希是否匹配 +func VerifyFileMD5(filePath, hash string) (bool, error) { + fileHash, err := EncryptFile(filePath) + if err != nil { + return false, err + } + return fileHash == strings.ToLower(hash), nil +} + +// MD5格式化为指定位数 + +// Get16 获取16位MD5值(取32位结果的中间16位) +func Get16(s string) string { + result := EncryptString(s) + return result[8:24] +} + +// Get8 获取8位MD5值 +func Get8(s string) string { + result := EncryptString(s) + return result[12:20] +} + +// MD5主要用于校验而非安全存储,对于需要高安全性的场景,应考虑: +// 1. bcrypt, scrypt或Argon2等专门为密码设计的算法 +// 2. HMAC-MD5等方式以防御彩虹表攻击 +// 3. 加盐并使用多次哈希迭代提高安全性 + +// MD5HMAC 使用HMAC-MD5算法 +func MD5HMAC(message, key string) string { + hash := md5.New() + + // 如果key长度超出block size,先进行哈希 + if len(key) > 64 { + hash.Write([]byte(key)) + key = hex.EncodeToString(hash.Sum(nil)) + hash.Reset() + } + + // 内部填充 + k_ipad := make([]byte, 64) + k_opad := make([]byte, 64) + copy(k_ipad, []byte(key)) + copy(k_opad, []byte(key)) + + for i := 0; i < 64; i++ { + k_ipad[i] ^= 0x36 + k_opad[i] ^= 0x5c + } + + // 内部哈希 + hash.Write(k_ipad) + hash.Write([]byte(message)) + innerHash := hash.Sum(nil) + hash.Reset() + + // 外部哈希 + hash.Write(k_opad) + hash.Write(innerHash) + + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/pkg/lzkit/md5/md5_test.go b/pkg/lzkit/md5/md5_test.go new file mode 100644 index 0000000..198c24a --- /dev/null +++ b/pkg/lzkit/md5/md5_test.go @@ -0,0 +1,192 @@ +package md5 + +import ( + "fmt" + "os" + "testing" +) + +func TestEncryptString(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"", "d41d8cd98f00b204e9800998ecf8427e"}, + {"hello", "5d41402abc4b2a76b9719d911017c592"}, + {"123456", "e10adc3949ba59abbe56e057f20f883e"}, + {"Hello World!", "ed076287532e86365e841e92bfc50d8c"}, + } + + for _, test := range tests { + result := EncryptString(test.input) + fmt.Println(result) + if result != test.expected { + t.Errorf("EncryptString(%s) = %s; want %s", test.input, result, test.expected) + } + } +} + +func TestEncryptBytes(t *testing.T) { + tests := []struct { + input []byte + expected string + }{ + {[]byte(""), "d41d8cd98f00b204e9800998ecf8427e"}, + {[]byte("hello"), "5d41402abc4b2a76b9719d911017c592"}, + {[]byte{0, 1, 2, 3, 4}, "5267768822ee624d48fce15ec5ca79b6"}, + } + + for _, test := range tests { + result := EncryptBytes(test.input) + if result != test.expected { + t.Errorf("EncryptBytes(%v) = %s; want %s", test.input, result, test.expected) + } + } +} + +func TestMD5Chain(t *testing.T) { + // 测试链式调用 + result := New(). + Add("hello"). + Add(" "). + Add("world"). + Sum() + + expected := "fc5e038d38a57032085441e7fe7010b0" // MD5("hello world") + if result != expected { + t.Errorf("Chain MD5 = %s; want %s", result, expected) + } + + // 测试从字符串初始化 + result = FromString("hello").Add(" world").Sum() + if result != expected { + t.Errorf("FromString MD5 = %s; want %s", result, expected) + } + + // 测试从字节切片初始化 + result = FromBytes([]byte("hello")).AddBytes([]byte(" world")).Sum() + if result != expected { + t.Errorf("FromBytes MD5 = %s; want %s", result, expected) + } +} + +func TestVerifyMD5(t *testing.T) { + if !VerifyMD5("hello", "5d41402abc4b2a76b9719d911017c592") { + t.Error("VerifyMD5 failed for correct match") + } + + if VerifyMD5("hello", "wrong-hash") { + t.Error("VerifyMD5 succeeded for incorrect match") + } + + // 测试大小写不敏感 + if !VerifyMD5("hello", "5D41402ABC4B2A76B9719D911017C592") { + t.Error("VerifyMD5 failed for uppercase hash") + } +} + +func TestSaltAndPrefix(t *testing.T) { + // 测试加盐 + saltResult := EncryptStringWithSalt("password", "salt123") + expectedSalt := EncryptString("passwordsalt123") + if saltResult != expectedSalt { + t.Errorf("EncryptStringWithSalt = %s; want %s", saltResult, expectedSalt) + } + + // 测试前缀 + prefixResult := EncryptStringWithPrefix("password", "prefix123") + expectedPrefix := EncryptString("prefix123password") + if prefixResult != expectedPrefix { + t.Errorf("EncryptStringWithPrefix = %s; want %s", prefixResult, expectedPrefix) + } + + // 验证带盐值的MD5 + if !VerifyMD5WithSalt("password", "salt123", saltResult) { + t.Error("VerifyMD5WithSalt failed for correct match") + } +} + +func TestGet16And8(t *testing.T) { + full := EncryptString("test-string") + + // 测试16位MD5 + result16 := Get16("test-string") + expected16 := full[8:24] + if result16 != expected16 { + t.Errorf("Get16 = %s; want %s", result16, expected16) + } + + // 测试8位MD5 + result8 := Get8("test-string") + expected8 := full[12:20] + if result8 != expected8 { + t.Errorf("Get8 = %s; want %s", result8, expected8) + } +} + +func TestMD5HMAC(t *testing.T) { + // 已知的HMAC-MD5结果 + tests := []struct { + message string + key string + expected string + }{ + {"message", "key", "4e4748e62b463521f6775fbf921234b5"}, + {"test", "secret", "8b11d99898918564dda1a9fe205b5310"}, + } + + for _, test := range tests { + result := MD5HMAC(test.message, test.key) + if result != test.expected { + t.Errorf("MD5HMAC(%s, %s) = %s; want %s", + test.message, test.key, result, test.expected) + } + } +} + +func TestEncryptFile(t *testing.T) { + // 创建临时测试文件 + content := []byte("test file content for MD5") + tmpFile, err := os.CreateTemp("", "md5test-*.txt") + if err != nil { + t.Fatalf("无法创建临时文件: %v", err) + } + defer os.Remove(tmpFile.Name()) + + if _, err := tmpFile.Write(content); err != nil { + t.Fatalf("无法写入临时文件: %v", err) + } + if err := tmpFile.Close(); err != nil { + t.Fatalf("无法关闭临时文件: %v", err) + } + + // 计算文件MD5 + fileHash, err := EncryptFile(tmpFile.Name()) + if err != nil { + t.Fatalf("计算文件MD5失败: %v", err) + } + + // 验证文件MD5 + expectedHash := EncryptBytes(content) + if fileHash != expectedHash { + t.Errorf("文件MD5 = %s; 应为 %s", fileHash, expectedHash) + } + + // 测试VerifyFileMD5 + match, err := VerifyFileMD5(tmpFile.Name(), expectedHash) + if err != nil { + t.Fatalf("验证文件MD5失败: %v", err) + } + if !match { + t.Error("VerifyFileMD5返回false,应返回true") + } + + // 测试不匹配的情况 + match, err = VerifyFileMD5(tmpFile.Name(), "wronghash") + if err != nil { + t.Fatalf("验证文件MD5失败: %v", err) + } + if match { + t.Error("VerifyFileMD5对错误的哈希返回true,应返回false") + } +} diff --git a/pkg/lzkit/validator/error_messages.go b/pkg/lzkit/validator/error_messages.go new file mode 100644 index 0000000..a470cdf --- /dev/null +++ b/pkg/lzkit/validator/error_messages.go @@ -0,0 +1,38 @@ +package validator + +// 定义自定义错误消息 +var customMessages = map[string]string{ + "Name.min": "姓名不能少于1个字", + "Name.required": "姓名是必填项", + "Name.name": "姓名只能包含中文", + "NameMan.min": "男方姓名不能少于1个字", + "NameMan.required": "男方姓名是必填项", + "NameMan.name": "男方姓名只能包含中文", + "NameWoman.min": "女方姓名不能少于1个字", + "NameWoman.required": "女方姓名是必填项", + "NameWoman.name": "女方姓名只能包含中文", + "Mobile.required": "手机号是必填项", + "Mobile.min": "电话号码必须为有效的中国电话号码", + "Mobile.max": "电话号码必须为有效的中国电话号码", + "Mobile.mobile": "电话号码必须为有效的中国电话号码", + "IDCard.required": "身份证号是必填项", + "IDCard.idCard": "无效的身份证号码", + "IDCardMan.required": "男方身份证号是必填项", + "IDCardMan.idCard": "无效的男方身份证号码", + "IDCardWoman.required": "女方身份证号是必填项", + "IDCardWoman.idCard": "无效的女方身份证号码", + "Password.min": "密码不能少于8位数", + "Password.max": "密码不能超过32位数", + "Password.password": "密码强度太弱", + //"EntCode.required":"请输入统一社会信用代码", + //"EntCode.USCI": "请输入正确的统一社会信用代码", +} + +// 获取自定义错误消息 +func GetErrorMessage(field, tag string) string { + key := field + "." + tag + if msg, exists := customMessages[key]; exists { + return msg + } + return "请输入正确格式的参数" +} diff --git a/pkg/lzkit/validator/validator.go b/pkg/lzkit/validator/validator.go new file mode 100644 index 0000000..594696f --- /dev/null +++ b/pkg/lzkit/validator/validator.go @@ -0,0 +1,225 @@ +package validator + +import ( + "errors" + "fmt" + "regexp" + "strconv" + "strings" + "time" + + "github.com/go-playground/validator/v10" +) + +var validate *validator.Validate + +// 初始化自定义校验器 +func init() { + validate = validator.New() + + if err := validate.RegisterValidation("name", validName); err != nil { + panic(fmt.Sprintf("注册 name 验证器时发生错误: %v", err)) + } + + // 注册自定义验证器 validmobile + if err := validate.RegisterValidation("mobile", validmobile); err != nil { + panic(fmt.Sprintf("注册 mobile 验证器时发生错误: %v", err)) + } + + // 注册自定义验证器 validDate + if err := validate.RegisterValidation("date", validDate); err != nil { + panic(fmt.Sprintf("注册 date 验证器时发生错误: %v", err)) + } + + // 注册自定义验证器 validIDCard + if err := validate.RegisterValidation("idCard", validIDCard); err != nil { + panic(fmt.Sprintf("注册 idCard 验证器时发生错误: %v", err)) + } + + if err := validate.RegisterValidation("bankCard", validBankCard); err != nil { + panic(fmt.Sprintf("注册 bankCard 验证器时发生错误: %v", err)) + } + + if err := validate.RegisterValidation("USCI", validUSCI); err != nil { + panic(fmt.Sprintf("注册 USCI 社会统一信用代码 验证器时发生错误: %v", err)) + } + + if err := validate.RegisterValidation("mobileType", validMobileType); err != nil { + panic(fmt.Sprintf("注册 mobileType 验证器时发生错误: %v", err)) + } + if err := validate.RegisterValidation("password", validatePassword); err != nil { + panic(fmt.Sprintf("注册 password 验证器时发生错误: %v", err)) + } + if err := validate.RegisterValidation("payMethod", validatePayMethod); err != nil { + panic(fmt.Sprintf("注册 payMethod 验证器时发生错误: %v", err)) + } + +} + +// 弱口令列表 +var weakPasswords = []string{ + "12345678", "password", "123456789", "qwerty", "123456", "letmein", + "1234567", "welcome", "abc123", "password1", "1234", "111111", "admin", +} + +// Validate 校验参数逻辑 +func Validate(req interface{}) error { + if err := validate.Struct(req); err != nil { + // 检查 err 是否是 ValidationErrors 类型 + if validationErrors, ok := err.(validator.ValidationErrors); ok { + for _, validationErr := range validationErrors { + field := validationErr.StructField() + tag := validationErr.Tag() + return errors.New(GetErrorMessage(field, tag)) + } + } else { + // 其他错误处理 + return fmt.Errorf("验证时出现未知错误: %v", err) + } + } + return nil +} + +// 自定义的名称验证 +func validName(fl validator.FieldLevel) bool { + name := fl.Field().String() + validNamePattern := `^[\p{Han}]+$` + matched, _ := regexp.MatchString(validNamePattern, name) + return matched +} + +// 自定义的手机号验证 +func validmobile(fl validator.FieldLevel) bool { + phone := fl.Field().String() + validmobilePattern := `^1[3-9]\d{9}$` + matched, _ := regexp.MatchString(validmobilePattern, phone) + return matched +} + +// 自定义正则表达式校验 yyyyMMdd 格式 +func validDate(fl validator.FieldLevel) bool { + date := fl.Field().String() + validDatePattern := `^\d{4}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])$` + matched, _ := regexp.MatchString(validDatePattern, date) + return matched +} + + +// 自定义身份证校验(增强版) +// 校验规则: +// 1. 格式:18位,前6位地区码(首位不为0),7-14位出生日期,15-17位顺序码,18位校验码 +// 2. 出生日期必须合法(验证年月日有效性,包括闰年) +// 3. 校验码按照GB 11643-1999标准计算验证 +func validIDCard(fl validator.FieldLevel) bool { + idCard := fl.Field().String() + + // 1. 基本格式验证:地区码(6位) + 年(4位) + 月(2位) + 日(2位) + 顺序码(3位) + 校验码(1位) + validIDPattern := `^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$` + matched, _ := regexp.MatchString(validIDPattern, idCard) + if !matched { + return false + } + + // 2. 验证出生日期的合法性 + year, _ := strconv.Atoi(idCard[6:10]) + month, _ := strconv.Atoi(idCard[10:12]) + day, _ := strconv.Atoi(idCard[12:14]) + + // 构造日期并验证是否合法(time包会自动处理闰年等情况) + birthDate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) + if birthDate.Year() != year || int(birthDate.Month()) != month || birthDate.Day() != day { + return false // 日期不合法,如2月30日、4月31日等 + } + + // 3. 验证校验码(按照GB 11643-1999标准) + return validateIDCardChecksum(idCard) +} + +// 验证身份证校验码(GB 11643-1999标准) +func validateIDCardChecksum(idCard string) bool { + // 加权因子 + weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2} + // 校验码对应值 + checksumChars := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'} + + sum := 0 + for i := 0; i < 17; i++ { + num := int(idCard[i] - '0') + sum += num * weights[i] + } + + // 计算校验码 + checksum := checksumChars[sum%11] + lastChar := idCard[17] + + // 支持小写x + if lastChar == 'x' { + lastChar = 'X' + } + + return byte(lastChar) == checksum +} + +func validBankCard(fl validator.FieldLevel) bool { + bankCard := fl.Field().String() + // 银行卡号一般是13到19位的数字 + validBankCardPattern := `^\d{13,19}$` + matched, _ := regexp.MatchString(validBankCardPattern, bankCard) + return matched +} + +func validUSCI(fl validator.FieldLevel) bool { + usci := fl.Field().String() + // 社会信用代码为18位数字和大写字母的组合,最后一位为校验码 + validUSCIPattern := `^[1-9A-Z]{2}[0-9]{6}[0-9A-Z]{9}[0-9A-Z]$` + matched, _ := regexp.MatchString(validUSCIPattern, usci) + return matched +} + +// 自定义的手机号类型验证(可以为空) +func validMobileType(fl validator.FieldLevel) bool { + mobileType := fl.Field().String() + if mobileType == "" { + return true // 如果为空,认为是有效的 + } + + // 校验是否是 CTCC, CMCC, CUCC 之一 + validTypes := map[string]bool{ + "CTCC": true, // 中国电信 + "CMCC": true, // 中国移动 + "CUCC": true, // 中国联通 + } + + return validTypes[mobileType] +} + +// 自定义密码强度校验函数 +func validatePassword(fl validator.FieldLevel) bool { + password := fl.Field().String() + + // 检查密码是否在弱口令列表中 + for _, weakPwd := range weakPasswords { + if strings.ToLower(password) == weakPwd { + return false + } + } + + return true +} + +// 支付方式 +func validatePayMethod(fl validator.FieldLevel) bool { + payMethod := fl.Field().String() + + if payMethod == "" { + return true // 如果为空,认为是有效的 + } + + validTypes := map[string]bool{ + "alipay": true, // 中国电信 + "wechatpay": true, // 中国移动 + } + + return validTypes[payMethod] + +}