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:
parent
f3b5b05317
commit
49f1fb6089
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 走 journal(journalctl -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
|
||||||
|
|
@ -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`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/, ''),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue