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/.venv/
|
||||
backend/.venv/
|
||||
backend/venv/
|
||||
backend/env/
|
||||
backend/ENV/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
- Mock 验证码:`123456`
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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/, ''),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue