feat: dynamic URL (#3444)

This commit is contained in:
shaohuzhang1 2025-07-01 15:43:00 +08:00 committed by GitHub
parent 9b89e8f75c
commit 2ad5883aef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 106 additions and 45 deletions

View File

@ -102,11 +102,15 @@ class Config(dict):
return self.get('LOG_LEVEL', 'DEBUG') return self.get('LOG_LEVEL', 'DEBUG')
def get_sandbox_python_package_paths(self): def get_sandbox_python_package_paths(self):
return self.get('SANDBOX_PYTHON_PACKAGE_PATHS', '/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages') return self.get('SANDBOX_PYTHON_PACKAGE_PATHS',
'/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages')
def get_admin_path(self): def get_admin_path(self):
return self.get('ADMIN_PATH', 'admin') return self.get('ADMIN_PATH', 'admin')
def get_chat_path(self):
return self.get('CHAT_PATH', '/chat')
def get_session_timeout(self): def get_session_timeout(self):
return int(self.get('SESSION_TIMEOUT', 28800)) return int(self.get('SESSION_TIMEOUT', 28800))

View File

@ -55,7 +55,7 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
] ]
REST_FRAMEWORK = { REST_FRAMEWORK = {
@ -63,7 +63,7 @@ REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication'] 'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication']
} }
STATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist')), (os.path.join(PROJECT_DIR, 'chat', 'dist'))] STATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist'))]
STATIC_ROOT = os.path.join(BASE_DIR.parent, 'static') STATIC_ROOT = os.path.join(BASE_DIR.parent, 'static')
ROOT_URLCONF = 'maxkb.urls' ROOT_URLCONF = 'maxkb.urls'
APPS_DIR = os.path.join(PROJECT_DIR, 'apps') APPS_DIR = os.path.join(PROJECT_DIR, 'apps')
@ -71,7 +71,7 @@ APPS_DIR = os.path.join(PROJECT_DIR, 'apps')
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ["apps/static/ui"], 'DIRS': ["apps/static/admin"],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [

View File

@ -16,7 +16,7 @@ Including another URLconf
""" """
import os import os
from django.http import HttpResponse from django.http import HttpResponse,HttpResponseRedirect
from django.urls import path, re_path, include from django.urls import path, re_path, include
from django.views import static from django.views import static
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
@ -25,16 +25,21 @@ from rest_framework import status
from common.result import Result from common.result import Result
from maxkb import settings from maxkb import settings
from maxkb.conf import PROJECT_DIR from maxkb.conf import PROJECT_DIR
from maxkb.const import CONFIG
admin_api_prefix = CONFIG.get_admin_path()[1:] + '/api/'
admin_ui_prefix = CONFIG.get_admin_path()
chat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/'
chat_ui_prefix = CONFIG.get_chat_path()
urlpatterns = [ urlpatterns = [
path("api/", include("users.urls")), path(admin_api_prefix, include("users.urls")),
path("api/", include("tools.urls")), path(admin_api_prefix, include("tools.urls")),
path("api/", include("models_provider.urls")), path(admin_api_prefix, include("models_provider.urls")),
path("api/", include("folders.urls")), path(admin_api_prefix, include("folders.urls")),
path("api/", include("knowledge.urls")), path(admin_api_prefix, include("knowledge.urls")),
path("api/", include("system_manage.urls")), path(admin_api_prefix, include("system_manage.urls")),
path("api/", include("application.urls")), path(admin_api_prefix, include("application.urls")),
path("chat/api/", include("chat.urls")), path(chat_api_prefix, include("chat.urls")),
path('oss/', include('oss.urls')), path('oss/', include('oss.urls')),
] ]
urlpatterns += [ urlpatterns += [
@ -54,12 +59,14 @@ def pro():
) )
# 暴露ui静态资源 # 暴露ui静态资源
urlpatterns.append( urlpatterns.append(
re_path(r'^ui/(?P<path>.*)$', static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "ui")}, re_path(rf"^{CONFIG.get_admin_path()[1:]}/(?P<path>.*)$", static.serve,
name='ui'), {'document_root': os.path.join(settings.STATIC_ROOT, "admin")},
name='admin'),
) )
# 暴露ui静态资源 # 暴露ui静态资源
urlpatterns.append( urlpatterns.append(
re_path(r'^chat/(?P<path>.*)$', static.serve, {'document_root': os.path.join(settings.STATIC_ROOT, "chat")}, re_path(rf'^{CONFIG.get_chat_path()[1:]}/(?P<path>.*)$', static.serve,
{'document_root': os.path.join(settings.STATIC_ROOT, "chat")},
name='chat'), name='chat'),
) )
@ -79,18 +86,26 @@ def page_not_found(request, exception):
""" """
页面不存在处理 页面不存在处理
""" """
if request.path.startswith("/api/"): if request.path.startswith(admin_ui_prefix + '/api/'):
return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND") return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND")
if request.path.startswith("/chat/api/"): if request.path.startswith(chat_ui_prefix + '/api/'):
return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND") return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message="HTTP_404_NOT_FOUND")
if request.path.startswith('/chat'): if request.path.startswith(chat_ui_prefix):
index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'chat', 'index.html') index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'chat', 'index.html')
content = get_index_html(index_path)
content.replace("prefix: '/chat'", f"prefix: {CONFIG.get_chat_path()}")
if not os.path.exists(index_path):
return HttpResponse("页面不存在", status=404)
return HttpResponse(content, status=200)
elif request.path.startswith(admin_ui_prefix):
index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'admin', 'index.html')
if not os.path.exists(index_path):
return HttpResponse("页面不存在", status=404)
content = get_index_html(index_path)
content = content.replace("prefix: '/admin'", f"prefix: '{CONFIG.get_admin_path()}'")
return HttpResponse(content, status=200)
else: else:
index_path = os.path.join(PROJECT_DIR, 'apps', "static", 'ui', 'index.html') return HttpResponseRedirect(admin_ui_prefix+'/')
if not os.path.exists(index_path):
return HttpResponse("页面不存在", status=404)
content = get_index_html(index_path)
return HttpResponse(content, status=200)
handler404 = page_not_found handler404 = page_not_found

