feat: 新增微信参数配置模块(Model、DTO、Store、Service)
- wechat_config.go: WechatConfig GORM 模型,含 ProviderTypeWechat/Fuiou 常量 - wechat_config_dto.go: Create/Update/List 请求 DTO,响应 DTO 含脱敏逻辑 - wechat_config_store.go: CRUD、GetActive、ActivateInTx(事务内唯一激活)、软删除保护查询 - service.go: 业务逻辑,按渠道校验必填字段、Redis 缓存管理(wechat:config:active)、删除保护、审计日志 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
186
internal/model/dto/wechat_config_dto.go
Normal file
186
internal/model/dto/wechat_config_dto.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateWechatConfigRequest 创建微信参数配置请求
|
||||||
|
type CreateWechatConfigRequest struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"配置名称"`
|
||||||
|
Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"配置描述"`
|
||||||
|
ProviderType string `json:"provider_type" validate:"required,oneof=wechat fuiou" required:"true" description:"支付渠道类型 (wechat:微信直连, fuiou:富友)"`
|
||||||
|
|
||||||
|
OaAppID string `json:"oa_app_id" validate:"omitempty,max=100" maxLength:"100" description:"公众号AppID"`
|
||||||
|
OaAppSecret string `json:"oa_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"公众号AppSecret"`
|
||||||
|
OaToken string `json:"oa_token" validate:"omitempty,max=200" maxLength:"200" description:"公众号Token"`
|
||||||
|
OaAesKey string `json:"oa_aes_key" validate:"omitempty,max=200" maxLength:"200" description:"公众号AES加密Key"`
|
||||||
|
OaOAuthRedirectURL string `json:"oa_oauth_redirect_url" validate:"omitempty,max=500" maxLength:"500" description:"OAuth回调地址"`
|
||||||
|
|
||||||
|
MiniappAppID string `json:"miniapp_app_id" validate:"omitempty,max=100" maxLength:"100" description:"小程序AppID"`
|
||||||
|
MiniappAppSecret string `json:"miniapp_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"小程序AppSecret"`
|
||||||
|
|
||||||
|
WxMchID string `json:"wx_mch_id" validate:"omitempty,max=100" maxLength:"100" description:"微信商户号"`
|
||||||
|
WxAPIV3Key string `json:"wx_api_v3_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv3密钥"`
|
||||||
|
WxAPIV2Key string `json:"wx_api_v2_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv2密钥"`
|
||||||
|
WxCertContent string `json:"wx_cert_content" validate:"omitempty" description:"微信支付证书内容(PEM格式)"`
|
||||||
|
WxKeyContent string `json:"wx_key_content" validate:"omitempty" description:"微信支付密钥内容(PEM格式)"`
|
||||||
|
WxSerialNo string `json:"wx_serial_no" validate:"omitempty,max=200" maxLength:"200" description:"微信证书序列号"`
|
||||||
|
WxNotifyURL string `json:"wx_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"微信支付回调地址"`
|
||||||
|
|
||||||
|
FyInsCd string `json:"fy_ins_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友机构号"`
|
||||||
|
FyMchntCd string `json:"fy_mchnt_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友商户号"`
|
||||||
|
FyTermID string `json:"fy_term_id" validate:"omitempty,max=50" maxLength:"50" description:"富友终端号"`
|
||||||
|
FyPrivateKey string `json:"fy_private_key" validate:"omitempty" description:"富友私钥(PEM格式)"`
|
||||||
|
FyPublicKey string `json:"fy_public_key" validate:"omitempty" description:"富友公钥(PEM格式)"`
|
||||||
|
FyAPIURL string `json:"fy_api_url" validate:"omitempty,max=500" maxLength:"500" description:"富友API地址"`
|
||||||
|
FyNotifyURL string `json:"fy_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"富友支付回调地址"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWechatConfigRequest 更新微信参数配置请求
|
||||||
|
type UpdateWechatConfigRequest struct {
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=100" minLength:"1" maxLength:"100" description:"配置名称"`
|
||||||
|
Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"配置描述"`
|
||||||
|
ProviderType *string `json:"provider_type" validate:"omitempty,oneof=wechat fuiou" description:"支付渠道类型 (wechat:微信直连, fuiou:富友)"`
|
||||||
|
|
||||||
|
OaAppID *string `json:"oa_app_id" validate:"omitempty,max=100" maxLength:"100" description:"公众号AppID"`
|
||||||
|
OaAppSecret *string `json:"oa_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"公众号AppSecret"`
|
||||||
|
OaToken *string `json:"oa_token" validate:"omitempty,max=200" maxLength:"200" description:"公众号Token"`
|
||||||
|
OaAesKey *string `json:"oa_aes_key" validate:"omitempty,max=200" maxLength:"200" description:"公众号AES加密Key"`
|
||||||
|
OaOAuthRedirectURL *string `json:"oa_oauth_redirect_url" validate:"omitempty,max=500" maxLength:"500" description:"OAuth回调地址"`
|
||||||
|
|
||||||
|
MiniappAppID *string `json:"miniapp_app_id" validate:"omitempty,max=100" maxLength:"100" description:"小程序AppID"`
|
||||||
|
MiniappAppSecret *string `json:"miniapp_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"小程序AppSecret"`
|
||||||
|
|
||||||
|
WxMchID *string `json:"wx_mch_id" validate:"omitempty,max=100" maxLength:"100" description:"微信商户号"`
|
||||||
|
WxAPIV3Key *string `json:"wx_api_v3_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv3密钥"`
|
||||||
|
WxAPIV2Key *string `json:"wx_api_v2_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv2密钥"`
|
||||||
|
WxCertContent *string `json:"wx_cert_content" validate:"omitempty" description:"微信支付证书内容(PEM格式)"`
|
||||||
|
WxKeyContent *string `json:"wx_key_content" validate:"omitempty" description:"微信支付密钥内容(PEM格式)"`
|
||||||
|
WxSerialNo *string `json:"wx_serial_no" validate:"omitempty,max=200" maxLength:"200" description:"微信证书序列号"`
|
||||||
|
WxNotifyURL *string `json:"wx_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"微信支付回调地址"`
|
||||||
|
|
||||||
|
FyInsCd *string `json:"fy_ins_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友机构号"`
|
||||||
|
FyMchntCd *string `json:"fy_mchnt_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友商户号"`
|
||||||
|
FyTermID *string `json:"fy_term_id" validate:"omitempty,max=50" maxLength:"50" description:"富友终端号"`
|
||||||
|
FyPrivateKey *string `json:"fy_private_key" validate:"omitempty" description:"富友私钥(PEM格式)"`
|
||||||
|
FyPublicKey *string `json:"fy_public_key" validate:"omitempty" description:"富友公钥(PEM格式)"`
|
||||||
|
FyAPIURL *string `json:"fy_api_url" validate:"omitempty,max=500" maxLength:"500" description:"富友API地址"`
|
||||||
|
FyNotifyURL *string `json:"fy_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"富友支付回调地址"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatConfigListRequest 微信参数配置列表查询请求
|
||||||
|
type WechatConfigListRequest struct {
|
||||||
|
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
|
||||||
|
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
|
||||||
|
ProviderType *string `json:"provider_type" query:"provider_type" validate:"omitempty,oneof=wechat fuiou" description:"支付渠道类型 (wechat:微信直连, fuiou:富友)"`
|
||||||
|
IsActive *bool `json:"is_active" query:"is_active" description:"是否激活 (true:已激活, false:未激活)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatConfigResponse 微信参数配置响应
|
||||||
|
type WechatConfigResponse struct {
|
||||||
|
ID uint `json:"id" description:"配置ID"`
|
||||||
|
Name string `json:"name" description:"配置名称"`
|
||||||
|
Description string `json:"description" description:"配置描述"`
|
||||||
|
ProviderType string `json:"provider_type" description:"支付渠道类型 (wechat:微信直连, fuiou:富友)"`
|
||||||
|
IsActive bool `json:"is_active" description:"是否激活"`
|
||||||
|
|
||||||
|
OaAppID string `json:"oa_app_id" description:"公众号AppID"`
|
||||||
|
OaAppSecret string `json:"oa_app_secret" description:"公众号AppSecret(已脱敏)"`
|
||||||
|
OaToken string `json:"oa_token" description:"公众号Token(已脱敏)"`
|
||||||
|
OaAesKey string `json:"oa_aes_key" description:"公众号AES加密Key(已脱敏)"`
|
||||||
|
OaOAuthRedirectURL string `json:"oa_oauth_redirect_url" description:"OAuth回调地址"`
|
||||||
|
|
||||||
|
MiniappAppID string `json:"miniapp_app_id" description:"小程序AppID"`
|
||||||
|
MiniappAppSecret string `json:"miniapp_app_secret" description:"小程序AppSecret(已脱敏)"`
|
||||||
|
|
||||||
|
WxMchID string `json:"wx_mch_id" description:"微信商户号"`
|
||||||
|
WxAPIV3Key string `json:"wx_api_v3_key" description:"微信APIv3密钥(已脱敏)"`
|
||||||
|
WxAPIV2Key string `json:"wx_api_v2_key" description:"微信APIv2密钥(已脱敏)"`
|
||||||
|
WxCertContent string `json:"wx_cert_content" description:"微信支付证书内容(配置状态)"`
|
||||||
|
WxKeyContent string `json:"wx_key_content" description:"微信支付密钥内容(配置状态)"`
|
||||||
|
WxSerialNo string `json:"wx_serial_no" description:"微信证书序列号"`
|
||||||
|
WxNotifyURL string `json:"wx_notify_url" description:"微信支付回调地址"`
|
||||||
|
|
||||||
|
FyInsCd string `json:"fy_ins_cd" description:"富友机构号"`
|
||||||
|
FyMchntCd string `json:"fy_mchnt_cd" description:"富友商户号"`
|
||||||
|
FyTermID string `json:"fy_term_id" description:"富友终端号"`
|
||||||
|
FyPrivateKey string `json:"fy_private_key" description:"富友私钥(配置状态)"`
|
||||||
|
FyPublicKey string `json:"fy_public_key" description:"富友公钥(配置状态)"`
|
||||||
|
FyAPIURL string `json:"fy_api_url" description:"富友API地址"`
|
||||||
|
FyNotifyURL string `json:"fy_notify_url" description:"富友支付回调地址"`
|
||||||
|
|
||||||
|
CreatedAt string `json:"created_at" description:"创建时间"`
|
||||||
|
UpdatedAt string `json:"updated_at" description:"更新时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WechatConfigListResponse 微信参数配置列表响应
|
||||||
|
type WechatConfigListResponse struct {
|
||||||
|
List []*WechatConfigResponse `json:"list" description:"配置列表"`
|
||||||
|
Total int64 `json:"total" description:"总数"`
|
||||||
|
Page int `json:"page" description:"当前页"`
|
||||||
|
PageSize int `json:"page_size" description:"每页数量"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskShortSecret 对短密钥进行脱敏处理
|
||||||
|
// 长度小于 8 返回 "***",否则保留前4位和后4位
|
||||||
|
func MaskShortSecret(val string) string {
|
||||||
|
if len(val) < 8 {
|
||||||
|
return "***"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s***%s", val[:4], val[len(val)-4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskLongSecret 对长密钥/证书进行脱敏处理
|
||||||
|
// 空值返回 "[未配置]",否则返回 "[已配置]"
|
||||||
|
func MaskLongSecret(val string) string {
|
||||||
|
if val == "" {
|
||||||
|
return "[未配置]"
|
||||||
|
}
|
||||||
|
return "[已配置]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromWechatConfigModel 从模型转换为响应 DTO,敏感字段自动脱敏
|
||||||
|
func FromWechatConfigModel(m *model.WechatConfig) *WechatConfigResponse {
|
||||||
|
resp := &WechatConfigResponse{
|
||||||
|
ID: m.ID,
|
||||||
|
Name: m.Name,
|
||||||
|
ProviderType: m.ProviderType,
|
||||||
|
IsActive: m.IsActive,
|
||||||
|
|
||||||
|
OaAppID: m.OaAppID,
|
||||||
|
OaAppSecret: MaskShortSecret(m.OaAppSecret),
|
||||||
|
OaToken: MaskShortSecret(m.OaToken),
|
||||||
|
OaAesKey: MaskShortSecret(m.OaAesKey),
|
||||||
|
OaOAuthRedirectURL: m.OaOAuthRedirectURL,
|
||||||
|
|
||||||
|
MiniappAppID: m.MiniappAppID,
|
||||||
|
MiniappAppSecret: MaskShortSecret(m.MiniappAppSecret),
|
||||||
|
|
||||||
|
WxMchID: m.WxMchID,
|
||||||
|
WxAPIV3Key: MaskShortSecret(m.WxAPIV3Key),
|
||||||
|
WxAPIV2Key: MaskShortSecret(m.WxAPIV2Key),
|
||||||
|
WxCertContent: MaskLongSecret(m.WxCertContent),
|
||||||
|
WxKeyContent: MaskLongSecret(m.WxKeyContent),
|
||||||
|
WxSerialNo: m.WxSerialNo,
|
||||||
|
WxNotifyURL: m.WxNotifyURL,
|
||||||
|
|
||||||
|
FyInsCd: m.FyInsCd,
|
||||||
|
FyMchntCd: m.FyMchntCd,
|
||||||
|
FyTermID: m.FyTermID,
|
||||||
|
FyPrivateKey: MaskLongSecret(m.FyPrivateKey),
|
||||||
|
FyPublicKey: MaskLongSecret(m.FyPublicKey),
|
||||||
|
FyAPIURL: m.FyAPIURL,
|
||||||
|
FyNotifyURL: m.FyNotifyURL,
|
||||||
|
|
||||||
|
CreatedAt: m.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
UpdatedAt: m.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Description != nil {
|
||||||
|
resp.Description = *m.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
56
internal/model/wechat_config.go
Normal file
56
internal/model/wechat_config.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
// 支付渠道类型常量
|
||||||
|
const (
|
||||||
|
ProviderTypeWechat = "wechat" // 微信直连
|
||||||
|
ProviderTypeFuiou = "fuiou" // 富友
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatConfig 微信参数配置模型
|
||||||
|
// 管理微信公众号 OAuth、小程序、微信直连支付、富友支付等多套配置
|
||||||
|
// 同一时间只有一条记录处于 is_active=true(全局唯一生效配置)
|
||||||
|
type WechatConfig struct {
|
||||||
|
gorm.Model
|
||||||
|
BaseModel `gorm:"embedded"`
|
||||||
|
|
||||||
|
Name string `gorm:"column:name;type:varchar(100);not null;comment:配置名称" json:"name"`
|
||||||
|
Description *string `gorm:"column:description;type:text;comment:配置描述" json:"description,omitempty"`
|
||||||
|
ProviderType string `gorm:"column:provider_type;type:varchar(20);not null;comment:支付渠道类型(wechat-微信直连,fuiou-富友)" json:"provider_type"`
|
||||||
|
IsActive bool `gorm:"column:is_active;type:boolean;not null;default:false;comment:是否激活(全局唯一)" json:"is_active"`
|
||||||
|
|
||||||
|
// OAuth 公众号
|
||||||
|
OaAppID string `gorm:"column:oa_app_id;type:varchar(100);not null;default:'';comment:公众号AppID" json:"oa_app_id"`
|
||||||
|
OaAppSecret string `gorm:"column:oa_app_secret;type:varchar(200);not null;default:'';comment:公众号AppSecret" json:"oa_app_secret"`
|
||||||
|
OaToken string `gorm:"column:oa_token;type:varchar(200);default:'';comment:公众号Token" json:"oa_token"`
|
||||||
|
OaAesKey string `gorm:"column:oa_aes_key;type:varchar(200);default:'';comment:公众号AES加密Key" json:"oa_aes_key"`
|
||||||
|
OaOAuthRedirectURL string `gorm:"column:oa_oauth_redirect_url;type:varchar(500);default:'';comment:OAuth回调地址" json:"oa_oauth_redirect_url"`
|
||||||
|
|
||||||
|
// OAuth 小程序
|
||||||
|
MiniappAppID string `gorm:"column:miniapp_app_id;type:varchar(100);default:'';comment:小程序AppID" json:"miniapp_app_id"`
|
||||||
|
MiniappAppSecret string `gorm:"column:miniapp_app_secret;type:varchar(200);default:'';comment:小程序AppSecret" json:"miniapp_app_secret"`
|
||||||
|
|
||||||
|
// 支付-微信直连
|
||||||
|
WxMchID string `gorm:"column:wx_mch_id;type:varchar(100);default:'';comment:微信商户号" json:"wx_mch_id"`
|
||||||
|
WxAPIV3Key string `gorm:"column:wx_api_v3_key;type:varchar(200);default:'';comment:微信APIv3密钥" json:"wx_api_v3_key"`
|
||||||
|
WxAPIV2Key string `gorm:"column:wx_api_v2_key;type:varchar(200);default:'';comment:微信APIv2密钥" json:"wx_api_v2_key"`
|
||||||
|
WxCertContent string `gorm:"column:wx_cert_content;type:text;default:'';comment:微信支付证书内容" json:"wx_cert_content"`
|
||||||
|
WxKeyContent string `gorm:"column:wx_key_content;type:text;default:'';comment:微信支付密钥内容" json:"wx_key_content"`
|
||||||
|
WxSerialNo string `gorm:"column:wx_serial_no;type:varchar(200);default:'';comment:微信证书序列号" json:"wx_serial_no"`
|
||||||
|
WxNotifyURL string `gorm:"column:wx_notify_url;type:varchar(500);default:'';comment:微信支付回调地址" json:"wx_notify_url"`
|
||||||
|
|
||||||
|
// 支付-富友
|
||||||
|
FyInsCd string `gorm:"column:fy_ins_cd;type:varchar(50);default:'';comment:富友机构号" json:"fy_ins_cd"`
|
||||||
|
FyMchntCd string `gorm:"column:fy_mchnt_cd;type:varchar(50);default:'';comment:富友商户号" json:"fy_mchnt_cd"`
|
||||||
|
FyTermID string `gorm:"column:fy_term_id;type:varchar(50);default:'';comment:富友终端号" json:"fy_term_id"`
|
||||||
|
FyPrivateKey string `gorm:"column:fy_private_key;type:text;default:'';comment:富友私钥" json:"fy_private_key"`
|
||||||
|
FyPublicKey string `gorm:"column:fy_public_key;type:text;default:'';comment:富友公钥" json:"fy_public_key"`
|
||||||
|
FyAPIURL string `gorm:"column:fy_api_url;type:varchar(500);default:'';comment:富友API地址" json:"fy_api_url"`
|
||||||
|
FyNotifyURL string `gorm:"column:fy_notify_url;type:varchar(500);default:'';comment:富友支付回调地址" json:"fy_notify_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定表名
|
||||||
|
func (WechatConfig) TableName() string {
|
||||||
|
return "tb_wechat_config"
|
||||||
|
}
|
||||||
473
internal/service/wechat_config/service.go
Normal file
473
internal/service/wechat_config/service.go
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
// Package wechat_config 提供微信参数配置管理的业务逻辑服务
|
||||||
|
// 包含配置的 CRUD、激活/停用、Redis 缓存等功能
|
||||||
|
package wechat_config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redis 缓存键
|
||||||
|
const redisActiveConfigKey = "wechat:config:active"
|
||||||
|
|
||||||
|
// AuditServiceInterface 审计日志服务接口
|
||||||
|
type AuditServiceInterface interface {
|
||||||
|
LogOperation(ctx context.Context, log *model.AccountOperationLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service 微信参数配置业务服务
|
||||||
|
type Service struct {
|
||||||
|
store *postgres.WechatConfigStore
|
||||||
|
orderStore *postgres.OrderStore
|
||||||
|
auditService AuditServiceInterface
|
||||||
|
redis *redis.Client
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 创建微信参数配置服务实例
|
||||||
|
func New(store *postgres.WechatConfigStore, orderStore *postgres.OrderStore, auditService AuditServiceInterface, rdb *redis.Client, logger *zap.Logger) *Service {
|
||||||
|
return &Service{
|
||||||
|
store: store,
|
||||||
|
orderStore: orderStore,
|
||||||
|
auditService: auditService,
|
||||||
|
redis: rdb,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建微信参数配置
|
||||||
|
// POST /api/admin/wechat-configs
|
||||||
|
func (s *Service) Create(ctx context.Context, req *dto.CreateWechatConfigRequest) (*dto.WechatConfigResponse, error) {
|
||||||
|
// 根据 provider_type 校验必填字段
|
||||||
|
if err := s.validateProviderFields(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var desc *string
|
||||||
|
if req.Description != "" {
|
||||||
|
desc = &req.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &model.WechatConfig{
|
||||||
|
Name: req.Name,
|
||||||
|
Description: desc,
|
||||||
|
ProviderType: req.ProviderType,
|
||||||
|
IsActive: false,
|
||||||
|
OaAppID: req.OaAppID,
|
||||||
|
OaAppSecret: req.OaAppSecret,
|
||||||
|
OaToken: req.OaToken,
|
||||||
|
OaAesKey: req.OaAesKey,
|
||||||
|
OaOAuthRedirectURL: req.OaOAuthRedirectURL,
|
||||||
|
MiniappAppID: req.MiniappAppID,
|
||||||
|
MiniappAppSecret: req.MiniappAppSecret,
|
||||||
|
WxMchID: req.WxMchID,
|
||||||
|
WxAPIV3Key: req.WxAPIV3Key,
|
||||||
|
WxAPIV2Key: req.WxAPIV2Key,
|
||||||
|
WxCertContent: req.WxCertContent,
|
||||||
|
WxKeyContent: req.WxKeyContent,
|
||||||
|
WxSerialNo: req.WxSerialNo,
|
||||||
|
WxNotifyURL: req.WxNotifyURL,
|
||||||
|
FyInsCd: req.FyInsCd,
|
||||||
|
FyMchntCd: req.FyMchntCd,
|
||||||
|
FyTermID: req.FyTermID,
|
||||||
|
FyPrivateKey: req.FyPrivateKey,
|
||||||
|
FyPublicKey: req.FyPublicKey,
|
||||||
|
FyAPIURL: req.FyAPIURL,
|
||||||
|
FyNotifyURL: req.FyNotifyURL,
|
||||||
|
}
|
||||||
|
config.Creator = middleware.GetUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if err := s.store.Create(ctx, config); err != nil {
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "创建微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审计日志
|
||||||
|
afterData := model.JSONB{
|
||||||
|
"id": config.ID,
|
||||||
|
"name": config.Name,
|
||||||
|
"provider_type": config.ProviderType,
|
||||||
|
}
|
||||||
|
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||||
|
OperatorID: middleware.GetUserIDFromContext(ctx),
|
||||||
|
OperatorType: middleware.GetUserTypeFromContext(ctx),
|
||||||
|
OperatorName: "",
|
||||||
|
OperationType: "create",
|
||||||
|
OperationDesc: fmt.Sprintf("创建微信支付配置:%s", config.Name),
|
||||||
|
AfterData: afterData,
|
||||||
|
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||||
|
IPAddress: middleware.GetIPFromContext(ctx),
|
||||||
|
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取配置列表
|
||||||
|
// GET /api/admin/wechat-configs
|
||||||
|
func (s *Service) List(ctx context.Context, req *dto.WechatConfigListRequest) ([]*dto.WechatConfigResponse, int64, error) {
|
||||||
|
opts := &store.QueryOptions{
|
||||||
|
Page: req.Page,
|
||||||
|
PageSize: req.PageSize,
|
||||||
|
OrderBy: "id DESC",
|
||||||
|
}
|
||||||
|
if opts.Page == 0 {
|
||||||
|
opts.Page = 1
|
||||||
|
}
|
||||||
|
if opts.PageSize == 0 {
|
||||||
|
opts.PageSize = constants.DefaultPageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if req.ProviderType != nil {
|
||||||
|
filters["provider_type"] = *req.ProviderType
|
||||||
|
}
|
||||||
|
filters["is_active"] = req.IsActive
|
||||||
|
|
||||||
|
configs, total, err := s.store.List(ctx, opts, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.Wrap(errors.CodeInternalError, err, "查询微信支付配置列表失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]*dto.WechatConfigResponse, len(configs))
|
||||||
|
for i, c := range configs {
|
||||||
|
responses[i] = dto.FromWechatConfigModel(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取配置详情
|
||||||
|
// GET /api/admin/wechat-configs/:id
|
||||||
|
func (s *Service) Get(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) {
|
||||||
|
config, err := s.store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, errors.New(errors.CodeWechatConfigNotFound)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败")
|
||||||
|
}
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新微信参数配置
|
||||||
|
// PUT /api/admin/wechat-configs/:id
|
||||||
|
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateWechatConfigRequest) (*dto.WechatConfigResponse, error) {
|
||||||
|
config, err := s.store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, errors.New(errors.CodeWechatConfigNotFound)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并字段:指针非 nil 时更新,敏感字段空字符串表示保持原值
|
||||||
|
if req.Name != nil {
|
||||||
|
config.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
config.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.ProviderType != nil {
|
||||||
|
config.ProviderType = *req.ProviderType
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth 公众号
|
||||||
|
s.mergeStringField(&config.OaAppID, req.OaAppID)
|
||||||
|
s.mergeSensitiveField(&config.OaAppSecret, req.OaAppSecret)
|
||||||
|
s.mergeSensitiveField(&config.OaToken, req.OaToken)
|
||||||
|
s.mergeSensitiveField(&config.OaAesKey, req.OaAesKey)
|
||||||
|
s.mergeStringField(&config.OaOAuthRedirectURL, req.OaOAuthRedirectURL)
|
||||||
|
|
||||||
|
// OAuth 小程序
|
||||||
|
s.mergeStringField(&config.MiniappAppID, req.MiniappAppID)
|
||||||
|
s.mergeSensitiveField(&config.MiniappAppSecret, req.MiniappAppSecret)
|
||||||
|
|
||||||
|
// 微信直连支付
|
||||||
|
s.mergeStringField(&config.WxMchID, req.WxMchID)
|
||||||
|
s.mergeSensitiveField(&config.WxAPIV3Key, req.WxAPIV3Key)
|
||||||
|
s.mergeSensitiveField(&config.WxAPIV2Key, req.WxAPIV2Key)
|
||||||
|
s.mergeSensitiveField(&config.WxCertContent, req.WxCertContent)
|
||||||
|
s.mergeSensitiveField(&config.WxKeyContent, req.WxKeyContent)
|
||||||
|
s.mergeStringField(&config.WxSerialNo, req.WxSerialNo)
|
||||||
|
s.mergeStringField(&config.WxNotifyURL, req.WxNotifyURL)
|
||||||
|
|
||||||
|
// 富友支付
|
||||||
|
s.mergeStringField(&config.FyInsCd, req.FyInsCd)
|
||||||
|
s.mergeStringField(&config.FyMchntCd, req.FyMchntCd)
|
||||||
|
s.mergeStringField(&config.FyTermID, req.FyTermID)
|
||||||
|
s.mergeSensitiveField(&config.FyPrivateKey, req.FyPrivateKey)
|
||||||
|
s.mergeSensitiveField(&config.FyPublicKey, req.FyPublicKey)
|
||||||
|
s.mergeStringField(&config.FyAPIURL, req.FyAPIURL)
|
||||||
|
s.mergeStringField(&config.FyNotifyURL, req.FyNotifyURL)
|
||||||
|
|
||||||
|
config.Updater = middleware.GetUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if err := s.store.Update(ctx, config); err != nil {
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "更新微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前配置处于激活状态,清除缓存
|
||||||
|
if config.IsActive {
|
||||||
|
s.clearActiveConfigCache(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
afterData := model.JSONB{
|
||||||
|
"id": config.ID,
|
||||||
|
"name": config.Name,
|
||||||
|
"provider_type": config.ProviderType,
|
||||||
|
"is_active": config.IsActive,
|
||||||
|
}
|
||||||
|
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||||
|
OperatorID: middleware.GetUserIDFromContext(ctx),
|
||||||
|
OperatorType: middleware.GetUserTypeFromContext(ctx),
|
||||||
|
OperatorName: "",
|
||||||
|
OperationType: "update",
|
||||||
|
OperationDesc: fmt.Sprintf("更新微信支付配置:%s", config.Name),
|
||||||
|
AfterData: afterData,
|
||||||
|
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||||
|
IPAddress: middleware.GetIPFromContext(ctx),
|
||||||
|
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除微信参数配置
|
||||||
|
// DELETE /api/admin/wechat-configs/:id
|
||||||
|
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||||
|
config, err := s.store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return errors.New(errors.CodeWechatConfigNotFound)
|
||||||
|
}
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不允许删除正在激活的配置
|
||||||
|
if config.IsActive {
|
||||||
|
return errors.New(errors.CodeWechatConfigActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否存在待支付订单
|
||||||
|
pendingOrders, err := s.store.CountPendingOrdersByConfigID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "检查在途订单失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRecharges, err := s.store.CountPendingRechargesByConfigID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "检查在途充值失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pendingOrders > 0 || pendingRecharges > 0 {
|
||||||
|
return errors.New(errors.CodeWechatConfigHasPendingOrders)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.SoftDelete(ctx, id); err != nil {
|
||||||
|
return errors.Wrap(errors.CodeInternalError, err, "删除微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.clearActiveConfigCache(ctx)
|
||||||
|
|
||||||
|
beforeData := model.JSONB{
|
||||||
|
"id": config.ID,
|
||||||
|
"name": config.Name,
|
||||||
|
"provider_type": config.ProviderType,
|
||||||
|
}
|
||||||
|
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||||
|
OperatorID: middleware.GetUserIDFromContext(ctx),
|
||||||
|
OperatorType: middleware.GetUserTypeFromContext(ctx),
|
||||||
|
OperatorName: "",
|
||||||
|
OperationType: "delete",
|
||||||
|
OperationDesc: fmt.Sprintf("删除微信支付配置:%s", config.Name),
|
||||||
|
BeforeData: beforeData,
|
||||||
|
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||||
|
IPAddress: middleware.GetIPFromContext(ctx),
|
||||||
|
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate 激活指定配置(同一时间只有一个激活配置)
|
||||||
|
// POST /api/admin/wechat-configs/:id/activate
|
||||||
|
func (s *Service) Activate(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) {
|
||||||
|
config, err := s.store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, errors.New(errors.CodeWechatConfigNotFound)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录旧的激活配置名称
|
||||||
|
oldActiveName := ""
|
||||||
|
oldActive, oldErr := s.store.GetActive(ctx)
|
||||||
|
if oldErr == nil && oldActive != nil {
|
||||||
|
oldActiveName = oldActive.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事务内激活
|
||||||
|
db := s.store.DB()
|
||||||
|
if err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
return s.store.ActivateInTx(ctx, tx, id)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "激活微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.clearActiveConfigCache(ctx)
|
||||||
|
|
||||||
|
// 重新查询最新状态
|
||||||
|
config, _ = s.store.GetByID(ctx, id)
|
||||||
|
|
||||||
|
desc := fmt.Sprintf("激活微信支付配置:%s", config.Name)
|
||||||
|
if oldActiveName != "" && oldActiveName != config.Name {
|
||||||
|
desc = fmt.Sprintf("激活微信支付配置:%s(原激活配置:%s)", config.Name, oldActiveName)
|
||||||
|
}
|
||||||
|
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||||
|
OperatorID: middleware.GetUserIDFromContext(ctx),
|
||||||
|
OperatorType: middleware.GetUserTypeFromContext(ctx),
|
||||||
|
OperatorName: "",
|
||||||
|
OperationType: "activate",
|
||||||
|
OperationDesc: desc,
|
||||||
|
AfterData: model.JSONB{"id": config.ID, "name": config.Name, "is_active": true},
|
||||||
|
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||||
|
IPAddress: middleware.GetIPFromContext(ctx),
|
||||||
|
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate 停用指定配置
|
||||||
|
// POST /api/admin/wechat-configs/:id/deactivate
|
||||||
|
func (s *Service) Deactivate(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) {
|
||||||
|
config, err := s.store.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, errors.New(errors.CodeWechatConfigNotFound)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.Deactivate(ctx, id); err != nil {
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "停用微信支付配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.clearActiveConfigCache(ctx)
|
||||||
|
|
||||||
|
// 重新查询最新状态
|
||||||
|
config, _ = s.store.GetByID(ctx, id)
|
||||||
|
|
||||||
|
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||||
|
OperatorID: middleware.GetUserIDFromContext(ctx),
|
||||||
|
OperatorType: middleware.GetUserTypeFromContext(ctx),
|
||||||
|
OperatorName: "",
|
||||||
|
OperationType: "deactivate",
|
||||||
|
OperationDesc: fmt.Sprintf("停用微信支付配置:%s", config.Name),
|
||||||
|
AfterData: model.JSONB{"id": config.ID, "name": config.Name, "is_active": false},
|
||||||
|
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||||
|
IPAddress: middleware.GetIPFromContext(ctx),
|
||||||
|
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveConfig 获取当前生效的支付配置(带 Redis 缓存)
|
||||||
|
// 缓存策略:命中直接返回,未命中查 DB 后缓存 5 分钟,无记录缓存 "none" 1 分钟
|
||||||
|
func (s *Service) GetActiveConfig(ctx context.Context) (*model.WechatConfig, error) {
|
||||||
|
// 尝试从 Redis 获取
|
||||||
|
val, err := s.redis.Get(ctx, redisActiveConfigKey).Result()
|
||||||
|
if err == nil {
|
||||||
|
if val == "none" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var config model.WechatConfig
|
||||||
|
if err := sonic.UnmarshalString(val, &config); err == nil {
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis 未命中,查询数据库
|
||||||
|
config, err := s.store.GetActive(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// 无激活配置,缓存空标记 1 分钟
|
||||||
|
s.redis.Set(ctx, redisActiveConfigKey, "none", 1*time.Minute)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(errors.CodeInternalError, err, "查询激活配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存配置 5 分钟
|
||||||
|
if data, err := sonic.MarshalString(config); err == nil {
|
||||||
|
s.redis.Set(ctx, redisActiveConfigKey, data, 5*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveConfigForAPI 获取当前生效的支付配置(API 响应,已脱敏)
|
||||||
|
func (s *Service) GetActiveConfigForAPI(ctx context.Context) (*dto.WechatConfigResponse, error) {
|
||||||
|
config, err := s.GetActiveConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return dto.FromWechatConfigModel(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearActiveConfigCache 清除激活配置的 Redis 缓存
|
||||||
|
func (s *Service) clearActiveConfigCache(ctx context.Context) {
|
||||||
|
if err := s.redis.Del(ctx, redisActiveConfigKey).Err(); err != nil {
|
||||||
|
s.logger.Warn("清除微信支付配置缓存失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateProviderFields 根据支付渠道类型校验必填字段
|
||||||
|
func (s *Service) validateProviderFields(req *dto.CreateWechatConfigRequest) error {
|
||||||
|
switch req.ProviderType {
|
||||||
|
case model.ProviderTypeWechat:
|
||||||
|
if req.WxMchID == "" || req.WxAPIV3Key == "" || req.WxCertContent == "" ||
|
||||||
|
req.WxKeyContent == "" || req.WxSerialNo == "" || req.WxNotifyURL == "" {
|
||||||
|
return errors.New(errors.CodeInvalidParam, "微信直连支付必填字段不完整:wx_mch_id, wx_api_v3_key, wx_cert_content, wx_key_content, wx_serial_no, wx_notify_url")
|
||||||
|
}
|
||||||
|
case model.ProviderTypeFuiou:
|
||||||
|
if req.FyInsCd == "" || req.FyMchntCd == "" || req.FyTermID == "" ||
|
||||||
|
req.FyPrivateKey == "" || req.FyPublicKey == "" || req.FyAPIURL == "" || req.FyNotifyURL == "" {
|
||||||
|
return errors.New(errors.CodeInvalidParam, "富友支付必填字段不完整:fy_ins_cd, fy_mchnt_cd, fy_term_id, fy_private_key, fy_public_key, fy_api_url, fy_notify_url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeStringField 合并普通字符串字段:指针非 nil 时用新值覆盖
|
||||||
|
func (s *Service) mergeStringField(target *string, newVal *string) {
|
||||||
|
if newVal != nil {
|
||||||
|
*target = *newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeSensitiveField 合并敏感字段:指针非 nil 且非空字符串时覆盖,空字符串保持原值
|
||||||
|
func (s *Service) mergeSensitiveField(target *string, newVal *string) {
|
||||||
|
if newVal != nil && *newVal != "" {
|
||||||
|
*target = *newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
145
internal/store/postgres/wechat_config_store.go
Normal file
145
internal/store/postgres/wechat_config_store.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WechatConfigStore 微信参数配置数据访问层
|
||||||
|
type WechatConfigStore struct {
|
||||||
|
db *gorm.DB
|
||||||
|
rdb *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWechatConfigStore 创建微信参数配置 Store 实例
|
||||||
|
func NewWechatConfigStore(db *gorm.DB, rdb *redis.Client) *WechatConfigStore {
|
||||||
|
return &WechatConfigStore{db: db, rdb: rdb}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建微信参数配置
|
||||||
|
func (s *WechatConfigStore) Create(ctx context.Context, config *model.WechatConfig) error {
|
||||||
|
return s.db.WithContext(ctx).Create(config).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 根据 ID 获取配置(遵循软删除)
|
||||||
|
func (s *WechatConfigStore) GetByID(ctx context.Context, id uint) (*model.WechatConfig, error) {
|
||||||
|
var config model.WechatConfig
|
||||||
|
if err := s.db.WithContext(ctx).First(&config, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByIDUnscoped 根据 ID 获取配置(包含已软删除的记录,用于回调处理)
|
||||||
|
func (s *WechatConfigStore) GetByIDUnscoped(ctx context.Context, id uint) (*model.WechatConfig, error) {
|
||||||
|
var config model.WechatConfig
|
||||||
|
if err := s.db.WithContext(ctx).Unscoped().First(&config, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 查询配置列表,支持按 provider_type 和 is_active 过滤
|
||||||
|
func (s *WechatConfigStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.WechatConfig, int64, error) {
|
||||||
|
var configs []*model.WechatConfig
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := s.db.WithContext(ctx).Model(&model.WechatConfig{})
|
||||||
|
|
||||||
|
if providerType, ok := filters["provider_type"].(string); ok && providerType != "" {
|
||||||
|
query = query.Where("provider_type = ?", providerType)
|
||||||
|
}
|
||||||
|
if isActive, ok := filters["is_active"].(*bool); ok && isActive != nil {
|
||||||
|
query = query.Where("is_active = ?", *isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts == nil {
|
||||||
|
opts = store.DefaultQueryOptions()
|
||||||
|
}
|
||||||
|
offset := (opts.Page - 1) * opts.PageSize
|
||||||
|
query = query.Offset(offset).Limit(opts.PageSize)
|
||||||
|
|
||||||
|
if opts.OrderBy != "" {
|
||||||
|
query = query.Order(opts.OrderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Find(&configs).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新微信参数配置
|
||||||
|
func (s *WechatConfigStore) Update(ctx context.Context, config *model.WechatConfig) error {
|
||||||
|
return s.db.WithContext(ctx).Save(config).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoftDelete 软删除微信参数配置
|
||||||
|
func (s *WechatConfigStore) SoftDelete(ctx context.Context, id uint) error {
|
||||||
|
return s.db.WithContext(ctx).Delete(&model.WechatConfig{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActive 获取当前激活的配置
|
||||||
|
// 返回 gorm.ErrRecordNotFound 如果没有激活的配置
|
||||||
|
func (s *WechatConfigStore) GetActive(ctx context.Context) (*model.WechatConfig, error) {
|
||||||
|
var config model.WechatConfig
|
||||||
|
if err := s.db.WithContext(ctx).Where("is_active = ? AND deleted_at IS NULL", true).First(&config).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivateInTx 在事务内激活指定配置(先停用所有,再激活指定记录)
|
||||||
|
func (s *WechatConfigStore) ActivateInTx(ctx context.Context, tx *gorm.DB, id uint) error {
|
||||||
|
// 先停用所有激活的配置
|
||||||
|
if err := tx.WithContext(ctx).Model(&model.WechatConfig{}).
|
||||||
|
Where("is_active = ?", true).
|
||||||
|
Update("is_active", false).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 再激活指定配置
|
||||||
|
return tx.WithContext(ctx).Model(&model.WechatConfig{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("is_active", true).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB 返回底层数据库连接,用于事务操作
|
||||||
|
func (s *WechatConfigStore) DB() *gorm.DB {
|
||||||
|
return s.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate 停用指定配置
|
||||||
|
func (s *WechatConfigStore) Deactivate(ctx context.Context, id uint) error {
|
||||||
|
return s.db.WithContext(ctx).Model(&model.WechatConfig{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("is_active", false).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPendingOrdersByConfigID 统计指定配置的待支付订单数
|
||||||
|
func (s *WechatConfigStore) CountPendingOrdersByConfigID(ctx context.Context, configID uint) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := s.db.WithContext(ctx).
|
||||||
|
Table("tb_order").
|
||||||
|
Where("payment_config_id = ? AND payment_status = ? AND deleted_at IS NULL", configID, 1).
|
||||||
|
Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPendingRechargesByConfigID 统计指定配置的待支付充值记录数
|
||||||
|
func (s *WechatConfigStore) CountPendingRechargesByConfigID(ctx context.Context, configID uint) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := s.db.WithContext(ctx).
|
||||||
|
Table("tb_asset_recharge_record").
|
||||||
|
Where("payment_config_id = ? AND status = ? AND deleted_at IS NULL", configID, 1).
|
||||||
|
Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user