生成代码工程
This commit is contained in:
parent
ef4dcc8a43
commit
32d28dcae2
123
README.md
123
README.md
|
|
@ -1,3 +1,122 @@
|
|||
# 多智能体协作开发平台项目
|
||||
# User Management Service - 用户管理服务
|
||||
|
||||
基于当前产品进行研发实施等
|
||||
基于 FastAPI 构建的用户管理单体服务,采用内存假数据存储,无需外部依赖。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 用户注册(POST /api/users)
|
||||
- ✅ 获取用户列表(GET /api/users)
|
||||
- ✅ 获取单个用户(GET /api/users/{user_id})
|
||||
- ✅ 更新用户信息(PUT /api/users/{user_id})
|
||||
- ✅ 删除用户(DELETE /api/users/{user_id})
|
||||
- ✅ 用户登录验证(POST /api/users/login)
|
||||
- ✅ 健康检查(GET /health)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
project-files/
|
||||
├── requirements.txt
|
||||
├── README.md
|
||||
├── app/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # FastAPI 应用入口及路由
|
||||
│ ├── models.py # 数据模型定义
|
||||
│ ├── services.py # 业务逻辑层
|
||||
│ └── database.py # 内存数据库(假数据)
|
||||
└── tests/
|
||||
└── test_basic.py # 基础单元测试
|
||||
```
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python >= 3.11
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
cd codegen-runs/codegen_216c32ee7c414070891e6bd4259621a9
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 启动服务
|
||||
|
||||
方式一:直接启动
|
||||
|
||||
```bash
|
||||
python app/main.py
|
||||
```
|
||||
|
||||
方式二:使用 uvicorn
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
服务启动后访问:
|
||||
- API 文档(Swagger UI):http://localhost:8000/docs
|
||||
- API 文档(Redoc):http://localhost:8000/redoc
|
||||
- 健康检查:http://localhost:8000/health
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
cd codegen-runs/codegen_216c32ee7c414070891e6bd4259621a9
|
||||
pytest -v
|
||||
```
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 健康检查
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
### 用户注册
|
||||
```
|
||||
POST /api/users
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "alice",
|
||||
"email": "alice@example.com",
|
||||
"password": "secret123"
|
||||
}
|
||||
```
|
||||
|
||||
### 获取用户列表
|
||||
```
|
||||
GET /api/users?skip=0&limit=10
|
||||
```
|
||||
|
||||
### 获取单个用户
|
||||
```
|
||||
GET /api/users/{user_id}
|
||||
```
|
||||
|
||||
### 更新用户
|
||||
```
|
||||
PUT /api/users/{user_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "alice_new",
|
||||
"email": "alice_new@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
### 删除用户
|
||||
```
|
||||
DELETE /api/users/{user_id}
|
||||
```
|
||||
|
||||
### 用户登录
|
||||
```
|
||||
POST /api/users/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "alice",
|
||||
"password": "secret123"
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# User Management Service - 用户管理服务应用包
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
"""用户管理服务 - 内存数据库模块。
|
||||
|
||||
使用线程安全的 dict 模拟数据库存储,提供基础的 CRUD 操作。
|
||||
所有数据存储在内存中,服务重启后数据会丢失。
|
||||
"""
|
||||
|
||||
import threading
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from app.models import UserInDB
|
||||
|
||||
|
||||
class InMemoryDatabase:
|
||||
"""基于内存的简易数据库,线程安全。
|
||||
|
||||
使用 dict 存储用户数据,key 为用户 ID,value 为 UserInDB 对象。
|
||||
同时维护 username -> user_id 的映射表以支持按用户名查询。
|
||||
|
||||
Attributes:
|
||||
_users: 用户数据存储,格式 {user_id: UserInDB}
|
||||
_username_index: 用户名索引,格式 {username: user_id}
|
||||
_lock: 线程锁,保证并发安全
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化内存数据库,并插入预设的假数据。"""
|
||||
self._users: dict[str, UserInDB] = {}
|
||||
self._username_index: dict[str, str] = {}
|
||||
self._lock = threading.Lock()
|
||||
self._init_seed_data()
|
||||
|
||||
def _init_seed_data(self):
|
||||
"""初始化预设的假数据,用于演示和测试。"""
|
||||
seed_users = [
|
||||
{
|
||||
"id": "user-001",
|
||||
"username": "alice",
|
||||
"email": "alice@example.com",
|
||||
"hashed_password": "e99a18c428cb38d5f260853678922e03", # hash of "abc123"
|
||||
"created_at": "2024-01-15T08:30:00",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"id": "user-002",
|
||||
"username": "bob",
|
||||
"email": "bob@example.com",
|
||||
"hashed_password": "e99a18c428cb38d5f260853678922e03",
|
||||
"created_at": "2024-02-20T10:15:00",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"id": "user-003",
|
||||
"username": "charlie",
|
||||
"email": "charlie@example.com",
|
||||
"hashed_password": "e99a18c428cb38d5f260853678922e03",
|
||||
"created_at": "2024-03-10T14:45:00",
|
||||
"is_active": True,
|
||||
},
|
||||
]
|
||||
for user_data in seed_users:
|
||||
user = UserInDB(**user_data)
|
||||
self._users[user.id] = user
|
||||
self._username_index[user.username] = user.id
|
||||
|
||||
def insert_user(self, user: UserInDB) -> UserInDB:
|
||||
"""插入一个新用户。
|
||||
|
||||
Args:
|
||||
user: 要插入的用户对象
|
||||
|
||||
Returns:
|
||||
插入后的用户对象
|
||||
"""
|
||||
with self._lock:
|
||||
self._users[user.id] = user
|
||||
self._username_index[user.username] = user.id
|
||||
return user
|
||||
|
||||
def get_user_by_id(self, user_id: str) -> Optional[UserInDB]:
|
||||
"""根据用户 ID 查询用户。
|
||||
|
||||
Args:
|
||||
user_id: 用户唯一标识
|
||||
|
||||
Returns:
|
||||
如果找到则返回用户对象,否则返回 None
|
||||
"""
|
||||
with self._lock:
|
||||
return self._users.get(user_id)
|
||||
|
||||
def get_user_by_username(self, username: str) -> Optional[UserInDB]:
|
||||
"""根据用户名查询用户。
|
||||
|
||||
Args:
|
||||
username: 用户名
|
||||
|
||||
Returns:
|
||||
如果找到则返回用户对象,否则返回 None
|
||||
"""
|
||||
with self._lock:
|
||||
user_id = self._username_index.get(username)
|
||||
if user_id is None:
|
||||
return None
|
||||
return self._users.get(user_id)
|
||||
|
||||
def get_all_users(self, skip: int = 0, limit: int = 10) -> list[UserInDB]:
|
||||
"""获取用户列表,支持分页。
|
||||
|
||||
Args:
|
||||
skip: 跳过的记录数,默认 0
|
||||
limit: 返回的最大记录数,默认 10
|
||||
|
||||
Returns:
|
||||
用户对象列表
|
||||
"""
|
||||
with self._lock:
|
||||
all_users = list(self._users.values())
|
||||
return all_users[skip : skip + limit]
|
||||
|
||||
def update_user(self, user_id: str, update_data: dict) -> Optional[UserInDB]:
|
||||
"""更新用户信息。
|
||||
|
||||
Args:
|
||||
user_id: 要更新的用户 ID
|
||||
update_data: 要更新的字段字典
|
||||
|
||||
Returns:
|
||||
更新后的用户对象,如果用户不存在则返回 None
|
||||
"""
|
||||
with self._lock:
|
||||
user = self._users.get(user_id)
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
# 如果更新了用户名,需要同步更新索引
|
||||
old_username = user.username
|
||||
new_username = update_data.get("username")
|
||||
if new_username and new_username != old_username:
|
||||
# 检查新用户名是否已被占用
|
||||
if new_username in self._username_index:
|
||||
return None # 用户名冲突
|
||||
del self._username_index[old_username]
|
||||
self._username_index[new_username] = user_id
|
||||
|
||||
updated_user = user.model_copy(update=update_data)
|
||||
self._users[user_id] = updated_user
|
||||
return updated_user
|
||||
|
||||
def delete_user(self, user_id: str) -> bool:
|
||||
"""删除用户。
|
||||
|
||||
Args:
|
||||
user_id: 要删除的用户 ID
|
||||
|
||||
Returns:
|
||||
删除成功返回 True,用户不存在返回 False
|
||||
"""
|
||||
with self._lock:
|
||||
user = self._users.get(user_id)
|
||||
if user is None:
|
||||
return False
|
||||
del self._users[user_id]
|
||||
del self._username_index[user.username]
|
||||
return True
|
||||
|
||||
def count_users(self) -> int:
|
||||
"""统计用户总数。
|
||||
|
||||
Returns:
|
||||
用户数量
|
||||
"""
|
||||
with self._lock:
|
||||
return len(self._users)
|
||||
|
||||
|
||||
# 全局单例数据库实例
|
||||
db = InMemoryDatabase()
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
"""用户管理服务 - FastAPI 应用入口模块。
|
||||
|
||||
提供用户注册、登录、查询、更新、删除等 RESTful API 接口。
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Query, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.models import (
|
||||
ErrorResponse,
|
||||
SuccessResponse,
|
||||
UserCreate,
|
||||
UserLogin,
|
||||
UserResponse,
|
||||
UserUpdate,
|
||||
)
|
||||
from app.services import (
|
||||
create_user,
|
||||
delete_user,
|
||||
get_all_users,
|
||||
get_user_by_id,
|
||||
get_user_count,
|
||||
login,
|
||||
update_user,
|
||||
)
|
||||
|
||||
# 创建 FastAPI 应用实例
|
||||
app = FastAPI(
|
||||
title="User Management API",
|
||||
description="用户管理服务 - 提供用户注册、登录、CRUD 等基础功能",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
# 添加 CORS 中间件,允许跨域访问
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health", tags=["系统"])
|
||||
async def health_check():
|
||||
"""健康检查接口。
|
||||
|
||||
Returns:
|
||||
服务运行状态信息
|
||||
"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "user-management-service",
|
||||
"version": "1.0.0",
|
||||
"total_users": get_user_count(),
|
||||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户管理 API
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/users",
|
||||
response_model=UserResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
tags=["用户管理"],
|
||||
summary="注册新用户",
|
||||
description="创建一个新的用户账户,需要提供用户名、邮箱和密码。",
|
||||
responses={
|
||||
201: {"description": "用户创建成功", "model": UserResponse},
|
||||
400: {"description": "请求数据不合法", "model": ErrorResponse},
|
||||
409: {"description": "用户名或邮箱已被占用", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def api_create_user(user_data: UserCreate):
|
||||
"""用户注册接口。
|
||||
|
||||
Args:
|
||||
user_data: 用户注册信息(用户名、邮箱、密码)
|
||||
|
||||
Returns:
|
||||
创建成功的用户信息
|
||||
|
||||
Raises:
|
||||
HTTPException: 当输入验证失败或用户名/邮箱已存在时抛出
|
||||
"""
|
||||
try:
|
||||
return create_user(user_data)
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
|
||||
@app.get(
|
||||
"/api/users",
|
||||
response_model=list[UserResponse],
|
||||
tags=["用户管理"],
|
||||
summary="获取用户列表",
|
||||
description="分页获取所有用户信息列表。",
|
||||
)
|
||||
async def api_get_users(
|
||||
skip: int = Query(0, ge=0, description="跳过的记录数"),
|
||||
limit: int = Query(10, ge=1, le=100, description="每页数量"),
|
||||
):
|
||||
"""获取用户列表接口。
|
||||
|
||||
Args:
|
||||
skip: 跳过的记录数,默认 0
|
||||
limit: 每页数量,默认 10,最大 100
|
||||
|
||||
Returns:
|
||||
用户信息列表
|
||||
"""
|
||||
return get_all_users(skip, limit)
|
||||
|
||||
|
||||
@app.get(
|
||||
"/api/users/{user_id}",
|
||||
response_model=UserResponse,
|
||||
tags=["用户管理"],
|
||||
summary="获取单个用户",
|
||||
description="根据用户 ID 获取指定用户的详细信息。",
|
||||
responses={
|
||||
200: {"description": "成功获取用户信息", "model": UserResponse},
|
||||
404: {"description": "用户不存在", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def api_get_user(user_id: str):
|
||||
"""获取单个用户接口。
|
||||
|
||||
Args:
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
用户详细信息
|
||||
|
||||
Raises:
|
||||
HTTPException: 当用户不存在时抛出 404
|
||||
"""
|
||||
user = get_user_by_id(user_id)
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"用户 '{user_id}' 不存在",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@app.put(
|
||||
"/api/users/{user_id}",
|
||||
response_model=UserResponse,
|
||||
tags=["用户管理"],
|
||||
summary="更新用户信息",
|
||||
description="更新指定用户的用户名和/或邮箱。",
|
||||
responses={
|
||||
200: {"description": "更新成功", "model": UserResponse},
|
||||
404: {"description": "用户不存在", "model": ErrorResponse},
|
||||
409: {"description": "新用户名已被占用", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def api_update_user(user_id: str, update_data: UserUpdate):
|
||||
"""更新用户信息接口。
|
||||
|
||||
Args:
|
||||
user_id: 要更新的用户 ID
|
||||
update_data: 待更新的字段
|
||||
|
||||
Returns:
|
||||
更新后的用户信息
|
||||
|
||||
Raises:
|
||||
HTTPException: 用户不存在抛出 404,用户名冲突抛出 409
|
||||
"""
|
||||
try:
|
||||
result = update_user(user_id, update_data)
|
||||
if result is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"用户 '{user_id}' 不存在",
|
||||
)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
|
||||
@app.delete(
|
||||
"/api/users/{user_id}",
|
||||
response_model=SuccessResponse,
|
||||
tags=["用户管理"],
|
||||
summary="删除用户",
|
||||
description="删除指定的用户账户。",
|
||||
responses={
|
||||
200: {"description": "删除成功", "model": SuccessResponse},
|
||||
404: {"description": "用户不存在", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def api_delete_user(user_id: str):
|
||||
"""删除用户接口。
|
||||
|
||||
Args:
|
||||
user_id: 要删除的用户 ID
|
||||
|
||||
Returns:
|
||||
删除成功消息
|
||||
|
||||
Raises:
|
||||
HTTPException: 用户不存在时抛出 404
|
||||
"""
|
||||
success = delete_user(user_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"用户 '{user_id}' 不存在",
|
||||
)
|
||||
return {"message": f"用户 '{user_id}' 已成功删除"}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户登录 API
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/users/login",
|
||||
response_model=UserResponse,
|
||||
tags=["用户管理"],
|
||||
summary="用户登录",
|
||||
description="使用用户名和密码进行登录验证。",
|
||||
responses={
|
||||
200: {"description": "登录成功", "model": UserResponse},
|
||||
401: {"description": "用户名或密码错误", "model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def api_login(login_data: UserLogin):
|
||||
"""用户登录接口。
|
||||
|
||||
Args:
|
||||
login_data: 登录凭证(用户名和密码)
|
||||
|
||||
Returns:
|
||||
登录成功的用户信息
|
||||
|
||||
Raises:
|
||||
HTTPException: 用户名或密码错误时抛出 401
|
||||
"""
|
||||
user = login(login_data)
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户名或密码错误",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 应用入口
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
"""启动 FastAPI 应用的入口函数。
|
||||
|
||||
使用 uvicorn 运行服务,监听 0.0.0.0:8000。
|
||||
"""
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
"""用户管理服务 - 数据模型定义模块。
|
||||
|
||||
定义系统中使用的 Pydantic 模型,包括请求体和响应体的数据结构。
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
"""用户创建请求模型。
|
||||
|
||||
Attributes:
|
||||
username: 用户名,3-50个字符
|
||||
email: 邮箱地址
|
||||
password: 密码,6-100个字符
|
||||
"""
|
||||
|
||||
username: str = Field(..., min_length=3, max_length=50, description="用户名")
|
||||
email: str = Field(..., description="邮箱地址")
|
||||
password: str = Field(..., min_length=6, max_length=100, description="密码")
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
"""用户更新请求模型。
|
||||
|
||||
Attributes:
|
||||
username: 可选,用户名
|
||||
email: 可选,邮箱地址
|
||||
"""
|
||||
|
||||
username: Optional[str] = Field(None, min_length=3, max_length=50, description="用户名")
|
||||
email: Optional[str] = Field(None, description="邮箱地址")
|
||||
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
"""用户登录请求模型。
|
||||
|
||||
Attributes:
|
||||
username: 用户名
|
||||
password: 密码
|
||||
"""
|
||||
|
||||
username: str = Field(..., description="用户名")
|
||||
password: str = Field(..., description="密码")
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""用户信息响应模型。
|
||||
|
||||
Attributes:
|
||||
id: 用户唯一标识
|
||||
username: 用户名
|
||||
email: 邮箱地址
|
||||
created_at: 创建时间
|
||||
is_active: 是否激活
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="用户ID")
|
||||
username: str = Field(..., description="用户名")
|
||||
email: str = Field(..., description="邮箱地址")
|
||||
created_at: str = Field(..., description="创建时间")
|
||||
is_active: bool = Field(default=True, description="是否激活")
|
||||
|
||||
|
||||
class UserInDB(BaseModel):
|
||||
"""数据库中的用户模型(包含密码哈希)。
|
||||
|
||||
Attributes:
|
||||
id: 用户唯一标识
|
||||
username: 用户名
|
||||
email: 邮箱地址
|
||||
hashed_password: 密码哈希值
|
||||
created_at: 创建时间
|
||||
is_active: 是否激活
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="用户ID")
|
||||
username: str = Field(..., description="用户名")
|
||||
email: str = Field(..., description="邮箱地址")
|
||||
hashed_password: str = Field(..., description="密码哈希值")
|
||||
created_at: str = Field(..., description="创建时间")
|
||||
is_active: bool = Field(default=True, description="是否激活")
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""错误响应模型。
|
||||
|
||||
Attributes:
|
||||
detail: 错误详情描述
|
||||
"""
|
||||
|
||||
detail: str = Field(..., description="错误描述")
|
||||
|
||||
|
||||
class SuccessResponse(BaseModel):
|
||||
"""成功响应模型。
|
||||
|
||||
Attributes:
|
||||
message: 成功消息
|
||||
"""
|
||||
|
||||
message: str = Field(..., description="成功消息")
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
"""用户管理服务 - 业务逻辑层模块。
|
||||
|
||||
封装用户相关的业务逻辑,包括用户注册、登录验证、信息查询与更新等。
|
||||
"""
|
||||
import hashlib
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from app.database import db
|
||||
from app.models import UserCreate, UserInDB, UserLogin, UserResponse, UserUpdate
|
||||
|
||||
|
||||
def _hash_password(password: str) -> str:
|
||||
"""对密码进行哈希处理。
|
||||
|
||||
使用 SHA-256 对密码进行哈希(演示用途,生产环境应使用 bcrypt 等安全算法)。
|
||||
|
||||
Args:
|
||||
password: 原始密码字符串
|
||||
|
||||
Returns:
|
||||
密码的十六进制哈希值
|
||||
"""
|
||||
return hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _user_to_response(user: UserInDB) -> UserResponse:
|
||||
"""将数据库用户模型转换为响应模型。
|
||||
|
||||
Args:
|
||||
user: 数据库中的用户对象
|
||||
|
||||
Returns:
|
||||
对外暴露的用户响应对象(不包含密码)
|
||||
"""
|
||||
return UserResponse(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
created_at=user.created_at,
|
||||
is_active=user.is_active,
|
||||
)
|
||||
|
||||
|
||||
def create_user(user_data: UserCreate) -> UserResponse:
|
||||
"""注册新用户。
|
||||
|
||||
执行业务校验:检查用户名是否已被占用,然后创建用户并存储。
|
||||
|
||||
Args:
|
||||
user_data: 用户注册请求数据
|
||||
|
||||
Returns:
|
||||
创建成功的用户响应对象
|
||||
|
||||
Raises:
|
||||
ValueError: 如果用户名或邮箱已被占用
|
||||
"""
|
||||
# 检查用户名是否已存在
|
||||
existing_user = db.get_user_by_username(user_data.username)
|
||||
if existing_user is not None:
|
||||
raise ValueError(f"用户名 '{user_data.username}' 已被占用")
|
||||
|
||||
# 检查邮箱是否已存在(遍历检查)
|
||||
all_users = db.get_all_users(0, 1000)
|
||||
for user in all_users:
|
||||
if user.email == user_data.email:
|
||||
raise ValueError(f"邮箱 '{user_data.email}' 已被注册")
|
||||
|
||||
now = datetime.now().isoformat(timespec="seconds")
|
||||
user_id = f"user-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
user_in_db = UserInDB(
|
||||
id=user_id,
|
||||
username=user_data.username,
|
||||
email=user_data.email,
|
||||
hashed_password=_hash_password(user_data.password),
|
||||
created_at=now,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
db.insert_user(user_in_db)
|
||||
return _user_to_response(user_in_db)
|
||||
|
||||
|
||||
def get_user_by_id(user_id: str) -> Optional[UserResponse]:
|
||||
"""根据用户 ID 获取用户信息。
|
||||
|
||||
Args:
|
||||
user_id: 用户 ID
|
||||
|
||||
Returns:
|
||||
用户响应对象,如果不存在则返回 None
|
||||
"""
|
||||
user = db.get_user_by_id(user_id)
|
||||
if user is None:
|
||||
return None
|
||||
return _user_to_response(user)
|
||||
|
||||
|
||||
def get_user_by_username(username: str) -> Optional[UserResponse]:
|
||||
"""根据用户名获取用户信息。
|
||||
|
||||
Args:
|
||||
username: 用户名
|
||||
|
||||
Returns:
|
||||
用户响应对象,如果不存在则返回 None
|
||||
"""
|
||||
user = db.get_user_by_username(username)
|
||||
if user is None:
|
||||
return None
|
||||
return _user_to_response(user)
|
||||
|
||||
|
||||
def get_all_users(skip: int = 0, limit: int = 10) -> list[UserResponse]:
|
||||
"""获取用户列表(分页)。
|
||||
|
||||
Args:
|
||||
skip: 跳过的记录数
|
||||
limit: 每页数量
|
||||
|
||||
Returns:
|
||||
用户响应对象列表
|
||||
"""
|
||||
users = db.get_all_users(skip, limit)
|
||||
return [_user_to_response(u) for u in users]
|
||||
|
||||
|
||||
def update_user(user_id: str, update_data: UserUpdate) -> Optional[UserResponse]:
|
||||
"""更新用户信息。
|
||||
|
||||
Args:
|
||||
user_id: 要更新的用户 ID
|
||||
update_data: 待更新的字段
|
||||
|
||||
Returns:
|
||||
更新后的用户响应对象
|
||||
|
||||
Raises:
|
||||
ValueError: 如果新用户名已被占用
|
||||
"""
|
||||
update_dict = update_data.model_dump(exclude_none=True)
|
||||
if not update_dict:
|
||||
return get_user_by_id(user_id)
|
||||
|
||||
updated_user = db.update_user(user_id, update_dict)
|
||||
if updated_user is None:
|
||||
return None
|
||||
return _user_to_response(updated_user)
|
||||
|
||||
|
||||
def delete_user(user_id: str) -> bool:
|
||||
"""删除用户。
|
||||
|
||||
Args:
|
||||
user_id: 要删除的用户 ID
|
||||
|
||||
Returns:
|
||||
删除成功返回 True
|
||||
"""
|
||||
return db.delete_user(user_id)
|
||||
|
||||
|
||||
def login(login_data: UserLogin) -> Optional[UserResponse]:
|
||||
"""用户登录验证。
|
||||
|
||||
验证用户名和密码是否匹配。
|
||||
|
||||
Args:
|
||||
login_data: 登录请求数据(用户名和密码)
|
||||
|
||||
Returns:
|
||||
登录成功返回用户信息,失败返回 None
|
||||
"""
|
||||
user = db.get_user_by_username(login_data.username)
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
if user.hashed_password != _hash_password(login_data.password):
|
||||
return None
|
||||
|
||||
return _user_to_response(user)
|
||||
|
||||
|
||||
def get_user_count() -> int:
|
||||
"""获取用户总数。
|
||||
|
||||
Returns:
|
||||
系统中用户的总数量
|
||||
"""
|
||||
return db.count_users()
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"projectId": 40,
|
||||
"generationId": "codegen_216c32ee7c414070891e6bd4259621a9",
|
||||
"language": "python",
|
||||
"status": "completed",
|
||||
"fileIds": [],
|
||||
"outputDir": "D:\\workspace\\agent\\DocumentGenerateAgent\\agents\\ai_agents\\project-files\\codegen-runs\\codegen_216c32ee7c414070891e6bd4259621a9",
|
||||
"relativeOutputDir": "codegen-runs/codegen_216c32ee7c414070891e6bd4259621a9",
|
||||
"generatedFiles": [
|
||||
"README.md",
|
||||
"app/__init__.py",
|
||||
"app/database.py",
|
||||
"app/main.py",
|
||||
"app/models.py",
|
||||
"app/services.py",
|
||||
"events.ndjson",
|
||||
"requirements.txt",
|
||||
"tests/__init__.py",
|
||||
"tests/test_basic.py"
|
||||
],
|
||||
"analysisSummary": "未提供参考文件,请仅根据用户自然语言描述生成Python工程。",
|
||||
"eventLogFile": "D:\\workspace\\agent\\DocumentGenerateAgent\\agents\\ai_agents\\project-files\\codegen-runs\\codegen_216c32ee7c414070891e6bd4259621a9\\events.ndjson",
|
||||
"repoSettings": {
|
||||
"username": "root",
|
||||
"password": "pAssW0rd",
|
||||
"repoUrl": "http://47.108.255.216:3000/root/more_agent.git",
|
||||
"branch": "main"
|
||||
},
|
||||
"repoUrl": "http://47.108.255.216:3000/root/more_agent.git",
|
||||
"branch": "main"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.2
|
||||
pytest==7.4.3
|
||||
httpx==0.25.2
|
||||
|
|
@ -0,0 +1 @@
|
|||
# 测试包
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
"""用户管理服务 - 基础单元测试模块。
|
||||
|
||||
使用 pytest 和 httpx 对 API 接口进行测试。
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
from app.database import db
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 测试客户端
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_database():
|
||||
"""每个测试用例执行前重置数据库状态。
|
||||
|
||||
保存原始数据并在测试后恢复,避免测试间的数据污染。
|
||||
"""
|
||||
# 清空数据库并重新初始化种子数据
|
||||
db._users.clear()
|
||||
db._username_index.clear()
|
||||
db._init_seed_data()
|
||||
yield
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 健康检查测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestHealthCheck:
|
||||
"""健康检查接口测试类。"""
|
||||
|
||||
def test_health_check_returns_ok(self):
|
||||
"""测试健康检查接口返回状态 ok。"""
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert data["service"] == "user-management-service"
|
||||
assert data["total_users"] >= 3
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户创建测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestCreateUser:
|
||||
"""用户注册接口测试类。"""
|
||||
|
||||
def test_create_user_success(self):
|
||||
"""测试成功创建用户。"""
|
||||
response = client.post(
|
||||
"/api/users",
|
||||
json={
|
||||
"username": "newuser",
|
||||
"email": "newuser@example.com",
|
||||
"password": "password123",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["username"] == "newuser"
|
||||
assert data["email"] == "newuser@example.com"
|
||||
assert data["is_active"] is True
|
||||
assert "id" in data
|
||||
assert "created_at" in data
|
||||
|
||||
def test_create_user_duplicate_username(self):
|
||||
"""测试创建重复用户名应返回 409。"""
|
||||
response = client.post(
|
||||
"/api/users",
|
||||
json={
|
||||
"username": "alice",
|
||||
"email": "alice2@example.com",
|
||||
"password": "password123",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert "已被占用" in response.json()["detail"]
|
||||
|
||||
def test_create_user_duplicate_email(self):
|
||||
"""测试创建重复邮箱应返回 409。"""
|
||||
response = client.post(
|
||||
"/api/users",
|
||||
json={
|
||||
"username": "alice2",
|
||||
"email": "alice@example.com",
|
||||
"password": "password123",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert "已被注册" in response.json()["detail"]
|
||||
|
||||
def test_create_user_validation_error(self):
|
||||
"""测试用户名太短应返回 422。"""
|
||||
response = client.post(
|
||||
"/api/users",
|
||||
json={
|
||||
"username": "ab",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户查询测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestGetUsers:
|
||||
"""用户查询接口测试类。"""
|
||||
|
||||
def test_get_all_users(self):
|
||||
"""测试获取所有用户(分页)。"""
|
||||
response = client.get("/api/users?skip=0&limit=10")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) >= 3
|
||||
# 验证返回的字段不包含密码
|
||||
for user in data:
|
||||
assert "password" not in user
|
||||
assert "hashed_password" not in user
|
||||
|
||||
def test_get_user_by_id_success(self):
|
||||
"""测试通过 ID 获取用户成功。"""
|
||||
response = client.get("/api/users/user-001")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == "alice"
|
||||
assert data["email"] == "alice@example.com"
|
||||
|
||||
def test_get_user_by_id_not_found(self):
|
||||
"""测试获取不存在的用户返回 404。"""
|
||||
response = client.get("/api/users/user-999")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_get_users_pagination(self):
|
||||
"""测试分页参数生效。"""
|
||||
response = client.get("/api/users?skip=0&limit=1")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户更新测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestUpdateUser:
|
||||
"""用户更新接口测试类。"""
|
||||
|
||||
def test_update_username_success(self):
|
||||
"""测试成功更新用户名。"""
|
||||
response = client.put(
|
||||
"/api/users/user-001",
|
||||
json={"username": "alice_updated"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == "alice_updated"
|
||||
|
||||
def test_update_user_not_found(self):
|
||||
"""测试更新不存在的用户返回 404。"""
|
||||
response = client.put(
|
||||
"/api/users/user-999",
|
||||
json={"username": "new_name"},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_username_conflict(self):
|
||||
"""测试更新为已存在的用户名返回 409。"""
|
||||
response = client.put(
|
||||
"/api/users/user-001",
|
||||
json={"username": "bob"},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户删除测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestDeleteUser:
|
||||
"""用户删除接口测试类。"""
|
||||
|
||||
def test_delete_user_success(self):
|
||||
"""测试成功删除用户。"""
|
||||
response = client.delete("/api/users/user-001")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "成功删除" in data["message"]
|
||||
|
||||
# 验证用户确实被删除
|
||||
get_response = client.get("/api/users/user-001")
|
||||
assert get_response.status_code == 404
|
||||
|
||||
def test_delete_user_not_found(self):
|
||||
"""测试删除不存在的用户返回 404。"""
|
||||
response = client.delete("/api/users/user-999")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# 用户登录测试
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestLogin:
|
||||
"""用户登录接口测试类。"""
|
||||
|
||||
def test_login_success(self):
|
||||
"""测试使用正确的用户名和密码登录成功。"""
|
||||
response = client.post(
|
||||
"/api/users/login",
|
||||
json={"username": "alice", "password": "abc123"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == "alice"
|
||||
|
||||
def test_login_wrong_password(self):
|
||||
"""测试使用错误密码登录返回 401。"""
|
||||
response = client.post(
|
||||
"/api/users/login",
|
||||
json={"username": "alice", "password": "wrong_password"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_login_user_not_found(self):
|
||||
"""测试使用不存在的用户名登录返回 401。"""
|
||||
response = client.post(
|
||||
"/api/users/login",
|
||||
json={"username": "nonexistent", "password": "password123"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
Loading…
Reference in New Issue