View File

@ -7,6 +7,11 @@
<base target="_blank" /> <base target="_blank" />
<title>%VITE_APP_TITLE%</title> <title>%VITE_APP_TITLE%</title>
</head> </head>
<script>
window.MaxKB = {
prefix: '/admin',
}
</script>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>

View File

@ -7,6 +7,11 @@
<base target="_blank" /> <base target="_blank" />
<title>%VITE_APP_TITLE%</title> <title>%VITE_APP_TITLE%</title>
</head> </head>
<script>
window.MaxKB = {
prefix: '/chat',
}
</script>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/chat.ts"></script> <script type="module" src="/src/chat.ts"></script>

6
ui/env.d.ts vendored
View File

@ -1 +1,7 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface Window {
sendMessage: ?((message: string, other_params_data: any) => void)
MaxKB: {
prefix: string
}
}

6
ui/env/.env vendored
View File

@ -1,5 +1,5 @@
VITE_APP_NAME=ui VITE_APP_NAME=admin
VITE_BASE_PATH=/ui/ VITE_BASE_PATH=/admin/
VITE_APP_PORT=3000 VITE_APP_PORT=3000
VITE_APP_TITLE = 'MaxKB' VITE_APP_TITLE = 'MaxKB'
VITE_ENTRY="entry/system/index.html" VITE_ENTRY="admin.html"

2
ui/env/.env.chat vendored
View File

@ -2,4 +2,4 @@ VITE_APP_NAME=chat
VITE_BASE_PATH=/chat/ VITE_BASE_PATH=/chat/
VITE_APP_PORT=3001 VITE_APP_PORT=3001
VITE_APP_TITLE = 'MaxKB' VITE_APP_TITLE = 'MaxKB'
VITE_ENTRY="entry/chat/index.html" VITE_ENTRY="chat.html"

View File

