feat: 拆分新旧前端访问端口
This commit is contained in:
@@ -71,7 +71,8 @@ cd ..
|
|||||||
访问地址:
|
访问地址:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://localhost:9081
|
新版前端:http://localhost:9081
|
||||||
|
旧版前端:http://localhost:9082
|
||||||
```
|
```
|
||||||
|
|
||||||
开发前端时可运行:
|
开发前端时可运行:
|
||||||
|
|||||||
+110
-37
@@ -1,10 +1,11 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import socket
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI, HTTPException, Request
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse, JSONResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
|
|
||||||
from app import state
|
from app import state
|
||||||
from app.api.routers import auth, cache, config, database, health, history, script, service, tasks, upload
|
from app.api.routers import auth, cache, config, database, health, history, script, service, tasks, upload
|
||||||
@@ -13,12 +14,15 @@ from app.config import BASE_DIR
|
|||||||
|
|
||||||
|
|
||||||
APP_VERSION = "2.0.2"
|
APP_VERSION = "2.0.2"
|
||||||
|
APP_HOST = "0.0.0.0"
|
||||||
|
NEW_FRONTEND_PORT = 9081
|
||||||
|
OLD_FRONTEND_PORT = 9082
|
||||||
FRONTEND_DIST_DIR = BASE_DIR / "frontend" / "dist"
|
FRONTEND_DIST_DIR = BASE_DIR / "frontend" / "dist"
|
||||||
FRONTEND_ASSETS_DIR = FRONTEND_DIST_DIR / "assets"
|
|
||||||
FRONTEND_OLD_DIR = BASE_DIR / "frontend_old"
|
FRONTEND_OLD_DIR = BASE_DIR / "frontend_old"
|
||||||
|
FrontendMode = Literal["new", "old", "split"]
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> FastAPI:
|
def create_app(frontend_mode: FrontendMode = "new") -> FastAPI:
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="CapacityReport",
|
title="CapacityReport",
|
||||||
description="容量报表数据处理系统",
|
description="容量报表数据处理系统",
|
||||||
@@ -38,7 +42,7 @@ def create_app() -> FastAPI:
|
|||||||
|
|
||||||
app.middleware("http")(jwt_middleware)
|
app.middleware("http")(jwt_middleware)
|
||||||
register_routes(app)
|
register_routes(app)
|
||||||
register_frontend(app)
|
register_frontend(app, frontend_mode)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@@ -73,61 +77,130 @@ def register_routes(app: FastAPI) -> None:
|
|||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|
||||||
|
|
||||||
def register_frontend(app: FastAPI) -> None:
|
def register_frontend(app: FastAPI, frontend_mode: FrontendMode) -> None:
|
||||||
@app.get("/old", include_in_schema=False)
|
@app.get("/old", include_in_schema=False)
|
||||||
async def serve_old_frontend():
|
async def serve_old_frontend(request: Request):
|
||||||
index_file = FRONTEND_OLD_DIR / "index.html"
|
if not _should_serve_old_frontend(request, frontend_mode):
|
||||||
if index_file.exists():
|
raise HTTPException(status_code=404, detail=f"旧版前端请访问 {OLD_FRONTEND_PORT} 端口")
|
||||||
return FileResponse(str(index_file))
|
return _serve_old_index()
|
||||||
return JSONResponse(status_code=503, content={"detail": "旧版前端入口文件不存在"})
|
|
||||||
|
|
||||||
if FRONTEND_OLD_DIR.exists():
|
@app.get("/old/{path:path}", include_in_schema=False)
|
||||||
app.mount("/old", StaticFiles(directory=str(FRONTEND_OLD_DIR), html=True), name="frontend_old")
|
async def serve_old_frontend_file(request: Request, path: str):
|
||||||
|
if not _should_serve_old_frontend(request, frontend_mode):
|
||||||
if FRONTEND_ASSETS_DIR.exists():
|
raise HTTPException(status_code=404, detail=f"旧版前端请访问 {OLD_FRONTEND_PORT} 端口")
|
||||||
app.mount("/assets", StaticFiles(directory=str(FRONTEND_ASSETS_DIR)), name="assets")
|
return _serve_old_file(path)
|
||||||
|
|
||||||
@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
@app.get("/{path:path}", include_in_schema=False)
|
@app.get("/{path:path}", include_in_schema=False)
|
||||||
async def serve_spa(path: str = ""):
|
async def serve_frontend(request: Request, path: str = ""):
|
||||||
if path.startswith("api/"):
|
if path.startswith("api/"):
|
||||||
raise HTTPException(status_code=404, detail="接口不存在")
|
raise HTTPException(status_code=404, detail="接口不存在")
|
||||||
|
|
||||||
if not FRONTEND_DIST_DIR.exists():
|
if _should_serve_old_frontend(request, frontend_mode):
|
||||||
return JSONResponse(
|
return _serve_old_frontend_path(path)
|
||||||
status_code=503,
|
|
||||||
content={
|
|
||||||
"detail": "前端构建产物不存在,请先执行 cd frontend && npm install && npm run build"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
requested_file = _safe_frontend_file(path)
|
return _serve_new_frontend_path(path)
|
||||||
if requested_file and requested_file.exists() and requested_file.is_file():
|
|
||||||
return FileResponse(str(requested_file))
|
|
||||||
|
|
||||||
index_file = FRONTEND_DIST_DIR / "index.html"
|
|
||||||
if index_file.exists():
|
|
||||||
return FileResponse(str(index_file))
|
|
||||||
|
|
||||||
return JSONResponse(status_code=503, content={"detail": "前端入口文件不存在"})
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_frontend_file(path: str) -> Path | None:
|
def _should_serve_old_frontend(request: Request, frontend_mode: FrontendMode) -> bool:
|
||||||
|
if frontend_mode == "old":
|
||||||
|
return True
|
||||||
|
if frontend_mode == "new":
|
||||||
|
return False
|
||||||
|
return _request_port(request) == OLD_FRONTEND_PORT
|
||||||
|
|
||||||
|
|
||||||
|
def _request_port(request: Request) -> int | None:
|
||||||
|
server = request.scope.get("server")
|
||||||
|
if isinstance(server, tuple) and len(server) >= 2:
|
||||||
|
return server[1]
|
||||||
|
return request.url.port
|
||||||
|
|
||||||
|
|
||||||
|
def _serve_new_frontend_path(path: str):
|
||||||
|
if not FRONTEND_DIST_DIR.exists():
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=503,
|
||||||
|
content={"detail": "前端构建产物不存在,请先执行 cd frontend && npm install && npm run build"},
|
||||||
|
)
|
||||||
|
|
||||||
|
requested_file = _safe_file(FRONTEND_DIST_DIR, path)
|
||||||
|
if requested_file and requested_file.exists() and requested_file.is_file():
|
||||||
|
return FileResponse(str(requested_file))
|
||||||
|
|
||||||
|
index_file = FRONTEND_DIST_DIR / "index.html"
|
||||||
|
if index_file.exists():
|
||||||
|
return FileResponse(str(index_file))
|
||||||
|
|
||||||
|
return JSONResponse(status_code=503, content={"detail": "前端入口文件不存在"})
|
||||||
|
|
||||||
|
|
||||||
|
def _serve_old_frontend_path(path: str):
|
||||||
|
if not path:
|
||||||
|
return _serve_old_index()
|
||||||
|
|
||||||
|
requested_file = _safe_file(FRONTEND_OLD_DIR, path)
|
||||||
|
if requested_file and requested_file.exists() and requested_file.is_file():
|
||||||
|
return FileResponse(str(requested_file))
|
||||||
|
|
||||||
|
return _serve_old_index()
|
||||||
|
|
||||||
|
|
||||||
|
def _serve_old_file(path: str):
|
||||||
|
requested_file = _safe_file(FRONTEND_OLD_DIR, path)
|
||||||
|
if requested_file and requested_file.exists() and requested_file.is_file():
|
||||||
|
return FileResponse(str(requested_file))
|
||||||
|
raise HTTPException(status_code=404, detail="旧版前端文件不存在")
|
||||||
|
|
||||||
|
|
||||||
|
def _serve_old_index():
|
||||||
|
if not FRONTEND_OLD_DIR.exists():
|
||||||
|
return JSONResponse(status_code=503, content={"detail": "旧版前端目录不存在"})
|
||||||
|
|
||||||
|
index_file = FRONTEND_OLD_DIR / "index.html"
|
||||||
|
if index_file.exists():
|
||||||
|
return FileResponse(str(index_file))
|
||||||
|
|
||||||
|
return JSONResponse(status_code=503, content={"detail": "旧版前端入口文件不存在"})
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_file(root: Path, path: str) -> Path | None:
|
||||||
if not path:
|
if not path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
requested = (FRONTEND_DIST_DIR / path).resolve()
|
root = root.resolve()
|
||||||
|
requested = (root / path).resolve()
|
||||||
try:
|
try:
|
||||||
requested.relative_to(FRONTEND_DIST_DIR.resolve())
|
requested.relative_to(root)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
return requested
|
return requested
|
||||||
|
|
||||||
|
|
||||||
app = create_app()
|
def _create_listen_socket(port: int) -> socket.socket:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.bind((APP_HOST, port))
|
||||||
|
sock.listen(2048)
|
||||||
|
sock.set_inheritable(True)
|
||||||
|
return sock
|
||||||
|
|
||||||
|
|
||||||
|
def run_split_frontend_servers() -> None:
|
||||||
|
sockets = [_create_listen_socket(NEW_FRONTEND_PORT), _create_listen_socket(OLD_FRONTEND_PORT)]
|
||||||
|
config = uvicorn.Config(split_app, host=APP_HOST, port=NEW_FRONTEND_PORT, reload=False)
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
server.run(sockets=sockets)
|
||||||
|
|
||||||
|
|
||||||
|
app = create_app("new")
|
||||||
|
old_app = create_app("old")
|
||||||
|
split_app = create_app("split")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(f"CapacityReport v{APP_VERSION}")
|
print(f"CapacityReport v{APP_VERSION}")
|
||||||
print(f"配置更新时间: {state.config.update}")
|
print(f"配置更新时间: {state.config.update}")
|
||||||
uvicorn.run("app.main:app", host="0.0.0.0", port=9081, reload=False)
|
print(f"新版前端: http://localhost:{NEW_FRONTEND_PORT}")
|
||||||
|
print(f"旧版前端: http://localhost:{OLD_FRONTEND_PORT}")
|
||||||
|
run_split_frontend_servers()
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ COPY --from=frontend-build /frontend/dist /app/frontend/dist
|
|||||||
|
|
||||||
RUN chmod +x /app/start.sh 2>/dev/null || true
|
RUN chmod +x /app/start.sh 2>/dev/null || true
|
||||||
|
|
||||||
EXPOSE 9081
|
EXPOSE 9081 9082
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||||
CMD curl -f http://localhost:9081/health || exit 1
|
CMD curl -f http://localhost:9081/health || exit 1
|
||||||
|
|||||||
+1
-1
@@ -55,4 +55,4 @@ sh update.sh
|
|||||||
- 默认镜像版本
|
- 默认镜像版本
|
||||||
- 版本要求范围
|
- 版本要求范围
|
||||||
- 数据库账号密码
|
- 数据库账号密码
|
||||||
- 端口映射
|
- 端口映射:新版前端 `19081 -> 9081`,旧版前端 `19082 -> 9082`
|
||||||
|
|||||||
+12
-5
@@ -36,8 +36,12 @@ MYSQL_HOST = "capacity-mysql"
|
|||||||
MYSQL_PORT = 3306
|
MYSQL_PORT = 3306
|
||||||
|
|
||||||
# 应用端口映射
|
# 应用端口映射
|
||||||
APP_PORT_HOST = 19081
|
APP_NEW_PORT_HOST = 19081
|
||||||
APP_PORT_CONTAINER = 9081
|
APP_NEW_PORT_CONTAINER = 9081
|
||||||
|
APP_OLD_PORT_HOST = 19082
|
||||||
|
APP_OLD_PORT_CONTAINER = 9082
|
||||||
|
APP_PORT_HOST = APP_NEW_PORT_HOST
|
||||||
|
APP_PORT_CONTAINER = APP_NEW_PORT_CONTAINER
|
||||||
MYSQL_PORT_HOST = 13306
|
MYSQL_PORT_HOST = 13306
|
||||||
MYSQL_PORT_CONTAINER = 3306
|
MYSQL_PORT_CONTAINER = 3306
|
||||||
|
|
||||||
@@ -294,7 +298,8 @@ check_port() {{
|
|||||||
fi
|
fi
|
||||||
{{}}
|
{{}}
|
||||||
|
|
||||||
check_port {APP_PORT_HOST} "App"
|
check_port {APP_NEW_PORT_HOST} "New frontend"
|
||||||
|
check_port {APP_OLD_PORT_HOST} "Old frontend"
|
||||||
check_port {MYSQL_PORT_HOST} "MySQL"
|
check_port {MYSQL_PORT_HOST} "MySQL"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -441,7 +446,8 @@ echo "=========================================="
|
|||||||
echo "Deployment completed!"
|
echo "Deployment completed!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Access: http://localhost:{APP_PORT_HOST}"
|
echo "New frontend: http://localhost:{APP_NEW_PORT_HOST}"
|
||||||
|
echo "Old frontend: http://localhost:{APP_OLD_PORT_HOST}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " Logs: $DOCKER_COMPOSE_CMD logs -f"
|
echo " Logs: $DOCKER_COMPOSE_CMD logs -f"
|
||||||
@@ -637,7 +643,8 @@ echo "=========================================="
|
|||||||
echo "Update completed!"
|
echo "Update completed!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Application: http://localhost:{APP_PORT_HOST}"
|
echo "New frontend: http://localhost:{APP_NEW_PORT_HOST}"
|
||||||
|
echo "Old frontend: http://localhost:{APP_OLD_PORT_HOST}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Database status:"
|
echo "Database status:"
|
||||||
if docker ps --format "{{.Names}}" | grep -q "capacity-mysql"; then
|
if docker ps --format "{{.Names}}" | grep -q "capacity-mysql"; then
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "19081:9081"
|
- "19081:9081"
|
||||||
|
- "19082:9082"
|
||||||
volumes:
|
volumes:
|
||||||
# 持久化数据
|
# 持久化数据
|
||||||
- ./cache:/app/cache
|
- ./cache:/app/cache
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# 项目上下文记录
|
# 项目上下文记录
|
||||||
|
|
||||||
|
## 2026-05-15:拆分新旧前端访问端口
|
||||||
|
|
||||||
|
- `python -m app.main` 现在由同一个 FastAPI 进程同时监听 `9081` 和 `9082`,共享后端运行状态、任务锁和 API。
|
||||||
|
- `9081` 固定服务新版 Vue 3 前端,`9082` 固定服务 `frontend_old/` 旧版 HTML/CSS/JS 前端;旧版页面仍通过 `/old/...` 加载本地 Monaco 等静态资源。
|
||||||
|
- `app.main:app` 保留为新版单端口 ASGI 实例,`app.main:old_app` 保留为旧版单端口 ASGI 实例,`app.main:split_app` 用于按请求端口切换前端。
|
||||||
|
- `run.bat`、`supervisord.conf`、Dockerfile、Docker Compose 和离线构建脚本已同步新旧端口:本地为 `9081/9082`,容器宿主机映射为 `19081/19082`。
|
||||||
|
|
||||||
## 2026-05-15:增加旧版前端对照入口和新版路由
|
## 2026-05-15:增加旧版前端对照入口和新版路由
|
||||||
|
|
||||||
- 从旧提交 `54773f547f6fcb853d73785f05ff5ac39ab2e5f5` 恢复原生 HTML/CSS/JS 前端到 `frontend_old/`,包含旧版 `index.html`、`login.html`、样式、脚本和本地 Monaco 资源。
|
- 从旧提交 `54773f547f6fcb853d73785f05ff5ac39ab2e5f5` 恢复原生 HTML/CSS/JS 前端到 `frontend_old/`,包含旧版 `index.html`、`login.html`、样式、脚本和本地 Monaco 资源。
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ if not exist "frontend\dist\index.html" (
|
|||||||
:loop
|
:loop
|
||||||
echo.
|
echo.
|
||||||
echo [%date% %time%] Starting service...
|
echo [%date% %time%] Starting service...
|
||||||
echo [INFO] Service URL: http://localhost:9081
|
echo [INFO] New frontend: http://localhost:9081
|
||||||
|
echo [INFO] Old frontend: http://localhost:9082
|
||||||
echo [INFO] Close this window to stop the service.
|
echo [INFO] Close this window to stop the service.
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|||||||
serverurl=unix:///var/run/supervisor.sock
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
[program:fastapi]
|
[program:fastapi]
|
||||||
command=uvicorn app.main:app --host 0.0.0.0 --port 9081 --workers 1
|
command=python -m app.main
|
||||||
directory=/app
|
directory=/app
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
|
|||||||
Reference in New Issue
Block a user