最后一版

This commit is contained in:
ZhuJW
2026-03-13 21:09:44 +08:00
parent 8584821f36
commit 265cb3a4e6
7 changed files with 244 additions and 183 deletions

View File

@@ -33,11 +33,11 @@ dist/
Dockerfile
.dockerignore
# Documentation
README.md
*.md
# Test
.pytest_cache/
.coverage
htmlcov/
# Logs
logs/
*.log

147
DOCKER.md Normal file
View File

@@ -0,0 +1,147 @@
# Docker 部署指南
## 快速启动
### 方式一:使用 Docker Compose (推荐)
1. **配置环境变量**
```bash
# 复制环境变量文件
cp .env.example .env
# 编辑 .env 文件,填入你的 DashScope API Key
DASHSCOPE_API_KEY=your_api_key_here
```
2. **启动服务**
```bash
docker-compose up -d
```
3. **查看日志**
```bash
docker-compose logs -f
```
4. **访问应用**
- API 文档http://localhost:8080/docs
- 前端界面http://localhost:8080/static/index.html
5. **停止服务**
```bash
docker-compose down
```
### 方式二:使用 Docker 构建
1. **构建镜像**
```bash
docker build -t sdlc-agent-demo:latest .
```
2. **运行容器**
```bash
docker run -d \
-p 8080:8080 \
-e DASHSCOPE_API_KEY=your_api_key_here \
-v $(pwd)/logs:/app/logs \
--name sdlc-agent \
sdlc-agent-demo:latest
```
3. **查看日志**
```bash
docker logs -f sdlc-agent
```
4. **停止并删除容器**
```bash
docker stop sdlc-agent && docker rm sdlc-agent
```
## 环境变量
| 变量名 | 说明 | 默认值 | 必需 |
|--------|------|--------|------|
| `DASHSCOPE_API_KEY` | 阿里云 DashScope API Key | 无 | ✅ |
| `PYTHONUNBUFFERED` | Python 输出缓冲设置 | 1 | ❌ |
## 端口说明
| 端口 | 说明 |
|------|------|
| 8080 | HTTP 服务端口 |
## 数据卷
| 路径 | 说明 |
|------|------|
| `./logs` | 日志文件存储目录 |
## 常见问题
### 1. 构建失败
如果遇到依赖安装失败,尝试使用国内镜像:
```dockerfile
# 在 Dockerfile 开头添加
FROM registry.cn-hangzhou.aliyuncs.com/library/python:3.11-slim
```
### 2. API Key 配置
确保在 `.env` 文件中正确配置了 DashScope API Key:
```bash
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
```
### 3. 网络问题
如果容器无法访问外网,检查 Docker 网络配置:
```bash
docker network inspect sdlc-network
```
## 生产环境建议
1. **使用 secrets 管理敏感信息**
```yaml
secrets:
dashscope_api_key:
external: true
services:
sdlc-agent:
secrets:
- dashscope_api_key
```
2. **添加资源限制**
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 2G
```
3. **配置日志轮转**
```yaml
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
4. **使用持久化存储**
```yaml
volumes:
- sdlc-data:/app/logs
volumes:
sdlc-data:
```

View File

@@ -1,46 +1,36 @@
# 多阶段构建 - SDLC Agent Demo
FROM python:3.11-slim as builder
# SDLC Agent Demo - Dockerfile
# 基于 CrewAI + Qwen3.5-flash + FastAPI 的多智能体软件交付系统
# 设置工作目录
WORKDIR /app
FROM python:3.11-slim
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 安装依赖
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 运行阶段
FROM python:3.11-slim
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PYTHONPATH=/app
# 设置工作目录
WORKDIR /app
# 从 builder 阶段复制安装的包
COPY --from=builder /root/.local /root/.local
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 确保脚本路径在 PATH 中
ENV PATH=/root/.local/bin:$PATH \
PYTHONPATH=/app
# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非 root 用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')" || exit 1
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/docs')" || exit 1
# 启动命令
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["python", "main.py"]

View File

@@ -95,7 +95,24 @@ uvicorn main:app --reload --host 0.0.0.0 --port 8000
打开浏览器访问http://localhost:8000/static/index.html
### 方法二Docker 运行
### 方法二:使用 Docker Compose (推荐)
```bash
# 1. 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填入你的 DashScope API Key
# 2. 启动服务
docker-compose up -d
# 3. 查看日志
docker-compose logs -f
# 4. 停止服务
docker-compose down
```
### 方法三:使用 Docker 构建
#### 1. 构建镜像
@@ -106,16 +123,34 @@ docker build -t sdlc-agent-demo .
#### 2. 运行容器
```bash
# 使用 docker-compose (推荐)
docker-compose up -d
# 或单独运行容器
docker run -d \
-p 8000:8000 \
-p 8080:8080 \
-e DASHSCOPE_API_KEY=your_api_key \
-v $(pwd)/logs:/app/logs \
--name sdlc-demo \
sdlc-agent-demo
```
#### 3. 访问服务
http://localhost:8000/static/index.html
- 前端界面:http://localhost:8080/static/index.html
- API 文档http://localhost:8080/docs
#### 4. 查看日志
```bash
docker logs -f sdlc-demo
```
#### 5. 停止服务
```bash
docker stop sdlc-demo && docker rm sdlc-demo
```
## 📡 API 接口

33
docker-compose.yml Normal file
View File

@@ -0,0 +1,33 @@
version: '3.8'
services:
sdlc-agent:
build:
context: .
dockerfile: Dockerfile
container_name: sdlc-agent-demo
restart: unless-stopped
ports:
- "8080:8080"
environment:
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:sk-616332b2afa94699b4572d0fe6ac370a}
- PYTHONUNBUFFERED=1
env_file:
- .env
volumes:
- ./logs:/app/logs
networks:
- sdlc-network
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/docs')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "com.sdlc.agent.name=SDLC Agent Demo"
- "com.sdlc.agent.version=1.0"
networks:
sdlc-network:
driver: bridge

