feat: 新增全局操作密码功能,将线下充值验证从登录密码改为统一操作密码
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 新增 RedisSystemOperationPasswordKey 常量函数(pkg/constants/redis.go)
- 新增 operation_password Service(Set/IsSet/Verify,bcrypt 哈希存 Redis)
- 新增 SuperAdminHandler 及两个接口:
  - POST /api/admin/super-admin/operation-password(设置/重置,仅超级管理员)
  - GET /api/admin/super-admin/operation-password/status(查询是否已设置)
- AgentRecharge.OfflinePay 操作密码验证从"查当前用户登录密码"改为"全局操作密码 Verify"
- Bootstrap/路由/文档生成器同步注册
This commit is contained in:
2026-04-18 09:06:33 +08:00
parent 6e15e1b853
commit 6caf0f6141
17 changed files with 568 additions and 30 deletions

View File

@@ -11,7 +11,6 @@ import (
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
@@ -27,6 +26,11 @@ type AuditServiceInterface interface {
LogOperation(ctx context.Context, log *model.AccountOperationLog)
}
// OperationPasswordServiceInterface 全局操作密码服务接口
type OperationPasswordServiceInterface interface {
Verify(ctx context.Context, inputPassword string) error
}
// WechatConfigServiceInterface 支付配置服务接口
type WechatConfigServiceInterface interface {
GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
@@ -35,16 +39,16 @@ type WechatConfigServiceInterface interface {
// Service 代理预充值业务服务
// 负责代理钱包充值订单的创建、线下确认、回调处理等业务逻辑
type Service struct {
db *gorm.DB
agentRechargeStore *postgres.AgentRechargeStore
agentWalletStore *postgres.AgentWalletStore
agentWalletTxStore *postgres.AgentWalletTransactionStore
shopStore *postgres.ShopStore
accountStore *postgres.AccountStore
wechatConfigService WechatConfigServiceInterface
auditService AuditServiceInterface
redis *redis.Client
logger *zap.Logger
db *gorm.DB
agentRechargeStore *postgres.AgentRechargeStore
agentWalletStore *postgres.AgentWalletStore
agentWalletTxStore *postgres.AgentWalletTransactionStore
shopStore *postgres.ShopStore
wechatConfigService WechatConfigServiceInterface
auditService AuditServiceInterface
operationPasswordService OperationPasswordServiceInterface
redis *redis.Client
logger *zap.Logger
}
// New 创建代理预充值服务实例
@@ -54,23 +58,23 @@ func New(
agentWalletStore *postgres.AgentWalletStore,
agentWalletTxStore *postgres.AgentWalletTransactionStore,
shopStore *postgres.ShopStore,
accountStore *postgres.AccountStore,
wechatConfigService WechatConfigServiceInterface,
auditService AuditServiceInterface,
operationPasswordService OperationPasswordServiceInterface,
rdb *redis.Client,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
agentRechargeStore: agentRechargeStore,
agentWalletStore: agentWalletStore,
agentWalletTxStore: agentWalletTxStore,
shopStore: shopStore,
accountStore: accountStore,
wechatConfigService: wechatConfigService,
auditService: auditService,
redis: rdb,
logger: logger,
db: db,
agentRechargeStore: agentRechargeStore,
agentWalletStore: agentWalletStore,
agentWalletTxStore: agentWalletTxStore,
shopStore: shopStore,
wechatConfigService: wechatConfigService,
auditService: auditService,
operationPasswordService: operationPasswordService,
redis: rdb,
logger: logger,
}
}
@@ -175,13 +179,9 @@ func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOffline
return nil, errors.New(errors.CodeForbidden, "仅平台管理员可确认线下充值")
}
// 验证操作密码
account, err := s.accountStore.GetByID(ctx, userID)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询操作人账号失败")
}
if err := bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(req.OperationPassword)); err != nil {
return nil, errors.New(errors.CodeInvalidParam, "操作密码错误")
// 验证全局操作密码
if err := s.operationPasswordService.Verify(ctx, req.OperationPassword); err != nil {
return nil, err
}
record, err := s.agentRechargeStore.GetByID(ctx, id)

View File

@@ -0,0 +1,68 @@
// Package operation_password 提供全局操作密码的设置、查询与验证服务
// 操作密码以 bcrypt 哈希存储于 Redis仅超级管理员可维护
package operation_password
import (
"context"
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/bcrypt"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// Service 全局操作密码服务
type Service struct {
redis *redis.Client
}
// New 创建操作密码服务实例
func New(rdb *redis.Client) *Service {
return &Service{redis: rdb}
}
// Set 设置/修改全局操作密码
// 对明文密码做 bcrypt 哈希后永久存储到 Redis无 TTL
func (s *Service) Set(ctx context.Context, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "操作密码加密失败")
}
key := constants.RedisSystemOperationPasswordKey()
if err := s.redis.Set(ctx, key, string(hash), 0).Err(); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "操作密码存储失败")
}
return nil
}
// IsSet 查询操作密码是否已设置
func (s *Service) IsSet(ctx context.Context) bool {
key := constants.RedisSystemOperationPasswordKey()
n, err := s.redis.Exists(ctx, key).Result()
if err != nil {
return false
}
return n > 0
}
// Verify 验证操作密码是否正确
// key 不存在时返回"操作密码未设置"错误,密码错误时返回"操作密码错误"
func (s *Service) Verify(ctx context.Context, inputPassword string) error {
key := constants.RedisSystemOperationPasswordKey()
hash, err := s.redis.Get(ctx, key).Result()
if err == redis.Nil {
return errors.New(errors.CodeInvalidParam, "操作密码未设置,请联系超级管理员")
}
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "查询操作密码失败")
}
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(inputPassword)); err != nil {
return errors.New(errors.CodeInvalidParam, "操作密码错误")
}
return nil
}