fix: resolve runtime issues in Docker environment

This commit is contained in:
Petar Zivkovic 2026-02-07 00:56:47 +01:00
parent d60bb77bf0
commit 83a7c36f3b
12 changed files with 155 additions and 14 deletions

View File

@ -20,7 +20,6 @@ WareHouse/
data/
temp/
logs
.aider*
/frontend
README*

View File

@ -5,3 +5,6 @@ FRONTEND_PORT=5173
# Frontend points to the backend service name and exposed port
VITE_API_BASE_URL=http://backend:6400
# Explicit CORS origins for Docker-based dev (comma-separated)
CORS_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173

View File

@ -190,6 +190,23 @@ make dev
> 服务在异常退出后会自动重启,本地文件的修改会同步映射到容器中,便于实时开发。
#### 生产环境Docker + nginx
- 构建并运行:
```bash
docker compose -f compose.yml -f compose.prod.yml up -d --build
```
- 访问:
- 前端http://localhost:8080
- 后端:不对外暴露,由 nginx 通过 /api 与 /ws 转发
- 停止:
```bash
docker compose -f compose.yml -f compose.prod.yml down
```
- 说明:
- .env 保存密钥API Keys.env.docker 保存容器相关配置。
- nginx 将 /api 与 /ws 代理到 backend:6400见 frontend/nginx.conf
### 🔑 配置
* **环境变量**:在项目根目录创建一个 `.env` 文件。

44
compose.prod.yml Normal file
View File

@ -0,0 +1,44 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile
target: runtime
container_name: chatdev_backend
env_file:
- .env
- .env.docker
expose:
- "6400"
restart: unless-stopped
networks:
- appnet
# Optional healthcheck (adjust path if different)
# healthcheck:
# test: ["CMD-SHELL", "curl -fsS http://localhost:6400/api/health || exit 1"]
# interval: 10s
# timeout: 3s
# retries: 5
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: prod
container_name: chatdev_frontend
ports:
- "8080:80"
depends_on:
- backend
restart: unless-stopped
networks:
- appnet
# Optional healthcheck
# healthcheck:
# test: ["CMD-SHELL", "wget -q -O - http://localhost/ >/dev/null 2>&1 || exit 1"]
# interval: 10s
# timeout: 3s
# retries: 5
networks:
appnet: {}

View File

@ -18,7 +18,6 @@ venv/
node_modules/
temp/
logs
.aider*
README*
compose.yml

2
frontend/.gitignore vendored
View File

@ -7,7 +7,7 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules/
node_modules
dist
dist-ssr
*.local

View File

@ -27,7 +27,7 @@ RUN npm run build
# ---- Prod runtime: serve static dist with nginx ----
FROM nginx:alpine AS prod
# For SPA deep links you might add a custom nginx.conf with index.html fallback.
# COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

32
frontend/nginx.conf Normal file
View File

@ -0,0 +1,32 @@
server {
listen 80;
server_name _;
# Serve built assets
root /usr/share/nginx/html;
index index.html;
# Proxy API
location /api/ {
proxy_pass http://backend:6400/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy WebSocket
location /ws {
proxy_pass http://backend:6400/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@ -1363,10 +1363,24 @@ const establishWebSocketConnection = () => {
return
}
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
const wsProtocol = baseUrl.startsWith('https') ? 'wss:' : 'ws:'
const urlObj = new URL(baseUrl)
const wsUrl = `${wsProtocol}//${urlObj.host}/ws`
const apiBase = import.meta.env.VITE_API_BASE_URL || ''
// Defaults: same-origin (works with Vite dev proxy)
const defaultScheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
let scheme = defaultScheme
let host = window.location.host
// In production, prefer explicit API base if provided
if (!import.meta.env.DEV && apiBase) {
try {
const api = new URL(apiBase, window.location.origin)
scheme = api.protocol === 'https:' ? 'wss:' : 'ws:'
host = api.host
} catch {
// keep defaults
}
}
const wsUrl = `${scheme}//${host}/ws`
const socket = new WebSocket(wsUrl)
ws = socket

View File

@ -1,7 +1,6 @@
import yaml from 'js-yaml'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
const apiUrl = (path) => `${API_BASE_URL}${path}`
const apiUrl = (path) => path
const addYamlSuffix = (filename) => {
const trimmed = (filename || '').trim()

View File

@ -4,15 +4,21 @@ import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const target = env.VITE_API_BASE_URL || 'http://localhost:8000'
const target = env.VITE_API_BASE_URL || 'http://localhost:6400'
return {
plugins: [vue()],
server: {
host: true,
proxy: {
'/api': {
target: target,
changeOrigin: true,
},
'/ws': {
target: target,
ws: true,
changeOrigin: true,
}
}
}

View File

@ -2,10 +2,12 @@
import uuid
from typing import Callable, Awaitable
from fastapi import Request, HTTPException
from fastapi import Request, HTTPException, FastAPI
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import time
import re
import os
from utils.structured_logger import get_server_logger, LogType
from utils.exceptions import SecurityError
@ -85,9 +87,35 @@ async def rate_limit_middleware(request: Request, call_next: Callable):
return response
def add_middleware(app):
def add_middleware(app: FastAPI):
"""Add all middleware to the FastAPI application."""
# Add middleware in the appropriate order
# CORS (dev defaults; override via CORS_ALLOW_ORIGINS comma-separated list)
default_origins = [
"http://localhost:5173",
"http://127.0.0.1:5173",
]
env_origins = os.getenv("CORS_ALLOW_ORIGINS")
if env_origins:
origins = [o.strip() for o in env_origins.split(",") if o.strip()]
origin_regex = None
else:
origins = default_origins
# Helpful in dev: allow localhost/127.0.0.1 on any port
origin_regex = r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$"
# Add CORS middleware first to handle preflight requests and allow origins.
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_origin_regex=origin_regex,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Correlation-ID"],
max_age=600,
)
# Add other middleware
app.middleware("http")(correlation_id_middleware)
app.middleware("http")(security_middleware)
# app.middleware("http")(rate_limit_middleware) # Enable if needed