feat: add superadmin wallet view

This commit is contained in:
2026-01-15 11:21:37 +08:00
parent 37325ab1b4
commit 56082bad4f
10 changed files with 495 additions and 5 deletions

View File

@@ -203,6 +203,36 @@ type UserItem struct {
JoinedTenantCount int64 `json:"joined_tenant_count"`
}
type SuperWalletResponse struct {
// Balance 账户可用余额(分)。
Balance int64 `json:"balance"`
// BalanceFrozen 账户冻结余额(分)。
BalanceFrozen int64 `json:"balance_frozen"`
// Transactions 最近交易记录。
Transactions []SuperWalletTransaction `json:"transactions"`
}
type SuperWalletTransaction struct {
// ID 订单ID。
ID int64 `json:"id"`
// OrderType 订单类型。
OrderType consts.OrderType `json:"order_type"`
// Title 交易标题。
Title string `json:"title"`
// Amount 交易金额(分)。
Amount int64 `json:"amount"`
// Type 交易流向income/expense
Type string `json:"type"`
// Date 交易时间RFC3339
Date string `json:"date"`
// TenantID 交易所属租户ID充值为0
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
}
type UserStatistics struct {
// Status 用户状态枚举。
Status consts.UserStatus `json:"status"`

View File

@@ -185,6 +185,11 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Query[dto.SuperUserTenantListFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/users/:id<int>/wallet -> users.Wallet")
router.Get("/super/v1/users/:id<int>/wallet"[len(r.Path()):], DataFunc1(
r.users.Wallet,
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Get /super/v1/users/statistics -> users.Statistics")
router.Get("/super/v1/users/statistics"[len(r.Path()):], DataFunc0(
r.users.Statistics,

View File

@@ -43,6 +43,21 @@ func (c *users) Get(ctx fiber.Ctx, id int64) (*dto.UserItem, error) {
return services.Super.GetUser(ctx, id)
}
// Get user wallet
//
// @Router /super/v1/users/:id<int>/wallet [get]
// @Summary Get user wallet
// @Description Get user wallet balance and transactions
// @Tags User
// @Accept json
// @Produce json
// @Param id path int64 true "User ID"
// @Success 200 {object} dto.SuperWalletResponse
// @Bind id path
func (c *users) Wallet(ctx fiber.Ctx, id int64) (*dto.SuperWalletResponse, error) {
return services.Super.GetUserWallet(ctx, id)
}
// List user tenants
//
// @Router /super/v1/users/:id<int>/tenants [get]

View File

@@ -294,6 +294,94 @@ func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, err
}, nil
}
func (s *super) GetUserWallet(ctx context.Context, userID int64) (*super_dto.SuperWalletResponse, error) {
if userID == 0 {
return nil, errorx.ErrBadRequest.WithMsg("用户ID不能为空")
}
// 查询用户余额。
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
u, err := userQuery.Where(userTbl.ID.Eq(userID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// 仅返回最近交易记录,避免超管页面加载过重。
orderTbl, orderQuery := models.OrderQuery.QueryContext(ctx)
orders, err := orderQuery.
Where(orderTbl.UserID.Eq(userID), orderTbl.Status.Eq(consts.OrderStatusPaid)).
Order(orderTbl.CreatedAt.Desc()).
Limit(20).
Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// 补齐订单对应的租户信息。
tenantIDMap := make(map[int64]struct{})
for _, o := range orders {
if o.TenantID > 0 {
tenantIDMap[o.TenantID] = struct{}{}
}
}
tenantIDs := make([]int64, 0, len(tenantIDMap))
for id := range tenantIDMap {
tenantIDs = append(tenantIDs, id)
}
tenantMap := make(map[int64]*models.Tenant)
if len(tenantIDs) > 0 {
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
txs := make([]super_dto.SuperWalletTransaction, 0, len(orders))
for _, o := range orders {
txType := "expense"
switch o.Type {
case consts.OrderTypeRecharge:
txType = "income"
case consts.OrderTypeWithdrawal:
txType = "expense"
case consts.OrderTypeContentPurchase:
txType = "expense"
}
tenant := tenantMap[o.TenantID]
tenantCode := ""
tenantName := ""
if tenant != nil {
tenantCode = tenant.Code
tenantName = tenant.Name
}
txs = append(txs, super_dto.SuperWalletTransaction{
ID: o.ID,
OrderType: o.Type,
Title: o.Type.Description(),
Amount: o.AmountPaid,
Type: txType,
Date: o.CreatedAt.Format(time.RFC3339),
TenantID: o.TenantID,
TenantCode: tenantCode,
TenantName: tenantName,
})
}
return &super_dto.SuperWalletResponse{
Balance: u.Balance,
BalanceFrozen: u.BalanceFrozen,
Transactions: txs,
}, nil
}
func (s *super) UpdateUserStatus(ctx context.Context, id int64, form *super_dto.UserStatusUpdateForm) error {
tbl, q := models.UserQuery.QueryContext(ctx)
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.UserStatus(form.Status))