修复鉴权: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:
yml2213
2026-05-24 07:00:13 +08:00
parent 3631e70321
commit cdee93c7c5
61 changed files with 1735 additions and 722 deletions
+60 -20
View File
@@ -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 {