生成代码工程
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
|
||||
# 编译
|
||||
mvn clean package
|
||||
|
||||
# 运行
|
||||
mvn spring-boot:run
|
||||
|
||||
# 或直接运行生成的 jar
|
||||
java -jar target/demo-0.0.1-SNAPSHOT.jar
|
||||
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 接口文档访问
|
||||
## 启动服务
|
||||
|
||||
启动后,打开浏览器访问:
|
||||
|
||||
- **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 # 业务逻辑(内存假数据)
|
||||
```bash
|
||||
uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
### 扩展说明
|
||||
访问 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