feat: 后端服务前端 dist 并统一使用 8775 端口

- backend/app/main.py 挂载 dist/ 作为 SPA 静态资源,/assets 走 StaticFiles,其它路径回落 index.html
- 前后端 API base 统一切换到 127.0.0.1:8775,避开 8000 占用
- run.bat 自动创建/复用 backend/.venv,用 venv 启动 uvicorn
- .gitignore 忽略 backend/.venv/
- 新增 deploy/finai-backend.service

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
shijing 2026-07-10 15:29:03 +08:00
parent f3b5b05317
commit 49f1fb6089
10 changed files with 136 additions and 13 deletions

2
.gitignore vendored
View File

@ -37,7 +37,7 @@ backend/.eggs/
backend/wheels/ backend/wheels/
# 虚拟环境 # 虚拟环境
# backend/.venv/ backend/.venv/
backend/venv/ backend/venv/
backend/env/ backend/env/
backend/ENV/ backend/ENV/

View File

@ -1,7 +1,7 @@
APP_NAME=财务大模型智能问答客服系统 APP_NAME=财务大模型智能问答客服系统
ENVIRONMENT=development ENVIRONMENT=development
# DATABASE_URL=postgresql+psycopg://finai:finai@127.0.0.1:5432/finai DATABASE_URL=postgresql+psycopg://finance_ai:finance_ai@127.0.0.1:5432/finance_ai
DATABASE_URL=postgresql+psycopg://postgres:zcDsj2026@127.0.0.1:5432/finai # DATABASE_URL=postgresql+psycopg://postgres:zcDsj2026@127.0.0.1:5432/finai
JWT_SECRET_KEY=vWQqGzVAi01hq126fXZdHV4W4S2Tx4BiOvWuwe5m8ubAPPitLpSh-Y4mXC50d7lM JWT_SECRET_KEY=vWQqGzVAi01hq126fXZdHV4W4S2Tx4BiOvWuwe5m8ubAPPitLpSh-Y4mXC50d7lM
JWT_EXPIRE_MINUTES=480 JWT_EXPIRE_MINUTES=480
SMS_PROVIDER=tencent SMS_PROVIDER=tencent

View File

@ -1,11 +1,17 @@
from fastapi import FastAPI from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from app.api import api_router from app.api import api_router
from app.config import get_settings from app.config import get_settings
from app.db import SessionLocal, init_db from app.db import SessionLocal, init_db
from app.services.user_service import ensure_default_admin from app.services.user_service import ensure_default_admin
FRONTEND_DIST = Path(__file__).resolve().parent.parent / "dist"
def create_app() -> FastAPI: def create_app() -> FastAPI:
settings = get_settings() settings = get_settings()
@ -33,7 +39,33 @@ def create_app() -> FastAPI:
def health() -> dict: def health() -> dict:
return {"status": "ok", "app": settings.app_name} return {"status": "ok", "app": settings.app_name}
_mount_frontend(app)
return app return app
def _mount_frontend(app: FastAPI) -> None:
if not FRONTEND_DIST.is_dir():
return
assets_dir = FRONTEND_DIST / "assets"
if assets_dir.is_dir():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
index_file = FRONTEND_DIST / "index.html"
@app.get("/", include_in_schema=False)
def _spa_root() -> FileResponse:
return FileResponse(index_file)
@app.get("/{full_path:path}", include_in_schema=False)
def _spa_fallback(full_path: str) -> FileResponse:
if full_path.startswith(("api/", "assets/", "health")):
raise HTTPException(status_code=404)
candidate = FRONTEND_DIST / full_path
if candidate.is_file():
return FileResponse(candidate)
return FileResponse(index_file)
app = create_app() app = create_app()

View File

@ -4,8 +4,22 @@ setlocal
cd /d "%~dp0" cd /d "%~dp0"
echo [backend] 启动 uvicorn app.main:app http://127.0.0.1:8000 if not exist ".venv\Scripts\python.exe" (
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 echo [backend] 未找到 .venv正在创建虚拟环境...
python -m venv .venv || goto :error
echo [backend] 正在安装依赖...
".venv\Scripts\python.exe" -m pip install --upgrade pip || goto :error
".venv\Scripts\python.exe" -m pip install fastapi "uvicorn[standard]" sqlalchemy "psycopg[binary]" pydantic-settings "python-jose[cryptography]" "passlib[bcrypt]" python-multipart httpx tencentcloud-sdk-python-lke || goto :error
)
echo [backend] 启动 uvicorn app.main:app http://127.0.0.1:8775
".venv\Scripts\python.exe" -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8775
goto :end
:error
echo [backend] 启动失败
exit /b 1
:end
endlocal endlocal
pause pause

View File

