完成模块: - auth: 3 个 Repository 方法 + Service + Handler + Middleware - wallet: 已有 context 支持,修复依赖调用 - payment: 已有 context 支持,修复 wallet 调用 - adminaudit: 1 个方法 - notification: 2 个方法 - realname: 2 个方法 - systemconfig: 4 个方法 - adminauth: 7 个方法 - adminuser: 6 个方法 所有数据库调用已改为 r.db.WithContext(ctx),完整传递 context 链路。 待完成模块: order, listing, chat 等 12 个模块(约 157 个方法)
190 lines
7.0 KiB
Go
190 lines
7.0 KiB
Go
package adminuser
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"hfb_sys/backend/internal/auditlog"
|
|
"hfb_sys/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
type AuditMeta = auditlog.Meta
|
|
|
|
func NewRepository(db *gorm.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
func (r *Repository) List(ctx context.Context, page, pageSize int) (*PaginatedResult, error) {
|
|
var total int64
|
|
if err := r.db.WithContext(ctx).Model(&model.User{}).Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
offset := (page - 1) * pageSize
|
|
var rows []userRow
|
|
err := r.db.WithContext(ctx).Table("users AS u").
|
|
Select(`u.*,
|
|
COALESCE(o.order_count, 0) AS order_count,
|
|
COALESCE(l.listing_count, 0) AS listing_count,
|
|
COALESCE(d.dispute_count, 0) AS dispute_count,
|
|
COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
|
|
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
|
Order("u.id DESC").
|
|
Offset(offset).Limit(pageSize).
|
|
Scan(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]UserDTO, 0, len(rows))
|
|
for _, row := range rows {
|
|
items = append(items, row.toDTO())
|
|
}
|
|
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
|
}
|
|
|
|
func (r *Repository) Freeze(ctx context.Context, adminID uint64, userID uint64, req FreezeRequest, meta AuditMeta) (*UserDTO, error) {
|
|
return r.updateStatus(ctx, adminID, userID, "frozen", "frozen", "admin_user.freeze", req.Reason, meta)
|
|
}
|
|
|
|
func (r *Repository) Unfreeze(ctx context.Context, adminID uint64, userID uint64, meta AuditMeta) (*UserDTO, error) {
|
|
return r.updateStatus(ctx, adminID, userID, "active", "normal", "admin_user.unfreeze", "", meta)
|
|
}
|
|
|
|
func (r *Repository) SetDepositFreeQuota(ctx context.Context, adminID uint64, userID uint64, req DepositFreeQuotaRequest, meta AuditMeta) (*UserDTO, error) {
|
|
amountCent := depositFreeQuotaAmountCent(req)
|
|
if amountCent < 0 {
|
|
return nil, ErrInvalidUser
|
|
}
|
|
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
|
|
}
|
|
beforeAmountCent := user.DepositFreeQuotaCent
|
|
user.DepositFreeQuotaCent = amountCent
|
|
if err := tx.Save(&user).Error; err != nil {
|
|
return err
|
|
}
|
|
return appendAuditLog(tx, adminID, "admin_user.set_deposit_free_quota", user.ID, meta, map[string]any{
|
|
"user_id": user.ID,
|
|
"before_amount_cent": beforeAmountCent,
|
|
"after_amount_cent": amountCent,
|
|
})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Find(ctx, userID)
|
|
}
|
|
|
|
func (r *Repository) updateStatus(ctx context.Context, adminID uint64, userID uint64, status string, riskStatus string, action string, reason string, meta AuditMeta) (*UserDTO, error) {
|
|
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
|
|
}
|
|
beforeStatus := user.Status
|
|
beforeRisk := user.RiskStatus
|
|
user.Status = status
|
|
user.RiskStatus = riskStatus
|
|
if err := tx.Save(&user).Error; err != nil {
|
|
return err
|
|
}
|
|
return appendAuditLog(tx, adminID, action, user.ID, meta, map[string]any{
|
|
"user_id": user.ID,
|
|
"reason": reason,
|
|
"before_status": beforeStatus,
|
|
"after_status": status,
|
|
"before_risk_status": beforeRisk,
|
|
"after_risk_status": riskStatus,
|
|
})
|
|
})
|
|
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").
|
|
Select(`u.*,
|
|
COALESCE(o.order_count, 0) AS order_count,
|
|
COALESCE(l.listing_count, 0) AS listing_count,
|
|
COALESCE(d.dispute_count, 0) AS dispute_count,
|
|
COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
|
|
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
|
|
Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
|
|
Where("u.id = ?", userID).
|
|
First(&row).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dto := row.toDTO()
|
|
return &dto, nil
|
|
}
|
|
|
|
type userRow struct {
|
|
model.User
|
|
OrderCount int64
|
|
ListingCount int64
|
|
DisputeCount int64
|
|
DepositFreeUsedCent int64
|
|
}
|
|
|
|
func (row userRow) toDTO() UserDTO {
|
|
remaining := row.DepositFreeQuotaCent - row.DepositFreeUsedCent
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
return UserDTO{
|
|
ID: row.ID,
|
|
Phone: row.Phone,
|
|
Nickname: row.Nickname,
|
|
RealnameStatus: row.RealnameStatus,
|
|
RiskStatus: row.RiskStatus,
|
|
CreditScore: row.CreditScore,
|
|
DepositFreeQuotaCent: row.DepositFreeQuotaCent,
|
|
DepositFreeUsedCent: row.DepositFreeUsedCent,
|
|
DepositFreeRemainingCent: remaining,
|
|
Status: row.Status,
|
|
OrderCount: row.OrderCount,
|
|
ListingCount: row.ListingCount,
|
|
DisputeCount: row.DisputeCount,
|
|
LastLoginAt: row.LastLoginAt,
|
|
CreatedAt: row.CreatedAt,
|
|
UpdatedAt: row.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func depositFreeQuotaAmountCent(req DepositFreeQuotaRequest) int64 {
|
|
return req.AmountCent
|
|
}
|
|
|
|
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",
|
|
ActorID: actorID,
|
|
Action: action,
|
|
BizType: "user",
|
|
BizID: &bizID,
|
|
Meta: meta,
|
|
Detail: detail,
|
|
})
|
|
}
|
|
|
|
func IsNotFound(err error) bool {
|
|
return errors.Is(err, gorm.ErrRecordNotFound)
|
|
}
|