From e68ad068298d5dc404963c86627444a18ddcf91a Mon Sep 17 00:00:00 2001 From: zly <644706215@qq.com> Date: Sat, 22 Nov 2025 21:29:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/ws/backend/.env.example | 22 ++ web/ws/backend/.gitignore | 38 +++ web/ws/backend/CONTRIBUTING.md | 314 ++++++++++++++++++ web/ws/backend/Dockerfile | 33 ++ web/ws/backend/README.md | 297 +++++++++++++++++ web/ws/backend/app/__init__.py | 0 web/ws/backend/app/api/__init__.py | 0 web/ws/backend/app/api/v1/__init__.py | 0 .../backend/app/api/v1/endpoints/__init__.py | 0 .../backend/app/api/v1/endpoints/analysis.py | 112 +++++++ web/ws/backend/app/api/v1/endpoints/genes.py | 56 ++++ .../backend/app/api/v1/endpoints/genomes.py | 51 +++ .../backend/app/api/v1/endpoints/strains.py | 56 ++++ web/ws/backend/app/api/v1/endpoints/upload.py | 107 ++++++ web/ws/backend/app/api/v1/router.py | 14 + web/ws/backend/app/core/__init__.py | 0 web/ws/backend/app/core/config.py | 43 +++ web/ws/backend/app/core/security.py | 39 +++ web/ws/backend/app/main.py | 43 +++ web/ws/backend/app/models/__init__.py | 0 web/ws/backend/app/schemas/__init__.py | 0 web/ws/backend/app/services/__init__.py | 0 web/ws/backend/manage.py | 22 ++ web/ws/backend/requirements.txt | 27 ++ web/ws/backend/start.sh | 41 +++ web/ws/backend/test_api.sh | 35 ++ 26 files changed, 1350 insertions(+) create mode 100644 web/ws/backend/.env.example create mode 100644 web/ws/backend/.gitignore create mode 100644 web/ws/backend/CONTRIBUTING.md create mode 100644 web/ws/backend/Dockerfile create mode 100644 web/ws/backend/README.md create mode 100644 web/ws/backend/app/__init__.py create mode 100644 web/ws/backend/app/api/__init__.py create mode 100644 web/ws/backend/app/api/v1/__init__.py create mode 100644 web/ws/backend/app/api/v1/endpoints/__init__.py create mode 100644 web/ws/backend/app/api/v1/endpoints/analysis.py create mode 100644 web/ws/backend/app/api/v1/endpoints/genes.py create mode 100644 web/ws/backend/app/api/v1/endpoints/genomes.py create mode 100644 web/ws/backend/app/api/v1/endpoints/strains.py create mode 100644 web/ws/backend/app/api/v1/endpoints/upload.py create mode 100644 web/ws/backend/app/api/v1/router.py create mode 100644 web/ws/backend/app/core/__init__.py create mode 100644 web/ws/backend/app/core/config.py create mode 100644 web/ws/backend/app/core/security.py create mode 100644 web/ws/backend/app/main.py create mode 100644 web/ws/backend/app/models/__init__.py create mode 100644 web/ws/backend/app/schemas/__init__.py create mode 100644 web/ws/backend/app/services/__init__.py create mode 100644 web/ws/backend/manage.py create mode 100644 web/ws/backend/requirements.txt create mode 100755 web/ws/backend/start.sh create mode 100755 web/ws/backend/test_api.sh diff --git a/web/ws/backend/.env.example b/web/ws/backend/.env.example new file mode 100644 index 0000000..a652d4f --- /dev/null +++ b/web/ws/backend/.env.example @@ -0,0 +1,22 @@ +# 项目信息 +PROJECT_NAME=PBMDB API +VERSION=1.0.0 +API_V1_STR=/api/v1 + +# 数据库配置 +DATABASE_URL=postgresql://webws_admin:your_password_here@postgres:5432/webws_database + +# 安全配置 +SECRET_KEY=your-secret-key-here-change-in-production-use-at-least-32-chars +ACCESS_TOKEN_EXPIRE_MINUTES=10080 + +# CORS 配置 +ALLOWED_ORIGINS=https://amiap.hzau.edu.cn,http://localhost:3000 + +# 文件上传配置 +MAX_UPLOAD_SIZE=104857600 +UPLOAD_DIR=/app/uploads + +# 分页配置 +DEFAULT_PAGE_SIZE=20 +MAX_PAGE_SIZE=100 diff --git a/web/ws/backend/.gitignore b/web/ws/backend/.gitignore new file mode 100644 index 0000000..46d37a0 --- /dev/null +++ b/web/ws/backend/.gitignore @@ -0,0 +1,38 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +env/ +ENV/ +*.egg-info/ +dist/ +build/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# 环境变量 +.env + +# 数据库 +*.db +*.sqlite3 + +# 上传文件 +uploads/ +*.log + +# 测试 +.pytest_cache/ +.coverage +htmlcov/ + +# Alembic +alembic/versions/*.pyc diff --git a/web/ws/backend/CONTRIBUTING.md b/web/ws/backend/CONTRIBUTING.md new file mode 100644 index 0000000..3de5ab4 --- /dev/null +++ b/web/ws/backend/CONTRIBUTING.md @@ -0,0 +1,314 @@ +# 开发指南 + +## 快速开始 + +### 本地开发 + +```bash +# 1. 进入后端目录 +cd /vol1/1000/docker_server/traefik/web/ws/backend + +# 2. 使用启动脚本(自动创建虚拟环境、安装依赖) +./start.sh + +# 或者手动启动 +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +访问: +- API 文档: http://localhost:8000/docs +- 健康检查: http://localhost:8000/health + +### Docker 开发 + +```bash +cd /vol1/1000/docker_server/traefik/web/ws + +# 构建镜像 +docker compose build backend + +# 启动服务 +docker compose up -d backend + +# 查看日志 +docker compose logs -f backend + +# 进入容器 +docker compose exec backend bash +``` + +## 开发流程 + +### 1. 添加新的 API 端点 + +**示例:添加实验数据 API** + +```bash +# 1. 创建端点文件 +touch app/api/v1/endpoints/experiments.py +``` + +```python +# app/api/v1/endpoints/experiments.py +from fastapi import APIRouter + +router = APIRouter() + +@router.get("/") +async def get_experiments(): + """获取实验列表""" + return {"experiments": []} + +@router.get("/{experiment_id}") +async def get_experiment(experiment_id: str): + """获取实验详情""" + return {"id": experiment_id} +``` + +```python +# 2. 在 app/api/v1/router.py 中注册 +from app.api.v1.endpoints import experiments + +api_router.include_router( + experiments.router, + prefix="/experiments", + tags=["experiments"] +) +``` + +### 2. 添加数据模型 + +```bash +# 创建模型文件 +touch app/models/experiment.py +``` + +```python +# app/models/experiment.py +from sqlalchemy import Column, String, Integer, DateTime +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class Experiment(Base): + __tablename__ = "experiments" + + id = Column(Integer, primary_key=True) + name = Column(String(200)) + description = Column(String(1000)) + created_at = Column(DateTime) +``` + +### 3. 添加 Pydantic 模式 + +```bash +# 创建模式文件 +touch app/schemas/experiment.py +``` + +```python +# app/schemas/experiment.py +from pydantic import BaseModel +from datetime import datetime +from typing import Optional + +class ExperimentBase(BaseModel): + name: str + description: Optional[str] = None + +class ExperimentCreate(ExperimentBase): + pass + +class ExperimentResponse(ExperimentBase): + id: int + created_at: datetime + + class Config: + from_attributes = True +``` + +### 4. 添加业务逻辑 + +```bash +# 创建服务文件 +touch app/services/experiment_service.py +``` + +```python +# app/services/experiment_service.py +from sqlalchemy.orm import Session +from app.models.experiment import Experiment +from app.schemas.experiment import ExperimentCreate + +async def create_experiment(db: Session, data: ExperimentCreate): + """创建实验""" + experiment = Experiment(**data.dict()) + db.add(experiment) + db.commit() + db.refresh(experiment) + return experiment +``` + +## 代码规范 + +### Python 风格 + +- 遵循 PEP 8 规范 +- 使用类型提示 +- 函数添加文档字符串 + +```python +async def get_strain_detail(strain_id: str) -> dict: + """ + 获取菌种详情 + + Args: + strain_id: 菌种 ID + + Returns: + dict: 菌种详细信息 + + Raises: + HTTPException: 菌种不存在时 + """ + pass +``` + +### 命名规范 + +- 文件名:`snake_case.py` +- 类名:`PascalCase` +- 函数名:`snake_case` +- 常量:`UPPER_CASE` + +### 目录组织 + +``` +app/ +├── api/v1/endpoints/ # 按功能模块组织 +│ ├── strains.py # 菌种相关 +│ ├── genes.py # 基因相关 +│ └── upload.py # 上传相关 +├── models/ # 数据库模型 +├── schemas/ # 数据验证模式 +└── services/ # 业务逻辑 +``` + +## 测试 + +### 单元测试 + +```bash +# 安装测试依赖 +pip install pytest pytest-asyncio httpx + +# 运行测试 +pytest +``` + +### API 测试 + +```bash +# 使用测试脚本 +./test_api.sh + +# 或使用 curl +curl http://localhost:8000/health +``` + +## 数据库迁移 + +```bash +# 安装 alembic +pip install alembic + +# 初始化 +alembic init alembic + +# 创建迁移 +alembic revision --autogenerate -m "Add experiments table" + +# 执行迁移 +alembic upgrade head + +# 回滚 +alembic downgrade -1 +``` + +## 调试技巧 + +### 1. 日志输出 + +```python +import logging + +logger = logging.getLogger(__name__) + +@router.get("/debug") +async def debug_endpoint(): + logger.info("Debug endpoint called") + logger.debug(f"Data: {data}") + return {"status": "ok"} +``` + +### 2. 断点调试 + +```python +# 使用 pdb +import pdb; pdb.set_trace() + +# 或使用 breakpoint() +breakpoint() +``` + +### 3. 查看 SQL 查询 + +```python +# 在配置中启用 SQL 日志 +DATABASE_URL = "postgresql://...?echo=true" +``` + +## 常见问题 + +### Q: 导入错误? + +确保在项目根目录运行,并使用正确的 Python 路径: + +```bash +export PYTHONPATH=/vol1/1000/docker_server/traefik/web/ws/backend:$PYTHONPATH +``` + +### Q: 数据库连接失败? + +检查 `.env` 文件中的 `DATABASE_URL` 配置。 + +### Q: CORS 错误? + +在 `app/core/config.py` 中添加允许的源。 + +## 部署检查清单 + +- [ ] 修改生产环境 `SECRET_KEY` +- [ ] 配置正确的 `DATABASE_URL` +- [ ] 更新 `ALLOWED_ORIGINS` +- [ ] 运行数据库迁移 +- [ ] 测试所有 API 端点 +- [ ] 配置日志记录 +- [ ] 设置监控和告警 +- [ ] 备份数据库 + +## 贡献流程 + +1. 创建功能分支 +2. 开发并测试 +3. 提交代码(commit message 规范) +4. 提交 Pull Request +5. Code Review +6. 合并到主分支 + +## 联系方式 + +如有问题,请联系开发团队。 diff --git a/web/ws/backend/Dockerfile b/web/ws/backend/Dockerfile new file mode 100644 index 0000000..126c1d4 --- /dev/null +++ b/web/ws/backend/Dockerfile @@ -0,0 +1,33 @@ +# FastAPI 后端 Dockerfile +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY app/ ./app/ + +# 创建上传目录 +RUN mkdir -p /app/uploads + +# 暴露端口 +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +# 启动命令 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/web/ws/backend/README.md b/web/ws/backend/README.md new file mode 100644 index 0000000..0432513 --- /dev/null +++ b/web/ws/backend/README.md @@ -0,0 +1,297 @@ +# FastAPI 后端项目 + +PBMDB (Plant Beneficial Microbe Database) 农业有益微生物综合信息数据库后端 API 服务。 + +## 📁 项目结构 + +``` +backend/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI 应用入口 +│ ├── api/ +│ │ ├── __init__.py +│ │ └── v1/ +│ │ ├── __init__.py +│ │ ├── router.py # API 路由汇总 +│ │ └── endpoints/ +│ │ ├── __init__.py +│ │ ├── strains.py # 菌种资源 API +│ │ ├── genomes.py # 基因组学数据 API +│ │ ├── genes.py # 基因资源 API +│ │ ├── upload.py # 数据汇交上传 API +│ │ └── analysis.py # 智能分析 API +│ ├── core/ +│ │ ├── __init__.py +│ │ ├── config.py # 应用配置 +│ │ └── security.py # 认证安全 +│ ├── models/ # SQLAlchemy 数据模型 +│ │ └── __init__.py +│ ├── schemas/ # Pydantic 数据模式 +│ │ └── __init__.py +│ └── services/ # 业务逻辑层 +│ └── __init__.py +├── requirements.txt # Python 依赖 +├── Dockerfile # Docker 构建文件 +├── .env.example # 环境变量示例 +└── README.md # 本文件 +``` + +## 🚀 技术栈 + +- **FastAPI**: 现代、高性能 Python Web 框架 +- **Uvicorn**: ASGI 服务器 +- **SQLAlchemy**: ORM 数据库工具 +- **PostgreSQL**: 关系型数据库 +- **Pydantic**: 数据验证和设置管理 + +## 🎯 核心功能模块 + +### 1. 菌种资源 (`/api/v1/strains`) +- 菌种列表查询(分类、搜索、分页) +- 菌种详情展示 +- 菌种分类树 + +### 2. 基因组学数据 (`/api/v1/genomes`) +- 基因组数据列表(基因组学/转录组学/其他组学) +- 基因组详情 +- 物种分类树 + +### 3. 基因资源 (`/api/v1/genes`) +- 基因列表查询(功能分类) +- 基因详情 +- 功能基因分类 + +### 4. 数据汇交 (`/api/v1/upload`) +- 基因组数据上传 +- 宏基因组数据上传 +- 原始测序数据上传 +- 上传口令验证 +- 上传状态查询 + +### 5. 智能分析 (`/api/v1/analysis`) +- 智能问答(基于知识图谱) +- 基因挖掘(序列比对) +- 菌株评估 +- 知识图谱查询 +- 分析任务状态查询 + +## ⚙️ 环境配置 + +### 1. 创建环境变量文件 + +```bash +cd /vol1/1000/docker_server/traefik/web/ws/backend +cp .env.example .env +``` + +编辑 `.env` 文件: + +```bash +# 项目信息 +PROJECT_NAME=PBMDB API +VERSION=1.0.0 + +# 数据库配置 +DATABASE_URL=postgresql://webws_admin:your_password@postgres:5432/webws_database + +# 安全配置 +SECRET_KEY=your-secret-key-here-change-in-production + +# CORS 配置 +ALLOWED_ORIGINS=https://amiap.hzau.edu.cn,http://localhost:3000 + +# 文件上传配置 +MAX_UPLOAD_SIZE=104857600 +UPLOAD_DIR=/app/uploads +``` + +## 🏃 本地开发 + +### 使用虚拟环境 + +```bash +cd /vol1/1000/docker_server/traefik/web/ws/backend + +# 创建虚拟环境 +python3 -m venv venv +source venv/bin/activate + +# 安装依赖 +pip install -r requirements.txt + +# 运行开发服务器 +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +访问: +- API 文档: http://localhost:8000/docs +- ReDoc 文档: http://localhost:8000/redoc +- 健康检查: http://localhost:8000/health + +## 🐳 Docker 部署 + +### 1. 更新 docker-compose.yml + +在 `/vol1/1000/docker_server/traefik/web/ws/docker-compose.yml` 中添加后端服务: + +```yaml +services: + # ... 现有服务 ... + + backend: + build: ./backend + container_name: webws-backend + restart: unless-stopped + environment: + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + volumes: + - ./backend/uploads:/app/uploads + networks: + - frontend + depends_on: + - postgres + labels: + - "traefik.enable=true" + + # HTTP 路由 - 重定向到 HTTPS + - "traefik.http.routers.webws-api-http.rule=Host(`amiap.hzau.edu.cn`) && PathPrefix(`/ABM/api`)" + - "traefik.http.routers.webws-api-http.entrypoints=web" + - "traefik.http.routers.webws-api-http.priority=100" + - "traefik.http.routers.webws-api-http.middlewares=redirect-to-https" + + # HTTPS 路由 + - "traefik.http.routers.webws-api-https.rule=Host(`amiap.hzau.edu.cn`) && PathPrefix(`/ABM/api`)" + - "traefik.http.routers.webws-api-https.entrypoints=websecure" + - "traefik.http.routers.webws-api-https.priority=100" + - "traefik.http.routers.webws-api-https.tls.certresolver=myresolver" + + # StripPrefix 中间件(移除 /ABM/api 前缀) + - "traefik.http.routers.webws-api-https.middlewares=api-stripprefix" + - "traefik.http.middlewares.api-stripprefix.stripprefix.prefixes=/ABM/api" + + - "traefik.http.services.webws-api.loadbalancer.server.port=8000" +``` + +### 2. 构建并启动 + +```bash +cd /vol1/1000/docker_server/traefik/web/ws + +# 构建镜像 +docker compose build backend + +# 启动服务 +docker compose up -d backend + +# 查看日志 +docker compose logs -f backend +``` + +## 📡 API 访问地址 + +部署后,API 通过以下地址访问: + +- **外部访问**: https://amiap.hzau.edu.cn/ABM/api/ +- **API 文档**: https://amiap.hzau.edu.cn/ABM/api/docs +- **容器间访问**: http://webws-backend:8000/ + +## 🔧 路由说明 + +### URL 路径处理 + +由于使用了 Traefik `StripPrefix` 中间件,路径会自动处理: + +**用户访问**: `https://amiap.hzau.edu.cn/ABM/api/strains/` +**FastAPI 接收**: `/api/v1/strains/` (已移除 `/ABM/api`) + +### 路由优先级 + +``` +Priority 100: /ABM/api/** → 后端 API 服务 +Priority 47: /ABM/** → Nginx 静态文件服务 +``` + +不会产生冲突,Traefik 优先匹配更高优先级和更具体的路径。 + +## 📊 数据库迁移 + +### 使用 Alembic + +```bash +# 进入容器 +docker compose exec backend bash + +# 初始化迁移(首次) +alembic init alembic + +# 创建迁移 +alembic revision --autogenerate -m "Initial migration" + +# 执行迁移 +alembic upgrade head +``` + +## 🧪 测试 API + +### 使用 curl + +```bash +# 健康检查 +curl https://amiap.hzau.edu.cn/ABM/api/health + +# 获取菌种列表 +curl https://amiap.hzau.edu.cn/ABM/api/api/v1/strains/ + +# 获取基因组数据 +curl https://amiap.hzau.edu.cn/ABM/api/api/v1/genomes/ +``` + +### 使用 Swagger UI + +访问 https://amiap.hzau.edu.cn/ABM/api/docs 进行交互式测试。 + +## 📝 开发指南 + +### 添加新的 API 端点 + +1. 在 `app/api/v1/endpoints/` 下创建新文件 +2. 定义路由和处理函数 +3. 在 `app/api/v1/router.py` 中注册路由 + +### 添加数据模型 + +1. 在 `app/models/` 下创建 SQLAlchemy 模型 +2. 在 `app/schemas/` 下创建 Pydantic 模式 +3. 运行数据库迁移 + +### 添加业务逻辑 + +在 `app/services/` 下创建服务层代码,保持控制器简洁。 + +## 🔒 安全注意事项 + +1. **生产环境**必须修改 `SECRET_KEY` +2. 使用环境变量管理敏感信息 +3. 启用 HTTPS(已配置) +4. 实现完整的认证系统(待实现) +5. 添加请求频率限制 + +## 🚀 性能优化 + +1. 使用数据库连接池 +2. 启用查询缓存(Redis) +3. 异步文件处理 +4. 压缩响应数据 +5. CDN 加速静态资源 + +## 📚 相关文档 + +- [FastAPI 官方文档](https://fastapi.tiangolo.com/) +- [Pydantic 文档](https://docs.pydantic.dev/) +- [SQLAlchemy 文档](https://docs.sqlalchemy.org/) +- [Uvicorn 文档](https://www.uvicorn.org/) + +## 📞 联系方式 + +如有问题,请联系项目维护团队。 diff --git a/web/ws/backend/app/__init__.py b/web/ws/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/api/__init__.py b/web/ws/backend/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/api/v1/__init__.py b/web/ws/backend/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/api/v1/endpoints/__init__.py b/web/ws/backend/app/api/v1/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/api/v1/endpoints/analysis.py b/web/ws/backend/app/api/v1/endpoints/analysis.py new file mode 100644 index 0000000..b339e7b --- /dev/null +++ b/web/ws/backend/app/api/v1/endpoints/analysis.py @@ -0,0 +1,112 @@ +""" +智能分析 API +""" +from typing import Optional +from fastapi import APIRouter, BackgroundTasks, HTTPException +from pydantic import BaseModel + +router = APIRouter() + + +class QuestionRequest(BaseModel): + """智能问答请求""" + question: str + context: Optional[str] = None + + +class GeneMiningRequest(BaseModel): + """基因挖掘请求""" + sequence: str + algorithm: str = "blast" + parameters: Optional[dict] = None + + +class StrainEvaluationRequest(BaseModel): + """菌株评估请求""" + strain_id: str + evaluation_type: str # growth/resistance/etc + + +@router.post("/qa") +async def intelligent_qa(request: QuestionRequest): + """ + 智能问答 + + 基于知识图谱的智能问答系统 + """ + # TODO: 集成 AI 模型进行问答 + return { + "question": request.question, + "answer": "This is a placeholder answer. AI integration pending.", + "confidence": 0.0 + } + + +@router.post("/gene-mining") +async def gene_mining( + background_tasks: BackgroundTasks, + request: GeneMiningRequest +): + """ + 基因挖掘 + + 通过序列比对等方法挖掘功能基因 + """ + # TODO: 实现基因挖掘算法 + # background_tasks.add_task(run_gene_mining, request) + + return { + "status": "processing", + "task_id": "task_123", + "message": "Gene mining task started" + } + + +@router.post("/strain-evaluation") +async def strain_evaluation( + background_tasks: BackgroundTasks, + request: StrainEvaluationRequest +): + """ + 菌株评估 + + 评估菌株的各项指标和应用潜力 + """ + # TODO: 实现菌株评估逻辑 + return { + "status": "processing", + "task_id": "task_456", + "message": "Strain evaluation started" + } + + +@router.get("/knowledge-graph") +async def get_knowledge_graph( + entity: Optional[str] = None, + relationship: Optional[str] = None, +): + """ + 知识图谱查询 + + 查询微生物知识图谱 + """ + # TODO: 实现知识图谱查询 + return { + "nodes": [], + "edges": [], + "message": "Knowledge graph feature pending" + } + + +@router.get("/task/{task_id}") +async def get_analysis_task_status(task_id: str): + """ + 查询分析任务状态 + """ + # TODO: 从数据库或缓存查询任务状态 + return { + "task_id": task_id, + "status": "completed", + "progress": 100, + "result": None + } diff --git a/web/ws/backend/app/api/v1/endpoints/genes.py b/web/ws/backend/app/api/v1/endpoints/genes.py new file mode 100644 index 0000000..9e335c5 --- /dev/null +++ b/web/ws/backend/app/api/v1/endpoints/genes.py @@ -0,0 +1,56 @@ +""" +基因资源 API +""" +from typing import List, Optional +from fastapi import APIRouter, Query, HTTPException + +router = APIRouter() + + +@router.get("/") +async def get_genes( + skip: int = Query(0, ge=0), + limit: int = Query(20, ge=1, le=100), + function_type: Optional[str] = None, + search: Optional[str] = None, +): + """ + 获取基因列表 + + - **skip**: 跳过记录数 + - **limit**: 返回记录数 + - **function_type**: 功能类型 (抗逆/促生/杀虫/抗病/其他) + - **search**: 搜索关键词 + """ + # TODO: 实现数据库查询 + return { + "total": 0, + "items": [], + "skip": skip, + "limit": limit + } + + +@router.get("/{gene_id}") +async def get_gene_detail(gene_id: str): + """ + 获取基因详情 + """ + # TODO: 从数据库查询基因详细信息 + raise HTTPException(status_code=404, detail="Gene not found") + + +@router.get("/functions/categories") +async def get_gene_functions(): + """ + 获取基因功能分类 + """ + return { + "categories": [ + {"id": 1, "name": "抗逆基因", "count": 0}, + {"id": 2, "name": "促生基因", "count": 0}, + {"id": 3, "name": "杀虫基因", "count": 0}, + {"id": 4, "name": "抗病基因", "count": 0}, + {"id": 5, "name": "其他功能基因", "count": 0}, + ] + } diff --git a/web/ws/backend/app/api/v1/endpoints/genomes.py b/web/ws/backend/app/api/v1/endpoints/genomes.py new file mode 100644 index 0000000..72e4155 --- /dev/null +++ b/web/ws/backend/app/api/v1/endpoints/genomes.py @@ -0,0 +1,51 @@ +""" +基因组学数据 API +""" +from typing import List, Optional +from fastapi import APIRouter, Query, HTTPException + +router = APIRouter() + + +@router.get("/") +async def get_genomes( + skip: int = Query(0, ge=0), + limit: int = Query(20, ge=1, le=100), + data_type: Optional[str] = None, + species: Optional[str] = None, +): + """ + 获取基因组数据列表 + + - **skip**: 跳过记录数 + - **limit**: 返回记录数 + - **data_type**: 数据类型 (基因组学/转录组学/其他组学) + - **species**: 物种名称 + """ + # TODO: 实现数据库查询 + return { + "total": 0, + "items": [], + "skip": skip, + "limit": limit + } + + +@router.get("/{genome_id}") +async def get_genome_detail(genome_id: str): + """ + 获取基因组详情 + """ + # TODO: 从数据库查询基因组详细信息 + raise HTTPException(status_code=404, detail="Genome not found") + + +@router.get("/species/tree") +async def get_species_tree(): + """ + 获取物种分类树 + """ + # TODO: 返回物种分类树结构 + return { + "tree": [] + } diff --git a/web/ws/backend/app/api/v1/endpoints/strains.py b/web/ws/backend/app/api/v1/endpoints/strains.py new file mode 100644 index 0000000..00a375e --- /dev/null +++ b/web/ws/backend/app/api/v1/endpoints/strains.py @@ -0,0 +1,56 @@ +""" +菌种资源 API +""" +from typing import List, Optional +from fastapi import APIRouter, Query, HTTPException + +router = APIRouter() + + +@router.get("/") +async def get_strains( + skip: int = Query(0, ge=0), + limit: int = Query(20, ge=1, le=100), + category: Optional[str] = None, + search: Optional[str] = None, +): + """ + 获取菌种列表 + + - **skip**: 跳过记录数 + - **limit**: 返回记录数 + - **category**: 菌种分类 (抗逆/促生/杀虫/抗病/食药用) + - **search**: 搜索关键词 + """ + # TODO: 实现数据库查询 + return { + "total": 0, + "items": [], + "skip": skip, + "limit": limit + } + + +@router.get("/{strain_id}") +async def get_strain_detail(strain_id: str): + """ + 获取菌种详情 + """ + # TODO: 从数据库查询菌种详细信息 + raise HTTPException(status_code=404, detail="Strain not found") + + +@router.get("/categories/tree") +async def get_strain_categories(): + """ + 获取菌种分类树 + """ + return { + "categories": [ + {"id": 1, "name": "抗逆微生物", "count": 0}, + {"id": 2, "name": "促生微生物", "count": 0}, + {"id": 3, "name": "杀虫微生物", "count": 0}, + {"id": 4, "name": "抗病微生物", "count": 0}, + {"id": 5, "name": "食药用微生物", "count": 0}, + ] + } diff --git a/web/ws/backend/app/api/v1/endpoints/upload.py b/web/ws/backend/app/api/v1/endpoints/upload.py new file mode 100644 index 0000000..1995c22 --- /dev/null +++ b/web/ws/backend/app/api/v1/endpoints/upload.py @@ -0,0 +1,107 @@ +""" +数据汇交上传 API +""" +from typing import Optional +from fastapi import APIRouter, UploadFile, File, Form, HTTPException, BackgroundTasks +from app.core.security import verify_upload_key + +router = APIRouter() + + +@router.post("/verify-key") +async def verify_key(key: str = Form(...)): + """ + 验证上传口令 + """ + is_valid = await verify_upload_key(key) + if not is_valid: + raise HTTPException(status_code=401, detail="Invalid upload key") + return {"status": "success", "message": "Key verified"} + + +@router.post("/genome") +async def upload_genome_data( + background_tasks: BackgroundTasks, + project_id: str = Form(...), + submitter_name: str = Form(...), + submitter_email: str = Form(...), + upload_key: str = Form(...), + file: Optional[UploadFile] = File(None), +): + """ + 上传基因组数据 + + 支持逐条提交和批量上传 + """ + # 验证口令 + if not await verify_upload_key(upload_key): + raise HTTPException(status_code=401, detail="Invalid upload key") + + # TODO: 处理文件上传 + if file: + # 异步保存文件 + filename = file.filename + # background_tasks.add_task(save_file, file, filename) + + # TODO: 保存到数据库 + return { + "status": "success", + "message": "Data uploaded successfully", + "project_id": project_id + } + + +@router.post("/metagenome") +async def upload_metagenome_data( + background_tasks: BackgroundTasks, + project_id: str = Form(...), + submitter_name: str = Form(...), + upload_key: str = Form(...), + file: Optional[UploadFile] = File(None), +): + """ + 上传宏基因组数据 + """ + if not await verify_upload_key(upload_key): + raise HTTPException(status_code=401, detail="Invalid upload key") + + # TODO: 实现宏基因组数据上传逻辑 + return { + "status": "success", + "message": "Metagenome data uploaded", + "project_id": project_id + } + + +@router.post("/raw-data") +async def upload_raw_data( + background_tasks: BackgroundTasks, + project_id: str = Form(...), + upload_key: str = Form(...), + file: UploadFile = File(...), +): + """ + 上传原始测序数据 + """ + if not await verify_upload_key(upload_key): + raise HTTPException(status_code=401, detail="Invalid upload key") + + # TODO: 实现原始数据上传逻辑 + return { + "status": "success", + "message": "Raw data uploaded", + "filename": file.filename + } + + +@router.get("/status/{upload_id}") +async def get_upload_status(upload_id: str): + """ + 查询上传状态 + """ + # TODO: 从数据库查询上传状态 + return { + "upload_id": upload_id, + "status": "processing", + "progress": 50 + } diff --git a/web/ws/backend/app/api/v1/router.py b/web/ws/backend/app/api/v1/router.py new file mode 100644 index 0000000..c5b5e86 --- /dev/null +++ b/web/ws/backend/app/api/v1/router.py @@ -0,0 +1,14 @@ +""" +API v1 路由汇总 +""" +from fastapi import APIRouter +from app.api.v1.endpoints import strains, genomes, genes, upload, analysis + +api_router = APIRouter() + +# 注册各模块路由 +api_router.include_router(strains.router, prefix="/strains", tags=["strains"]) +api_router.include_router(genomes.router, prefix="/genomes", tags=["genomes"]) +api_router.include_router(genes.router, prefix="/genes", tags=["genes"]) +api_router.include_router(upload.router, prefix="/upload", tags=["upload"]) +api_router.include_router(analysis.router, prefix="/analysis", tags=["analysis"]) diff --git a/web/ws/backend/app/core/__init__.py b/web/ws/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/core/config.py b/web/ws/backend/app/core/config.py new file mode 100644 index 0000000..12c9ee1 --- /dev/null +++ b/web/ws/backend/app/core/config.py @@ -0,0 +1,43 @@ +""" +应用配置 +""" +from typing import List +from pydantic_settings import BaseSettings +from pydantic import AnyHttpUrl + + +class Settings(BaseSettings): + """应用配置类""" + + # 项目信息 + PROJECT_NAME: str = "PBMDB API" + VERSION: str = "1.0.0" + API_V1_STR: str = "/api/v1" + + # 数据库配置 + DATABASE_URL: str = "postgresql://webws_admin:password@postgres:5432/webws_database" + + # CORS 配置 + ALLOWED_ORIGINS: List[str] = [ + "https://amiap.hzau.edu.cn", + "http://localhost:3000", # 本地开发 + ] + + # 安全配置 + SECRET_KEY: str = "your-secret-key-here-change-in-production" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days + + # 文件上传配置 + MAX_UPLOAD_SIZE: int = 1024 * 1024 * 100 # 100MB + UPLOAD_DIR: str = "/app/uploads" + + # 分页配置 + DEFAULT_PAGE_SIZE: int = 20 + MAX_PAGE_SIZE: int = 100 + + class Config: + env_file = ".env" + case_sensitive = True + + +settings = Settings() diff --git a/web/ws/backend/app/core/security.py b/web/ws/backend/app/core/security.py new file mode 100644 index 0000000..2338219 --- /dev/null +++ b/web/ws/backend/app/core/security.py @@ -0,0 +1,39 @@ +""" +安全认证模块 +""" +from datetime import datetime, timedelta +from typing import Optional +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials + + +security = HTTPBearer() + + +async def verify_token( + credentials: HTTPAuthorizationCredentials = Depends(security) +) -> dict: + """ + 验证 Token + """ + token = credentials.credentials + # TODO: 实现 JWT token 验证逻辑 + return {"user_id": "example"} + + +async def verify_upload_key(key: str) -> bool: + """ + 验证上传口令 + 用于数据汇交功能 + """ + # TODO: 从数据库或配置中验证口令 + valid_keys = ["temp_key_123"] # 临时示例 + return key in valid_keys + + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + """ + 创建访问令牌 + """ + # TODO: 实现 JWT token 生成 + return "temporary_token" diff --git a/web/ws/backend/app/main.py b/web/ws/backend/app/main.py new file mode 100644 index 0000000..7c6b9eb --- /dev/null +++ b/web/ws/backend/app/main.py @@ -0,0 +1,43 @@ +""" +FastAPI 应用入口 +""" +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.core.config import settings +from app.api.v1.router import api_router + +app = FastAPI( + title=settings.PROJECT_NAME, + version=settings.VERSION, + description="PBMDB (Plant Beneficial Microbe Database) API", + docs_url="/docs", + redoc_url="/redoc", +) + +# CORS 配置 +app.add_middleware( + CORSMiddleware, + allow_origins=settings.ALLOWED_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 注册路由 +app.include_router(api_router, prefix="/api/v1") + + +@app.get("/") +async def root(): + """健康检查端点""" + return { + "status": "ok", + "message": "PBMDB API is running", + "version": settings.VERSION + } + + +@app.get("/health") +async def health_check(): + """健康检查""" + return {"status": "healthy"} diff --git a/web/ws/backend/app/models/__init__.py b/web/ws/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/schemas/__init__.py b/web/ws/backend/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/app/services/__init__.py b/web/ws/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/ws/backend/manage.py b/web/ws/backend/manage.py new file mode 100644 index 0000000..6361598 --- /dev/null +++ b/web/ws/backend/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.base') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/web/ws/backend/requirements.txt b/web/ws/backend/requirements.txt new file mode 100644 index 0000000..4c86cf8 --- /dev/null +++ b/web/ws/backend/requirements.txt @@ -0,0 +1,27 @@ +# FastAPI 核心 +fastapi>=0.104.0 +uvicorn[standard]>=0.24.0 +pydantic>=2.5.0 +pydantic-settings>=2.1.0 + +# 数据库 +sqlalchemy>=2.0.0 +asyncpg>=0.29.0 +alembic>=1.13.0 + +# 文件处理 +aiofiles>=23.2.0 +python-multipart>=0.0.6 + +# 认证 +python-jose[cryptography]>=3.3.0 +passlib[bcrypt]>=1.7.4 + +# 其他常用库 +python-dateutil>=2.8.2 +pytz>=2023.3 + +# 可选:AI/ML 相关(按需取消注释) +# transformers>=4.35.0 +# torch>=2.1.0 +# scikit-learn>=1.3.0 diff --git a/web/ws/backend/start.sh b/web/ws/backend/start.sh new file mode 100755 index 0000000..966785d --- /dev/null +++ b/web/ws/backend/start.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# FastAPI 开发服务器启动脚本 + +set -e + +echo "==========================================" +echo "启动 FastAPI 开发服务器" +echo "==========================================" + +# 检查虚拟环境 +if [ ! -d "venv" ]; then + echo "创建虚拟环境..." + python3 -m venv venv +fi + +# 激活虚拟环境 +echo "激活虚拟环境..." +source venv/bin/activate + +# 安装依赖 +echo "安装依赖..." +pip install -r requirements.txt + +# 检查环境变量 +if [ ! -f ".env" ]; then + echo "警告: .env 文件不存在,复制 .env.example..." + cp .env.example .env + echo "请编辑 .env 文件配置数据库等信息" +fi + +# 启动服务器 +echo "" +echo "==========================================" +echo "启动服务器..." +echo "==========================================" +echo "API 文档: http://localhost:8000/docs" +echo "健康检查: http://localhost:8000/health" +echo "==========================================" +echo "" + +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 diff --git a/web/ws/backend/test_api.sh b/web/ws/backend/test_api.sh new file mode 100755 index 0000000..bb8b03e --- /dev/null +++ b/web/ws/backend/test_api.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# FastAPI API 测试脚本 + +BASE_URL="https://amiap.hzau.edu.cn/ABM/api" + +echo "==========================================" +echo "测试 PBMDB FastAPI 接口" +echo "==========================================" + +echo -e "\n✓ 1. 健康检查..." +curl -s "${BASE_URL}/health" | jq '.' || echo "Failed" + +echo -e "\n✓ 2. 根路径..." +curl -s "${BASE_URL}/" | jq '.' || echo "Failed" + +echo -e "\n✓ 3. 获取菌种列表..." +curl -s "${BASE_URL}/api/v1/strains/" | jq '.' || echo "Failed" + +echo -e "\n✓ 4. 获取菌种分类..." +curl -s "${BASE_URL}/api/v1/strains/categories/tree" | jq '.' || echo "Failed" + +echo -e "\n✓ 5. 获取基因组列表..." +curl -s "${BASE_URL}/api/v1/genomes/" | jq '.' || echo "Failed" + +echo -e "\n✓ 6. 获取基因列表..." +curl -s "${BASE_URL}/api/v1/genes/" | jq '.' || echo "Failed" + +echo -e "\n✓ 7. 获取基因功能分类..." +curl -s "${BASE_URL}/api/v1/genes/functions/categories" | jq '.' || echo "Failed" + +echo -e "\n==========================================" +echo "测试完成" +echo "==========================================" +echo "访问 API 文档: ${BASE_URL}/docs" +echo "=========================================="