后台用户管理新增“人工实名
This commit is contained in:
@@ -5,22 +5,29 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/pkg/crypto"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
db *gorm.DB
|
||||
encryptor crypto.Encryptor
|
||||
}
|
||||
|
||||
type AuditMeta = auditlog.Meta
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
func NewRepository(db *gorm.DB, encryptors ...crypto.Encryptor) *Repository {
|
||||
encryptor := crypto.Encryptor(&crypto.MockEncryptor{})
|
||||
if len(encryptors) > 0 && encryptors[0] != nil {
|
||||
encryptor = encryptors[0]
|
||||
}
|
||||
return &Repository{db: db, encryptor: encryptor}
|
||||
}
|
||||
|
||||
func (r *Repository) List(ctx context.Context, page, pageSize int, query ListQuery) (*PaginatedResult, error) {
|
||||
@@ -176,6 +183,86 @@ func (r *Repository) RevokeRealname(ctx context.Context, adminID uint64, userID
|
||||
return r.Find(ctx, userID)
|
||||
}
|
||||
|
||||
// ManualRealname 人工实名:第三方实名服务不可用或异常时,由后台审核后补录实名信息。
|
||||
func (r *Repository) ManualRealname(ctx context.Context, adminID uint64, userID uint64, req ManualRealnameRequest, meta AuditMeta) (*UserDTO, error) {
|
||||
name := strings.TrimSpace(req.Name)
|
||||
idNo := strings.ToUpper(strings.TrimSpace(req.IDNo))
|
||||
reason := strings.TrimSpace(req.Reason)
|
||||
if !validManualRealname(name, idNo) {
|
||||
return nil, ErrInvalidRealnameInput
|
||||
}
|
||||
|
||||
encryptedName, err := r.encryptor.Encrypt(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedIDNo, err := r.encryptor.Encrypt(idNo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
record := model.UserRealname{
|
||||
UserID: userID,
|
||||
Provider: "admin_manual",
|
||||
ProviderOrderNo: manualRealnameOrderNo(adminID, now),
|
||||
Status: "verified",
|
||||
MaskedName: maskManualName(name),
|
||||
EncryptedName: encryptedName,
|
||||
MaskedIDNo: maskManualIDNo(idNo),
|
||||
EncryptedIDNo: encryptedIDNo,
|
||||
VerifiedAt: &now,
|
||||
FailReason: "",
|
||||
}
|
||||
|
||||
err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var user model.User
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if user.RealnameStatus == "verified" {
|
||||
return ErrRealnameAlreadyVerified
|
||||
}
|
||||
|
||||
beforeRealnameStatus := user.RealnameStatus
|
||||
user.RealnameStatus = "verified"
|
||||
if err := tx.Save(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}},
|
||||
DoUpdates: clause.Assignments(map[string]any{
|
||||
"provider": record.Provider,
|
||||
"provider_order_no": record.ProviderOrderNo,
|
||||
"status": record.Status,
|
||||
"masked_name": record.MaskedName,
|
||||
"encrypted_name": record.EncryptedName,
|
||||
"masked_id_no": record.MaskedIDNo,
|
||||
"encrypted_id_no": record.EncryptedIDNo,
|
||||
"verified_at": record.VerifiedAt,
|
||||
"fail_reason": record.FailReason,
|
||||
"updated_at": now,
|
||||
}),
|
||||
}).Create(&record).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return appendAuditLog(tx, adminID, "admin_user.manual_realname", user.ID, meta, map[string]any{
|
||||
"user_id": user.ID,
|
||||
"reason": reason,
|
||||
"before_realname_status": beforeRealnameStatus,
|
||||
"after_realname_status": user.RealnameStatus,
|
||||
"masked_name": record.MaskedName,
|
||||
"masked_id_no": record.MaskedIDNo,
|
||||
"provider_order_no": record.ProviderOrderNo,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.Find(ctx, userID)
|
||||
}
|
||||
|
||||
func (r *Repository) Find(ctx context.Context, userID uint64) (*UserDTO, error) {
|
||||
var row userRow
|
||||
err := r.db.WithContext(ctx).Table("users AS u").
|
||||
@@ -234,6 +321,27 @@ func depositFreeQuotaAmountCent(req DepositFreeQuotaRequest) int64 {
|
||||
return req.AmountCent
|
||||
}
|
||||
|
||||
func manualRealnameOrderNo(adminID uint64, verifiedAt time.Time) string {
|
||||
return "manual_" + strconv.FormatUint(adminID, 10) + "_" + strconv.FormatInt(verifiedAt.UnixNano(), 10)
|
||||
}
|
||||
|
||||
func maskManualName(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if len([]rune(name)) <= 1 {
|
||||
return name
|
||||
}
|
||||
runes := []rune(name)
|
||||
return string(runes[0]) + strings.Repeat("*", len(runes)-1)
|
||||
}
|
||||
|
||||
func maskManualIDNo(idNo string) string {
|
||||
idNo = strings.TrimSpace(idNo)
|
||||
if len(idNo) <= 8 {
|
||||
return idNo
|
||||
}
|
||||
return idNo[:4] + strings.Repeat("*", len(idNo)-8) + idNo[len(idNo)-4:]
|
||||
}
|
||||
|
||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||
return auditlog.Append(tx, auditlog.Entry{
|
||||
ActorType: "admin",
|
||||
|
||||
Reference in New Issue
Block a user