feat: add admin risk actions and arbitration settlement

This commit is contained in:
yml
2026-05-22 18:11:01 +08:00
parent dfe686ce0e
commit 55477d0ac6
26 changed files with 888 additions and 23 deletions
+9
View File
@@ -56,3 +56,12 @@ type AdminListQuery struct {
ReviewStatus string
Limit int
}
type AdminActionRequest struct {
Reason string `json:"reason" binding:"required"`
}
type AuditMeta struct {
IP string
UserAgent string
}
@@ -101,6 +101,50 @@ func (h *Handler) ListAdmin(c *gin.Context) {
response.OK(c, gin.H{"items": items})
}
func (h *Handler) FindAdmin(c *gin.Context) {
id, ok := parseID(c)
if !ok {
return
}
item, err := h.service.FindAdmin(id)
if err != nil {
writeListingError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) AdminOffline(c *gin.Context) {
h.adminAction(c, h.service.AdminOffline)
}
func (h *Handler) AdminMarkAbnormal(c *gin.Context) {
h.adminAction(c, h.service.AdminMarkAbnormal)
}
func (h *Handler) adminAction(c *gin.Context, fn func(uint64, uint64, AdminActionRequest, AuditMeta) (*ListingDTO, error)) {
adminID, ok := currentAdminID(c)
if !ok {
response.Unauthorized(c, "缺少管理员上下文")
return
}
id, ok := parseID(c)
if !ok {
return
}
var req AdminActionRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "操作原因不能为空")
return
}
item, err := fn(adminID, id, req, AuditMeta{IP: c.ClientIP(), UserAgent: c.GetHeader("User-Agent")})
if err != nil {
writeListingError(c, err)
return
}
response.OK(c, item)
}
func (h *Handler) Approve(c *gin.Context) {
id, ok := parseID(c)
if !ok {
@@ -212,6 +256,15 @@ func currentUserID(c *gin.Context) (uint64, bool) {
return userID, ok
}
func currentAdminID(c *gin.Context) (uint64, bool) {
value, ok := c.Get(middleware.ContextAdminID)
if !ok {
return 0, false
}
adminID, ok := value.(uint64)
return adminID, ok
}
func parseID(c *gin.Context) (uint64, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || id == 0 {
@@ -1,12 +1,14 @@
package listing
import (
"encoding/json"
"errors"
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/notification"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -162,6 +164,73 @@ func (r *Repository) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
return rowsToDTO(rows), nil
}
func (r *Repository) FindAdmin(listingID uint64) (*ListingDTO, error) {
return r.findDTO("l.id = ?", listingID)
}
func (r *Repository) AdminOffline(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
return r.adminUpdateStatus(adminID, listingID, req, meta, "offline", "offline", "listing.admin_offline", "商品已被后台下架", "你的租号商品已被后台下架,请查看原因后处理。")
}
func (r *Repository) AdminMarkAbnormal(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
return r.adminUpdateStatus(adminID, listingID, req, meta, "abnormal", "abnormal", "listing.mark_abnormal", "商品已被标记异常", "你的租号商品已被后台标记异常,请联系客服处理。")
}
func (r *Repository) adminUpdateStatus(adminID uint64, listingID uint64, req AdminActionRequest, meta AuditMeta, listingStatus string, accountStatus string, action string, title string, content string) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.Transaction(func(tx *gorm.DB) error {
listing, account, err := r.findForReviewUpdate(tx, listingID)
if err != nil {
return err
}
if listing.Status == "rented" {
return ErrListingLocked
}
beforeListingStatus := listing.Status
beforeAccountStatus := account.Status
beforeReviewReason := listing.ReviewReason
listing.Status = listingStatus
listing.ReviewReason = req.Reason
if listingStatus != "published" {
listing.PublishedAt = nil
}
account.Status = accountStatus
if err := tx.Save(account).Error; err != nil {
return err
}
if err := tx.Save(listing).Error; err != nil {
return err
}
if err := notification.Append(tx, notification.Entry{
UserID: listing.OwnerID,
Type: "listing_admin",
Title: title,
Content: content,
BizType: "listing",
BizID: &listingID,
}); err != nil {
return err
}
if err := appendAuditLog(tx, adminID, action, "listing", listing.ID, meta, map[string]any{
"listing_id": listing.ID,
"account_id": account.ID,
"owner_id": listing.OwnerID,
"reason": req.Reason,
"before_listing_status": beforeListingStatus,
"after_listing_status": listing.Status,
"before_account_status": beforeAccountStatus,
"after_account_status": account.Status,
"before_review_reason": beforeReviewReason,
"after_review_reason": listing.ReviewReason,
}); err != nil {
return err
}
dto = toDTO(*account, *listing)
return nil
})
return dto, err
}
func (r *Repository) Approve(listingID uint64) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.Transaction(func(tx *gorm.DB) error {
@@ -411,6 +480,24 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
}
}
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
raw, err := json.Marshal(detail)
if err != nil {
return err
}
row := model.AuditLog{
ActorType: "admin",
ActorID: actorID,
Action: action,
BizType: bizType,
BizID: &bizID,
IP: meta.IP,
UserAgent: meta.UserAgent,
Detail: datatypes.JSON(raw),
}
return tx.Create(&row).Error
}
func IsNotFound(err error) bool {
return errors.Is(err, gorm.ErrRecordNotFound)
}
@@ -59,6 +59,33 @@ func (s *Service) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
return s.repo.ListAdmin(query)
}
func (s *Service) FindAdmin(id uint64) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
return s.repo.FindAdmin(id)
}
func (s *Service) AdminOffline(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
if req.Reason == "" {
return nil, ErrInvalidInput
}
return s.repo.AdminOffline(adminID, id, req, meta)
}
func (s *Service) AdminMarkAbnormal(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
if req.Reason == "" {
return nil, ErrInvalidInput
}
return s.repo.AdminMarkAbnormal(adminID, id, req, meta)
}
func (s *Service) Approve(id uint64) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable