Commit 86504a9e authored by uuo00_n's avatar uuo00_n

feat(docs): 更新开发文档和启动脚本使用说明,简化启动流程

feat(start): 重构启动脚本,增加命令行参数支持
chore: 移除不再使用的部署脚本
fix(config): 优化环境变量加载逻辑,支持多层次.env文件
parent a103c002
......@@ -10,21 +10,45 @@
---
## 快速启动(Docker
## 快速启动(统一脚本
### 前置要求
- Docker & Docker Compose
- 可选:根目录 `.env`(建议生产/团队环境使用,避免把密钥写进配置文件)
- Docker
- Docker Compose(`docker compose``docker-compose`
- Python3(用于首次生成 `.env`
### 启动命令
### 推荐流程(只用 `start.sh`)
在项目根目录执行:
```bash
# 构建并后台启动所有服务
docker-compose up -d --build
# 1) 首次生成 .env
./start.sh init-env
# 查看运行日志
docker-compose logs -f
# 2) 启动全部服务
./start.sh up
# 3) 查看状态 / 日志
./start.sh ps
./start.sh logs
```
### `.env` 如何生成和使用
1. 执行 `./start.sh init-env` 会调用 `scripts/generate_secrets.py` 自动生成根目录 `.env`
2. 生成后至少检查这些配置:`DB_PASSWORD``JWT_SECRET``DIFY_API_KEY`
3. `./start.sh up` 启动时会自动读取根目录 `.env` 注入各服务容器。
4. 修改 `.env` 后,优先执行 `./start.sh rebuild` 使配置生效(或 `./start.sh down && ./start.sh up` 全量重启)。
### 常用脚本命令
```bash
./start.sh up # 启动全部服务
./start.sh rebuild # 重构全部服务容器
./start.sh rebuild auth-service # 重构指定服务容器
./start.sh down # 停止并删除容器
./start.sh rm gateway # 删除指定容器(可传多个服务)
./start.sh reset # 停止并清空数据卷(危险操作)
./start.sh ps # 查看容器状态
./start.sh logs # 查看全部日志
./start.sh logs gateway # 查看单个服务日志
```
### 访问服务
......@@ -202,4 +226,3 @@ curl -X POST "http://localhost:8003/api/v1/security/analysis" \
1. **端口冲突**:如果 `5432` 被本地 Postgres 占用,Docker 会映射到 `5433`。代码中已默认适配 `5433`,如需修改请检查 `.env` 和各服务的配置文件。
2. **Maven 下载慢**:请使用项目提供的 `microservices/edu-service/settings.xml`,已配置阿里云镜像。
3. **鉴权失败 (401)**:请确保所有服务 (`.env` 或配置文件中) 的 `JWT_SECRET` / `SECRET_KEY` 保持一致。
......@@ -6,6 +6,30 @@
系统采用 **Go (认证)** + **Java (教务)** + **Python (LLM)** 的混合技术栈,充分发挥各语言优势,通过 **Docker Compose** 统一编排部署。
## 快速开始(只使用 `start.sh`)
```bash
# 1) 首次生成 .env
./start.sh init-env
# 2) 启动全部服务
./start.sh up
# 3) 查看状态与日志
./start.sh ps
./start.sh logs
```
`.env` 使用说明:
1. `./start.sh init-env` 会自动生成根目录 `.env`
2. 启动时 `./start.sh up` 会把 `.env` 注入所有容器。
3. 修改 `.env` 后可重构容器生效:`./start.sh rebuild`(或全量重启:`./start.sh down && ./start.sh up`)。
4. 如果 `.env` 权限异常,可执行:`sudo chown $USER:staff .env && chmod 640 .env`
常用维护命令:
- `./start.sh rebuild [service]`:重构容器(默认全部,可指定服务)
- `./start.sh rm <service...>`:删除指定容器(可多个)
## 系统架构
本项目包含以下核心服务,通过 **Gateway (Nginx)** 统一对外暴露:
......
# LLM Filter 启动脚本使用说明
本文档说明项目唯一启动脚本 `start.sh` 的使用方式。
## 1. 前置要求
- Docker
- Docker Compose(支持 `docker compose``docker-compose`
- Python3(用于首次生成 `.env`
## 2. 快速开始
```bash
# 1) 首次生成 .env
./start.sh init-env
# 2) 启动全部服务
./start.sh up
# 3) 查看状态
./start.sh ps
```
## 3. .env 生成与使用
### 3.1 生成 `.env`
```bash
./start.sh init-env
```
脚本会调用 `scripts/generate_secrets.py` 自动生成根目录 `.env`
### 3.2 修改 `.env`
生成后建议至少确认以下配置:
- `DB_PASSWORD`
- `JWT_SECRET`
- `DIFY_API_KEY`
### 3.3 生效方式
`.env` 修改后需要重启容器:
```bash
./start.sh down
./start.sh up
```
或仅重构指定服务容器:
```bash
./start.sh rebuild auth-service
```
## 4. 常用命令
```bash
./start.sh help # 查看帮助
./start.sh init-env # 生成 .env(首次)
./start.sh up # 构建并启动所有服务
./start.sh rebuild # 重构全部服务容器
./start.sh rebuild gateway # 重构指定服务容器
./start.sh down # 停止并删除容器
./start.sh rm gateway # 删除指定容器(可传多个服务)
./start.sh reset # 停止并删除容器+数据卷(清空数据库)
./start.sh ps # 查看容器状态
./start.sh logs # 查看全部日志
./start.sh logs gateway # 查看单个服务日志
```
## 5. 访问地址
- 网关入口:`http://localhost:8080`
- Auth 文档:`http://localhost:8080/docs/auth/`
- Edu 文档:`http://localhost:8080/docs/edu/`
- LLM 文档:`http://localhost:8080/docs/llm/`
- Security 文档:`http://localhost:8080/docs/security/`
## 6. 常见问题
### 6.1 `.env` 无法读取(权限问题)
如果之前用 `sudo` 生成过 `.env`,可能出现权限错误:
```bash
sudo chown $USER:staff .env
chmod 640 .env
```
### 6.2 数据库密码不匹配导致服务启动失败
若日志出现数据库认证失败(如 `password authentication failed`),可二选一:
1.`.env``DB_PASSWORD` 改回旧值再重启
2. 清空数据卷重建(会删除数据库数据):
```bash
./start.sh reset
./start.sh up
```
#!/bin/bash
# =============================================================================
# LLM Filter 项目生产环境部署脚本 (Ubuntu/Debian)
# =============================================================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}======================================================${NC}"
echo -e "${BLUE} LLM Filter 系统 - 生产环境部署脚本 (Ubuntu) ${NC}"
echo -e "${BLUE}======================================================${NC}"
echo ""
# 1. 权限检查
if [ "$EUID" -ne 0 ]; then
echo -e "${YELLOW}[提示] 请使用 sudo 运行此脚本,以便管理 Docker 服务。${NC}"
echo -e "示例: sudo ./deploy.sh"
exit 1
fi
# 2. 系统环境检查与依赖安装
echo -e "${BLUE}[1/5] 检查系统环境...${NC}"
# 检查 Docker
if ! command -v docker &> /dev/null; then
echo -e "${YELLOW}未检测到 Docker,正在尝试自动安装 (适用于 Ubuntu)...${NC}"
apt-get update
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=\"$(dpkg --print-architecture)\" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
if ! command -v docker &> /dev/null; then
echo -e "${RED}[错误] Docker 安装失败,请手动安装后重试。${NC}"
exit 1
fi
echo -e "${GREEN}Docker 安装成功!${NC}"
else
echo -e "${GREEN}Docker 已安装。${NC}"
fi
# 确定 Docker Compose 命令
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
echo -e "${RED}[错误] 未找到 Docker Compose 插件。${NC}"
echo "请尝试运行: apt-get install docker-compose-plugin"
exit 1
fi
echo -e "${GREEN}使用 Compose 命令: $COMPOSE_CMD${NC}"
# 3. 配置检查
echo -e "${BLUE}[2/5] 检查环境配置...${NC}"
if [ ! -f .env ]; then
echo -e "${YELLOW}.env 文件不存在,正在生成默认配置...${NC}"
# 检查 python3
if command -v python3 &> /dev/null; then
python3 scripts/generate_secrets.py
if [ $? -eq 0 ]; then
echo -e "${GREEN}.env 文件已生成。请务必检查其中的配置(如数据库密码、API Key)。${NC}"
echo -e "${YELLOW}是否现在暂停脚本以编辑 .env 文件? (y/n)${NC}"
read -r -p "输入 y 编辑,n 继续: " choice
if [[ "$choice" =~ ^[Yy]$ ]]; then
echo "请编辑 .env 文件后重新运行此脚本。"
exit 0
fi
else
echo -e "${RED}[错误] 生成 .env 失败。${NC}"
exit 1
fi
else
echo -e "${RED}[错误] 未找到 python3,无法自动生成配置。请手动创建 .env 文件。${NC}"
exit 1
fi
else
echo -e "${GREEN}.env 文件已存在。${NC}"
fi
# 4. 创建必要的目录
echo -e "${BLUE}[3/5] 准备数据目录...${NC}"
mkdir -p logs
mkdir -p postgres_data
mkdir -p mongo_data
mkdir -p redis_data
# 设置权限 (根据实际情况调整,这里设为当前用户或宽松权限)
chmod 777 logs
# 5. 部署服务
echo -e "${BLUE}[4/5] 构建并启动服务...${NC}"
echo "停止旧容器..."
$COMPOSE_CMD -f docker-compose.prod.yml down --remove-orphans
echo "拉取/构建镜像..."
$COMPOSE_CMD -f docker-compose.prod.yml build
echo "启动服务..."
$COMPOSE_CMD -f docker-compose.prod.yml up -d
if [ $? -ne 0 ]; then
echo -e "${RED}[错误] 服务启动失败,请检查 Docker 日志。${NC}"
exit 1
fi
# 6. 健康检查与状态展示
echo -e "${BLUE}[5/5] 等待服务就绪...${NC}"
# 简单的等待,生产环境可以使用更复杂的健康检查脚本
progress_bar() {
local duration=$1
local interval=0.5
local steps=$(echo "$duration / $interval" | bc)
local i=0
while [ $i -lt $steps ]; do
echo -ne "Waiting... ["
for ((j=0; j<i; j++)); do echo -ne "#"; done
for ((j=i; j<steps; j++)); do echo -ne " "; done
echo -ne "]\r"
sleep $interval
((i++))
done
echo ""
}
# 检查 bc 是否存在,不存在用简单 sleep
if command -v bc &> /dev/null; then
progress_bar 10
else
sleep 10
fi
echo -e "${GREEN}=== 部署完成! ===${NC}"
echo ""
echo "服务运行状态:"
$COMPOSE_CMD -f docker-compose.prod.yml ps
echo ""
echo -e "${YELLOW}注意: 如果是首次部署,请确保已初始化数据库。${NC}"
echo -e "查看日志命令: $COMPOSE_CMD -f docker-compose.prod.yml logs -f"
version: '3.8'
services:
# 数据库服务
postgres:
......
version: '3.8'
services:
# 数据库服务
postgres:
......
from pathlib import Path
from pydantic import Field
from pydantic import Field, ValidationInfo, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
def _resolve_env_files():
"""
Resolve available .env files from project root to service root.
Load order is far-to-near so closer files can override shared defaults.
"""
current_file = Path(__file__).resolve()
env_files = [parent / ".env" for parent in current_file.parents if (parent / ".env").exists()]
if not env_files:
return None
return tuple(reversed(env_files))
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file=_resolve_env_files(),
env_file_encoding="utf-8",
case_sensitive=True,
env_ignore_empty=True,
extra="ignore"
)
......@@ -55,4 +65,24 @@ class Settings(BaseSettings):
REDIS_DB: int = 0
REDIS_PASSWORD: str = ""
@model_validator(mode="before")
@classmethod
def _drop_empty_env_values(cls, data):
if isinstance(data, dict):
return {key: value for key, value in data.items() if value != ""}
return data
@field_validator("ACCESS_TOKEN_EXPIRE_MINUTES", "REDIS_PORT", "REDIS_DB", mode="before")
@classmethod
def _fallback_numeric_defaults_for_empty_env(cls, value, info: ValidationInfo):
if value != "":
return value
defaults = {
"ACCESS_TOKEN_EXPIRE_MINUTES": 30,
"REDIS_PORT": 6379,
"REDIS_DB": 0,
}
return defaults[info.field_name]
settings = Settings()
......@@ -3,7 +3,7 @@ from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1.router import api_router
from app.core.config import settings
from app.db.mongodb import connect_to_mongo, close_mongo_connection, client
from app.db.mongodb import connect_to_mongo, close_mongo_connection, db
from app.utils.sensitive_word_filter import sensitive_word_filter
from datetime import datetime
......@@ -84,7 +84,7 @@ async def health_check():
"""健康检查端点"""
try:
# 检查数据库连接
db_status = "ok" if client else "error"
db_status = "ok" if db.client else "error"
return {
"status": "healthy",
......@@ -103,7 +103,7 @@ async def readiness_check():
"""就绪检查端点"""
try:
# 检查关键依赖是否就绪
if not client:
if not db.client:
return {
"status": "not_ready",
"timestamp": datetime.now().isoformat(),
......
from pathlib import Path
from pydantic import Field
from pydantic import Field, ValidationInfo, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
def _resolve_env_files():
"""
Resolve available .env files from project root to service root.
Load order is far-to-near so closer files can override shared defaults.
"""
current_file = Path(__file__).resolve()
env_files = [parent / ".env" for parent in current_file.parents if (parent / ".env").exists()]
if not env_files:
return None
return tuple(reversed(env_files))
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file=_resolve_env_files(),
env_file_encoding="utf-8",
case_sensitive=True,
env_ignore_empty=True,
)
API_V1_STR: str = "/api/v1"
......@@ -45,4 +55,31 @@ class Settings(BaseSettings):
ZABBIX_SYNC_INTERVAL: int = 3600
ZABBIX_AUTO_SYNC: bool = True
@model_validator(mode="before")
@classmethod
def _drop_empty_env_values(cls, data):
if isinstance(data, dict):
return {key: value for key, value in data.items() if value != ""}
return data
@field_validator("REDIS_PORT", "REDIS_DB", "ZABBIX_SYNC_INTERVAL", mode="before")
@classmethod
def _fallback_numeric_defaults_for_empty_env(cls, value, info: ValidationInfo):
if value != "":
return value
defaults = {
"REDIS_PORT": 6379,
"REDIS_DB": 0,
"ZABBIX_SYNC_INTERVAL": 3600,
}
return defaults[info.field_name]
@field_validator("ZABBIX_AUTO_SYNC", mode="before")
@classmethod
def _fallback_bool_default_for_empty_env(cls, value):
if value == "":
return True
return value
settings = Settings()
#!/bin/bash
# LLM Filter 项目启动脚本
echo "=========================================="
echo " LLM Filter 项目启动脚本"
echo "=========================================="
echo ""
# 检查 Docker 和 Docker Compose
if ! command -v docker &> /dev/null; then
echo "[ERROR] Docker 未安装"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
echo "[ERROR] Docker Compose 未安装"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD=(docker-compose)
else
echo "[ERROR] 未检测到 Docker Compose。请安装 Docker Desktop 或 docker-compose。"
exit 1
fi
echo "[INFO] Docker 和 Docker Compose 已安装"
echo ""
# 检查 .env 文件
if [ ! -f .env ]; then
echo "[ERROR] .env 文件不存在,请先运行:"
echo " python scripts/generate_secrets.py"
if ! command -v docker >/dev/null 2>&1; then
echo "[ERROR] 未检测到 Docker。"
exit 1
fi
echo "[INFO] .env 文件存在"
echo ""
# 步骤 1:停止现有服务
echo "=========================================="
echo "步骤 1: 停止现有服务"
echo "=========================================="
docker-compose down
echo ""
# 步骤 2:启动基础设施
echo "=========================================="
echo "步骤 2: 启动基础设施 (PostgreSQL, Redis, MongoDB)"
echo "=========================================="
docker-compose up -d postgres redis mongo
echo "[INFO] 等待数据库启动..."
sleep 10
echo ""
echo "[INFO] 检查数据库状态..."
docker-compose ps postgres redis mongo
echo ""
# 步骤 3:启动业务服务
echo "=========================================="
echo "步骤 3: 启动业务服务"
echo "=========================================="
docker-compose up -d auth-service edu-service llm-service security-service
echo "[INFO] 等待服务启动..."
sleep 15
echo ""
echo "[INFO] 检查业务服务状态..."
docker-compose ps auth-service edu-service llm-service security-service
echo ""
# 步骤 4:启动网关
echo "=========================================="
echo "步骤 4: 启动 API 网关"
echo "=========================================="
docker-compose up -d gateway
echo "[INFO] 等待网关启动..."
sleep 5
echo ""
echo "[INFO] 检查网关状态..."
docker-compose ps gateway
echo ""
# 步骤 5:验证服务
echo "=========================================="
echo "步骤 5: 验证所有服务"
echo "=========================================="
docker-compose ps
echo ""
echo "=========================================="
echo " 启动完成!"
echo "=========================================="
echo ""
echo "📚 文档地址:"
echo " - Gateway: http://localhost:8080"
echo " - Auth Service: http://localhost:8080/docs/auth/"
echo " - Edu Service: http://localhost:8080/docs/edu/"
echo " - LLM Service: http://localhost:8080/docs/llm/"
echo " - Security: http://localhost:8080/docs/security/"
echo ""
echo "🔑 默认管理员账号:"
echo " - 用户名: admin"
echo " - 密码: 查看 .env 文件中的 ADMIN_PASSWORD"
echo ""
echo "📊 查看日志:"
echo " - 所有服务: docker-compose logs -f"
echo " - 特定服务: docker-compose logs -f [service-name]"
echo ""
usage() {
cat <<'EOF'
LLM Filter 统一脚本
用法:
./start.sh init-env 生成 .env(仅首次)
./start.sh up 构建并启动所有服务
./start.sh rebuild [svc] 重构容器(默认全部,可指定服务)
./start.sh down 停止并删除容器
./start.sh rm <svc...> 删除指定容器(可多个)
./start.sh reset 停止并删除容器+数据卷(会清空数据库)
./start.sh ps 查看容器状态
./start.sh logs [service] 查看日志(默认全部)
./start.sh help 查看帮助
推荐首次使用:
1) ./start.sh init-env
2) 编辑 .env(至少确认 DB_PASSWORD / JWT_SECRET / DIFY_API_KEY)
3) ./start.sh up
EOF
}
require_env() {
if [ ! -f .env ]; then
echo "[ERROR] .env 不存在。先执行: ./start.sh init-env"
exit 1
fi
if [ ! -r .env ]; then
echo "[ERROR] .env 不可读。可执行: sudo chown \$USER:staff .env && chmod 640 .env"
exit 1
fi
}
cmd_init_env() {
if [ -f .env ]; then
echo "[INFO] .env 已存在,跳过生成。"
echo "[INFO] 如需重建,请先备份后删除 .env 再执行本命令。"
return 0
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "[ERROR] 未检测到 python3,无法生成 .env。"
exit 1
fi
python3 scripts/generate_secrets.py
echo "[INFO] .env 生成完成。请检查关键配置后再启动。"
}
cmd_up() {
require_env
"${COMPOSE_CMD[@]}" up -d --build
echo "[INFO] 服务启动完成。"
echo "[INFO] 网关地址: http://localhost:8080"
echo "[INFO] 文档地址:"
echo " - http://localhost:8080/docs/auth/"
echo " - http://localhost:8080/docs/edu/"
echo " - http://localhost:8080/docs/llm/"
echo " - http://localhost:8080/docs/security/"
}
cmd_rebuild() {
require_env
if [ "${1:-}" = "" ]; then
"${COMPOSE_CMD[@]}" up -d --build --force-recreate
echo "[INFO] 已重构全部服务容器。"
else
"${COMPOSE_CMD[@]}" up -d --build --force-recreate "$@"
echo "[INFO] 已重构服务容器: $*"
fi
}
cmd_down() {
"${COMPOSE_CMD[@]}" down
}
cmd_rm() {
if [ "${1:-}" = "" ]; then
echo "[ERROR] rm 需要至少一个服务名。"
echo "[INFO] 用法: ./start.sh rm <service1> [service2 ...]"
exit 1
fi
"${COMPOSE_CMD[@]}" rm -f -s "$@"
}
cmd_reset() {
echo "[WARN] reset 会删除数据库卷(postgres/mongo/redis 数据将清空)"
read -r -p "确认继续? (yes/no): " answer
if [ "$answer" != "yes" ]; then
echo "[INFO] 已取消。"
return 0
fi
"${COMPOSE_CMD[@]}" down -v
}
cmd_ps() {
"${COMPOSE_CMD[@]}" ps
}
cmd_logs() {
if [ "${1:-}" = "" ]; then
"${COMPOSE_CMD[@]}" logs -f
else
"${COMPOSE_CMD[@]}" logs -f "$1"
fi
}
ACTION="${1:-help}"
case "$ACTION" in
init-env)
cmd_init_env
;;
up)
cmd_up
;;
rebuild)
cmd_rebuild "${@:2}"
;;
down)
cmd_down
;;
rm)
cmd_rm "${@:2}"
;;
reset)
cmd_reset
;;
ps)
cmd_ps
;;
logs)
cmd_logs "${2:-}"
;;
help|-h|--help)
usage
;;
*)
echo "[ERROR] 未知命令: $ACTION"
usage
exit 1
;;
esac
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment