修复鉴权:401拦截器加refresh重试 + admin refresh接口 + 路由守卫完善
根因:access_token每2小时过期,前端收到401直接清token跳登录,没有用refresh_token续期 后端修复: - adminauth模块新增 POST /admin/auth/refresh 接口 - Service 注入 JWTManager,支持 admin refresh token 换新 token pair - Refresh 方法验证 subjectType=admin + tokenType=refresh 前端修复: - 401 拦截器核心改造:收到401先调 refresh 接口续期 - 加 isRefreshing 锁 + pendingRequests 队列防止并发刷新 - refresh 用原生 axios.post 避免拦截器递归 - 成功则更新 localStorage + 重试原请求,失败才清 token 跳登录 - 排除 /auth/refresh 自身避免死循环 - 支持 /admin/ 请求独立 token 管理 - auth.ts/adminAuth.ts 新增手动 refreshUserToken/refreshAdminSession - 路由守卫给所有需登录路由添加 meta.requiresAuth - 守卫同时支持 PC 端 /login 和移动端 /m/login
This commit is contained in:
@@ -56,12 +56,19 @@ type AdminListQuery struct {
|
||||
OwnerID uint64
|
||||
Status string
|
||||
ReviewStatus string
|
||||
Limit int
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type AdminActionRequest struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
type PaginatedResult struct {
|
||||
Items []ListingDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type AuditMeta struct {
|
||||
IP string
|
||||
|
||||
@@ -82,12 +82,13 @@ func (h *Handler) SubmitReview(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) ListPendingReview(c *gin.Context) {
|
||||
items, err := h.service.ListPendingReview()
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListPendingReview(page, pageSize)
|
||||
if err != nil {
|
||||
writeListingError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) ListAdmin(c *gin.Context) {
|
||||
@@ -95,12 +96,12 @@ func (h *Handler) ListAdmin(c *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := h.service.ListAdmin(query)
|
||||
result, err := h.service.ListAdmin(query)
|
||||
if err != nil {
|
||||
writeListingError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) FindAdmin(c *gin.Context) {
|
||||
@@ -196,12 +197,13 @@ func (h *Handler) Offline(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) ListPublic(c *gin.Context) {
|
||||
items, err := h.service.ListPublic()
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListPublic(page, pageSize)
|
||||
if err != nil {
|
||||
writeListingError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) FindPublic(c *gin.Context) {
|
||||
@@ -276,12 +278,13 @@ func (h *Handler) ListMine(c *gin.Context) {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
items, err := h.service.ListMine(ownerID)
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListMine(ownerID, page, pageSize)
|
||||
if err != nil {
|
||||
writeListingError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) FindMine(c *gin.Context) {
|
||||
@@ -339,19 +342,27 @@ func parseAdminListQuery(c *gin.Context) (AdminListQuery, bool) {
|
||||
}
|
||||
query.OwnerID = value
|
||||
}
|
||||
if raw := c.Query("limit"); raw != "" {
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value <= 0 {
|
||||
response.BadRequest(c, "查询条数不正确")
|
||||
return query, false
|
||||
}
|
||||
query.Limit = value
|
||||
}
|
||||
query.Status = c.Query("status")
|
||||
query.ReviewStatus = c.Query("review_status")
|
||||
query.Page, query.PageSize = parsePagination(c)
|
||||
return query, true
|
||||
}
|
||||
|
||||
func parsePagination(c *gin.Context) (int, int) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
func writeListingError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
|
||||
@@ -158,26 +158,41 @@ func (r *Repository) SubmitReview(ownerID uint64, listingID uint64, reviewRequir
|
||||
return dto, err
|
||||
}
|
||||
|
||||
func (r *Repository) ListPendingReview() ([]ListingDTO, error) {
|
||||
func (r *Repository) ListPendingReview(page, pageSize int) (*PaginatedResult, error) {
|
||||
conditions := r.db.Model(&model.RentalListing{}).Where("review_status = ?", "pending")
|
||||
var total int64
|
||||
if err := conditions.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var rows []listingRow
|
||||
err := r.baseQuery().
|
||||
err := r.listQuery().
|
||||
Where("l.review_status = ?", "pending").
|
||||
Order("l.updated_at ASC, l.id ASC").
|
||||
Limit(200).
|
||||
Offset(offset).Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rowsToDTO(rows), nil
|
||||
return &PaginatedResult{Items: rowsToDTO(rows), Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
|
||||
limit := query.Limit
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 200
|
||||
func (r *Repository) ListAdmin(query AdminListQuery) (*PaginatedResult, error) {
|
||||
conditions := r.db.Model(&model.RentalListing{})
|
||||
if query.OwnerID > 0 {
|
||||
conditions = conditions.Where("owner_id = ?", query.OwnerID)
|
||||
}
|
||||
|
||||
db := r.baseQuery()
|
||||
if query.Status != "" {
|
||||
conditions = conditions.Where("status = ?", query.Status)
|
||||
}
|
||||
if query.ReviewStatus != "" {
|
||||
conditions = conditions.Where("review_status = ?", query.ReviewStatus)
|
||||
}
|
||||
var total int64
|
||||
if err := conditions.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db := r.listQuery()
|
||||
if query.OwnerID > 0 {
|
||||
db = db.Where("l.owner_id = ?", query.OwnerID)
|
||||
}
|
||||
@@ -187,13 +202,13 @@ func (r *Repository) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
|
||||
if query.ReviewStatus != "" {
|
||||
db = db.Where("l.review_status = ?", query.ReviewStatus)
|
||||
}
|
||||
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
var rows []listingRow
|
||||
err := db.Order("l.id DESC").Limit(limit).Scan(&rows).Error
|
||||
err := db.Order("l.id DESC").Offset(offset).Limit(query.PageSize).Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rowsToDTO(rows), nil
|
||||
return &PaginatedResult{Items: rowsToDTO(rows), Total: total, Page: query.Page, PageSize: query.PageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindAdmin(listingID uint64) (*ListingDTO, error) {
|
||||
@@ -358,29 +373,43 @@ func (r *Repository) Offline(ownerID uint64, listingID uint64) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) ListPublic() ([]ListingDTO, error) {
|
||||
func (r *Repository) ListPublic(page, pageSize int) (*PaginatedResult, error) {
|
||||
conditions := r.db.Model(&model.RentalListing{}).Where("status = ? AND review_status = ?", "published", "approved")
|
||||
var total int64
|
||||
if err := conditions.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var rows []listingRow
|
||||
err := r.baseQuery().
|
||||
err := r.listQuery().
|
||||
Where("l.status = ? AND l.review_status = ?", "published", "approved").
|
||||
Order("l.published_at DESC, l.id DESC").
|
||||
Limit(100).
|
||||
Offset(offset).Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return publicListings(rowsToDTO(rows)), nil
|
||||
items := publicListings(rowsToDTO(rows))
|
||||
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
||||
func (r *Repository) ListMine(ownerID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
conditions := r.db.Model(&model.RentalListing{}).Where("owner_id = ?", ownerID)
|
||||
var total int64
|
||||
if err := conditions.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var rows []listingRow
|
||||
err := r.baseQuery().
|
||||
err := r.listQuery().
|
||||
Where("l.owner_id = ?", ownerID).
|
||||
Order("l.id DESC").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rowsToDTO(rows), nil
|
||||
return &PaginatedResult{Items: rowsToDTO(rows), Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindPublic(id uint64) (*ListingDTO, error) {
|
||||
@@ -449,6 +478,17 @@ func (r *Repository) baseQuery() *gorm.DB {
|
||||
Joins("LEFT JOIN users AS u ON u.id = l.owner_id")
|
||||
}
|
||||
|
||||
// listQuery returns a SELECT-optimized query for list endpoints (excludes heavy fields like description, screenshot_urls, asset_summary).
|
||||
func (r *Repository) listQuery() *gorm.DB {
|
||||
return r.db.Table("rental_listings AS l").
|
||||
Select(`l.id, l.account_id, l.owner_id, l.price_hourly, l.price_daily, l.price_weekly, l.deposit_amount,
|
||||
l.status, l.review_status, l.review_reason, l.published_at, l.created_at, l.updated_at,
|
||||
a.title, a.game_name, a.server_region, a.login_platform, a.rank_level, a.haf_coin_amount,
|
||||
COALESCE(u.phone, '') AS owner_phone, COALESCE(u.nickname, '') AS owner_nickname`).
|
||||
Joins("JOIN game_accounts AS a ON a.id = l.account_id").
|
||||
Joins("LEFT JOIN users AS u ON u.id = l.owner_id")
|
||||
}
|
||||
|
||||
func (r *Repository) findForReviewUpdate(tx *gorm.DB, listingID uint64) (*model.RentalListing, *model.GameAccount, error) {
|
||||
var listing model.RentalListing
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, listingID).Error; err != nil {
|
||||
|
||||
@@ -93,14 +93,14 @@ func (s *Service) SubmitReview(ownerID uint64, id uint64) (*ListingDTO, error) {
|
||||
return s.repo.SubmitReview(ownerID, id, reviewRequired)
|
||||
}
|
||||
|
||||
func (s *Service) ListPendingReview() ([]ListingDTO, error) {
|
||||
func (s *Service) ListPendingReview(page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListPendingReview()
|
||||
return s.repo.ListPendingReview(page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
|
||||
func (s *Service) ListAdmin(query AdminListQuery) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
@@ -158,18 +158,18 @@ func (s *Service) Offline(ownerID uint64, id uint64) error {
|
||||
return s.repo.Offline(ownerID, id)
|
||||
}
|
||||
|
||||
func (s *Service) ListPublic() ([]ListingDTO, error) {
|
||||
func (s *Service) ListPublic(page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListPublic()
|
||||
return s.repo.ListPublic(page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
||||
func (s *Service) ListMine(ownerID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListMine(ownerID)
|
||||
return s.repo.ListMine(ownerID, page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) FindPublic(id uint64) (*ListingDTO, error) {
|
||||
|
||||
Reference in New Issue
Block a user