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/.venv/
backend/.venv/
backend/venv/
backend/env/
backend/ENV/

View File

@ -1,7 +1,7 @@
APP_NAME=财务大模型智能问答客服系统
ENVIRONMENT=development
# DATABASE_URL=postgresql+psycopg://finai:finai@127.0.0.1:5432/finai
DATABASE_URL=postgresql+psycopg://postgres:zcDsj2026@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
JWT_SECRET_KEY=vWQqGzVAi01hq126fXZdHV4W4S2Tx4BiOvWuwe5m8ubAPPitLpSh-Y4mXC50d7lM
JWT_EXPIRE_MINUTES=480
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.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from app.api import api_router
from app.config import get_settings
from app.db import SessionLocal, init_db
from app.services.user_service import ensure_default_admin
FRONTEND_DIST = Path(__file__).resolve().parent.parent / "dist"
def create_app() -> FastAPI:
settings = get_settings()
@ -33,7 +39,33 @@ def create_app() -> FastAPI:
def health() -> dict:
return {"status": "ok", "app": settings.app_name}
_mount_frontend(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()

View File

@ -4,8 +4,22 @@ setlocal
cd /d "%~dp0"
echo [backend] 启动 uvicorn app.main:app http://127.0.0.1:8000
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
if not exist ".venv\Scripts\python.exe" (
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
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`
- Mock 验证码:`123456`

View File

@ -1,5 +1,5 @@
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") || "",
currentUser: null,
currentSessionId: null,
@ -62,7 +62,7 @@ async function request(path, options = {}) {
}
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);
}

View File

@ -33,7 +33,7 @@
<div id="loginForm">
<label>
<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>
<span>手机号</span>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
import { notify } from '@/utils/notify'
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 currentUser = ref(null)
const healthy = ref(false)
@ -17,7 +17,7 @@ export const useAppStore = defineStore('app', () => {
}
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)
}

View File

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