chore(init): 初始化解忧商行项目并完成 Phase 0 基座
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package adminuser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"hfb_sys/backend/internal/modules/realname"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
ErrInvalidUser = errors.New("invalid user")
|
||||
ErrInvalidRealnameInput = errors.New("invalid realname input")
|
||||
ErrRealnameAlreadyVerified = errors.New("realname already verified")
|
||||
ErrRealnameIDNoUsed = realname.ErrIDNoAlreadyUsed
|
||||
ErrRealnameNotRevocable = errors.New("realname not revocable")
|
||||
ErrInvalidWalletDirection = errors.New("invalid wallet direction")
|
||||
ErrInvalidWalletAmount = errors.New("invalid wallet amount")
|
||||
ErrInvalidWalletReason = errors.New("invalid wallet reason")
|
||||
ErrInsufficientBalance = errors.New("insufficient balance")
|
||||
ErrInvalidGrowthPoints = errors.New("invalid growth points")
|
||||
ErrInvalidGrowthReason = errors.New("invalid growth reason")
|
||||
ErrGrowthPointsUnchanged = errors.New("growth points unchanged")
|
||||
)
|
||||
|
||||
// 单笔人工调账上限:10 万元,防止误填。
|
||||
const maxWalletAdjustAmountCent int64 = 10_000_000
|
||||
|
||||
const maxRenterGrowthPoints int64 = 1_000_000_000
|
||||
|
||||
var manualRealnameIDPattern = regexp.MustCompile(`^\d{17}[\dXx]$`)
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
// SetSessionRevoker 设置用户会话撤销回调,例如关闭用户的实时连接。
|
||||
func (s *Service) SetSessionRevoker(revoker func(userID uint64)) {
|
||||
if s.repo != nil {
|
||||
s.repo.SetStatusChangeNotifier(revoker)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, page, pageSize int, query ListQuery) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.List(ctx, page, pageSize, query)
|
||||
}
|
||||
|
||||
func (s *Service) Freeze(ctx context.Context, adminID uint64, userID uint64, req FreezeRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
return s.repo.Freeze(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func (s *Service) Unfreeze(ctx context.Context, adminID uint64, userID uint64, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
return s.repo.Unfreeze(ctx, adminID, userID, meta)
|
||||
}
|
||||
|
||||
func (s *Service) SetDepositFreeQuota(ctx context.Context, adminID uint64, userID uint64, req DepositFreeQuotaRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 || req.AmountCent < 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
return s.repo.SetDepositFreeQuota(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func (s *Service) AdjustGrowthPoints(ctx context.Context, adminID uint64, userID uint64, req GrowthPointsAdjustRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 || req.TargetPoints < 0 || req.TargetPoints > maxRenterGrowthPoints {
|
||||
return nil, ErrInvalidGrowthPoints
|
||||
}
|
||||
reason := strings.TrimSpace(req.Reason)
|
||||
if reasonLen := len([]rune(reason)); reasonLen < 2 || reasonLen > 200 {
|
||||
return nil, ErrInvalidGrowthReason
|
||||
}
|
||||
req.Reason = reason
|
||||
return s.repo.AdjustGrowthPoints(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func (s *Service) RevokeRealname(ctx context.Context, adminID uint64, userID uint64, req RevokeRealnameRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
return s.repo.RevokeRealname(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func (s *Service) ManualRealname(ctx context.Context, adminID uint64, userID uint64, req ManualRealnameRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
if !validManualRealname(req.Name, req.IDNo) {
|
||||
return nil, ErrInvalidRealnameInput
|
||||
}
|
||||
return s.repo.ManualRealname(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func (s *Service) AdjustWallet(ctx context.Context, adminID uint64, userID uint64, req WalletAdjustRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrInvalidUser
|
||||
}
|
||||
direction := strings.TrimSpace(strings.ToLower(req.Direction))
|
||||
if direction != "in" && direction != "out" {
|
||||
return nil, ErrInvalidWalletDirection
|
||||
}
|
||||
if req.AmountCent <= 0 || req.AmountCent > maxWalletAdjustAmountCent {
|
||||
return nil, ErrInvalidWalletAmount
|
||||
}
|
||||
if reasonLen := len([]rune(strings.TrimSpace(req.Reason))); reasonLen < 2 || reasonLen > 200 {
|
||||
return nil, ErrInvalidWalletReason
|
||||
}
|
||||
req.Direction = direction
|
||||
req.Reason = strings.TrimSpace(req.Reason)
|
||||
req.ReferenceNo = strings.TrimSpace(req.ReferenceNo)
|
||||
return s.repo.AdjustWallet(ctx, adminID, userID, req, meta)
|
||||
}
|
||||
|
||||
func validManualRealname(name string, idNo string) bool {
|
||||
name = strings.TrimSpace(name)
|
||||
idNo = strings.TrimSpace(idNo)
|
||||
nameLen := len([]rune(name))
|
||||
return nameLen >= 2 && nameLen <= 30 && manualRealnameIDPattern.MatchString(idNo)
|
||||
}
|
||||
Reference in New Issue
Block a user