生成代码工程
This commit is contained in:
parent
4bc29c9a7d
commit
bd56f7a6d1
84
README.md
84
README.md
|
|
@ -1,61 +1,47 @@
|
||||||
# Demo Spring Boot Project
|
# 单体示例工程
|
||||||
|
|
||||||
## 项目说明
|
一个基于 FastAPI 的简单待办事项(Todo)管理服务,使用内存假数据存储。
|
||||||
|
|
||||||
本项目是一个使用 **Spring Boot 3.x + Java 17** 构建的单体后端工程,集成了 **Knife4j (Swagger/OpenAPI)** 用于接口文档展示。
|
## 功能
|
||||||
|
|
||||||
### 技术栈
|
- 获取所有待办事项列表
|
||||||
|
- 根据 ID 获取单个待办事项
|
||||||
|
- 创建新的待办事项
|
||||||
|
- 更新待办事项
|
||||||
|
- 删除待办事项
|
||||||
|
|
||||||
- Java 17
|
## 安装依赖
|
||||||
- Spring Boot 3.2.4
|
|
||||||
- Knife4j 4.5.0 (OpenAPI 3)
|
|
||||||
- Maven
|
|
||||||
- Lombok
|
|
||||||
|
|
||||||
### 业务说明
|
|
||||||
|
|
||||||
- 当前所有业务数据使用**内存假数据**,未接入任何数据库或外部基础设施。
|
|
||||||
- 已预留 Repository 接口层扩展点,后续可对接数据库实现。
|
|
||||||
|
|
||||||
### 启动方式
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 编译
|
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||||
mvn clean package
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# 运行
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 或直接运行生成的 jar
|
|
||||||
java -jar target/demo-0.0.1-SNAPSHOT.jar
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 接口文档访问
|
## 启动服务
|
||||||
|
|
||||||
启动后,打开浏览器访问:
|
```bash
|
||||||
|
uvicorn app.main:app --reload --port 8000
|
||||||
- **Knife4j UI**: http://localhost:8080/doc.html
|
|
||||||
- **Swagger UI**: http://localhost:8080/swagger-ui/index.html
|
|
||||||
- **OpenAPI JSON**: http://localhost:8080/v3/api-docs
|
|
||||||
|
|
||||||
### 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
src/main/java/com/example/demo/
|
|
||||||
├── DemoApplication.java # 启动类
|
|
||||||
├── config/
|
|
||||||
│ └── OpenApiConfig.java # Knife4j/Swagger 配置
|
|
||||||
├── controller/
|
|
||||||
│ ├── HealthController.java # 健康检查接口
|
|
||||||
│ └── BusinessController.java # 业务 REST 接口
|
|
||||||
├── model/
|
|
||||||
│ └── BusinessItem.java # 业务数据模型
|
|
||||||
└── service/
|
|
||||||
└── BusinessService.java # 业务逻辑(内存假数据)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 扩展说明
|
访问 http://127.0.0.1:8000/docs 查看自动生成的 API 文档(Swagger UI)。
|
||||||
|
|
||||||
- `repository/` 目录可扩展为数据库 Repository 层
|
## 运行测试
|
||||||
- `service/` 中注入 Repository 即可切换为真实数据源
|
|
||||||
- 配置文件已预留 Spring Data JPA / MyBatis 相关配置注释
|
```bash
|
||||||
|
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速验证
|
||||||
|
|
||||||
|
启动服务后,在另一个终端执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 获取所有待办事项
|
||||||
|
curl http://127.0.0.1:8000/todos
|
||||||
|
|
||||||
|
# 创建新待办事项
|
||||||
|
curl -X POST http://127.0.0.1:8000/todos \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"title": "学习 FastAPI", "completed": false}'
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# app 包初始化文件
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
"""
|
||||||
|
FastAPI 主应用入口
|
||||||
|
|
||||||
|
提供待办事项(Todo)管理的 RESTful API。
|
||||||
|
使用内存假数据存储,无需外部数据库。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException, status
|
||||||
|
from app.services import (
|
||||||
|
TodoService,
|
||||||
|
TodoCreate,
|
||||||
|
TodoUpdate,
|
||||||
|
TodoResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Todo 管理服务",
|
||||||
|
description="一个基于 FastAPI 的简单待办事项管理服务示例",
|
||||||
|
version="1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 全局服务实例(使用内存假数据)
|
||||||
|
todo_service = TodoService()
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# API 路由
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", tags=["健康检查"])
|
||||||
|
async def root():
|
||||||
|
"""服务根路径,返回基本信息。"""
|
||||||
|
return {
|
||||||
|
"service": "Todo 管理服务",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"docs": "/docs",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/todos", response_model=list[TodoResponse], tags=["待办事项"])
|
||||||
|
async def list_todos():
|
||||||
|
"""获取所有待办事项列表。"""
|
||||||
|
return todo_service.list_todos()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/todos/{todo_id}", response_model=TodoResponse, tags=["待办事项"])
|
||||||
|
async def get_todo(todo_id: int):
|
||||||
|
"""根据 ID 获取单个待办事项。"""
|
||||||
|
todo = todo_service.get_todo(todo_id)
|
||||||
|
if todo is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"待办事项 {todo_id} 不存在",
|
||||||
|
)
|
||||||
|
return todo
|
||||||
|
|
||||||
|
|
||||||
|
@app.post(
|
||||||
|
"/todos",
|
||||||
|
response_model=TodoResponse,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
tags=["待办事项"],
|
||||||
|
)
|
||||||
|
async def create_todo(todo_in: TodoCreate):
|
||||||
|
"""创建新的待办事项。"""
|
||||||
|
return todo_service.create_todo(todo_in)
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/todos/{todo_id}", response_model=TodoResponse, tags=["待办事项"])
|
||||||
|
async def update_todo(todo_id: int, todo_in: TodoUpdate):
|
||||||
|
"""更新指定待办事项。"""
|
||||||
|
todo = todo_service.update_todo(todo_id, todo_in)
|
||||||
|
if todo is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"待办事项 {todo_id} 不存在",
|
||||||
|
)
|
||||||
|
return todo
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/todos/{todo_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["待办事项"])
|
||||||
|
async def delete_todo(todo_id: int):
|
||||||
|
"""删除指定待办事项。"""
|
||||||
|
deleted = todo_service.delete_todo(todo_id)
|
||||||
|
if not deleted:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"待办事项 {todo_id} 不存在",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
"""
|
||||||
|
业务逻辑层 —— 使用内存假数据实现 Todo 的 CRUD 操作。
|
||||||
|
|
||||||
|
所有数据存储在内存字典中,服务重启后数据会丢失。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# Pydantic 模型(请求 / 响应)
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class TodoCreate(BaseModel):
|
||||||
|
"""创建待办事项的请求体。"""
|
||||||
|
title: str = Field(..., min_length=1, max_length=200, description="待办事项标题")
|
||||||
|
completed: bool = Field(False, description="是否已完成")
|
||||||
|
|
||||||
|
|
||||||
|
class TodoUpdate(BaseModel):
|
||||||
|
"""更新待办事项的请求体(部分更新)。"""
|
||||||
|
title: Optional[str] = Field(None, min_length=1, max_length=200, description="待办事项标题")
|
||||||
|
completed: Optional[bool] = Field(None, description="是否已完成")
|
||||||
|
|
||||||
|
|
||||||
|
class TodoResponse(BaseModel):
|
||||||
|
"""待办事项的响应体。"""
|
||||||
|
id: int = Field(..., description="待办事项 ID")
|
||||||
|
title: str = Field(..., description="待办事项标题")
|
||||||
|
completed: bool = Field(..., description="是否已完成")
|
||||||
|
|
||||||
|
model_config = {"from_attributes": True}
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# 内存假数据存储
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class TodoService:
|
||||||
|
"""待办事项服务 —— 使用内存字典存储数据。"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._todos: dict[int, dict] = {}
|
||||||
|
self._next_id: int = 1
|
||||||
|
self._init_mock_data()
|
||||||
|
|
||||||
|
def _init_mock_data(self) -> None:
|
||||||
|
"""初始化一批假数据,方便演示。"""
|
||||||
|
mock_todos = [
|
||||||
|
{"title": "学习 FastAPI", "completed": False},
|
||||||
|
{"title": "编写单元测试", "completed": False},
|
||||||
|
{"title": "阅读 Python 文档", "completed": True},
|
||||||
|
{"title": "完成项目报告", "completed": False},
|
||||||
|
{"title": "锻炼身体 30 分钟", "completed": True},
|
||||||
|
]
|
||||||
|
for item in mock_todos:
|
||||||
|
self.create_todo(TodoCreate(**item))
|
||||||
|
|
||||||
|
def list_todos(self) -> list[TodoResponse]:
|
||||||
|
"""返回所有待办事项。"""
|
||||||
|
return [TodoResponse(**todo) for todo in self._todos.values()]
|
||||||
|
|
||||||
|
def get_todo(self, todo_id: int) -> Optional[TodoResponse]:
|
||||||
|
"""根据 ID 获取单个待办事项,不存在则返回 None。"""
|
||||||
|
todo = self._todos.get(todo_id)
|
||||||
|
if todo is None:
|
||||||
|
return None
|
||||||
|
return TodoResponse(**todo)
|
||||||
|
|
||||||
|
def create_todo(self, todo_in: TodoCreate) -> TodoResponse:
|
||||||
|
"""创建新的待办事项并返回。"""
|
||||||
|
todo_id = self._next_id
|
||||||
|
self._next_id += 1
|
||||||
|
todo = {
|
||||||
|
"id": todo_id,
|
||||||
|
"title": todo_in.title,
|
||||||
|
"completed": todo_in.completed,
|
||||||
|
}
|
||||||
|
self._todos[todo_id] = todo
|
||||||
|
return TodoResponse(**todo)
|
||||||
|
|
||||||
|
def update_todo(self, todo_id: int, todo_in: TodoUpdate) -> Optional[TodoResponse]:
|
||||||
|
"""部分更新指定待办事项,不存在则返回 None。"""
|
||||||
|
todo = self._todos.get(todo_id)
|
||||||
|
if todo is None:
|
||||||
|
return None
|
||||||
|
update_data = todo_in.model_dump(exclude_unset=True)
|
||||||
|
todo.update(update_data)
|
||||||
|
return TodoResponse(**todo)
|
||||||
|
|
||||||
|
def delete_todo(self, todo_id: int) -> bool:
|
||||||
|
"""删除指定待办事项,成功返回 True,不存在返回 False。"""
|
||||||
|
if todo_id not in self._todos:
|
||||||
|
return False
|
||||||
|
del self._todos[todo_id]
|
||||||
|
return True
|
||||||
9378
events.ndjson
9378
events.ndjson
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,6 @@
|
||||||
|
fastapi==0.115.6
|
||||||
|
uvicorn==0.34.0
|
||||||
|
pydantic==2.10.4
|
||||||
|
httpx==0.28.1
|
||||||
|
pytest==8.3.4
|
||||||
|
pytest-asyncio==0.25.0
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
"""
|
||||||
|
基础单元测试 —— 测试 TodoService 的核心功能。
|
||||||
|
|
||||||
|
运行方式:
|
||||||
|
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||||
|
pytest tests/ -v
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from app.services import TodoService, TodoCreate, TodoUpdate
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def service() -> TodoService:
|
||||||
|
"""每个测试用例使用独立的 TodoService 实例。"""
|
||||||
|
return TodoService()
|
||||||
|
|
||||||
|
|
||||||
|
class TestTodoService:
|
||||||
|
"""TodoService 核心功能测试。"""
|
||||||
|
|
||||||
|
def test_list_todos_should_return_mock_data(self, service: TodoService):
|
||||||
|
"""测试初始化时自动生成假数据。"""
|
||||||
|
todos = service.list_todos()
|
||||||
|
assert len(todos) == 5 # 初始化了 5 条假数据
|
||||||
|
|
||||||
|
def test_create_todo_should_return_created_todo(self, service: TodoService):
|
||||||
|
"""测试创建待办事项。"""
|
||||||
|
todo_in = TodoCreate(title="新任务", completed=True)
|
||||||
|
todo = service.create_todo(todo_in)
|
||||||
|
assert todo.title == "新任务"
|
||||||
|
assert todo.completed is True
|
||||||
|
assert todo.id > 0
|
||||||
|
|
||||||
|
def test_get_todo_should_return_correct_todo(self, service: TodoService):
|
||||||
|
"""测试根据 ID 获取待办事项。"""
|
||||||
|
# 先创建
|
||||||
|
created = service.create_todo(TodoCreate(title="测试任务"))
|
||||||
|
# 再获取
|
||||||
|
fetched = service.get_todo(created.id)
|
||||||
|
assert fetched is not None
|
||||||
|
assert fetched.id == created.id
|
||||||
|
assert fetched.title == "测试任务"
|
||||||
|
|
||||||
|
def test_get_todo_should_return_none_when_not_found(self, service: TodoService):
|
||||||
|
"""测试获取不存在的待办事项返回 None。"""
|
||||||
|
todo = service.get_todo(99999)
|
||||||
|
assert todo is None
|
||||||
|
|
||||||
|
def test_update_todo_should_update_title(self, service: TodoService):
|
||||||
|
"""测试部分更新待办事项的标题。"""
|
||||||
|
created = service.create_todo(TodoCreate(title="旧标题"))
|
||||||
|
updated = service.update_todo(created.id, TodoUpdate(title="新标题"))
|
||||||
|
assert updated is not None
|
||||||
|
assert updated.title == "新标题"
|
||||||
|
assert updated.completed is False # 未更新的字段保持不变
|
||||||
|
|
||||||
|
def test_update_todo_should_update_completed(self, service: TodoService):
|
||||||
|
"""测试部分更新待办事项的完成状态。"""
|
||||||
|
created = service.create_todo(TodoCreate(title="任务", completed=False))
|
||||||
|
updated = service.update_todo(created.id, TodoUpdate(completed=True))
|
||||||
|
assert updated is not None
|
||||||
|
assert updated.completed is True
|
||||||
|
assert updated.title == "任务" # 未更新的字段保持不变
|
||||||
|
|
||||||
|
def test_update_todo_should_return_none_when_not_found(self, service: TodoService):
|
||||||
|
"""测试更新不存在的待办事项返回 None。"""
|
||||||
|
result = service.update_todo(99999, TodoUpdate(title="无"))
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_delete_todo_should_return_true(self, service: TodoService):
|
||||||
|
"""测试删除存在的待办事项返回 True。"""
|
||||||
|
created = service.create_todo(TodoCreate(title="待删除"))
|
||||||
|
result = service.delete_todo(created.id)
|
||||||
|
assert result is True
|
||||||
|
# 确认已删除
|
||||||
|
assert service.get_todo(created.id) is None
|
||||||
|
|
||||||
|
def test_delete_todo_should_return_false_when_not_found(self, service: TodoService):
|
||||||
|
"""测试删除不存在的待办事项返回 False。"""
|
||||||
|
result = service.delete_todo(99999)
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_list_todos_after_create_and_delete(self, service: TodoService):
|
||||||
|
"""测试增删操作后列表长度的变化。"""
|
||||||
|
initial_count = len(service.list_todos())
|
||||||
|
# 创建 2 个
|
||||||
|
t1 = service.create_todo(TodoCreate(title="A"))
|
||||||
|
t2 = service.create_todo(TodoCreate(title="B"))
|
||||||
|
assert len(service.list_todos()) == initial_count + 2
|
||||||
|
# 删除 1 个
|
||||||
|
service.delete_todo(t1.id)
|
||||||
|
assert len(service.list_todos()) == initial_count + 1
|
||||||
|
# 删除另一个
|
||||||
|
service.delete_todo(t2.id)
|
||||||
|
assert len(service.list_todos()) == initial_count
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_fastapi_health_check():
|
||||||
|
"""简单的 FastAPI 健康检查测试(使用 TestClient)。"""
|
||||||
|
from httpx import AsyncClient, ASGITransport
|
||||||
|
from app.main import app
|
||||||
|
|
||||||
|
transport = ASGITransport(app=app)
|
||||||
|
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||||
|
response = await client.get("/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "service" in data
|
||||||
|
assert data["service"] == "Todo 管理服务"
|
||||||
Loading…
Reference in New Issue