131
main.py
View File

@@ -1,5 +1,5 @@
"""
SDLC Agent Demo - FastAPI 主服务(异步版本)
SDLC Agent Demo - FastAPI 主服务(轮询版本)
多智能体端到端软件交付协同系统
"""
@@ -7,7 +7,6 @@ import json
import uuid
import asyncio
import threading
import queue
from typing import Dict, Optional, AsyncGenerator
from datetime import datetime
from fastapi import FastAPI, HTTPException
@@ -16,6 +15,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse
from pydantic import BaseModel, Field
import uvicorn
from concurrent.futures import ThreadPoolExecutor
from crews.sdlc_crew import SDLCCrew
from models.qwen_config import get_qwen_config
@@ -27,7 +27,7 @@ app = FastAPI(
version="1.0.0"
)
# 启用 CORS
# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -104,6 +104,9 @@ class TaskManager:
# 全局任务管理器
task_manager = TaskManager()
# 线程池
executor = ThreadPoolExecutor(max_workers=5)
# ========== API 端点 ==========
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
@@ -130,13 +133,6 @@ async def start_sdlc_process(request: StartRequest):
}
import threading
import asyncio
from concurrent.futures import ThreadPoolExecutor
# 线程池
executor = ThreadPoolExecutor(max_workers=5)
def execute_sdlc_flow(task_id: str, requirement: str):
"""
在线程池中执行 SDLC 流程,将事件保存到任务列表
@@ -195,42 +191,6 @@ async def poll_task_events(task_id: str, last_index: int = 0):
return result
@app.get("/api/v1/sdlc/stream/{task_id}")
async def stream_task_progress(task_id: str):
"""
SSE流式输出任务进度
直接在异步函数中使用 async for 和 yield
FastAPI 会自动将其转换为 StreamingResponse
"""
# 验证任务存在
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# 直接使用 async for 和 yield
while True:
event = await task_manager.get_event(task_id, timeout=120.0)
if event is None:
# 超时,检查任务状态
task_data = task_manager.get_task(task_id)
if task_data and task_data["status"] in ["completed", "failed"]:
yield f"event: end\ndata: {json.dumps({'status': task_data['status']}, ensure_ascii=False)}\n\n"
break
continue
# 格式化 SSE事件
event_type = event.get("event", "message")
event_data = event.get("data", {})
yield f"event: {event_type}\ndata: {json.dumps(event_data, ensure_ascii=False)}\n\n"
# 如果是结束事件,断开连接
if event_type in ["final_result", "error"]:
break
@app.get("/api/v1/sdlc/status/{task_id}")
def get_task_status(task_id: str):
"""
@@ -248,85 +208,6 @@ def get_task_status(task_id: str):
}
@app.get("/api/v1/sdlc/result/{task_id}")
def get_task_result(task_id: str):
"""
获取任务完整结果
"""
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
if task["status"] != "completed":
raise HTTPException(
status_code=400,
detail=f"Task not completed yet. Status: {task['status']}"
)
return task
@app.get("/api/v1/sdlc/download/{task_id}")
def download_result(task_id: str):
"""
打包下载任务结果ZIP 文件)
"""
import zipfile
import io
from fastapi.responses import StreamingResponse
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
if task["status"] != "completed":
raise HTTPException(
status_code=400,
detail=f"Task not completed yet. Status: {task['status']}"
)
# 创建 ZIP 文件
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
# 1. SRS 文档
srs_content = ""
# 注意:异步模式下需要从 events 中获取,这里简化处理
zip_file.writestr("01_SRS_需求规格说明书.md", "需求文档内容")
# 2. 测试用例
zip_file.writestr("02_Test_测试用例.md", "测试用例内容")
# 3. 代码实现
zip_file.writestr("03_Code_代码实现.md", "代码实现内容")
# 4. 项目摘要
summary = f"""# SDLC 项目交付摘要
## 项目信息
- 任务 ID: {task['task_id']}
- 创建时间:{task['created_at']}
- 完成时间:{task['updated_at']}
- 原始需求:{task['requirement']}
## 生成说明
本项目由 SDLC Agent Demo 自动生成
基于 CrewAI + Qwen3.5-flash + FastAPI
"""
zip_file.writestr("README_项目摘要.md", summary)
# 准备下载
zip_buffer.seek(0)
return StreamingResponse(
zip_buffer,
media_type="application/zip",
headers={
"Content-Disposition": f"attachment; filename=SDLC_Result_{task_id[:8]}.zip"
}
)
@app.get("/")
def root():
"""根路径重定向到测试页面"""

View File

@@ -226,21 +226,6 @@
<div class="space-y-6" v-show="results.length > 0">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900">4. 输出结果</h2>
<button
@click="downloadResults"
:disabled="!isCompleted"
:class="[
'px-4 py-2 rounded-lg font-medium text-white transition-all duration-200 flex items-center',
isCompleted
? 'bg-green-600 hover:bg-green-700 shadow-md'
: 'bg-gray-400 cursor-not-allowed'
]"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
{{ isCompleted ? '打包下载结果' : '执行完成后下载' }}
</button>
</div>
<div v-for="(result, index) in results" :key="index" class="bg-white rounded-lg shadow-md overflow-hidden">
@@ -355,16 +340,6 @@
},
methods: {
/**
* 下载打包结果
*/
downloadResults() {
if (!this.taskId || !this.isCompleted) return;
const url = `/api/v1/sdlc/download/${this.taskId}`;
window.open(url, '_blank');
},
/**
* 启动 SDLC 流程
*/