提交 bfe4669f authored 作者: 王鹏飞's avatar 王鹏飞

refactor: serve admin app from admin subpath

上级 265c9f40
VITE_API_URL = https://zijingebook.ezijing.com/api VITE_API_URL=https://zijingebook.ezijing.com/api/admin
VITE_API_PREFIX=/api/admin
VITE_API_PREFIX = /api VITE_ADMIN_API_TARGET=http://127.0.0.1:7419
VITE_API_WEBSOCKET_URL=wss://zijingebook.ezijing.com/ws
# VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com
VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com/ws
VITE_API_URL = https://zijingebook.ezijing.com/api VITE_API_URL=https://zijingebook.ezijing.com/api/admin
VITE_API_PREFIX=/api/admin
VITE_API_PREFIX = /api VITE_ADMIN_API_TARGET=http://127.0.0.1:7419
VITE_API_WEBSOCKET_URL=wss://zijingebook.ezijing.com/ws
# VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com
VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com/ws
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/formula-editor/dist/formula-editor.min.js"></script> <script src="%BASE_URL%formula-editor/dist/formula-editor.min.js"></script>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</body> </body>
</html> </html>
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
"build:dev": "vite build --mode development", "build:dev": "vite build --mode development",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore ./src", "lint": "eslint --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore ./src",
"test": "node --test src/store/storage-import.test.mjs" "test": "node --test src/store/storage-import.test.mjs src/utils/*.test.js"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.4.0", "@ant-design/icons": "^5.4.0",
......
...@@ -18,7 +18,7 @@ createRoot(document.getElementById('root')).render( ...@@ -18,7 +18,7 @@ createRoot(document.getElementById('root')).render(
<PersistGate loading={null} persistor={persistor}> <PersistGate loading={null} persistor={persistor}>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#ab1941' } }}> <ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#ab1941' } }}>
<App> <App>
<BrowserRouter> <BrowserRouter basename="/admin">
<MyApp /> <MyApp />
</BrowserRouter> </BrowserRouter>
</App> </App>
......
export const ADMIN_BASE_PATH = '/admin'
export const ADMIN_LOGIN_PATH = `${ADMIN_BASE_PATH}/login`
export function normalizeAdminApiPath(url = '') {
if (url === '/api/admin') return '/'
if (url.startsWith('/api/admin/')) return url.slice('/api/admin'.length)
if (url === '/api') return '/'
if (url.startsWith('/api/')) return url.slice('/api'.length)
return url
}
export function stripAdminGatewayPrefix(url = '') {
return url.replace('/api/admin', '')
}
import test from 'node:test'
import assert from 'node:assert/strict'
import {
ADMIN_LOGIN_PATH,
normalizeAdminApiPath,
stripAdminGatewayPrefix,
} from './adminPaths.js'
test('admin login stays under the admin base path', () => {
assert.equal(ADMIN_LOGIN_PATH, '/admin/login')
})
test('legacy /api requests are normalized for the admin gateway', () => {
assert.equal(normalizeAdminApiPath('/api/admin/user/login'), '/user/login')
assert.equal(normalizeAdminApiPath('/api/user/login'), '/user/login')
assert.equal(normalizeAdminApiPath('/user/login'), '/user/login')
})
test('gateway prefix is removed before backend signature calculation', () => {
assert.equal(
stripAdminGatewayPrefix('https://zijingebook.ezijing.com/api/admin/user/login'),
'https://zijingebook.ezijing.com/user/login'
)
})
...@@ -2,6 +2,7 @@ import axios from 'axios' ...@@ -2,6 +2,7 @@ import axios from 'axios'
import md5 from 'js-md5' import md5 from 'js-md5'
import qs from 'qs' import qs from 'qs'
import { Modal, notification } from 'antd' import { Modal, notification } from 'antd'
import { ADMIN_LOGIN_PATH, normalizeAdminApiPath } from '@/utils/adminPaths.js'
// import { sm4 as Sm4js } from 'evit-gm-crypt' // import { sm4 as Sm4js } from 'evit-gm-crypt'
// const sm4 = new Sm4js({ // const sm4 = new Sm4js({
...@@ -20,7 +21,7 @@ function showLoginModal() { ...@@ -20,7 +21,7 @@ function showLoginModal() {
title: '登录状态已过期,请重新登录', title: '登录状态已过期,请重新登录',
onOk() { onOk() {
localStorage.removeItem('kiwi.gpt.token') localStorage.removeItem('kiwi.gpt.token')
location.href = '/login' location.href = ADMIN_LOGIN_PATH
}, },
}) })
} }
...@@ -31,14 +32,15 @@ function alphabeticalSort(a, b) { ...@@ -31,14 +32,15 @@ function alphabeticalSort(a, b) {
} }
const httpRequest = axios.create({ const httpRequest = axios.create({
// baseURL: import.meta.env.VITE_API_PREFIX, baseURL: import.meta.env.VITE_API_PREFIX,
// timeout: 60000,
withCredentials: true, withCredentials: true,
}) })
// 请求拦截器 // 请求拦截器
httpRequest.interceptors.request.use( httpRequest.interceptors.request.use(
(config) => { (config) => {
config.url = normalizeAdminApiPath(config.url)
const token = getToken() const token = getToken()
// 应用设置 // 应用设置
const appId = 'TzEU5jPk2tu80266' const appId = 'TzEU5jPk2tu80266'
...@@ -85,7 +87,7 @@ httpRequest.interceptors.response.use( ...@@ -85,7 +87,7 @@ httpRequest.interceptors.response.use(
(response) => { (response) => {
const { data } = response const { data } = response
if (data.code === 401 || data.code === 403) { if (data.code === 401 || data.code === 403) {
if (location.pathname !== '/login') { if (location.pathname !== ADMIN_LOGIN_PATH) {
showLoginModal() showLoginModal()
} }
return Promise.reject(data) return Promise.reject(data)
......
...@@ -4,6 +4,7 @@ import { message, Modal, notification } from 'antd' ...@@ -4,6 +4,7 @@ import { message, Modal, notification } from 'antd'
import md5 from 'js-md5' import md5 from 'js-md5'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { sortObjASCII } from '@/utils/common.js' import { sortObjASCII } from '@/utils/common.js'
import { ADMIN_LOGIN_PATH, normalizeAdminApiPath, stripAdminGatewayPrefix } from '@/utils/adminPaths.js'
import qs from 'qs' import qs from 'qs'
// import { sm4 as Sm4js } from 'evit-gm-crypt' // import { sm4 as Sm4js } from 'evit-gm-crypt'
...@@ -38,6 +39,8 @@ function getToken() { ...@@ -38,6 +39,8 @@ function getToken() {
// 请求拦截器 // 请求拦截器
axios.interceptors.request.use( axios.interceptors.request.use(
(config) => { (config) => {
config.url = normalizeAdminApiPath(config.url)
// 登录流程控制中,根据本地是否存在token判断用户的登录情况 // 登录流程控制中,根据本地是否存在token判断用户的登录情况
// 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
// 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码 // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
...@@ -68,8 +71,7 @@ axios.interceptors.request.use( ...@@ -68,8 +71,7 @@ axios.interceptors.request.use(
if (config.url.indexOf('.tiangong.cn') > -1) { if (config.url.indexOf('.tiangong.cn') > -1) {
s = config.url s = config.url
} else { } else {
s = import.meta.env.VITE_API_URL + config.url s = stripAdminGatewayPrefix(import.meta.env.VITE_API_URL + config.url)
s = s.replace('/api', '')
} }
signData.url = decodeURIComponent(s) signData.url = decodeURIComponent(s)
// signData.url = 'http: //zijing.com/index.php/user/user/getList' // signData.url = 'http: //zijing.com/index.php/user/user/getList'
...@@ -130,7 +132,7 @@ axios.interceptors.response.use( ...@@ -130,7 +132,7 @@ axios.interceptors.response.use(
if (code === 401 || code === 403) { if (code === 401 || code === 403) {
if (!axios.errorNotified) { if (!axios.errorNotified) {
axios.errorNotified = true // 添加标志,表示通知已经弹出 axios.errorNotified = true // 添加标志,表示通知已经弹出
if (pathname === '/login') { if (pathname === ADMIN_LOGIN_PATH) {
return return
} }
Modal.warning({ Modal.warning({
...@@ -138,7 +140,7 @@ axios.interceptors.response.use( ...@@ -138,7 +140,7 @@ axios.interceptors.response.use(
// content: '请重新登录', // content: '请重新登录',
onOk() { onOk() {
localStorage.removeItem('kiwi.gpt.token') localStorage.removeItem('kiwi.gpt.token')
window.location.pathname = '/login' window.location.pathname = ADMIN_LOGIN_PATH
}, },
}) })
cancelPending(response.config) // 取消后续请求 cancelPending(response.config) // 取消后续请求
...@@ -163,7 +165,7 @@ axios.interceptors.response.use( ...@@ -163,7 +165,7 @@ axios.interceptors.response.use(
content: '请重新登录', content: '请重新登录',
onOk() { onOk() {
localStorage.removeItem('kiwi.gpt.token') localStorage.removeItem('kiwi.gpt.token')
window.location.href = '/login' window.location.href = ADMIN_LOGIN_PATH
}, },
}) })
} }
...@@ -178,7 +180,7 @@ axios.interceptors.response.use( ...@@ -178,7 +180,7 @@ axios.interceptors.response.use(
content: '请重新登录', content: '请重新登录',
onOk() { onOk() {
localStorage.removeItem('kiwi.gpt.token') localStorage.removeItem('kiwi.gpt.token')
window.location.href = '/login' window.location.href = ADMIN_LOGIN_PATH
}, },
}) })
} }
......
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert' import mkcert from 'vite-plugin-mkcert'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(() => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const adminApiTarget = env.VITE_ADMIN_API_TARGET || 'http://127.0.0.1:7419'
return { return {
base: '/admin/',
plugins: [react(), mkcert()], plugins: [react(), mkcert()],
server: { server: {
open: true, open: '/admin/',
host: 'dev.ezijing.com', host: 'dev.ezijing.com',
port: 5174,
strictPort: true,
proxy: { proxy: {
// '/api/tiangong': { '/api/admin': {
// target: 'https://api.singularity-ai.com', target: adminApiTarget,
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/tiangong/, ''),
// },
// '/api/deepseek': {
// target: 'https://api.deepseek.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/deepseek/, ''),
// },
// '/api/qwen': {
// target: 'https://dashscope.aliyuncs.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/qwen/, ''),
// },
// '/api/qianfan': {
// target: 'https://aip.baidubce.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/qianfan/, ''),
// },
// '/api/wenku': {
// target: 'https://wenchain.baidu.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/wenku/, '/'),
// },
// '/api/qianfan': {
// target: 'https://qianfan.baidubce.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/qianfan/, ''),
// },
// '/api/volcano': {
// target: 'https://ark.cn-beijing.volces.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/volcano/, ''),
// },
// '/api/xiling': {
// target: 'https://open.xiling.baidu.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/xiling/, ''),
// },
// '/api/volcano_file': {
// target: 'https://ark-content-generation-cn-beijing.tos-cn-beijing.volces.com',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/volcano_file/, ''),
// },
'/api': {
// target: 'http://localhost:7419',
target: 'https://zijingebook.ezijing.com',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
rewrite: path => path.replace(/^\/api\/admin/, ''),
},
}, },
}, },
build: {
outDir: '../../dist/admin',
emptyOutDir: true,
}, },
resolve: { resolve: {
alias: { alias: {
...@@ -72,9 +37,7 @@ export default defineConfig(() => { ...@@ -72,9 +37,7 @@ export default defineConfig(() => {
}, },
css: { css: {
preprocessorOptions: { preprocessorOptions: {
less: { less: { javascriptEnabled: true },
javascriptEnabled: true,
},
}, },
}, },
} }
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论