Commit 13111c9f authored by uuo00_n's avatar uuo00_n

merge: 合并 gitlab/main 分支,解决 docker-compose.prod.yml 冲突

- 解决 edu-service、llm-service 环境变量冲突,采用 gitlab/main 的 ${VAR} 风格
- 同步 Redis 服务密码配置(requirepass ${REDIS_PASSWORD})
- 合并 deploy.sh、docker-compose.yml 及各微服务更新
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parents 94e9c927 ff10be50
#!/bin/bash #!/bin/bash
# =============================================================================
# LLM Filter 项目生产环境部署脚本 (Ubuntu/Debian)
# =============================================================================
# 颜色定义 # 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
echo -e "${GREEN}=== LLM Filter 项目部署脚本 ===${NC}" 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 是否安装 # 检查 Docker
if ! command -v docker &> /dev/null; then if ! command -v docker &> /dev/null; then
echo -e "${YELLOW}未检测到 Docker,请先在宝塔面板安装 Docker。${NC}" 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 exit 1
fi fi
echo -e "${GREEN}使用 Compose 命令: $COMPOSE_CMD${NC}"
# 3. 配置检查
echo -e "${BLUE}[2/5] 检查环境配置...${NC}"
# 检查 Docker Compose 是否安装 if [ ! -f .env ]; then
if ! command -v docker-compose &> /dev/null; then echo -e "${YELLOW}.env 文件不存在,正在生成默认配置...${NC}"
# 尝试检查 docker compose (v2)
if ! docker compose version &> /dev/null; then # 检查 python3
echo -e "${YELLOW}未检测到 Docker Compose。${NC}" if command -v python3 &> /dev/null; then
echo "请尝试运行: pip install docker-compose 或在宝塔软件商店安装。" 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 exit 1
fi fi
DOCKER_COMPOSE_CMD="docker compose"
else else
DOCKER_COMPOSE_CMD="docker-compose" echo -e "${GREEN}.env 文件已存在。${NC}"
fi fi
echo -e "${GREEN}正在停止旧容器...${NC}" # 4. 创建必要的目录
$DOCKER_COMPOSE_CMD -f docker-compose.prod.yml down 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 -e "${GREEN}正在构建并启动服务 (这可能需要几分钟)...${NC}" echo "启动服务..."
$DOCKER_COMPOSE_CMD -f docker-compose.prod.yml up -d --build $COMPOSE_CMD -f docker-compose.prod.yml up -d
if [ $? -eq 0 ]; then if [ $? -ne 0 ]; then
echo -e "${GREEN}=== 部署成功! ===${NC}" echo -e "${RED}[错误] 服务启动失败,请检查 Docker 日志。${NC}"
echo "服务状态:" exit 1
$DOCKER_COMPOSE_CMD -f docker-compose.prod.yml ps 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 "" echo ""
echo "访问地址 (本地部署请使用 localhost,服务器部署请使用服务器IP):" }
echo "- API 网关: http://localhost:8080"
echo "- Auth Service 文档: http://localhost:8080/docs/auth/" # 检查 bc 是否存在,不存在用简单 sleep
echo "- Edu Service 文档: http://localhost:8080/docs/edu/" if command -v bc &> /dev/null; then
echo "- LLM Service 文档: http://localhost:8080/docs/llm/" progress_bar 10
echo "- Security Service 文档: http://localhost:8080/docs/security/"
else else
echo -e "${YELLOW}部署过程中出现错误,请检查日志。${NC}" sleep 10
fi 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"
...@@ -7,17 +7,17 @@ services: ...@@ -7,17 +7,17 @@ services:
container_name: llm-filter-db container_name: llm-filter-db
restart: always restart: always
environment: environment:
POSTGRES_USER: admin POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: llm_filter_db POSTGRES_DB: ${DB_NAME}
ports: ports:
- "5433:5432" - "${POSTGRES_PORT_EXTERNAL:-5433}:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
- llm-network - llm-network
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d llm_filter_db"] test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
...@@ -30,14 +30,18 @@ services: ...@@ -30,14 +30,18 @@ services:
container_name: llm-filter-auth container_name: llm-filter-auth
restart: always restart: always
ports: ports:
- "8081:8081" - "${AUTH_SERVICE_PORT:-8081}:8081"
environment: environment:
- DB_HOST=postgres - DB_HOST=${DB_HOST}
- DB_USER=admin - DB_PORT=${DB_PORT}
- DB_PASSWORD=password - DB_USER=${DB_USER}
- DB_NAME=llm_filter_db - DB_PASSWORD=${DB_PASSWORD}
- DB_PORT=5432 - DB_NAME=${DB_NAME}
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- ADMIN_EMAIL=${ADMIN_EMAIL}
- TZ=${TZ}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
...@@ -52,16 +56,23 @@ services: ...@@ -52,16 +56,23 @@ services:
container_name: llm-filter-edu container_name: llm-filter-edu
restart: always restart: always
ports: ports:
- "8082:8082" - "${EDU_SERVICE_PORT:-8082}:8082"
environment: environment:
- DB_HOST=postgres - DB_HOST=${DB_HOST}
- DB_USER=admin - DB_PORT=${DB_PORT}
- DB_PASSWORD=password - DB_USER=${DB_USER}
- DB_NAME=llm_filter_db - DB_PASSWORD=${DB_PASSWORD}
- DB_PORT=5432 - DB_NAME=${DB_NAME}
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- SPRING_REDIS_HOST=redis - SERVER_PORT=${SERVER_PORT}
- SPRING_REDIS_PORT=6379 - JPA_DDL_AUTO=${JPA_DDL_AUTO}
- JPA_SHOW_SQL=${JPA_SHOW_SQL}
- JPA_FORMAT_SQL=${JPA_FORMAT_SQL}
- JPA_DIALECT=${JPA_DIALECT}
- LOGGING_LEVEL=${LOGGING_LEVEL}
- SPRING_REDIS_HOST=${REDIS_HOST}
- SPRING_REDIS_PORT=${REDIS_PORT}
- TZ=${TZ}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
...@@ -77,28 +88,38 @@ services: ...@@ -77,28 +88,38 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: llm-filter-llm container_name: llm-filter-llm
restart: always restart: always
# 生产环境不需要 --reload # 生产环境:移除--reload,使用多worker提高性能
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips '*' command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers --forwarded-allow-ips '*'
ports: ports:
- "8000:8000" - "${LLM_SERVICE_PORT:-8000}:8000"
# 生产环境移除代码挂载,使用镜像内的代码 # 生产环境移除代码挂载,使用镜像内的代码
volumes: volumes:
- ./scripts:/app/scripts - ./scripts:/app/scripts
environment: environment:
- MONGODB_URL=mongodb://mongo:27017 - MONGODB_URL=${MONGODB_URL}
- DB_NAME=llm_filter_db - DB_NAME=${DB_NAME}
- SECRET_KEY=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - SECRET_KEY=${JWT_SECRET}
- ALGORITHM=HS256 - ALGORITHM=${ALGORITHM}
- ACCESS_TOKEN_EXPIRE_MINUTES=30 - ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES}
- TERM_START_DATE=2025-09-01 - TERM_START_DATE=${TERM_START_DATE}
- OLLAMA_BASE_URL=http://192.168.6.6:11434/ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
- OLLAMA_MODEL=deepseek-r1:14b - OLLAMA_MODEL=${OLLAMA_MODEL}
# Dify Configuration # Dify Configuration
- DIFY_API_URL=http://192.168.6.6/v1 - DIFY_API_URL=${DIFY_API_URL}
- DIFY_API_KEY=app-lkK33EQOVXXrjD9x3SKbItr7 - DIFY_API_KEY=${DIFY_API_KEY}
- DIFY_RESPONSE_MODE=${DIFY_RESPONSE_MODE}
- DIFY_MESSAGE_ENDPOINT=${DIFY_MESSAGE_ENDPOINT}
# Redis Configuration # Redis Configuration
- REDIS_HOST=redis - REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=6379 - REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD}
# App Configuration
- APP_BASE_URL=${APP_BASE_URL}
- API_V1_STR=${API_V1_STR}
- APP_MODE=${APP_MODE}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
- TZ=${TZ}
depends_on: depends_on:
- mongo - mongo
- redis - redis
...@@ -112,17 +133,31 @@ services: ...@@ -112,17 +133,31 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: llm-filter-security container_name: llm-filter-security
restart: always restart: always
# 生产环境不需要 --reload # 生产环境:移除--reload,使用多worker提高性能
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips '*' command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers --forwarded-allow-ips '*'
ports: ports:
- "8003:8000" - "${SECURITY_SERVICE_PORT:-8003}:8000"
# 生产环境移除代码挂载 # 生产环境移除代码挂载
environment: environment:
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- DIFY_API_URL=http://192.168.6.6/v1 - ALGORITHM=${ALGORITHM}
- DIFY_API_KEY=app-ggTb0oC9WXQQm2r7KrvVUS6v - DIFY_API_URL=${DIFY_API_URL}
- MONGODB_URL=mongodb://mongo:27017 - DIFY_API_KEY=${DIFY_API_KEY}
- MONGODB_DB_NAME=security_service_db - DIFY_RESPONSE_MODE=${DIFY_RESPONSE_MODE}
- MONGODB_URL=${MONGODB_URL}
- MONGODB_DB_NAME=${MONGODB_DB_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD}
- ZABBIX_URL=${ZABBIX_URL:-http://localhost}
- ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin}
- ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix}
- ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600}
- ZABBIX_AUTO_SYNC=${ZABBIX_AUTO_SYNC:-true}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- LOG_FORMAT=${LOG_FORMAT:-json}
- TZ=${TZ}
networks: networks:
- llm-network - llm-network
...@@ -131,10 +166,13 @@ services: ...@@ -131,10 +166,13 @@ services:
image: redis:7-alpine image: redis:7-alpine
container_name: llm-filter-redis container_name: llm-filter-redis
restart: always restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
ports: ports:
- "6379:6379" - "${REDIS_PORT_EXTERNAL:-6379}:6379"
volumes: volumes:
- redis_data:/data - redis_data:/data
environment:
- TZ=${TZ}
networks: networks:
- llm-network - llm-network
...@@ -144,7 +182,7 @@ services: ...@@ -144,7 +182,7 @@ services:
container_name: llm-filter-mongo container_name: llm-filter-mongo
restart: always restart: always
ports: ports:
- "27017:27017" - "${MONGODB_PORT_EXTERNAL:-27017}:27017"
volumes: volumes:
- mongo_data:/data/db - mongo_data:/data/db
networks: networks:
...@@ -156,9 +194,11 @@ services: ...@@ -156,9 +194,11 @@ services:
container_name: llm-filter-gateway container_name: llm-filter-gateway
restart: always restart: always
ports: ports:
- "8080:80" - "${GATEWAY_PORT:-8080}:80"
volumes: volumes:
- ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro - ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
environment:
- TZ=${TZ}
depends_on: depends_on:
- auth-service - auth-service
- edu-service - edu-service
......
...@@ -6,11 +6,11 @@ services: ...@@ -6,11 +6,11 @@ services:
image: postgres:15-alpine image: postgres:15-alpine
container_name: llm-filter-db container_name: llm-filter-db
environment: environment:
POSTGRES_USER: admin POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: llm_filter_db POSTGRES_DB: ${DB_NAME}
ports: ports:
- "5433:5432" - "${POSTGRES_PORT_EXTERNAL:-5433}:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
...@@ -20,10 +20,13 @@ services: ...@@ -20,10 +20,13 @@ services:
redis: redis:
image: redis:7-alpine image: redis:7-alpine
container_name: llm-filter-redis container_name: llm-filter-redis
command: redis-server --requirepass ${REDIS_PASSWORD}
ports: ports:
- "6379:6379" - "${REDIS_PORT_EXTERNAL:-6379}:6379"
volumes: volumes:
- redis_data:/data - redis_data:/data
environment:
- TZ=${TZ}
networks: networks:
- llm-network - llm-network
...@@ -34,36 +37,47 @@ services: ...@@ -34,36 +37,47 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: llm-filter-auth container_name: llm-filter-auth
ports: ports:
- "8081:8081" - "${AUTH_SERVICE_PORT:-8081}:8081"
environment: environment:
- DB_HOST=postgres - DB_HOST=${DB_HOST}
- DB_USER=admin - DB_PORT=${DB_PORT}
- DB_PASSWORD=password - DB_USER=${DB_USER}
- DB_NAME=llm_filter_db - DB_PASSWORD=${DB_PASSWORD}
- DB_PORT=5432 - DB_NAME=${DB_NAME}
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- ADMIN_EMAIL=${ADMIN_EMAIL}
- TZ=${TZ}
depends_on: depends_on:
- postgres - postgres
networks: networks:
- llm-network - llm-network
# 教务核心服务 (Go) # 教务核心服务
edu-service: edu-service:
build: build:
context: ./microservices/edu-service context: ./microservices/edu-service
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: llm-filter-edu container_name: llm-filter-edu
ports: ports:
- "8082:8082" - "${EDU_SERVICE_PORT:-8082}:8082"
environment: environment:
- DB_HOST=postgres - DB_HOST=${DB_HOST}
- DB_USER=admin - DB_PORT=${DB_PORT}
- DB_PASSWORD=password - DB_USER=${DB_USER}
- DB_NAME=llm_filter_db - DB_PASSWORD=${DB_PASSWORD}
- DB_PORT=5432 - DB_NAME=${DB_NAME}
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- SPRING_REDIS_HOST=redis - SERVER_PORT=${SERVER_PORT}
- SPRING_REDIS_PORT=6379 - JPA_DDL_AUTO=${JPA_DDL_AUTO}
- JPA_SHOW_SQL=${JPA_SHOW_SQL}
- JPA_FORMAT_SQL=${JPA_FORMAT_SQL}
- JPA_DIALECT=${JPA_DIALECT}
- LOGGING_LEVEL=${LOGGING_LEVEL}
- SPRING_REDIS_HOST=${REDIS_HOST}
- SPRING_REDIS_PORT=${REDIS_PORT}
- TZ=${TZ}
depends_on: depends_on:
- postgres - postgres
- redis - redis
...@@ -78,25 +92,35 @@ services: ...@@ -78,25 +92,35 @@ services:
container_name: llm-filter-llm container_name: llm-filter-llm
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips '*' command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips '*'
ports: ports:
- "8000:8000" - "${LLM_SERVICE_PORT:-8000}:8000"
volumes: volumes:
- ./microservices/llm-service:/app - ./microservices/llm-service:/app
- ./scripts:/app/scripts - ./scripts:/app/scripts
environment: environment:
- MONGODB_URL=mongodb://mongo:27017 - MONGODB_URL=${MONGODB_URL}
- DB_NAME=llm_filter_db - DB_NAME=${DB_NAME}
- SECRET_KEY=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - SECRET_KEY=${JWT_SECRET}
- ALGORITHM=HS256 - ALGORITHM=${ALGORITHM}
- ACCESS_TOKEN_EXPIRE_MINUTES=30 - ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES}
- TERM_START_DATE=2025-09-01 - TERM_START_DATE=${TERM_START_DATE}
- OLLAMA_BASE_URL=http://192.168.6.6:11434/ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
- OLLAMA_MODEL=deepseek-r1:14b - OLLAMA_MODEL=${OLLAMA_MODEL}
# Dify Configuration # Dify Configuration
- DIFY_API_URL=http://192.168.6.6/v1 - DIFY_API_URL=${DIFY_API_URL}
- DIFY_API_KEY=app-sLnrbNjEi1GiTDGgL2B2DwLZ - DIFY_API_KEY=${DIFY_API_KEY}
- DIFY_RESPONSE_MODE=${DIFY_RESPONSE_MODE}
- DIFY_MESSAGE_ENDPOINT=${DIFY_MESSAGE_ENDPOINT}
# Redis Configuration # Redis Configuration
- REDIS_HOST=redis - REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=6379 - REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD}
# App Configuration
- APP_BASE_URL=${APP_BASE_URL}
- API_V1_STR=${API_V1_STR}
- APP_MODE=${APP_MODE}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
- TZ=${TZ}
depends_on: depends_on:
- mongo - mongo
- redis - redis
...@@ -111,15 +135,29 @@ services: ...@@ -111,15 +135,29 @@ services:
container_name: llm-filter-security container_name: llm-filter-security
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips '*' command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips '*'
ports: ports:
- "8003:8000" - "${SECURITY_SERVICE_PORT:-8003}:8000"
volumes: volumes:
- ./microservices/security-service/app:/app/app - ./microservices/security-service/app:/app/app
environment: environment:
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=${JWT_SECRET}
- DIFY_API_URL=http://192.168.6.6/v1 - ALGORITHM=${ALGORITHM}
- DIFY_API_KEY=app-ggTb0oC9WXQQm2r7KrvVUS6v - DIFY_API_URL=${DIFY_API_URL}
- MONGODB_URL=mongodb://mongo:27017 - DIFY_API_KEY=${DIFY_API_KEY}
- MONGODB_DB_NAME=security_service_db - DIFY_RESPONSE_MODE=${DIFY_RESPONSE_MODE}
- MONGODB_URL=${MONGODB_URL}
- MONGODB_DB_NAME=${MONGODB_DB_NAME}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD}
- ZABBIX_URL=${ZABBIX_URL:-http://localhost}
- ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin}
- ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix}
- ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600}
- ZABBIX_AUTO_SYNC=${ZABBIX_AUTO_SYNC:-true}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- LOG_FORMAT=${LOG_FORMAT:-text}
- TZ=${TZ}
depends_on: depends_on:
- mongo - mongo
networks: networks:
...@@ -130,7 +168,7 @@ services: ...@@ -130,7 +168,7 @@ services:
image: mongo:latest image: mongo:latest
container_name: llm-filter-mongo container_name: llm-filter-mongo
ports: ports:
- "27017:27017" - "${MONGODB_PORT_EXTERNAL:-27017}:27017"
volumes: volumes:
- mongo_data:/data/db - mongo_data:/data/db
networks: networks:
...@@ -141,9 +179,11 @@ services: ...@@ -141,9 +179,11 @@ services:
image: nginx:alpine image: nginx:alpine
container_name: llm-filter-gateway container_name: llm-filter-gateway
ports: ports:
- "8080:80" - "${GATEWAY_PORT:-8080}:80"
volumes: volumes:
- ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro - ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
environment:
- TZ=${TZ}
depends_on: depends_on:
- auth-service - auth-service
- edu-service - edu-service
......
module auth-service module auth-service
go 1.24.3 go 1.24.2
require github.com/gin-gonic/gin v1.11.0 require github.com/gin-gonic/gin v1.11.0
...@@ -12,6 +12,7 @@ require ( ...@@ -12,6 +12,7 @@ require (
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.19.15 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
......
...@@ -59,6 +59,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD ...@@ -59,6 +59,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
......
...@@ -60,11 +60,12 @@ func (s *AuthService) Register(req *RegisterRequest) (*model.User, error) { ...@@ -60,11 +60,12 @@ func (s *AuthService) Register(req *RegisterRequest) (*model.User, error) {
// 创建用户 // 创建用户
user := &model.User{ user := &model.User{
Username: req.Username, Username: req.Username,
Email: req.Email, Email: req.Email,
Password: hashedPwd, Password: hashedPwd,
Role: "user", Role: "user",
Edition: "edu", RoleLevel: 1, // 默认普通用户等级
Edition: "edu",
} }
if err := s.repo.Create(user); err != nil { if err := s.repo.Create(user); err != nil {
......
...@@ -10,10 +10,12 @@ import ( ...@@ -10,10 +10,12 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
_ "auth-service/docs" // docs is generated by Swag CLI _ "auth-service/docs" // docs is generated by Swag CLI
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/joho/godotenv"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
...@@ -30,6 +32,19 @@ import ( ...@@ -30,6 +32,19 @@ import (
// @name Authorization // @name Authorization
func main() { func main() {
// 尝试加载根目录 .env 文件 (用于本地开发)
// 优先级:当前目录 .env > 向上两级 .env > 向上三级 .env
// 注意:Docker 环境下通常不包含这些 .env 文件,而是直接通过 environment 注入,
// 所以这里的加载失败不应该阻断程序运行,只作为开发辅助。
// 1. 尝试当前目录
godotenv.Load()
// 2. 尝试项目根目录 (假设在 microservices/auth-service 下运行)
// 根目录在 ../../.env
rootEnvPath := filepath.Join("..", "..", ".env")
godotenv.Load(rootEnvPath)
// 获取环境变量配置 // 获取环境变量配置
dbHost := os.Getenv("DB_HOST") dbHost := os.Getenv("DB_HOST")
dbUser := os.Getenv("DB_USER") dbUser := os.Getenv("DB_USER")
...@@ -37,21 +52,9 @@ func main() { ...@@ -37,21 +52,9 @@ func main() {
dbName := os.Getenv("DB_NAME") dbName := os.Getenv("DB_NAME")
dbPort := os.Getenv("DB_PORT") dbPort := os.Getenv("DB_PORT")
// 默认值处理(用于本地开发) // 必需的环境变量检查
if dbHost == "" { if dbHost == "" || dbUser == "" || dbPassword == "" || dbName == "" || dbPort == "" {
dbHost = "localhost" log.Fatal("Missing required database environment variables: DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT")
}
if dbUser == "" {
dbUser = "admin"
}
if dbPassword == "" {
dbPassword = "password" // 请确保本地有此密码的数据库或修改此处
}
if dbName == "" {
dbName = "llm_filter_db"
}
if dbPort == "" {
dbPort = "5433"
} }
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai",
...@@ -111,7 +114,11 @@ func main() { ...@@ -111,7 +114,11 @@ func main() {
} }
func initAdminUser(repo *repository.UserRepository) { func initAdminUser(repo *repository.UserRepository) {
adminUsername := "admin" adminUsername := os.Getenv("ADMIN_USERNAME")
if adminUsername == "" {
adminUsername = "admin"
}
exists, err := repo.ExistsByUsername(adminUsername) exists, err := repo.ExistsByUsername(adminUsername)
if err != nil { if err != nil {
log.Printf("Failed to check admin user existence: %v", err) log.Printf("Failed to check admin user existence: %v", err)
...@@ -123,7 +130,7 @@ func initAdminUser(repo *repository.UserRepository) { ...@@ -123,7 +130,7 @@ func initAdminUser(repo *repository.UserRepository) {
adminPassword := os.Getenv("ADMIN_PASSWORD") adminPassword := os.Getenv("ADMIN_PASSWORD")
if adminPassword == "" { if adminPassword == "" {
adminPassword = "password123" log.Fatal("ADMIN_PASSWORD environment variable is required for initial admin user creation")
} }
hashedPwd, err := utils.HashPassword(adminPassword) hashedPwd, err := utils.HashPassword(adminPassword)
...@@ -132,11 +139,16 @@ func initAdminUser(repo *repository.UserRepository) { ...@@ -132,11 +139,16 @@ func initAdminUser(repo *repository.UserRepository) {
return return
} }
adminEmail := os.Getenv("ADMIN_EMAIL")
if adminEmail == "" {
adminEmail = "admin@example.com"
}
adminUser := &model.User{ adminUser := &model.User{
Username: adminUsername, Username: adminUsername,
Email: "admin@example.com", Email: adminEmail,
Password: hashedPwd, Password: hashedPwd,
Role: "administrator", // 兼容旧系统的最高权限角色 Role: "administrator",
RoleLevel: 5, RoleLevel: 5,
Edition: "edu", Edition: "edu",
} }
...@@ -144,7 +156,7 @@ func initAdminUser(repo *repository.UserRepository) { ...@@ -144,7 +156,7 @@ func initAdminUser(repo *repository.UserRepository) {
if err := repo.Create(adminUser); err != nil { if err := repo.Create(adminUser); err != nil {
log.Printf("Failed to create admin user: %v", err) log.Printf("Failed to create admin user: %v", err)
} else { } else {
log.Printf("Admin user created successfully. Username: %s, Password: %s", adminUsername, adminPassword) log.Printf("Admin user created successfully. Username: %s", adminUsername)
} }
} else { } else {
log.Println("Admin user already exists.") log.Println("Admin user already exists.")
......
...@@ -2,6 +2,7 @@ package utils ...@@ -2,6 +2,7 @@ package utils
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"time" "time"
...@@ -9,12 +10,17 @@ import ( ...@@ -9,12 +10,17 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
var jwtSecret = []byte("your-secret-key") var jwtSecret []byte
func init() { func init() {
if secret := os.Getenv("JWT_SECRET"); secret != "" { secret := os.Getenv("JWT_SECRET")
jwtSecret = []byte(secret) if secret == "" {
log.Fatal("JWT_SECRET environment variable is required")
} }
if len(secret) < 32 {
log.Fatal("JWT_SECRET must be at least 32 characters long")
}
jwtSecret = []byte(secret)
} }
func GetJWTSecret() []byte { func GetJWTSecret() []byte {
......
...@@ -21,6 +21,12 @@ import org.springframework.context.annotation.Bean; ...@@ -21,6 +21,12 @@ import org.springframework.context.annotation.Bean;
public class EduServiceApplication { public class EduServiceApplication {
public static void main(String[] args) { public static void main(String[] args) {
// 尝试加载根目录 .env 文件 (用于本地开发)
// 注意:生产环境 Docker 会直接注入环境变量,这里仅作为本地开发辅助
// Spring Boot 默认不加载 .env,这里使用 System.setProperty 模拟或推荐使用插件
// 为了简单起见,这里不做复杂的 .env 解析,建议本地开发使用 IDE 插件或手动设置环境变量
// 或者使用 java-dotenv 库
SpringApplication.run(EduServiceApplication.class, args); SpringApplication.run(EduServiceApplication.class, args);
} }
......
...@@ -6,6 +6,8 @@ import com.llmfilter.edu.service.ScheduleService; ...@@ -6,6 +6,8 @@ import com.llmfilter.edu.service.ScheduleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.llmfilter.edu.security.UserContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
...@@ -23,9 +25,18 @@ public class ScheduleController { ...@@ -23,9 +25,18 @@ public class ScheduleController {
@PutMapping("/assign-teacher") @PutMapping("/assign-teacher")
@Operation(summary = "分配任课教师", description = "为特定课程分配任课教师") @Operation(summary = "分配任课教师", description = "为特定课程分配任课教师")
public ResponseEntity<Map<String, Boolean>> assignTeacher(@RequestBody AssignTeacherPayload payload) { public ResponseEntity<Map<String, Object>> assignTeacher(@RequestBody AssignTeacherPayload payload) {
// 权限检查:仅管理员可分配教师
String role = UserContextHolder.getContext().getRole();
if (!"administrator".equals(role)) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "Permission denied: Administrator role required");
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
scheduleService.assignTeacher(payload); scheduleService.assignTeacher(payload);
Map<String, Boolean> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("success", true); result.put("success", true);
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} }
......
...@@ -21,7 +21,7 @@ import java.security.Key; ...@@ -21,7 +21,7 @@ import java.security.Key;
@Component @Component
public class JwtAuthenticationFilter implements Filter { public class JwtAuthenticationFilter implements Filter {
@Value("${jwt.secret:your_secret_key_here}") @Value("${jwt.secret}")
private String jwtSecret; private String jwtSecret;
private Key key; private Key key;
......
server.port=8082 server.port=${SERVER_PORT:8082}
spring.application.name=edu-service spring.application.name=edu-service
# Database Configuration # ============================================================
# 数据库配置
# ============================================================
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:llm_filter_db} spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:llm_filter_db}
spring.datasource.username=${DB_USER:admin} spring.datasource.username=${DB_USER:admin}
spring.datasource.password=${DB_PASSWORD:password} spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.driver-class-name=org.postgresql.Driver
# JPA Configuration # ============================================================
spring.jpa.hibernate.ddl-auto=update # JPA/Hibernate 配置
spring.jpa.show-sql=true # ============================================================
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=${JPA_DDL_AUTO:update}
spring.jpa.properties.hibernate.format_sql=true spring.jpa.show-sql=${JPA_SHOW_SQL:true}
spring.jpa.properties.hibernate.dialect=${JPA_DIALECT:org.hibernate.dialect.PostgreSQLDialect}
spring.jpa.properties.hibernate.format_sql=${JPA_FORMAT_SQL:true}
# Security # ============================================================
jwt.secret=${JWT_SECRET:your_secret_key_here_must_be_very_long_to_be_secure_at_least_32_bytes} # 安全配置
# ============================================================
jwt.secret=${JWT_SECRET}
# Redis Configuration # ============================================================
# Redis 配置
# ============================================================
spring.redis.host=${SPRING_REDIS_HOST:localhost} spring.redis.host=${SPRING_REDIS_HOST:localhost}
spring.redis.port=${SPRING_REDIS_PORT:6379} spring.redis.port=${SPRING_REDIS_PORT:6379}
spring.redis.timeout=2000 spring.redis.timeout=${REDIS_TIMEOUT:2000}
spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-active=${REDIS_POOL_MAX_ACTIVE:8}
spring.redis.lettuce.pool.max-wait=-1 spring.redis.lettuce.pool.max-wait=${REDIS_POOL_MAX_WAIT:-1}
spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.max-idle=${REDIS_POOL_MAX_IDLE:8}
spring.redis.lettuce.pool.min-idle=0 spring.redis.lettuce.pool.min-idle=${REDIS_POOL_MIN_IDLE:0}
# ============================================================
# 日志配置
# ============================================================
logging.level.root=${LOGGING_LEVEL:INFO}
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
FROM python:3.10-slim FROM python:3.10-slim
# 添加非root用户
RUN useradd -m -u 1000 appuser && \
mkdir -p /app && \
chown -R appuser:appuser /app
WORKDIR /app WORKDIR /app
# 设置时区 # 设置时区
...@@ -11,10 +16,14 @@ COPY requirements.txt . ...@@ -11,10 +16,14 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 复制业务代码 # 复制业务代码
COPY . . COPY --chown=appuser:appuser . .
# 切换到非root用户
USER appuser
# 暴露端口 # 暴露端口
EXPOSE 8000 EXPOSE 8000
# 启动命令 # 启动命令(生产环境,多worker)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
import os from pathlib import Path
# Pydantic v2 中 BaseSettings 已迁移到 pydantic-settings from pydantic import Field
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings, SettingsConfigDict
from dotenv import load_dotenv
# 加载环境变量 (尝试向上查找 .env)
load_dotenv(verbose=True) # 默认查找当前目录
if not os.getenv("MONGODB_URL"):
# 如果没找到,尝试向上级目录查找(本地开发场景)
load_dotenv(dotenv_path="../../.env")
load_dotenv(dotenv_path="../../../.env") # 备用:防止层级变动
class Settings(BaseSettings): class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file_encoding="utf-8",
case_sensitive=True,
extra="ignore"
)
# 应用配置 # 应用配置
APP_NAME: str = "LLM过滤系统" APP_NAME: str = "LLM过滤系统"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
APP_BASE_URL: str = os.getenv("APP_BASE_URL", "http://localhost:8000") APP_BASE_URL: str = "http://localhost:8000"
# 数据库配置 # 数据库配置
MONGODB_URL: str = os.getenv("MONGODB_URL", "mongodb://localhost:27017") MONGODB_URL: str = "mongodb://localhost:27017"
DB_NAME: str = os.getenv("DB_NAME", "llm_filter_db") DB_NAME: str = "llm_filter_db"
# JWT配置 # JWT配置
SECRET_KEY: str = os.getenv("SECRET_KEY", "your_secret_key_here") SECRET_KEY: str = ""
ALGORITHM: str = os.getenv("ALGORITHM", "HS256") ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30")) ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
# Ollama配置 # Ollama配置
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434") OLLAMA_BASE_URL: str = "http://192.168.6.6:11434/"
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "llama2") OLLAMA_MODEL: str = "deepseek-r1:14b"
# Dify配置 # Dify配置
DIFY_API_URL: str = os.getenv("DIFY_API_URL", "http://192.168.6.6/v1") DIFY_API_URL: str = "http://192.168.6.6/v1"
DIFY_API_KEY: str = os.getenv("DIFY_API_KEY", "app-sLnrbNjEi1GiTDGgL2B2DwLZ") # 使用别名从环境变量 DIFY_API_KEY_LLM 读取,代码中仍通过 settings.DIFY_API_KEY 访问
DIFY_RESPONSE_MODE: str = os.getenv("DIFY_RESPONSE_MODE", "streaming") DIFY_API_KEY: str = Field("", alias="DIFY_API_KEY_LLM")
DIFY_MESSAGE_ENDPOINT: str = os.getenv("DIFY_MESSAGE_ENDPOINT", "chat-messages") DIFY_RESPONSE_MODE: str = "streaming"
DIFY_MESSAGE_ENDPOINT: str = "chat-messages"
# 应用运行模式开关:仅运行教育版或企业版之一 # 应用运行模式开关:仅运行教育版或企业版之一
# 允许的值:"edu" / "biz";若未设置则默认使用 "edu" # 允许的值:"edu" / "biz";若未设置则默认使用 "edu"
# 注意:不再提供混合模式(mixed),如需混合请显式设置并在依赖中放行 # 注意:不再提供混合模式(mixed),如需混合请显式设置并在依赖中放行
APP_MODE: str = os.getenv("APP_MODE", "edu") APP_MODE: str = "edu"
CORS_ALLOWED_ORIGINS: str = os.getenv("CORS_ALLOWED_ORIGINS", "*") CORS_ALLOWED_ORIGINS: str = "*"
GITHUB_DEFAULT_REPO: str = os.getenv("GITHUB_DEFAULT_REPO", "") GITHUB_DEFAULT_REPO: str = ""
GITHUB_TOKEN: str = os.getenv("GITHUB_TOKEN", "") GITHUB_TOKEN: str = ""
# 学期配置 # 学期配置
TERM_START_DATE: str = os.getenv("TERM_START_DATE", "2025-09-01") # 默认开学日期 TERM_START_DATE: str = "2025-09-01" # 默认开学日期
# Redis 配置 # Redis 配置
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") REDIS_HOST: str = "localhost"
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379")) REDIS_PORT: int = 6379
REDIS_DB: int = int(os.getenv("REDIS_DB", "0")) REDIS_DB: int = 0
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "") REDIS_PASSWORD: str = ""
settings = Settings() settings = Settings()
from fastapi import FastAPI from fastapi import FastAPI, HTTPException
from fastapi.openapi.docs import get_swagger_ui_html from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.api.v1.router import api_router from app.api.v1.router import api_router
from app.core.config import settings from app.core.config import settings
from app.db.mongodb import connect_to_mongo, close_mongo_connection from app.db.mongodb import connect_to_mongo, close_mongo_connection, client
from app.utils.sensitive_word_filter import sensitive_word_filter from app.utils.sensitive_word_filter import sensitive_word_filter
from datetime import datetime
app = FastAPI( app = FastAPI(
title=settings.APP_NAME, title=settings.APP_NAME,
...@@ -77,3 +78,45 @@ async def root(): ...@@ -77,3 +78,45 @@ async def root():
"version": "1.0.0", "version": "1.0.0",
"message": "欢迎使用LLM过滤系统API" "message": "欢迎使用LLM过滤系统API"
} }
@app.get("/health")
async def health_check():
"""健康检查端点"""
try:
# 检查数据库连接
db_status = "ok" if client else "error"
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"service": "llm-service",
"version": "1.0.0",
"components": {
"mongodb": {"status": db_status}
}
}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Service unhealthy: {str(e)}")
@app.get("/ready")
async def readiness_check():
"""就绪检查端点"""
try:
# 检查关键依赖是否就绪
if not client:
return {
"status": "not_ready",
"timestamp": datetime.now().isoformat(),
"reason": "database_not_connected"
}
return {
"status": "ready",
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return {
"status": "not_ready",
"timestamp": datetime.now().isoformat(),
"reason": str(e)
}
...@@ -236,12 +236,30 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[ ...@@ -236,12 +236,30 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[
# 调用模型生成回复 # 调用模型生成回复
assistant_response = await generate_response(model_messages) assistant_response = await generate_response(model_messages)
# 检查助手回复是否包含敏感词
response_check = await sensitive_word_filter.check_text(assistant_response)
if response_check["contains_sensitive_words"]:
# 记录敏感词审计日志
sensitive_record = {
"user_id": user_id,
"conversation_id": ObjectId(conversation_id),
"message_content": assistant_response, # 记录原始违规内容
"source": "assistant", # 标记来源为助手
"sensitive_words_found": response_check["sensitive_words_found"],
"highest_severity": response_check["highest_severity"],
"timestamp": datetime.now()
}
await db.db.sensitive_records.insert_one(sensitive_record)
# 屏蔽回复内容
assistant_response = "(系统拦截)生成的内容包含敏感信息,已屏蔽。"
# 创建助手回复消息 # 创建助手回复消息
assistant_message = { assistant_message = {
"role": "assistant", "role": "assistant",
"content": assistant_response, "content": assistant_response,
"timestamp": datetime.now(), "timestamp": datetime.now(),
"contains_sensitive_words": False, "contains_sensitive_words": False, # 既然已屏蔽,标记为不包含(或者是 True 但内容已安全化)
"sensitive_words_found": [] "sensitive_words_found": []
} }
......
# Security Service API 测试与假数据示例
本文件用于说明如何通过 HTTP API(经网关或直连服务)调用 Security Service,并使用一组统一的假数据进行联调和回归测试。
> 注意:这些假数据仅用于测试,生产环境请接入真实监控和日志数据。
---
## 1. 环境与前提条件
- 网关地址(推荐):`http://localhost:8080`
- Security Service API 前缀:`/api/v1/security`
- 所有接口均需要携带管理员 JWT:
- Header:`Authorization: Bearer <admin_token>`
在以下示例中,请将 `<admin_token>` 替换为实际从 Auth Service 获取的 token。
---
## 2. 接口一览
- `POST /api/v1/security/analysis` —— 安全风险分析
- `POST /api/v1/security/attack-advice` —— 攻击应急建议
- `GET /api/v1/security/report` —— 安全日报
- `GET /api/v1/security/monitor` —— 风险监测与合规评估
- `GET /api/v1/security/rss/news` —— 安全新闻 RSS 订阅
---
## 3. 安全风险分析(/analysis)测试示例
### 3.1 请求说明
- 方法:`POST`
- URL:`http://localhost:8080/api/v1/security/analysis`
- 用途:基于设备信息列表做 AI 风险分析。
### 3.2 测试请求体(假数据)
```json
{
"devices": [
{
"id": "sw-001",
"name": "Core-Switch-A",
"type": "switch",
"status": "warning",
"version": "v1.2.0",
"logs": [
"Port 22 high traffic",
"Packet loss detected"
]
},
{
"id": "fw-001",
"name": "Edge-Firewall",
"type": "firewall",
"status": "active",
"version": "v2.1.patch3",
"logs": [
"Denied 1000+ requests from IP 192.168.1.50"
]
}
]
}
```
### 3.3 curl 示例
```bash
curl -X POST "http://localhost:8080/api/v1/security/analysis" \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{
"devices": [
{
"id": "sw-001",
"name": "Core-Switch-A",
"type": "switch",
"status": "warning",
"version": "v1.2.0",
"logs": ["Port 22 high traffic", "Packet loss detected"]
},
{
"id": "fw-001",
"name": "Edge-Firewall",
"type": "firewall",
"status": "active",
"version": "v2.1.patch3",
"logs": ["Denied 1000+ requests from IP 192.168.1.50"]
}
]
}'
```
### 3.4 期望响应结构
服务端返回 JSON 结构符合 `SecurityAnalysisResponse`
```json
{
"summary": "string",
"vulnerabilities": ["string"],
"suggestions": ["string"],
"risk_level": "string"
}
```
---
## 4. 攻击应急建议(/attack-advice)测试示例
### 4.1 请求说明
- 方法:`POST`
- URL:`http://localhost:8080/api/v1/security/attack-advice`
- 用途:当系统已遭受攻击或疑似攻击时,获取应急响应方案。
### 4.2 测试请求体(假数据)
```json
{
"attack_type": "Brute Force Login",
"target_device": "DB-Server-Prod",
"severity": "high",
"logs": "Failed login attempts from 10.0.0.10"
}
```
### 4.3 curl 示例
```bash
curl -X POST "http://localhost:8080/api/v1/security/attack-advice" \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{
"attack_type": "Brute Force Login",
"target_device": "DB-Server-Prod",
"severity": "high",
"logs": "Failed login attempts from 10.0.0.10"
}'
```
### 4.4 期望响应结构
```json
{
"immediate_actions": ["string"],
"analysis": "string",
"mitigation_plan": "string"
}
```
---
## 5. 安全日报(/report)测试示例
### 5.1 请求说明
- 方法:`GET`
- URL:`http://localhost:8080/api/v1/security/report`
- 用途:生成面向管理层的每日安全概览。
### 5.2 curl 示例
```bash
curl -X GET "http://localhost:8080/api/v1/security/report" \
-H "Authorization: Bearer <admin_token>"
```
> 当前实现中,后端会构造简单的统计信息传给 Dify。实际环境应接入真实监控数据。
### 5.3 期望响应结构
```json
{
"date": "string",
"overall_status": "string",
"device_summary": "string",
"incident_summary": "string",
"recommendations": "string"
}
```
---
## 6. 风险监测与合规评估(/monitor)测试示例
### 6.1 请求说明
- 方法:`GET`
- URL:`http://localhost:8080/api/v1/security/monitor`
- 用途:基于给定的漏洞信息进行整体风险与合规评估。
> 当前代码中 `context_data` 为空数组时,智能体可以根据默认经验或空数据做基线评估。也可以在后续版本中从漏洞库/配置注入具体列表。
### 6.2 curl 示例
```bash
curl -X GET "http://localhost:8080/api/v1/security/monitor" \
-H "Authorization: Bearer <admin_token>"
```
### 6.3 期望响应结构
```json
{
"detected_vulnerabilities": ["string"],
"compliance_risks": ["string"],
"ai_assessment": "string"
}
---
## 7. 安全新闻 RSS 订阅(/rss/news)测试示例
### 7.1 请求说明
- 方法:`GET`
- URL:`http://localhost:8080/api/v1/security/rss/news`
- 用途:获取来自天融信、360 CERT、绿盟等安全厂商的最新资讯。
### 7.2 curl 示例
```bash
curl -X GET "http://localhost:8080/api/v1/security/rss/news" \
-H "Authorization: Bearer <admin_token>"
```
### 7.3 期望响应结构
```json
{
"items": [
{
"title": "string",
"link": "string",
"description": "string",
"published": "string",
"source": "string"
}
]
}
---
## 8. 测试建议
- 在联调阶段,可以先固定一组假数据(如本文件中的示例),确保:
- Dify 智能体返回的 JSON 结构稳定且字段完整;
- Security Service 能正确解析并返回给前端。
- 回归测试时:
- 建议将这些请求录入到自动化测试脚本中(如 pytest + httpx/requests),
- 对响应结构做 Schema 校验,避免后续修改提示词或模型配置导致输出结构破坏。
\ No newline at end of file
FROM python:3.11-slim FROM python:3.11-slim
# 添加非root用户
RUN useradd -m -u 1000 appuser && \
mkdir -p /app && \
chown -R appuser:appuser /app
WORKDIR /app WORKDIR /app
# 安装依赖
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY . . # 复制应用代码
COPY --chown=appuser:appuser . .
# 切换到非root用户
USER appuser
# 暴露端口
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] # 启动命令(生产环境,多worker)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4", "--proxy-headers", "--forwarded-allow-ips", "*"]
# Security Service 使用说明
## 1. 服务定位
Security Service 是本项目的安全分析微服务,主要能力包括:
- 基于交换机 / 防火墙 / 服务器等设备信息做 AI 安全风险分析
- 在遭受攻击时给出 AI 应急响应建议
- 生成每日安全日报
- 基于互联网最新漏洞进行风险监测与合规性评估
当前实现为 **无状态服务**
- 不直接读写 PostgreSQL / MongoDB
- 所有分析结果仅在请求周期内计算并返回,不做持久化存储
## 2. 部署与访问入口
### 2.1 通过 Docker Compose 启动
在项目根目录执行:
```bash
cd /Users/uu/Desktop/dles_prj/llm-filter
docker-compose up -d --build security-service gateway
```
### 2.2 访问地址
- 统一网关入口(推荐):`http://localhost:8080`
- 安全服务 API:`/api/v1/security/*`
- 安全服务文档:`http://localhost:8080/docs/security/`
- 直连 Security Service 容器:
- Base URL:`http://localhost:8003`
- API 前缀:`/api/v1/security`
## 3. 鉴权与权限控制
- 所有接口均要求携带 Auth Service 签发的 **JWT**
- HTTP Header:`Authorization: Bearer <token>`
- 服务内部通过 [`get_current_admin`](app/core/security.py) 校验管理员身份:
- 仅当 `role``admin` / `administrator` / `root` 时允许访问
示例 Header:
```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
## 4. 接口一览
所有路径均在前缀 `/api/v1/security` 下,以下以 **网关地址** 为例:`http://localhost:8080`
### 4.1 安全风险分析
- 方法:`POST`
- URL:`/api/v1/security/analysis`
- 说明:
- 输入网络设备列表(交换机 / 防火墙 / 服务器等),由 AI 分析潜在安全隐患
- **注意**:必须传入有效的 `devices` 列表,否则将返回空结果或错误。
请求示例:
```json
POST http://localhost:8080/api/v1/security/analysis
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"devices": [
{
"id": "sw-001",
"name": "Core-Switch-A",
"type": "switch",
"status": "warning",
"logs": ["Port 22 high traffic", "Packet loss detected"],
"version": "v1.2.0"
}
]
}
```
响应字段(`SecurityAnalysisResponse`):
- `summary`: 总体安全概况
- `vulnerabilities`: 漏洞 / 风险列表
- `suggestions`: 修复建议列表
- `risk_level`: 风险等级(如 `low` / `medium` / `high` / `critical`
### 4.2 攻击应急建议
- 方法:`POST`
- URL:`/api/v1/security/attack-advice`
- 说明:当系统已遭受攻击时,提供应急响应与缓解方案
请求示例:
```json
POST http://localhost:8080/api/v1/security/attack-advice
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"attack_type": "Brute Force Login",
"target_device": "DB-Server-Prod",
"severity": "high",
"logs": "Failed login attempts from 10.0.0.10"
}
```
响应字段(`AttackAdviceResponse`):
- `immediate_actions`: 立即执行的操作建议列表
- `analysis`: 攻击分析说明
- `mitigation_plan`: 中长期缓解与防护计划
### 4.3 安全日报
- 方法:`GET`
- URL:`/api/v1/security/report`
- 说明:生成企业安全日报,用于面向管理层的安全概览展示
- **注意**:当前实现需要接入真实数据源才能生成有效报告,否则返回空状态。
响应字段(`SecurityReportResponse`):
- `date`: 报告日期(`YYYY-MM-DD`
- `overall_status`: 总体安全状态
- `device_summary`: 设备运行状况摘要
- `incident_summary`: 安全事件与拦截情况摘要
- `recommendations`: 后续安全改进建议
### 4.4 风险监测与合规评估
- 方法:`GET`
- URL:`/api/v1/security/monitor`
- 说明:基于互联网最新漏洞信息,评估当前企业的合规风险
- **注意**:需要配置或接入外部漏洞数据库,否则返回空列表。
响应字段(`RiskMonitorResponse`):
- `detected_vulnerabilities`: 识别到的漏洞列表
- `compliance_risks`: 合规风险点列表
- `ai_assessment`: AI 对整体风险的评估说明
### 4.5 安全新闻 RSS 订阅
- 方法:`GET`
- URL:`/api/v1/security/rss/news`
- 说明:获取来自天融信、360 CERT、绿盟等安全厂商的最新 RSS 安全资讯。
响应字段(`RSSFeedResponse`):
- `items`: 新闻列表,包含标题、链接、摘要、发布时间和来源。
## 5. Dify 集成与异常处理
服务内部通过 Dify 完成大部分安全分析逻辑:
- Dify 调用配置在 [config.py](app/core/config.py)
- `DIFY_API_URL`
- `DIFY_API_KEY`
- 请求由 [`SecurityService._call_llm`](app/services/analysis.py) 统一发起
- **智能体 Prompt 配置**:请参考 [DIFY_PROMPT.md](DIFY_PROMPT.md) 文档,在 Dify 平台配置对应的 System Prompt 和变量。
当 Dify 不可用(网络异常 / 超时 / 抛错)时:
- 服务将记录错误日志
- 抛出异常供上层处理或返回 HTTP 500 错误
- **不再提供 Mock 数据降级**,以确保运维人员能及时感知服务状态异常。
## 6. 数据存储与状态
当前版本的 Security Service:
- **数据持久化**:分析结果(风险分析与攻击建议)会异步存储到 **MongoDB** 数据库中。
- **历史查询**:提供接口查询历史分析记录。
## 7. 历史查询接口
### 7.1 安全分析历史
- 方法:`GET`
- URL:`/api/v1/security/analysis/history`
- 参数:
- `start_date` (可选): 开始时间 (ISO 8601)
- `end_date` (可选): 结束时间 (ISO 8601)
- `limit` (可选): 返回数量限制,默认 20
- 响应:包含 `total``items` (AnalysisHistoryItem)
### 7.2 攻击建议历史
- 方法:`GET`
- URL:`/api/v1/security/attack-advice/history`
- 参数:
- `start_date` (可选): 开始时间 (ISO 8601)
- `end_date` (可选): 结束时间 (ISO 8601)
- `limit` (可选): 返回数量限制,默认 20
- 响应:包含 `total``items` (AttackAdviceHistoryItem)
## 8. 快速调试示例(curl)
确保容器已启动后,可以在宿主机直接运行:
```bash
# 1. 使用管理员 Token 调用安全分析(通过网关)
# 注意:请确保传入真实的设备数据
curl -X POST "http://localhost:8080/api/v1/security/analysis" \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"devices": [{"id": "test-1", "name": "Test-Device", "type": "server", "status": "active"}]}'
# 2. 直接访问文档(网关统一入口)
open "http://localhost:8080/docs/security/"
```
from datetime import datetime
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from app.schemas.payloads import * from app.schemas.payloads import *
from app.services.analysis import SecurityService from app.services.analysis import SecurityService
from app.services.rss import RSSService from app.services.rss import RSSService
from app.services.zabbix_service import ZabbixService
from app.core.security import get_current_admin from app.core.security import get_current_admin
from app.core.database import db
from datetime import datetime, timezone
router = APIRouter() router = APIRouter()
service = SecurityService() zabbix_service = ZabbixService()
service = SecurityService(zabbix_service=zabbix_service)
rss_service = RSSService() rss_service = RSSService()
@router.post("/analysis", response_model=SecurityAnalysisResponse) @router.post("/analysis", response_model=SecurityAnalysisResponse)
async def analyze_risks(request: SecurityAnalysisRequest, admin: dict = Depends(get_current_admin)): async def analyze_risks(request: SecurityAnalysisRequest, admin: dict = Depends(get_current_admin)):
result = await service.analyze_risks(request.devices) return await service.analyze_risks(request.devices)
# 异步存储结果到 MongoDB
if db.db is not None:
log_entry = result.model_dump()
log_entry["created_at"] = datetime.now(timezone.utc)
await db.db.security_analysis_logs.insert_one(log_entry)
return result
@router.get("/analysis/history", response_model=HistoryQueryResponse) @router.get("/analysis/history", response_model=HistoryQueryResponse)
async def get_analysis_history( async def get_analysis_history(
...@@ -36,15 +30,12 @@ async def get_analysis_history( ...@@ -36,15 +30,12 @@ async def get_analysis_history(
@router.post("/attack-advice", response_model=AttackAdviceResponse) @router.post("/attack-advice", response_model=AttackAdviceResponse)
async def get_attack_advice(request: AttackAdviceRequest, admin: dict = Depends(get_current_admin)): async def get_attack_advice(request: AttackAdviceRequest, admin: dict = Depends(get_current_admin)):
result = await service.get_attack_advice(request.attack_type, request.target_device, request.logs) return await service.get_attack_advice(
attack_type=request.attack_type,
# 异步存储结果到 MongoDB target=request.target_device,
if db.db is not None: logs=request.logs,
log_entry = result.model_dump() severity=request.severity,
log_entry["created_at"] = datetime.now(timezone.utc) )
await db.db.attack_advice_logs.insert_one(log_entry)
return result
@router.get("/attack-advice/history", response_model=HistoryQueryResponse) @router.get("/attack-advice/history", response_model=HistoryQueryResponse)
async def get_attack_advice_history( async def get_attack_advice_history(
...@@ -73,3 +64,53 @@ async def get_security_news(admin: dict = Depends(get_current_admin)): ...@@ -73,3 +64,53 @@ async def get_security_news(admin: dict = Depends(get_current_admin)):
""" """
return await rss_service.get_security_news() return await rss_service.get_security_news()
@router.post("/zabbix/sync")
async def sync_zabbix_data(admin: dict = Depends(get_current_admin)):
"""
手动同步Zabbix数据
"""
try:
result = await zabbix_service.sync_data()
return {
"status": "success",
"message": "Zabbix数据同步完成",
"data": result
}
except Exception as e:
return {
"status": "error",
"message": f"Zabbix数据同步失败: {str(e)}",
"data": None
}
@router.get("/zabbix/status")
async def get_zabbix_status(admin: dict = Depends(get_current_admin)):
"""
获取Zabbix服务状态
"""
status = zabbix_service.get_sync_status()
return {
"status": "success",
"data": status
}
@router.post("/zabbix/devices")
async def get_zabbix_devices(admin: dict = Depends(get_current_admin)):
"""
获取Zabbix设备列表
"""
try:
device_data = await zabbix_service.collect_device_data()
return {
"status": "success",
"message": f"成功获取 {len(device_data.get('devices', []))} 台设备",
"data": device_data
}
except Exception as e:
return {
"status": "error",
"message": f"获取设备数据失败: {str(e)}",
"data": None
}
from pydantic_settings import BaseSettings from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file_encoding="utf-8",
case_sensitive=True,
)
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
PROJECT_NAME: str = "Security Service" PROJECT_NAME: str = "Security Service"
# 鉴权配置 (与 Auth Service 保持一致) # 鉴权配置 (与 Auth Service 保持一致)
JWT_SECRET: str = "llm_filter_secure_secret_key_2025_update_must_be_32_bytes" JWT_SECRET: str = ""
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
# Dify 配置 # Dify 配置
DIFY_API_URL: str = "http://192.168.6.6/v1" DIFY_API_URL: str = "http://192.168.6.6/v1"
DIFY_API_KEY: str = "app-lkK33EQOVXXrjD9x3SKbItr7" # 使用别名从环境变量 DIFY_API_KEY_SECURITY 读取
DIFY_API_KEY: str = Field("", alias="DIFY_API_KEY_SECURITY")
DIFY_RESPONSE_MODE: str = "streaming"
# MongoDB 配置 # MongoDB 配置
MONGODB_URL: str = "mongodb://localhost:27017" MONGODB_URL: str = "mongodb://localhost:27017"
MONGODB_DB_NAME: str = "security_service_db" MONGODB_DB_NAME: str = "security_service_db"
# Redis 配置
REDIS_HOST: str = "localhost"
REDIS_PORT: int = 6379
REDIS_DB: int = 0
REDIS_PASSWORD: str = ""
# Zabbix 配置
ZABBIX_URL: str = "http://localhost"
ZABBIX_USERNAME: str = "Admin"
ZABBIX_PASSWORD: str = "zabbix"
class Config: # 数据同步配置
case_sensitive = True ZABBIX_SYNC_INTERVAL: int = 3600
ZABBIX_AUTO_SYNC: bool = True
settings = Settings() settings = Settings()
from fastapi import FastAPI
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import datetime
import logging
from fastapi import FastAPI, HTTPException
from app.api.v1.endpoints import router as security_router from app.api.v1.endpoints import router as security_router
from app.core.config import settings from app.core.config import settings
from app.core.database import db from app.core.database import db
from app.services.zabbix_service import ZabbixService
logger = logging.getLogger(__name__)
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# Startup
db.connect() db.connect()
try:
zabbix_service = ZabbixService()
sync_status = zabbix_service.get_sync_status()
if sync_status.get("collector_initialized"):
logger.info("Zabbix服务初始化成功")
else:
logger.warning("Zabbix服务初始化失败,请检查Zabbix配置")
except Exception as e:
logger.error(f"Zabbix服务连接检查失败: {e}")
yield yield
# Shutdown
db.close() db.close()
app = FastAPI(title=settings.PROJECT_NAME, lifespan=lifespan)
# 注册路由 app = FastAPI(title=settings.PROJECT_NAME, version="2.0.0", lifespan=lifespan)
app.include_router(security_router, prefix=f"{settings.API_V1_STR}/security", tags=["Security"])
@app.get("/") @app.get("/")
def health_check(): def health_check():
return {"status": "ok", "service": "security-service"} return {"status": "ok", "service": "security-service", "version": "2.0.0"}
@app.get("/health")
async def detailed_health_check():
try:
db_status = "ok" if db.db else "error"
zabbix_status = {"status": "not_configured"}
try:
zabbix_service = ZabbixService()
sync_status = zabbix_service.get_sync_status()
if sync_status.get("collector_initialized"):
zabbix_status = {
"status": "ok",
"last_sync": sync_status.get("last_sync_time"),
}
else:
zabbix_status = {
"status": "error",
"message": "Zabbix collector未初始化",
}
except Exception as e:
zabbix_status = {
"status": "error",
"message": str(e),
}
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"service": "security-service",
"version": "2.0.0",
"components": {
"database": {"status": db_status},
"zabbix": zabbix_status,
},
}
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(status_code=503, detail=f"Service unhealthy: {str(e)}")
@app.get("/ready")
async def readiness_check():
try:
if not db.db:
return {
"status": "not_ready",
"timestamp": datetime.now().isoformat(),
"reason": "database_not_connected",
}
return {
"status": "ready",
"timestamp": datetime.now().isoformat(),
}
except Exception as e:
return {
"status": "not_ready",
"timestamp": datetime.now().isoformat(),
"reason": str(e),
}
app.include_router(security_router, prefix=f"{settings.API_V1_STR}/security", tags=["Security"])
This diff is collapsed.
...@@ -8,3 +8,5 @@ python-dotenv==1.0.1 ...@@ -8,3 +8,5 @@ python-dotenv==1.0.1
feedparser>=6.0.10 feedparser>=6.0.10
motor==3.3.2 motor==3.3.2
pymongo<4.7 pymongo<4.7
requests>=2.31.0
@echo off
REM Security Service 启动脚本 (Windows)
echo ============================================================
echo Security Service - Zabbix集成版本
echo ============================================================
echo.
REM 检查Python是否安装
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Python未安装或未添加到PATH
pause
exit /b 1
)
REM 检查虚拟环境
if not exist "venv" (
echo 📦 创建虚拟环境...
python -m venv venv
)
REM 激活虚拟环境
call venv\Scripts\activate.bat
REM 安装依赖
echo 📥 安装依赖...
pip install -r requirements.txt -q
REM 检查.env文件
if not exist ".env" (
echo ⚠️ .env文件不存在,复制.env.example...
copy .env.example .env
echo ❗ 请编辑.env文件,配置Zabbix服务器信息
echo ❗ 配置完成后重新运行此脚本
pause
exit /b 1
)
REM 启动服务
echo.
echo 🚀 启动Security Service...
echo 📍 服务地址: http://localhost:8002
echo 📚 API文档: http://localhost:8002/docs
echo.
echo 按 Ctrl+C 停止服务
echo.
python -m uvicorn app.main:app --host 0.0.0.0 --port 8002 --reload
pause
\ No newline at end of file
#!/bin/bash
# Security Service 启动脚本 (Linux/MacOS)
echo "============================================================"
echo " Security Service - Zabbix集成版本"
echo "============================================================"
echo
# 检查Python是否安装
if ! command -v python3 &> /dev/null; then
echo "❌ Python3未安装"
exit 1
fi
# 检查虚拟环境
if [ ! -d "venv" ]; then
echo "📦 创建虚拟环境..."
python3 -m venv venv
fi
# 激活虚拟环境
echo "🔄 激活虚拟环境..."
source venv/bin/activate
# 安装依赖
echo "📥 安装依赖..."
pip install -r requirements.txt -q
# 检查.env文件
if [ ! -f ".env" ]; then
echo "⚠️ .env文件不存在,复制.env.example..."
cp .env.example .env
echo "❗ 请编辑.env文件,配置Zabbix服务器信息"
echo "❗ 配置完成后重新运行此脚本"
exit 1
fi
# 启动服务
echo
echo "🚀 启动Security Service..."
echo "📍 服务地址: http://localhost:8002"
echo "📚 API文档: http://localhost:8002/docs"
echo
echo "按 Ctrl+C 停止服务"
echo
python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8002 --reload
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
#!/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 未安装"
exit 1
fi
echo "[INFO] Docker 和 Docker Compose 已安装"
echo ""
# 检查 .env 文件
if [ ! -f .env ]; then
echo "[ERROR] .env 文件不存在,请先运行:"
echo " python scripts/generate_secrets.py"
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 ""
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