From 75140fc3bbfbc815b2439a63595b51499b3a0f49 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jun 2026 18:18:10 +0800 Subject: [PATCH] migrate frontend --- .../frontend/lib/services/core/api-client.ts | 21 ++++++++++++++++++- .../internal/apps/openflare/compat/auth.go | 7 +++++-- docs/changelog/index.md | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Wavelet/frontend/lib/services/core/api-client.ts b/Wavelet/frontend/lib/services/core/api-client.ts index 384d49d6..e86b88c9 100644 --- a/Wavelet/frontend/lib/services/core/api-client.ts +++ b/Wavelet/frontend/lib/services/core/api-client.ts @@ -1,6 +1,6 @@ import axios, {AxiosError, AxiosResponse, CancelTokenSource, InternalAxiosRequestConfig} from 'axios'; import {toast} from 'sonner'; -import {apiConfig} from './config'; +import {apiConfig, getApiBaseUrl} from './config'; import { ApiErrorBase, ForbiddenError, @@ -21,11 +21,24 @@ const apiClient = axios.create({ baseURL: apiConfig.baseURL, timeout: apiConfig.timeout, withCredentials: apiConfig.withCredentials, + // Fail fast on slash redirect loops between Next dev proxy and Gin legacy routes. + maxRedirects: 5, headers: { 'Content-Type': 'application/json', }, }); +/** Normalize legacy /api/* paths: strip trailing slash to avoid 308/301 loops in dev proxy. */ +function normalizeApiPath(url?: string): string | undefined { + if (!url || !url.startsWith('/api/')) { + return url; + } + if (url.length > 5 && url.endsWith('/')) { + return url.replace(/\/+$/, ''); + } + return url; +} + /** * 请求取消令牌存储 */ @@ -66,6 +79,12 @@ function getRequestKey(config: { method?: string; url?: string; data?: unknown } */ apiClient.interceptors.request.use( (config: InternalAxiosRequestConfig) => { + // Browser must use same-origin relative URLs so Session Cookie hits Next rewrite (3010→3000). + if (typeof window !== 'undefined') { + config.baseURL = getApiBaseUrl(); + } + config.url = normalizeApiPath(config.url); + const requestKey = getRequestKey(config); const source = axios.CancelToken.source(); config.cancelToken = source.token; diff --git a/Wavelet/internal/apps/openflare/compat/auth.go b/Wavelet/internal/apps/openflare/compat/auth.go index d8091775..1660664c 100644 --- a/Wavelet/internal/apps/openflare/compat/auth.go +++ b/Wavelet/internal/apps/openflare/compat/auth.go @@ -28,7 +28,7 @@ func RequireRole(minRole int) gin.HandlerFunc { c.Abort() return } - role := resolveRole(user) + role := resolveRole(c, user) if role < minRole { Fail(c, "无权进行此操作,权限不足") c.Abort() @@ -50,10 +50,13 @@ func AdminAuth() gin.HandlerFunc { return RequireRole(RoleAdminUser) } // RootAuth requires role >= RootUser. func RootAuth() gin.HandlerFunc { return RequireRole(RoleRootUser) } -func resolveRole(user *model.User) int { +func resolveRole(c *gin.Context, user *model.User) int { if user == nil { return 0 } + if tokenAdmin, ok := oauth.GetFromContext[bool](c, oauth.TokenAdminKey); ok && tokenAdmin { + return RoleRootUser + } if user.IsAdmin { return RoleRootUser } diff --git a/docs/changelog/index.md b/docs/changelog/index.md index e2f12440..4082b382 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -43,7 +43,7 @@ sidebar: false - 修复静态导出构建中 `useSearchParams` 未包裹 Suspense 的问题;`pnpm build:embed` 全量 46 页通过。 - 补全前端迁移收尾:新增 `/openflare/about` 关于页与 `AboutService`;补全 `UpdateService` 升级流程(在线升级、手动上传、WebSocket 日志流)及顶栏版本升级入口。 - 前端 UI 打磨:节点/WAF/DNS/配置清理等 Dialog 补 RHF+Zod;代理规则详情与访问日志 Tab 补 error 态;`pnpm build:embed` 增至 47 静态页。 -- 修复开发模式下 `/api/*` 尾斜杠引发 Next.js 308 与 Gin 301 循环重定向:前端 Service 规范化路径、Next `skipTrailingSlashRedirect`、后端双路径注册。 +- 修复开发模式下 `/api/*` 尾斜杠引发 Next.js 308 与 Gin 301 循环重定向:前端 Service 与 `api-client` 请求拦截器规范化路径、强制浏览器同源代理、Next `skipTrailingSlashRedirect`、后端 `RegisterCollection` 双路径注册;Access Token 管理员权限校验对齐 `token_admin`。 ## [v2.3.4] - 2026-06-17