package paymentaccount import ( "context" "encoding/json" "errors" "strings" "hfb_sys/backend/internal/model" "hfb_sys/backend/pkg/crypto" "gorm.io/datatypes" "gorm.io/gorm" ) // 删除加密密钥常量 type Repository struct { db *gorm.DB } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) { db := r.db.WithContext(ctx) var total int64 if err := db.Model(&model.UserPaymentAccount{}). Where("user_id = ? AND status = ?", userID, "active"). Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var accounts []model.UserPaymentAccount if err := db.Where("user_id = ? AND status = ?", userID, "active"). Order("is_default DESC, created_at DESC"). Offset(offset).Limit(pageSize). Find(&accounts).Error; err != nil { return nil, err } items := make([]PaymentAccountDTO, 0, len(accounts)) for _, acc := range accounts { dto, err := r.toDTO(acc) if err != nil { continue } items = append(items, *dto) } return &PaginatedResult{ Items: items, Total: total, Page: page, PageSize: pageSize, }, nil } func (r *Repository) FindByID(ctx context.Context, userID, id uint64) (*PaymentAccountDTO, error) { var account model.UserPaymentAccount if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ? AND status = ?", id, userID, "active"). First(&account).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAccountNotFound } return nil, err } return r.toDTO(account) } func (r *Repository) Create(ctx context.Context, userID uint64, req CreatePaymentAccountRequest) (*PaymentAccountDTO, error) { db := r.db.WithContext(ctx) // 加密账号 encryptedNo, err := crypto.Encrypt(req.AccountNo) if err != nil { return nil, err } // 处理凭证URLs var certURLs datatypes.JSON if len(req.CertificateURLs) > 0 { certURLs, _ = json.Marshal(req.CertificateURLs) } // 如果是第一个账号,自动设为默认 isDefault := false var count int64 db.Model(&model.UserPaymentAccount{}).Where("user_id = ? AND status = ?", userID, "active").Count(&count) if count == 0 { isDefault = true } account := model.UserPaymentAccount{ UserID: userID, AccountType: req.AccountType, AccountName: req.AccountName, AccountNo: encryptedNo, BankName: req.BankName, BankBranch: req.BankBranch, CertificateURLs: certURLs, IsDefault: isDefault, Status: "active", } if err := db.Create(&account).Error; err != nil { return nil, err } return r.FindByID(ctx, userID, account.ID) } func (r *Repository) Update(ctx context.Context, userID, id uint64, req UpdatePaymentAccountRequest) (*PaymentAccountDTO, error) { db := r.db.WithContext(ctx) var account model.UserPaymentAccount if err := db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active"). First(&account).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAccountNotFound } return nil, err } updates := make(map[string]interface{}) if req.BankBranch != "" { updates["bank_branch"] = req.BankBranch } if len(req.CertificateURLs) > 0 { certURLs, _ := json.Marshal(req.CertificateURLs) updates["certificate_urls"] = certURLs } if req.IsDefault != nil && *req.IsDefault { // 先取消其他默认账号 db.Model(&model.UserPaymentAccount{}). Where("user_id = ? AND id != ?", userID, id). Update("is_default", false) updates["is_default"] = true } if len(updates) > 0 { if err := db.Model(&account).Updates(updates).Error; err != nil { return nil, err } } return r.FindByID(ctx, userID, id) } func (r *Repository) Delete(ctx context.Context, userID, id uint64) error { db := r.db.WithContext(ctx) var account model.UserPaymentAccount if err := db.Where("id = ? AND user_id = ? AND status = ?", id, userID, "active"). First(&account).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrAccountNotFound } return err } // 软删除 return db.Model(&account).Update("status", "disabled").Error } func (r *Repository) SetDefault(ctx context.Context, userID, id uint64) error { // 验证账号存在 var account model.UserPaymentAccount if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ? AND status = ?", id, userID, "active"). First(&account).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrAccountNotFound } return err } return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 取消其他默认账号 if err := tx.Model(&model.UserPaymentAccount{}). Where("user_id = ? AND id != ?", userID, id). Update("is_default", false).Error; err != nil { return err } // 设置当前为默认 return tx.Model(&account).Update("is_default", true).Error }) } func (r *Repository) CountByUser(ctx context.Context, userID uint64) (int64, error) { var count int64 err := r.db.WithContext(ctx).Model(&model.UserPaymentAccount{}). Where("user_id = ? AND status = ?", userID, "active"). Count(&count).Error return count, err } func (r *Repository) ValidateRealname(ctx context.Context, userID uint64, accountName string) error { var user model.User db := r.db.WithContext(ctx) if err := db.First(&user, userID).Error; err != nil { return err } if user.RealnameStatus != "verified" { return ErrRealnameRequired } // 获取实名信息 var realname model.UserRealname if err := db.Where("user_id = ? AND status = ?", userID, "verified"). First(&realname).Error; err != nil { return ErrRealnameRequired } // 验证姓名匹配 - 使用加密字段进行精确匹配 if realname.EncryptedName != "" { // 有加密字段,解密后精确匹配 decryptedName, err := crypto.Decrypt(realname.EncryptedName) if err != nil { // 解密失败,降级到前缀匹配 return r.validateByMaskedName(realname.MaskedName, accountName) } // 精确匹配(去除空格) if strings.ReplaceAll(decryptedName, " ", "") != strings.ReplaceAll(accountName, " ", "") { return ErrAccountNameMismatch } } else { // 没有加密字段(旧数据),使用前缀匹配 return r.validateByMaskedName(realname.MaskedName, accountName) } return nil } // 使用脱敏姓名进行前缀匹配(兼容旧数据) func (r *Repository) validateByMaskedName(maskedName, accountName string) error { maskedName = strings.ReplaceAll(maskedName, " ", "") inputName := strings.ReplaceAll(accountName, " ", "") if strings.Contains(maskedName, "*") { // 提取非星号部分(通常是姓氏) prefix := strings.Split(maskedName, "*")[0] if prefix != "" && !strings.HasPrefix(inputName, prefix) { return ErrAccountNameMismatch } } else { // 完整匹配 if maskedName != inputName { return ErrAccountNameMismatch } } return nil } func (r *Repository) toDTO(account model.UserPaymentAccount) (*PaymentAccountDTO, error) { // 解密账号并脱敏 decrypted, err := crypto.Decrypt(account.AccountNo) if err != nil { decrypted = account.AccountNo // 降级处理 } maskedNo := maskAccountNo(decrypted, account.AccountType) // 解析凭证URLs var certURLs []string if account.CertificateURLs != nil { json.Unmarshal(account.CertificateURLs, &certURLs) } return &PaymentAccountDTO{ ID: account.ID, UserID: account.UserID, AccountType: account.AccountType, AccountName: account.AccountName, AccountNo: maskedNo, BankName: account.BankName, BankBranch: account.BankBranch, CertificateURLs: certURLs, IsDefault: account.IsDefault, Status: account.Status, CreatedAt: account.CreatedAt, UpdatedAt: account.UpdatedAt, }, nil } // 账号脱敏 func maskAccountNo(accountNo, accountType string) string { length := len(accountNo) if length <= 4 { return accountNo } switch accountType { case "alipay", "wechat": // 手机号或邮箱 if length == 11 { return accountNo[:3] + "****" + accountNo[7:] } return accountNo[:2] + "****" + accountNo[length-2:] case "bank": // 银行卡号 if length > 8 { return accountNo[:4] + "****" + accountNo[length-4:] } return accountNo[:2] + "****" + accountNo[length-2:] } return accountNo } // 获取解密后的账号(仅供内部使用,如提现申请时) func (r *Repository) GetDecryptedAccountNo(ctx context.Context, userID, id uint64) (string, error) { var account model.UserPaymentAccount if err := r.db.WithContext(ctx).Where("id = ? AND user_id = ?", id, userID).First(&account).Error; err != nil { return "", err } return crypto.Decrypt(account.AccountNo) }