@ -177,7 +177,8 @@ const open: (application_id: string, loading?: Ref<boolean>) => Promise<Result<s
* data * data
*/ */
const chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => { const chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => {
return postStream(`/api/chat_message/${chat_id}`, data) const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'
return postStream(`${prefix}/chat_message/${chat_id}`, data)
} }
/** /**
* *

View File

@ -40,7 +40,8 @@ const open: (loading?: Ref<boolean>) => Promise<Result<string>> = (loading) => {
* data * data
*/ */
const chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => { const chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => {
return postStream(`/chat/api/chat_message/${chat_id}`, data) const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/chat') + '/api'
return postStream(`${prefix}/chat_message/${chat_id}`, data)
} }
/** /**

View File

@ -4,12 +4,11 @@ import type { NProgress } from 'nprogress'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { Result } from '@/request/Result' import type { Result } from '@/request/Result'
import useStore from '@/stores' import useStore from '@/stores'
import router from '@/router'
import { ref, type WritableComputedRef } from 'vue' import { ref, type WritableComputedRef } from 'vue'
const axiosConfig = { const axiosConfig = {
baseURL: '/chat/api', baseURL: (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/chat') + '/api',
withCredentials: false, withCredentials: false,
timeout: 600000, timeout: 600000,
headers: {}, headers: {},

View File

@ -9,7 +9,7 @@ import router from '@/router'
import { ref, type WritableComputedRef } from 'vue' import { ref, type WritableComputedRef } from 'vue'
const axiosConfig = { const axiosConfig = {
baseURL: '/api', baseURL: (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api',
withCredentials: false, withCredentials: false,
timeout: 600000, timeout: 600000,
headers: {}, headers: {},

View File

@ -12,7 +12,7 @@ import useStore from '@/stores'
import { routes } from '@/router/chat/routes' import { routes } from '@/router/chat/routes'
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 }) NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(window.MaxKB?.prefix ? window.MaxKB?.prefix : import.meta.env.BASE_URL),
routes: routes, routes: routes,
}) })

View File

@ -7,14 +7,13 @@ import {
createWebHistory, createWebHistory,
type NavigationGuardNext, type NavigationGuardNext,
type RouteLocationNormalized, type RouteLocationNormalized,
type RouteRecordRaw,
type RouteRecordName, type RouteRecordName,
} from 'vue-router' } from 'vue-router'
import useStore from '@/stores' import useStore from '@/stores'
import { routes } from '@/router/routes' import { routes } from '@/router/routes'
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 }) NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(window.MaxKB?.prefix ? window.MaxKB?.prefix : import.meta.env.BASE_URL),
routes: routes, routes: routes,
}) })

View File

@ -1,3 +1,4 @@
import { model } from '@/permission/model'
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import type { ProxyOptions } from 'vite' import type { ProxyOptions } from 'vite'
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from 'vite'
@ -6,21 +7,39 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
import DefineOptions from 'unplugin-vue-define-options/vite' import DefineOptions from 'unplugin-vue-define-options/vite'
import path from 'path' import path from 'path'
import { createHtmlPlugin } from 'vite-plugin-html' import { createHtmlPlugin } from 'vite-plugin-html'
import fs from 'fs'
// import vueDevTools from 'vite-plugin-vue-devtools' // import vueDevTools from 'vite-plugin-vue-devtools'
const envDir = './env' const envDir = './env'
// 自定义插件:重命名入口文件
const renameHtmlPlugin = (outDir: string, entry: string) => {
return {
name: 'rename-html',
closeBundle: () => {
const buildDir = path.resolve(__dirname, outDir)
const oldFile = path.join(buildDir, entry)
const newFile = path.join(buildDir, 'index.html')
// 检查文件是否存在
if (fs.existsSync(oldFile)) {
// 删除已存在的 index.html
if (fs.existsSync(newFile)) {
fs.unlinkSync(newFile)
}
// 重命名文件
fs.renameSync(oldFile, newFile)
}
},
}
}
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig(({ mode }) => { export default defineConfig((conf: any) => {
const mode = conf.mode
const ENV = loadEnv(mode, envDir) const ENV = loadEnv(mode, envDir)
console.log(ENV)
const prefix = process.env.VITE_DYNAMIC_PREFIX || ENV.VITE_BASE_PATH
const proxyConf: Record<string, string | ProxyOptions> = {} const proxyConf: Record<string, string | ProxyOptions> = {}
proxyConf['/api'] = { proxyConf['/admin/api'] = {
// target: 'http://47.92.195.88:8080', // target: 'http://47.92.195.88:8080',
target: 'http://127.0.0.1:8080', target: 'http://127.0.0.1:8080',
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
} }
proxyConf['/oss'] = { proxyConf['/oss'] = {
target: 'http://127.0.0.1:8080', target: 'http://127.0.0.1:8080',
@ -46,12 +65,19 @@ export default defineConfig(({ mode }) => {
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'), rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
} }
return { return {
preflight: false, preflight: false,
lintOnSave: false, lintOnSave: false,
base: prefix, base: './',
envDir: envDir, envDir: envDir,
plugins: [vue(), vueJsx(), DefineOptions(), createHtmlPlugin({ template: ENV.VITE_ENTRY })], plugins: [
vue(),
vueJsx(),
DefineOptions(),
createHtmlPlugin({ template: ENV.VITE_ENTRY }),
renameHtmlPlugin(`dist${ENV.VITE_BASE_PATH}`, ENV.VITE_ENTRY),
],
server: { server: {
cors: true, cors: true,
host: '0.0.0.0', host: '0.0.0.0',
@ -62,7 +88,7 @@ export default defineConfig(({ mode }) => {
build: { build: {
outDir: `dist${ENV.VITE_BASE_PATH}`, outDir: `dist${ENV.VITE_BASE_PATH}`,
rollupOptions: { rollupOptions: {
input: path.resolve(__dirname, ENV.VITE_ENTRY), input: ENV.VITE_ENTRY,
}, },
}, },
resolve: { resolve: {