package paymentaccount import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/json" "errors" "io" "strings" "hfb_sys/backend/internal/model" "gorm.io/datatypes" "gorm.io/gorm" ) // 加密密钥(生产环境应从配置文件读取) const encryptionKey = "your-32-byte-secret-key-here!!" // 32字节 type Repository struct { db *gorm.DB } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } func (r *Repository) List(userID uint64, page, pageSize int) (*PaginatedResult, error) { var total int64 if err := r.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 := r.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(userID, id uint64) (*PaymentAccountDTO, error) { var account model.UserPaymentAccount if err := r.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 } return r.toDTO(account) } func (r *Repository) Create(userID uint64, req CreatePaymentAccountRequest) (*PaymentAccountDTO, error) { // 加密账号 encryptedNo, err := 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 r.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 := r.db.Create(&account).Error; err != nil { return nil, err } return r.FindByID(userID, account.ID) } func (r *Repository) Update(userID, id uint64, req UpdatePaymentAccountRequest) (*PaymentAccountDTO, error) { var account model.UserPaymentAccount if err := r.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 { // 先取消其他默认账号 r.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 := r.db.Model(&account).Updates(updates).Error; err != nil { return nil, err } } return r.FindByID(userID, id) } func (r *Repository) Delete(userID, id uint64) error { var account model.UserPaymentAccount if err := r.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 r.db.Model(&account).Update("status", "disabled").Error } func (r *Repository) SetDefault(userID, id uint64) error { // 验证账号存在 var account model.UserPaymentAccount if err := r.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 r.db.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(userID uint64) (int64, error) { var count int64 err := r.db.Model(&model.UserPaymentAccount{}). Where("user_id = ? AND status = ?", userID, "active"). Count(&count).Error return count, err } func (r *Repository) ValidateRealname(userID uint64, accountName string) error { var user model.User if err := r.db.First(&user, userID).Error; err != nil { return err } if user.RealnameStatus != "verified" { return ErrRealnameRequired } // 获取实名信息 var realname model.UserRealname if err := r.db.Where("user_id = ? AND status = ?", userID, "success"). First(&realname).Error; err != nil { return ErrRealnameRequired } // 验证姓名匹配(去除空格后比较) if strings.ReplaceAll(realname.MaskedName, " ", "") != strings.ReplaceAll(accountName, " ", "") { return ErrAccountNameMismatch } return nil } func (r *Repository) toDTO(account model.UserPaymentAccount) (*PaymentAccountDTO, error) { // 解密账号并脱敏 decrypted, err := 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 } // AES加密 func encrypt(plainText string) (string, error) { block, err := aes.NewCipher([]byte(encryptionKey)) if err != nil { return "", err } plainBytes := []byte(plainText) cipherBytes := make([]byte, aes.BlockSize+len(plainBytes)) iv := cipherBytes[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { return "", err } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(cipherBytes[aes.BlockSize:], plainBytes) return base64.StdEncoding.EncodeToString(cipherBytes), nil } // AES解密 func decrypt(cipherText string) (string, error) { block, err := aes.NewCipher([]byte(encryptionKey)) if err != nil { return "", err } cipherBytes, err := base64.StdEncoding.DecodeString(cipherText) if err != nil { return "", err } if len(cipherBytes) < aes.BlockSize { return "", errors.New("ciphertext too short") } iv := cipherBytes[:aes.BlockSize] cipherBytes = cipherBytes[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(cipherBytes, cipherBytes) return string(cipherBytes), nil } // 获取解密后的账号(仅供内部使用,如提现申请时) func (r *Repository) GetDecryptedAccountNo(userID, id uint64) (string, error) { var account model.UserPaymentAccount if err := r.db.Where("id = ? AND user_id = ?", id, userID).First(&account).Error; err != nil { return "", err } return decrypt(account.AccountNo) }