@ -0,0 +1,77 @@
# /etc/systemd/system/finai-backend.service
#
# 财务大模型智能问答客服系统 · FastAPI 后端
# 代码目录:/home/ubuntu/finai/backend
# 虚拟环境:/home/ubuntu/finai/backend/.venv
# .env /home/ubuntu/finai/backend/.env (DSN / JWT / ADP 等)
# 监听 127.0.0.1:8775 (Nginx /api/ 反代到这里)
#
# 部署:
# sudo cp /home/ubuntu/finai/deploy/systemd/finai-backend.service \
# /etc/systemd/system/finai-backend.service
# sudo systemctl daemon-reload
# sudo systemctl enable --now finai-backend
#
# 常用:
# sudo systemctl status finai-backend
# sudo systemctl restart finai-backend
# journalctl -u finai-backend -f
[Unit]
Description=Finai Backend (FastAPI)
Documentation=https://finai.ctc-zc.com
After=network-online.target postgresql.service redis-server.service
Wants=network-online.target postgresql.service redis-server.service
[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/finai/backend
EnvironmentFile=/home/ubuntu/finai/backend/.env
ExecStart=/home/ubuntu/finai/backend/.venv/bin/uvicorn app.main:app \
--host 127.0.0.1 \
--port 8775 \
--workers 2 \
--proxy-headers \
--forwarded-allow-ips=127.0.0.1
# 收到 SIGTERM 后给 uvicorn 15s 优雅退出(结束在途 SSE 流)
KillSignal=SIGTERM
TimeoutStopSec=15
Restart=always
RestartSec=3
StartLimitBurst=5
StartLimitIntervalSec=60
# 让 stdout/stderr 走 journaljournalctl -u finai-backend 直接看)
StandardOutput=journal
StandardError=journal
SyslogIdentifier=finai-backend
# ─── 基础加固 ────────────────────────────────────────────────
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
# 工作目录在 /home/ubuntu必须放开 /home 保护
ProtectHome=false
# 精确开放需要写的路径storage 落图片、SQLite/日志 等)
ReadWritePaths=/home/ubuntu/finai/backend
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
# 只允许 IPv4/IPv6/UNIX屏蔽奇葩地址簇
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# ─── 资源限制 ────────────────────────────────────────────────
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target

View File

@ -26,7 +26,7 @@ http://127.0.0.1:5173/index.html
## 登录 ## 登录
- API 地址:`http://127.0.0.1:8000` - API 地址:`http://127.0.0.1:8775`
- 默认管理员手机号:`13800000000` - 默认管理员手机号:`13800000000`
- Mock 验证码:`123456` - Mock 验证码:`123456`

View File

@ -1,5 +1,5 @@
const state = { const state = {
apiBase: localStorage.getItem("financeAiApiBase") || "http://127.0.0.1:8000", apiBase: localStorage.getItem("financeAiApiBase") || "http://127.0.0.1:8775",
token: localStorage.getItem("financeAiToken") || "", token: localStorage.getItem("financeAiToken") || "",
currentUser: null, currentUser: null,
currentSessionId: null, currentSessionId: null,
@ -62,7 +62,7 @@ async function request(path, options = {}) {
} }
function setApiBase(value) { function setApiBase(value) {
state.apiBase = value.trim() || "http://127.0.0.1:8000"; state.apiBase = value.trim() || "http://127.0.0.1:8775";
localStorage.setItem("financeAiApiBase", state.apiBase); localStorage.setItem("financeAiApiBase", state.apiBase);
} }

View File

@ -33,7 +33,7 @@
<div id="loginForm"> <div id="loginForm">
<label> <label>
<span>API 地址</span> <span>API 地址</span>
<input id="apiBaseInput" value="http://127.0.0.1:8000" /> <input id="apiBaseInput" value="http://127.0.0.1:8775" />
</label> </label>
<label> <label>
<span>手机号</span> <span>手机号</span>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
import { notify } from '@/utils/notify' import { notify } from '@/utils/notify'
export const useAppStore = defineStore('app', () => { export const useAppStore = defineStore('app', () => {
const apiBase = ref(localStorage.getItem('financeAiApiBase') || 'http://127.0.0.1:8000') const apiBase = ref(localStorage.getItem('financeAiApiBase') || 'http://127.0.0.1:8775')
const token = ref(localStorage.getItem('financeAiToken') || '') const token = ref(localStorage.getItem('financeAiToken') || '')
const currentUser = ref(null) const currentUser = ref(null)
const healthy = ref(false) const healthy = ref(false)
@ -17,7 +17,7 @@ export const useAppStore = defineStore('app', () => {
} }
function setApiBase(value) { function setApiBase(value) {
apiBase.value = (value || '').trim() || 'http://127.0.0.1:8000' apiBase.value = (value || '').trim() || 'http://127.0.0.1:8775'
localStorage.setItem('financeAiApiBase', apiBase.value) localStorage.setItem('financeAiApiBase', apiBase.value)
} }

View File

@ -14,7 +14,7 @@ export default defineConfig({
port: 5173, port: 5173,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://127.0.0.1:8000', target: 'http://127.0.0.1:8775',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
}, },