资产模块功能实现

This commit is contained in:
2025-08-19 16:53:04 +08:00
parent caaa1737e9
commit de51102af6
10 changed files with 3439 additions and 68 deletions

View File

@@ -38,6 +38,11 @@ func Init() {
hlog.Error("AutoMigrate failed: " + err.Error())
return
}
err = DB.AutoMigrate(model.UserAssets{}, model.Diamond{}, model.GoldBean{}, model.GoldLeaf{}, model.Bankcard{}, model.ConsumptionRecord{})
if err != nil {
hlog.Error("AutoMigrate failed: " + err.Error())
return
}
// 连接池配置
sqlDB, _ := DB.DB()
sqlDB.SetMaxIdleConns(10)

View File

@@ -0,0 +1,700 @@
package mysql
import (
"acquaintances/biz/model"
"acquaintances/biz/model/user_assets"
"errors"
"fmt"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"regexp"
"time"
)
func GetUserAssets(req user_assets.GetUserAssetsRequest) (*user_assets.UserAssets, error) {
// 检查用户ID是否为空
if req.UserID == "" {
return nil, errors.New("用户ID不能为空")
}
// 初始化接收结果的结构体
userAssets := &user_assets.UserAssets{}
// 构建查询:关联用户资产主表和各类资产表
db := DB.Model(&model.UserAssets{}).
Joins("JOIN diamond ON user_assets.user_id = diamond.user_id").
Joins("JOIN gold_bean ON user_assets.user_id = gold_bean.user_id").
Joins("JOIN gold_leaf ON user_assets.user_id = gold_leaf.user_id").
Where("user_assets.user_id = ?", req.UserID)
// 执行查询并扫描结果到结构体
err := db.Select(`
user_assets.user_id,
user_assets.status,
user_assets.created_at,
user_assets.updated_at,
diamond.diamond,
gold_bean.gold_bean,
gold_leaf.gold_leaf
`).First(userAssets).Error
// 处理查询错误
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("用户资产记录不存在")
}
return nil, fmt.Errorf("查询用户资产失败: %w", err)
}
return userAssets, nil
}
// AddDiamond 为用户添加钻石(包含事务处理)
func AddDiamond(req user_assets.AddDiamondRequest) error {
// 1. 参数验证
if err := validateAddDiamondRequest(req); err != nil {
return fmt.Errorf("参数验证失败: %w", err)
}
// 2. 转换钻石数量为decimal类型
diamondNum, err := decimal.NewFromString(req.DiamondNumber)
if err != nil {
return fmt.Errorf("钻石数量格式错误: %w", err)
}
if diamondNum.LessThanOrEqual(decimal.Zero) {
return errors.New("钻石数量必须大于0")
}
// 3. 使用事务确保数据一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 4. 查询用户当前钻石数量
var diamond model.Diamond
result := tx.Where("user_id = ?", req.UserID).First(&diamond)
// 5. 处理用户钻石记录不存在的情况(自动初始化)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
diamond = model.Diamond{
UserID: req.UserID,
Diamond: decimal.Zero,
}
} else {
return fmt.Errorf("查询用户钻石记录失败: %w", result.Error)
}
}
// 6. 计算更新后的值
beforeAmount := diamond.Diamond
afterAmount := beforeAmount.Add(diamondNum)
// 7. 更新钻石数量
if diamond.ID == 0 {
// 新增记录
diamond.Diamond = afterAmount
if err := tx.Create(&diamond).Error; err != nil {
return fmt.Errorf("创建钻石记录失败: %w", err)
}
} else {
// 更新现有记录
if err := tx.Model(&diamond).Update("diamond", afterAmount).Error; err != nil {
return fmt.Errorf("更新钻石数量失败: %w", err)
}
}
// 8. 记录消费记录(作为增加记录)
record := model.ConsumptionRecord{
UserID: req.UserID,
ItemType: model.ItemTypeDiamond,
Amount: diamondNum, // 正数表示增加
BeforeAmount: beforeAmount,
AfterAmount: afterAmount,
BusinessType: "recharge_diamond", // 业务类型:钻石充值
BusinessID: generateBusinessID(), // 生成业务唯一ID
Remark: fmt.Sprintf("银行卡充值: %s", req.BankcardNumber),
Operator: "system",
SourceType: model.SourceTypeBankcard,
}
if err := tx.Create(&record).Error; err != nil {
return fmt.Errorf("记录消费日志失败: %w", err)
}
return nil
})
}
// 验证请求参数
func validateAddDiamondRequest(req user_assets.AddDiamondRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
if req.DiamondNumber == "" {
return errors.New("钻石数量不能为空")
}
// 可添加银行卡号格式验证
return nil
}
// 生成业务唯一ID
func generateBusinessID() string {
return fmt.Sprintf("diamond_%d", time.Now().UnixNano())
}
// AddGoldBean 为用户添加金豆(包含事务处理)
func AddGoldBean(req user_assets.AddGoldBeanRequest) error {
// 1. 参数验证
if err := validateAddGoldBeanRequest(req); err != nil {
return fmt.Errorf("参数验证失败: %w", err)
}
// 2. 转换金豆数量为decimal类型
goldBeanNum, err := decimal.NewFromString(req.GoldBeanNumber)
if err != nil {
return fmt.Errorf("金豆数量格式错误: %w", err)
}
if goldBeanNum.LessThanOrEqual(decimal.Zero) {
return errors.New("金豆数量必须大于0")
}
// 3. 使用事务确保数据一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 4. 查询用户当前金豆数量
var goldBean model.GoldBean
result := tx.Where("user_id = ?", req.UserID).First(&goldBean)
// 5. 处理用户金豆记录不存在的情况(自动初始化)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
goldBean = model.GoldBean{
UserID: req.UserID,
GoldBean: decimal.Zero,
}
} else {
return fmt.Errorf("查询用户金豆记录失败: %w", result.Error)
}
}
// 6. 计算更新后的值
beforeAmount := goldBean.GoldBean
afterAmount := beforeAmount.Add(goldBeanNum)
// 7. 更新金豆数量
if goldBean.ID == 0 {
// 新增记录
goldBean.GoldBean = afterAmount
if err := tx.Create(&goldBean).Error; err != nil {
return fmt.Errorf("创建金豆记录失败: %w", err)
}
} else {
// 更新现有记录
if err := tx.Model(&goldBean).Update("gold_bean", afterAmount).Error; err != nil {
return fmt.Errorf("更新金豆数量失败: %w", err)
}
}
// 8. 记录消费记录(作为增加记录)
record := model.ConsumptionRecord{
UserID: req.UserID,
ItemType: model.ItemTypeGoldBean, // 物品类型为金豆
Amount: goldBeanNum, // 正数表示增加
BeforeAmount: beforeAmount,
AfterAmount: afterAmount,
BusinessType: "recharge_gold_bean", // 业务类型:金豆充值
BusinessID: generateGoldBeanBusinessID(),
Remark: fmt.Sprintf("银行卡充值: %s", req.BankcardNumber),
Operator: "system",
SourceType: model.SourceTypeBankcard,
}
if err := tx.Create(&record).Error; err != nil {
return fmt.Errorf("记录金豆消费日志失败: %w", err)
}
return nil
})
}
// 验证请求参数
func validateAddGoldBeanRequest(req user_assets.AddGoldBeanRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
if req.GoldBeanNumber == "" {
return errors.New("金豆数量不能为空")
}
// 可添加银行卡号格式验证(如长度、校验位等)
return nil
}
// 生成金豆业务唯一ID
func generateGoldBeanBusinessID() string {
return fmt.Sprintf("gold_bean_%d", time.Now().UnixNano())
}
// AddGoldLeaf 为用户添加金叶(包含事务处理)
func AddGoldLeaf(req user_assets.AddGoldLeafRequest) error {
// 1. 参数验证
if err := validateAddGoldLeafRequest(req); err != nil {
return fmt.Errorf("参数验证失败: %w", err)
}
// 2. 转换金叶数量为decimal类型
goldLeafNum, err := decimal.NewFromString(req.GoldLeafNumber)
if err != nil {
return fmt.Errorf("金叶数量格式错误: %w", err)
}
if goldLeafNum.LessThanOrEqual(decimal.Zero) {
return errors.New("金叶数量必须大于0")
}
// 3. 使用事务确保数据一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 4. 查询用户当前金叶数量
var goldLeaf model.GoldLeaf
result := tx.Where("user_id = ?", req.UserID).First(&goldLeaf)
// 5. 处理用户金叶记录不存在的情况(自动初始化)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
goldLeaf = model.GoldLeaf{
UserID: req.UserID,
GoldLeaf: decimal.Zero,
}
} else {
return fmt.Errorf("查询用户金叶记录失败: %w", result.Error)
}
}
// 6. 计算更新后的值
beforeAmount := goldLeaf.GoldLeaf
afterAmount := beforeAmount.Add(goldLeafNum)
// 7. 更新金叶数量
if goldLeaf.ID == 0 {
// 新增记录
goldLeaf.GoldLeaf = afterAmount
if err := tx.Create(&goldLeaf).Error; err != nil {
return fmt.Errorf("创建金叶记录失败: %w", err)
}
} else {
// 更新现有记录
if err := tx.Model(&goldLeaf).Update("gold_leaf", afterAmount).Error; err != nil {
return fmt.Errorf("更新金叶数量失败: %w", err)
}
}
// 8. 记录消费记录(作为增加记录)
record := model.ConsumptionRecord{
UserID: req.UserID,
ItemType: model.ItemTypeGoldLeaf, // 物品类型为金叶
Amount: goldLeafNum, // 正数表示增加
BeforeAmount: beforeAmount,
AfterAmount: afterAmount,
BusinessType: "recharge_gold_leaf", // 业务类型:金叶充值
BusinessID: generateGoldLeafBusinessID(),
Remark: fmt.Sprintf("银行卡充值: %s", req.BankcardNumber),
Operator: "system",
SourceType: model.SourceTypeBankcard,
}
if err := tx.Create(&record).Error; err != nil {
return fmt.Errorf("记录金叶消费日志失败: %w", err)
}
return nil
})
}
// 验证请求参数
func validateAddGoldLeafRequest(req user_assets.AddGoldLeafRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
if req.GoldLeafNumber == "" {
return errors.New("金叶数量不能为空")
}
// 可添加银行卡号格式验证如16-19位数字校验
return nil
}
// 生成金叶业务唯一ID
func generateGoldLeafBusinessID() string {
return fmt.Sprintf("gold_leaf_%d", time.Now().UnixNano())
}
// 银行卡号格式验证正则支持16-19位数字
var bankcardRegex = regexp.MustCompile(`^\d{16,19}$`)
// AddBankcard 为用户添加银行卡
func AddBankcard(req user_assets.AddBankcardRequest) error {
// 1. 参数验证
if err := validateAddBankcardRequest(req); err != nil {
return fmt.Errorf("参数验证失败: %w", err)
}
// 2. 使用事务处理,确保默认卡状态一致性
return DB.Transaction(func(tx *gorm.DB) error {
// 3. 检查银行卡号是否已被当前用户添加
var existingCount int64
if err := tx.Model(&model.Bankcard{}).
Where("user_id = ? AND bankcard_number = ?", req.UserID, req.BankcardNumber).
Count(&existingCount).Error; err != nil {
return fmt.Errorf("查询银行卡记录失败: %w", err)
}
if existingCount > 0 {
return errors.New("该银行卡已添加,不可重复添加")
}
// 4. 如果设置为默认卡,需要将用户其他默认卡设为非默认
if req.IsDefault {
if err := tx.Model(&model.Bankcard{}).
Where("user_id = ? AND is_default = 1", req.UserID).
Update("is_default", false).Error; err != nil {
return fmt.Errorf("更新默认银行卡状态失败: %w", err)
}
} else {
// 5. 如果用户没有默认卡,强制设置当前卡为默认卡
var defaultCount int64
if err := tx.Model(&model.Bankcard{}).
Where("user_id = ? AND is_default = 1", req.UserID).
Count(&defaultCount).Error; err != nil {
return fmt.Errorf("查询默认银行卡失败: %w", err)
}
if defaultCount == 0 {
req.IsDefault = true // 自动设为默认卡
}
}
// 6. 创建银行卡记录
bankcard := model.Bankcard{
UserID: req.UserID,
BankCardNumber: req.BankcardNumber,
BankName: req.BankName,
AccountType: req.AccountType,
IsDefault: req.IsDefault,
AccountName: req.AccountName,
}
if err := tx.Create(&bankcard).Error; err != nil {
return fmt.Errorf("创建银行卡记录失败: %w", err)
}
return nil
})
}
// 验证银行卡添加请求参数
func validateAddBankcardRequest(req user_assets.AddBankcardRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
// 验证银行卡号格式16-19位数字
if !bankcardRegex.MatchString(req.BankcardNumber) {
return errors.New("银行卡号格式错误应为16-19位数字")
}
if req.BankName == "" {
return errors.New("银行名称不能为空")
}
if req.AccountName == "" {
return errors.New("账户名(持卡人姓名)不能为空")
}
// 账户类型为空时设置默认值
if req.AccountType == "" {
req.AccountType = "basic"
}
return nil
}
// RemoveBankcard 为用户删除银行卡
func RemoveBankcard(req user_assets.RemoveBankcardRequest) error {
// 1. 参数验证
if err := validateRemoveBankcardRequest(req); err != nil {
return fmt.Errorf("参数验证失败: %w", err)
}
// 2. 使用事务处理,确保删除后默认卡状态合理
return DB.Transaction(func(tx *gorm.DB) error {
// 2. 查询银行卡是否存在且属于当前用户
var bankcard model.Bankcard
if err := tx.Where("user_id = ? AND bankcard_number = ?",
req.UserID, req.BankcardNumber).First(&bankcard).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("银行卡不存在或不属于当前用户")
}
return fmt.Errorf("查询银行卡信息失败: %w", err)
}
// 3. 检查是否为默认卡,若为默认卡需要特殊处理
if bankcard.IsDefault {
// 3.1 查询用户是否还有其他银行卡
var otherCount int64
if err := tx.Model(&model.Bankcard{}).
Where("user_id = ? AND bankcard_number != ?",
req.UserID, req.BankcardNumber).
Count(&otherCount).Error; err != nil {
return fmt.Errorf("查询用户其他银行卡失败: %w", err)
}
// 3.2 若还有其他卡,需要指定一张作为新默认卡
if otherCount > 0 {
var firstBankcard model.Bankcard
if err := tx.Model(&model.Bankcard{}).
Where("user_id = ? AND bankcard_number != ?",
req.UserID, req.BankcardNumber).
First(&firstBankcard).Error; err != nil {
return fmt.Errorf("查询替代默认卡失败: %w", err)
}
// 设置新默认卡
if err := tx.Model(&model.Bankcard{}).
Where("id = ?", firstBankcard.ID).
Update("is_default", true).Error; err != nil {
return fmt.Errorf("更新默认卡失败: %w", err)
}
}
// 若没有其他卡,删除后用户将没有默认卡(允许这种状态)
}
// 4. 执行删除操作
if err := tx.Where("id = ?", bankcard.ID).Delete(&model.Bankcard{}).Error; err != nil {
return fmt.Errorf("删除银行卡失败: %w", err)
}
return nil
})
}
// 验证银行卡删除请求参数
func validateRemoveBankcardRequest(req user_assets.RemoveBankcardRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
// 验证银行卡号格式
if !bankcardRegex.MatchString(req.BankcardNumber) {
return errors.New("银行卡号格式错误应为16-19位数字")
}
return nil
}
// ListBankcardByUser 分页查询用户的银行卡列表
func ListBankcardByUser(req user_assets.ListBankcardByUserRequest) ([]*user_assets.Bankcard, int32, error) {
// 1. 参数验证与处理
if req.UserID == "" {
return nil, 0, errors.New("用户ID不能为空")
}
// 处理默认分页参数
page := req.Page
if page <= 0 {
page = 1 // 默认第一页
}
pageSize := req.PageSize
if pageSize <= 0 || pageSize > 100 {
pageSize = 10 // 默认每页10条最大100条
}
offset := (page - 1) * pageSize
// 2. 查询总记录数
var total int64
if err := DB.Model(&model.Bankcard{}).
Where("user_id = ?", req.UserID).
Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("查询银行卡总数失败: %w", err)
}
// 3. 查询当前页数据
var bankcards []*model.Bankcard
if err := DB.Where("user_id = ?", req.UserID).
Order("is_default DESC, created_at DESC"). // 默认卡优先,按创建时间倒序
Limit(int(pageSize)).
Offset(int(offset)).
Find(&bankcards).Error; err != nil {
return nil, 0, fmt.Errorf("查询银行卡列表失败: %w", err)
}
// 4. 转换为响应结构体
var resp []*user_assets.Bankcard
for _, card := range bankcards {
resp = append(resp, &user_assets.Bankcard{
UserID: card.UserID,
BankcardNumber: maskBankcard(card.BankCardNumber), // 脱敏处理
BankName: card.BankName,
AccountType: card.AccountType,
IsDefault: card.IsDefault,
AccountName: card.AccountName,
})
}
return resp, int32(total), nil
}
// 银行卡号脱敏处理保留前4位和后4位
func maskBankcard(bankcard string) string {
if len(bankcard) <= 8 {
return "****"
}
return bankcard[:4] + "****" + bankcard[len(bankcard)-4:]
}
// ListConsumptionByBankcard 根据银行卡号查询消费记录列表
func ListConsumptionByBankcard(req user_assets.ListConsumptionByBankcardRequest) ([]*user_assets.ConsumptionRecord, int32, error) {
// 1. 参数验证
if err := validateListConsumptionParams(req); err != nil {
return nil, 0, fmt.Errorf("参数验证失败: %w", err)
}
// 2. 处理分页参数
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 || pageSize > 100 {
pageSize = 10 // 限制最大每页数量
}
offset := (page - 1) * pageSize
// 3. 查询银行卡信息,验证存在性和归属关系
var bankcard model.Bankcard
if err := DB.Where("bankcard_number = ?", req.BankcardNumber).First(&bankcard).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, 0, errors.New("银行卡不存在")
}
return nil, 0, fmt.Errorf("查询银行卡信息失败: %w", err)
}
// 4. 验证用户ID如果指定
if req.UserID != "" && bankcard.UserID != req.UserID {
return nil, 0, errors.New("银行卡不属于指定用户")
}
targetUserID := bankcard.UserID
// 5. 构建查询条件用户ID + 银行卡来源类型
query := DB.Model(&model.ConsumptionRecord{}).
Where("user_id = ? AND source_type = ?", targetUserID, model.SourceTypeBankcard)
// 6. 查询总记录数
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("统计消费记录总数失败: %w", err)
}
// 7. 分页查询消费记录
var dbRecords []*model.ConsumptionRecord
if err := query.
Order("created_at DESC"). // 按创建时间倒序,最新记录在前
Limit(int(pageSize)).
Offset(int(offset)).
Find(&dbRecords).Error; err != nil {
return nil, 0, fmt.Errorf("查询消费记录失败: %w", err)
}
// 8. 转换为Thrift响应结构体
respRecords := make([]*user_assets.ConsumptionRecord, 0, len(dbRecords))
for _, dbRec := range dbRecords {
respRec := &user_assets.ConsumptionRecord{
UserID: dbRec.UserID,
ItemType: int8(dbRec.ItemType),
Amount: dbRec.Amount.String(),
BeforeAmount: dbRec.BeforeAmount.String(),
AfterAmount: dbRec.AfterAmount.String(),
BusinessType: dbRec.BusinessType,
BusinessID: dbRec.BusinessID,
SourceType: int8(dbRec.SourceType),
Remark: dbRec.Remark,
Operator: dbRec.Operator,
}
respRecords = append(respRecords, respRec)
}
return respRecords, int32(total), nil
}
// 参数验证函数
func validateListConsumptionParams(req user_assets.ListConsumptionByBankcardRequest) error {
if req.BankcardNumber == "" {
return errors.New("银行卡号不能为空")
}
if !bankcardRegex.MatchString(req.BankcardNumber) {
return errors.New("银行卡号格式错误应为16-19位数字")
}
return nil
}
// ListConsumptionByUser 根据用户ID查询消费记录列表
func ListConsumptionByUser(req user_assets.ListConsumptionByUserRequest) ([]*user_assets.ConsumptionRecord, int32, error) {
// 1. 参数验证
if err := validateListConsumptionByUserParams(req); err != nil {
return nil, 0, fmt.Errorf("参数验证失败: %w", err)
}
// 2. 处理分页参数
page := req.Page
if page <= 0 {
page = 1 // 默认第一页
}
pageSize := req.PageSize
if pageSize <= 0 || pageSize > 100 {
pageSize = 10 // 限制最大每页数量
}
offset := (page - 1) * pageSize
// 3. 构建查询条件用户ID
query := DB.Model(&model.ConsumptionRecord{}).
Where("user_id = ?", req.UserID)
// 4. 查询总记录数
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("统计消费记录总数失败: %w", err)
}
// 5. 分页查询消费记录
var dbRecords []*model.ConsumptionRecord
if err := query.
Order("created_at DESC"). // 按创建时间倒序,最新记录在前
Limit(int(pageSize)).
Offset(int(offset)).
Find(&dbRecords).Error; err != nil {
return nil, 0, fmt.Errorf("查询消费记录失败: %w", err)
}
// 6. 转换为Thrift响应结构体
respRecords := make([]*user_assets.ConsumptionRecord, 0, len(dbRecords))
for _, dbRec := range dbRecords {
respRec := &user_assets.ConsumptionRecord{
UserID: dbRec.UserID,
ItemType: int8(dbRec.ItemType),
Amount: dbRec.Amount.String(),
BeforeAmount: dbRec.BeforeAmount.String(),
AfterAmount: dbRec.AfterAmount.String(),
BusinessType: dbRec.BusinessType,
BusinessID: dbRec.BusinessID,
SourceType: int8(dbRec.SourceType),
Remark: dbRec.Remark,
Operator: dbRec.Operator,
}
respRecords = append(respRecords, respRec)
}
return respRecords, int32(total), nil
}
// 参数验证函数
func validateListConsumptionByUserParams(req user_assets.ListConsumptionByUserRequest) error {
if req.UserID == "" {
return errors.New("用户ID不能为空")
}
return nil
}

View File

@@ -3,6 +3,7 @@
package user_assets
import (
"acquaintances/biz/dal/mysql"
"context"
user_assets "acquaintances/biz/model/user_assets"
@@ -20,9 +21,13 @@ func GetUserAssets(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
assets, err := mysql.GetUserAssets(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.GetUserAssetsResponse)
resp.UserAssets = assets
c.JSON(consts.StatusOK, resp)
}
@@ -36,7 +41,11 @@ func AddDiamond(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.AddDiamond(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.AddDiamondResponse)
c.JSON(consts.StatusOK, resp)
@@ -52,7 +61,11 @@ func AddGoldBean(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.AddGoldBean(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.AddGoldBeanResponse)
c.JSON(consts.StatusOK, resp)
@@ -68,7 +81,11 @@ func AddGoldLeaf(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.AddGoldLeaf(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.AddGoldLeafResponse)
c.JSON(consts.StatusOK, resp)
@@ -84,7 +101,11 @@ func AddBankcard(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.AddBankcard(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.AddBankcardResponse)
c.JSON(consts.StatusOK, resp)
@@ -100,7 +121,11 @@ func RemoveBankcard(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
err = mysql.RemoveBankcard(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.RemoveBankcardResponse)
c.JSON(consts.StatusOK, resp)
@@ -116,8 +141,54 @@ func ListBankcardByUser(ctx context.Context, c *app.RequestContext) {
c.String(consts.StatusBadRequest, err.Error())
return
}
bankcards, total, err := mysql.ListBankcardByUser(req)
if err != nil {
return
}
resp := new(user_assets.ListBankcardByUserResponse)
resp.Bankcards = bankcards
resp.Total = total
c.JSON(consts.StatusOK, resp)
}
// ListConsumptionByBankcard .
// @router /v1/user_assets/bankcard/consumption/ [GET]
func ListConsumptionByBankcard(ctx context.Context, c *app.RequestContext) {
var err error
var req user_assets.ListConsumptionByBankcardRequest
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
record, total, err := mysql.ListConsumptionByBankcard(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.ListConsumptionByBankcardResponse)
resp.Records = record
resp.Total = total
c.JSON(consts.StatusOK, resp)
}
// ListConsumptionByUser .
// @router /v1/user_assets/consumption/ [GET]
func ListConsumptionByUser(ctx context.Context, c *app.RequestContext) {
var err error
var req user_assets.ListConsumptionByUserRequest
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
record, total, err := mysql.ListConsumptionByUser(req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
resp := new(user_assets.ListConsumptionByUserResponse)
resp.Records = record
resp.Total = total
c.JSON(consts.StatusOK, resp)
}

View File

@@ -1,21 +1,24 @@
package model
import (
"google.golang.org/genproto/googleapis/type/decimal"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// Asset Type
// source Type
type SourceType uint8
const (
diamond = iota
goldBean
goldLeaf
SourceTypeBankcard SourceType = iota + 1 // 银行卡
SourceTypeUSDT // USDT
)
// source Type
type ItemType uint8
const (
bankcard = iota
usdt
ItemTypeDiamond ItemType = 1 // 钻石
ItemTypeGoldBean ItemType = 2 // 金豆
ItemTypeGoldLeaf ItemType = 3 // 金叶
)
type UserAssets struct {
@@ -61,52 +64,33 @@ func (*GoldLeaf) TableName() string {
return "gold_leaf"
}
// Bankcard 银行卡信息
type Bankcard struct {
gorm.Model
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
BankCardNumber string `gorm:"column:bankcard_number;type:varchar(19);not null;uniqueIndex:uniq_bank_card_number;comment:'银行卡号码'"`
BankName string `gorm:"column:bank_name;type:varchar(100);not null;index:idx_bank_name;comment:'银行名称'"`
AccountName string `gorm:"column:account_name;type:varchar(100);not null;comment:'账户名'"` // 新增:持卡人姓名
AccountType string `gorm:"column:account_type;type:varchar(50);not null;default:'basic';comment:'账户类型'"`
IsDefault bool `gorm:"column:is_default;type:tinyint(1);not null;default:0;index:idx_is_default;comment:'是否默认银行卡'"`
ExpireDate string `gorm:"column:expire_date;type:varchar(7);comment:'有效期(信用卡)'"` // 新增:支持信用卡
}
func (*Bankcard) TableName() string {
return "bank_card"
}
type AssetHistory struct {
gorm.Model
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
TargetID string `gorm:"column:target_id;type:bigint;comment:'目标商品id'"`
AssetType int8 `gorm:"column:asset_type;type:tinyint;not null;comment:资产类型(0-钻石,1-金叶,2-金豆)"`
Amount decimal.Decimal `gorm:"column:amount;type:decimal(15,2);not null;default:0.00;comment:'数量'"`
SourceType int8 `gorm:"column:source_type;type:tinyint;not null;comment:来源类型(0-银行卡,1-usdt)"`
SourceAccount string `gorm:"column:source_account;type:varchar(50);not null;index:uniq_bank_card_number;comment:'来源账号'"`
}
func (*AssetHistory) TableName() string {
return "asset_history"
}
// 物品类型枚举,定义支持的消费物品类型
type ItemType uint8
const (
ItemTypeDiamond ItemType = 1 // 钻石
ItemTypeGoldBean ItemType = 2 // 金豆
ItemTypeGoldLeaf ItemType = 3 // 金叶
)
// ConsumptionRecord 物品消费记录表
type ConsumptionRecord struct {
gorm.Model
UserID string `gorm:"column:user_id;type:varchar(32);not null;index:idx_user_id;comment:'用户ID'"`
ItemType ItemType `gorm:"column:item_type;type:tinyint unsigned;not null;index:idx_item_type;comment:'物品类型 1:钻石 2:金豆 3:金叶'"`
Amount decimal.Decimal `gorm:"column:amount;type:decimal(15,2);not null;comment:'消费数量'"`
Amount decimal.Decimal `gorm:"column:amount;type:decimal(15,2);not null;comment:'变动数量,正数增加,负数减少'"`
BeforeAmount decimal.Decimal `gorm:"column:before_amount;type:decimal(15,2);not null;comment:'操作前数量'"`
AfterAmount decimal.Decimal `gorm:"column:after_amount;type:decimal(15,2);not null;comment:'操作后数量'"`
BusinessType string `gorm:"column:business_type;type:varchar(32);not null;index:idx_business_type;comment:'业务类型,如:buy_vip购买会员、send_gift赠送礼物'"`
BusinessID string `gorm:"column:business_id;type:varchar(64);index:idx_business_id;comment:'关联业务ID如订单ID、礼物ID'"`
BusinessType string `gorm:"column:business_type;type:varchar(32);not null;index:idx_business_type;comment:'业务类型'"`
BusinessID string `gorm:"column:business_id;type:varchar(64);index:idx_business_id;comment:'关联业务ID'"`
SourceType SourceType `gorm:"column:source_type;type:tinyint;comment:'来源类型 1:银行卡 2:USDT'"` // 新增:记录资金来源
Remark string `gorm:"column:remark;type:varchar(255);comment:'备注信息'"`
Operator string `gorm:"column:operator;type:varchar(32);comment:'操作人系统操作填system'"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -75,3 +75,23 @@ func _addgoldleafMw() []app.HandlerFunc {
// your code...
return nil
}
func _consumptionMw() []app.HandlerFunc {
// your code...
return nil
}
func _listconsumptionbybankcardMw() []app.HandlerFunc {
// your code...
return nil
}
func _consumption0Mw() []app.HandlerFunc {
// your code...
return nil
}
func _listconsumptionbyuserMw() []app.HandlerFunc {
// your code...
return nil
}

View File

@@ -27,6 +27,14 @@ func Register(r *server.Hertz) {
_bankcard.DELETE("/", append(_removebankcardMw(), user_assets.RemoveBankcard)...)
_bankcard.GET("/", append(_listbankcardbyuserMw(), user_assets.ListBankcardByUser)...)
_bankcard.POST("/", append(_addbankcardMw(), user_assets.AddBankcard)...)
{
_consumption := _bankcard.Group("/consumption", _consumptionMw()...)
_consumption.GET("/", append(_listconsumptionbybankcardMw(), user_assets.ListConsumptionByBankcard)...)
}
}
{
_consumption0 := _user_assets.Group("/consumption", _consumption0Mw()...)
_consumption0.GET("/", append(_listconsumptionbyuserMw(), user_assets.ListConsumptionByUser)...)
}
{
_diamond := _user_assets.Group("/diamond", _diamondMw()...)

View File

@@ -9,7 +9,9 @@ require (
github.com/cloudwego/hertz v0.10.1
github.com/hertz-contrib/cors v0.1.0
github.com/hertz-contrib/registry/etcd v0.0.0-20250319055937-8a220332e808
github.com/shopspring/decimal v1.4.0
golang.org/x/crypto v0.22.0
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c
gopkg.in/ini.v1 v1.67.0
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.0
@@ -47,7 +49,6 @@ require (
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/grpc v1.41.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
)

View File

@@ -310,6 +310,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

View File

@@ -25,7 +25,7 @@ struct GetUserAssetsResponse{
struct AddDiamondRequest {
1: string user_id
2: string bankcard_number
3: string diamond
3: string diamond_number
}
struct AddDiamondResponse {
@@ -36,7 +36,7 @@ struct AddDiamondResponse {
struct AddGoldBeanRequest {
1: string user_id
2: string bankcard_number
3: string diamond
3: string gold_bean_number
}
struct AddGoldBeanResponse {
@@ -47,7 +47,7 @@ struct AddGoldBeanResponse {
struct AddGoldLeafRequest {
1: string user_id
2: string bankcard_number
3: string diamond
3: string gold_leaf_number
}
struct AddGoldLeafResponse {
@@ -61,6 +61,7 @@ struct Bankcard {
3: string bank_name
4: string account_type
5: bool is_default
6: string account_name
}
struct AddBankcardRequest {
@@ -69,6 +70,7 @@ struct AddBankcardRequest {
3: string bank_name
4: string account_type
5: bool is_default
6: string account_name
}
struct AddBankcardResponse {
@@ -88,6 +90,8 @@ struct RemoveBankcardResponse {
struct ListBankcardByUserRequest {
1: string user_id
2: i32 page // 页码默认1
3: i32 page_size // 每页数量默认10
}
struct ListBankcardByUserResponse {
@@ -97,6 +101,48 @@ struct ListBankcardByUserResponse {
4: i32 total
}
// 消费记录结构体
struct ConsumptionRecord {
1: string user_id, // 用户ID
2: i8 item_type, // 物品类型
3: string amount, // 变动数量(使用字符串存储 decimal 类型)
4: string before_amount, // 操作前数量
5: string after_amount, // 操作后数量
6: string business_type, // 业务类型
7: string business_id, // 关联业务ID
8: i8 source_type,// 来源类型
9: string remark, // 备注信息
10: string operator, // 操作人
}
struct ListConsumptionByBankcardRequest{
1: string bankcard_number
2: string user_id
3: i32 page
4: i32 page_size
}
struct ListConsumptionByBankcardResponse{
1: list<ConsumptionRecord> records
2: i32 total
3: Code code
4: string msg
}
struct ListConsumptionByUserRequest{
1: string user_id
2: i32 page
3: i32 page_size
}
struct ListConsumptionByUserResponse{
1: list<ConsumptionRecord> records
2: i32 total
3: Code code
4: string msg
}
service UserAssetsService {
GetUserAssetsResponse GetUserAssets(1:GetUserAssetsRequest req)(api.get="/v1/user_assets/") //获取指定用户银行卡列表
AddDiamondResponse AddDiamond(1:AddDiamondRequest req)(api.post="/v1/user_assets/diamond/") //充值钻石
@@ -105,4 +151,6 @@ service UserAssetsService {
AddBankcardResponse AddBankcard(1:AddBankcardRequest req)(api.post="/v1/user_assets/bankcard/") //添加银行卡
RemoveBankcardResponse RemoveBankcard(1:RemoveBankcardRequest req)(api.delete="/v1/user_assets/bankcard/") //删除银行卡
ListBankcardByUserResponse ListBankcardByUser(1:ListBankcardByUserRequest req)(api.get="/v1/user_assets/bankcard/") //获取指定用户银行卡列表
ListConsumptionByBankcardResponse ListConsumptionByBankcard(1:ListConsumptionByBankcardRequest req)(api.get="/v1/user_assets/bankcard/consumption/") //获取指定用户银行卡消费记录
ListConsumptionByUserResponse ListConsumptionByUser(1:ListConsumptionByUserRequest req)(api.get="/v1/user_assets/consumption/") //获取指定用户消费记录
}