Commit 10e59f3c authored by uuo00_n's avatar uuo00_n

feat: 完善API文档和错误响应

refactor: 统一使用Path/Body参数装饰器
docs: 为所有API端点添加详细文档和错误响应示例
style: 格式化代码并修复文件结尾换行
parent f014fc61
Pipeline #60 canceled with stages
This diff is collapsed.
...@@ -12,8 +12,12 @@ router = APIRouter() ...@@ -12,8 +12,12 @@ router = APIRouter()
@router.post( @router.post(
"/register", "/register",
response_model=UserResponse, response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
summary="用户注册", summary="用户注册",
description="创建基础账户(默认教育版、角色等级为1)。", description="创建基础账户(默认教育版、角色等级为1)。",
responses={
400: {"description": "用户名或邮箱已存在", "content": {"application/json": {"example": {"detail": "用户名已存在"}}}},
},
) )
async def register(user_data: UserCreate): async def register(user_data: UserCreate):
"""注册新用户""" """注册新用户"""
...@@ -63,6 +67,9 @@ async def register(user_data: UserCreate): ...@@ -63,6 +67,9 @@ async def register(user_data: UserCreate):
response_model=Token, response_model=Token,
summary="用户登录", summary="用户登录",
description="使用表单登录(username/password),返回令牌与用户/绑定信息。", description="使用表单登录(username/password),返回令牌与用户/绑定信息。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "用户名或密码错误"}}}},
},
) )
async def login(form_data: OAuth2PasswordRequestForm = Depends()): async def login(form_data: OAuth2PasswordRequestForm = Depends()):
"""用户登录""" """用户登录"""
...@@ -102,4 +109,4 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()): ...@@ -102,4 +109,4 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()):
"person_type": b.get("type"), "person_type": b.get("type"),
"bound_primary": True, "bound_primary": True,
} if b else {}) } if b else {})
} }
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Path
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
from app.api.deps import require_edition_for_mode, get_current_active_user, require_role from app.api.deps import require_edition_for_mode, get_current_active_user, require_role
...@@ -26,6 +26,10 @@ class BindingOut(BaseModel): ...@@ -26,6 +26,10 @@ class BindingOut(BaseModel):
summary="创建主绑定", summary="创建主绑定",
description="为当前账号创建人物主绑定(student/teacher),同类型主绑定唯一。", description="为当前账号创建人物主绑定(student/teacher),同类型主绑定唯一。",
response_model=ActionResult, response_model=ActionResult,
responses={
400: {"description": "主绑定已存在或参数错误", "content": {"application/json": {"example": {"detail": "主绑定已存在或参数错误"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
) )
async def bind(payload: BindingPayload, current_user: dict = Depends(require_role(2))) -> ActionResult: async def bind(payload: BindingPayload, current_user: dict = Depends(require_role(2))) -> ActionResult:
ok = await create_binding(str(current_user["_id"]), payload.person_id, payload.type, payload.primary) ok = await create_binding(str(current_user["_id"]), payload.person_id, payload.type, payload.primary)
...@@ -38,8 +42,12 @@ async def bind(payload: BindingPayload, current_user: dict = Depends(require_rol ...@@ -38,8 +42,12 @@ async def bind(payload: BindingPayload, current_user: dict = Depends(require_rol
summary="删除绑定", summary="删除绑定",
description="删除当前账号与指定人物的绑定。", description="删除当前账号与指定人物的绑定。",
response_model=ActionResult, response_model=ActionResult,
responses={
404: {"description": "未找到绑定", "content": {"application/json": {"example": {"detail": "未找到绑定"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
) )
async def unbind(person_id: str, current_user: dict = Depends(require_role(2))) -> ActionResult: async def unbind(person_id: str = Path(..., description="人物ID"), current_user: dict = Depends(require_role(2))) -> ActionResult:
ok = await delete_binding(str(current_user["_id"]), person_id) ok = await delete_binding(str(current_user["_id"]), person_id)
if not ok: if not ok:
raise HTTPException(status_code=404, detail="未找到绑定") raise HTTPException(status_code=404, detail="未找到绑定")
...@@ -50,6 +58,10 @@ async def unbind(person_id: str, current_user: dict = Depends(require_role(2))) ...@@ -50,6 +58,10 @@ async def unbind(person_id: str, current_user: dict = Depends(require_role(2)))
summary="查询当前账号的主绑定", summary="查询当前账号的主绑定",
description="返回当前账号的主绑定信息(account_id/person_id/type/primary)。", description="返回当前账号的主绑定信息(account_id/person_id/type/primary)。",
response_model=BindingOut, response_model=BindingOut,
responses={
404: {"description": "未绑定人物", "content": {"application/json": {"example": {"detail": "未绑定人物"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
) )
async def me(current_user: dict = Depends(get_current_active_user)) -> BindingOut: async def me(current_user: dict = Depends(get_current_active_user)) -> BindingOut:
b = await get_binding_by_account(str(current_user["_id"])) b = await get_binding_by_account(str(current_user["_id"]))
...@@ -58,4 +70,4 @@ async def me(current_user: dict = Depends(get_current_active_user)) -> BindingOu ...@@ -58,4 +70,4 @@ async def me(current_user: dict = Depends(get_current_active_user)) -> BindingOu
b["_id"] = str(b["_id"]) b["_id"] = str(b["_id"])
b["account_id"] = str(b["account_id"]) b["account_id"] = str(b["account_id"])
b["person_id"] = str(b["person_id"]) b["person_id"] = str(b["person_id"])
return b return b
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Path, Body
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from bson import ObjectId from bson import ObjectId
...@@ -24,8 +24,13 @@ class ClassOut(BaseModel): ...@@ -24,8 +24,13 @@ class ClassOut(BaseModel):
summary="设置班主任人物", summary="设置班主任人物",
description="为班级设置 head_teacher_person_id(指向教师人物)。", description="为班级设置 head_teacher_person_id(指向教师人物)。",
response_model=ActionResult, response_model=ActionResult,
responses={
404: {"description": "班级不存在", "content": {"application/json": {"example": {"detail": "班级不存在"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
},
) )
async def set_head_teacher(class_id: str, payload: HeadTeacherPayload, current_user: dict = Depends(require_role(3))) -> ActionResult: async def set_head_teacher(class_id: str = Path(..., description="班级ID"), payload: HeadTeacherPayload = Body(..., description="班主任人物设置"), current_user: dict = Depends(require_role(3))) -> ActionResult:
res = await db.db.classes.update_one({"class_id": class_id}, {"$set": {"head_teacher_person_id": ObjectId(payload.head_teacher_person_id)}}) res = await db.db.classes.update_one({"class_id": class_id}, {"$set": {"head_teacher_person_id": ObjectId(payload.head_teacher_person_id)}})
if res.matched_count == 0: if res.matched_count == 0:
raise HTTPException(status_code=404, detail="班级不存在") raise HTTPException(status_code=404, detail="班级不存在")
...@@ -45,4 +50,4 @@ async def list_classes(current_user: dict = Depends(require_role(2))) -> List[Cl ...@@ -45,4 +50,4 @@ async def list_classes(current_user: dict = Depends(require_role(2))) -> List[Cl
if d.get("head_teacher_person_id"): if d.get("head_teacher_person_id"):
d["head_teacher_person_id"] = str(d["head_teacher_person_id"]) d["head_teacher_person_id"] = str(d["head_teacher_person_id"])
res.append(d) res.append(d)
return res return res
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status, Path, Body
from typing import List from typing import List
from app.api.deps import get_current_active_user, require_edition_for_mode from app.api.deps import get_current_active_user, require_edition_for_mode
from app.schemas.conversation import ( from app.schemas.conversation import (
...@@ -20,31 +20,47 @@ from app.services.conversation import ( ...@@ -20,31 +20,47 @@ from app.services.conversation import (
# 在路由层挂载版别运行模式依赖,限制仅允许当前模式的用户访问 # 在路由层挂载版别运行模式依赖,限制仅允许当前模式的用户访问
router = APIRouter(dependencies=[Depends(require_edition_for_mode())]) router = APIRouter(dependencies=[Depends(require_edition_for_mode())])
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=CreatedId) @router.post(
"/",
status_code=status.HTTP_201_CREATED,
response_model=CreatedId,
summary="创建对话",
description="为当前用户新建会话,返回新建的会话ID。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
)
async def create_new_conversation(current_user: dict = Depends(get_current_active_user)): async def create_new_conversation(current_user: dict = Depends(get_current_active_user)):
"""创建新对话(返回新建 ID)
用途:为当前用户新建会话,并返回新会话的 ID。
依赖:鉴权用户、版别运行模式。
"""
conversation_id = await create_conversation(str(current_user["_id"])) conversation_id = await create_conversation(str(current_user["_id"]))
return {"id": conversation_id} return {"id": conversation_id}
@router.get("/", response_model=List[ConversationResponse]) @router.get(
"/",
response_model=List[ConversationResponse],
summary="获取对话列表",
description="返回当前用户的对话列表(包含最近一条消息等精简信息)。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
)
async def list_conversations(current_user: dict = Depends(get_current_active_user)): async def list_conversations(current_user: dict = Depends(get_current_active_user)):
"""获取用户的所有对话(列表优化)
用途:返回当前用户的对话列表,统一字段并降低负载(仅最近一条消息)。
"""
conversations = await get_user_conversations(str(current_user["_id"])) conversations = await get_user_conversations(str(current_user["_id"]))
return conversations return conversations
@router.get("/{conversation_id}", response_model=ConversationDocOut) @router.get(
"/{conversation_id}",
response_model=ConversationDocOut,
summary="获取对话详情",
description="返回指定对话的完整消息列表。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
404: {"description": "对话不存在", "content": {"application/json": {"example": {"detail": "对话不存在"}}}},
},
)
async def get_single_conversation( async def get_single_conversation(
conversation_id: str, conversation_id: str = Path(..., description="对话ID"),
current_user: dict = Depends(get_current_active_user) current_user: dict = Depends(get_current_active_user)
): ):
"""获取单个对话详情
用途:返回指定对话的完整消息列表,统一 ID 与标题字段。
"""
conversation = await get_conversation(conversation_id, str(current_user["_id"])) conversation = await get_conversation(conversation_id, str(current_user["_id"]))
if not conversation: if not conversation:
raise HTTPException( raise HTTPException(
...@@ -53,29 +69,40 @@ async def get_single_conversation( ...@@ -53,29 +69,40 @@ async def get_single_conversation(
) )
return conversation return conversation
@router.delete("/{conversation_id}", response_model=DeleteResult) @router.delete(
"/{conversation_id}",
response_model=DeleteResult,
summary="删除对话",
description="删除当前用户归属的指定对话。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
404: {"description": "对话不存在或无权限", "content": {"application/json": {"example": {"detail": "对话不存在或无权限"}}}},
},
)
async def remove_conversation( async def remove_conversation(
conversation_id: str, conversation_id: str = Path(..., description="对话ID"),
current_user: dict = Depends(get_current_active_user) current_user: dict = Depends(get_current_active_user)
): ):
"""删除对话
用途:仅允许删除当前用户归属的对话,并清理关联敏感记录。
返回:删除结果(deleted: bool)
"""
ok = await delete_conversation(conversation_id, str(current_user["_id"])) ok = await delete_conversation(conversation_id, str(current_user["_id"]))
if not ok: if not ok:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="对话不存在或无权限") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="对话不存在或无权限")
return {"deleted": True, "message": "删除成功"} return {"deleted": True, "message": "删除成功"}
@router.post("/{conversation_id}/messages", response_model=MessageSendResult) @router.post(
"/{conversation_id}/messages",
response_model=MessageSendResult,
summary="发送消息并获取回复",
description="向指定对话发送消息,返回助手回复或敏感词拒绝信息。",
responses={
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
404: {"description": "对话不存在", "content": {"application/json": {"example": {"detail": "对话不存在"}}}},
},
)
async def send_message( async def send_message(
conversation_id: str, conversation_id: str = Path(..., description="对话ID"),
message: MessageCreate, message: MessageCreate = Body(..., description="消息内容"),
current_user: dict = Depends(get_current_active_user) current_user: dict = Depends(get_current_active_user)
): ):
"""发送消息并获取回复
用途:在指定对话中发送用户消息;若命中敏感词返回拒绝回复并记录;否则返回模型生成的助手回复。
"""
# 检查对话是否存在 # 检查对话是否存在
conversation = await get_conversation(conversation_id, str(current_user["_id"])) conversation = await get_conversation(conversation_id, str(current_user["_id"]))
if not conversation: if not conversation:
...@@ -87,4 +114,4 @@ async def send_message( ...@@ -87,4 +114,4 @@ async def send_message(
# 添加消息并获取回复 # 添加消息并获取回复
result = await add_message(conversation_id, str(current_user["_id"]), message.content) result = await add_message(conversation_id, str(current_user["_id"]), message.content)
return result return result
\ No newline at end of file
...@@ -89,7 +89,11 @@ class CampusOverview(BaseModel): ...@@ -89,7 +89,11 @@ class CampusOverview(BaseModel):
"/student/today", "/student/today",
summary="学生端:今日个人课表、出勤与操行", summary="学生端:今日个人课表、出勤与操行",
description="需角色等级≥1且主绑定为学生;返回当天课表(共享节次按班级定位教室)、今日出勤记录与操行评语。" description="需角色等级≥1且主绑定为学生;返回当天课表(共享节次按班级定位教室)、今日出勤记录与操行评语。"
, response_model=StudentTodaySummary , response_model=StudentTodaySummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定学生", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
) )
async def student_today(current_user: dict = Depends(require_role(1)), _b: dict = Depends(require_binding("student"))) -> StudentTodaySummary: async def student_today(current_user: dict = Depends(require_role(1)), _b: dict = Depends(require_binding("student"))) -> StudentTodaySummary:
return await student_today_summary(current_user) return await student_today_summary(current_user)
...@@ -98,7 +102,11 @@ async def student_today(current_user: dict = Depends(require_role(1)), _b: dict ...@@ -98,7 +102,11 @@ async def student_today(current_user: dict = Depends(require_role(1)), _b: dict
"/homeroom/current", "/homeroom/current",
summary="班主任端:当前节次课程与地点、出勤率、请假、指示", summary="班主任端:当前节次课程与地点、出勤率、请假、指示",
description="需角色等级≥2且主绑定为教师;按 head_teacher_person_id 定位所辖班,统计当前节次的课程与地点、节次出勤率、今日请假与部门指示。" description="需角色等级≥2且主绑定为教师;按 head_teacher_person_id 定位所辖班,统计当前节次的课程与地点、节次出勤率、今日请假与部门指示。"
, response_model=HomeroomCurrentSummary , response_model=HomeroomCurrentSummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定教师", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
) )
async def homeroom_current(current_user: dict = Depends(require_role(2)), _b: dict = Depends(require_binding("teacher"))) -> HomeroomCurrentSummary: async def homeroom_current(current_user: dict = Depends(require_role(2)), _b: dict = Depends(require_binding("teacher"))) -> HomeroomCurrentSummary:
return await homeroom_current_summary(current_user) return await homeroom_current_summary(current_user)
...@@ -107,7 +115,11 @@ async def homeroom_current(current_user: dict = Depends(require_role(2)), _b: di ...@@ -107,7 +115,11 @@ async def homeroom_current(current_user: dict = Depends(require_role(2)), _b: di
"/department/overview", "/department/overview",
summary="中层端:教师节次出勤率、学生出勤、异常班级、指示", summary="中层端:教师节次出勤率、学生出勤、异常班级、指示",
description="需角色等级≥3;按今日工作日统计每位教师的节次出勤率、全校学生出勤聚合、异常班级(缺勤+请假占比>0.3)与近期部门/校园指示。" description="需角色等级≥3;按今日工作日统计每位教师的节次出勤率、全校学生出勤聚合、异常班级(缺勤+请假占比>0.3)与近期部门/校园指示。"
, response_model=DepartmentOverview , response_model=DepartmentOverview,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
}
) )
async def department(current_user: dict = Depends(require_role(3))) -> DepartmentOverview: async def department(current_user: dict = Depends(require_role(3))) -> DepartmentOverview:
return await department_overview(current_user) return await department_overview(current_user)
...@@ -116,7 +128,11 @@ async def department(current_user: dict = Depends(require_role(3))) -> Departmen ...@@ -116,7 +128,11 @@ async def department(current_user: dict = Depends(require_role(3))) -> Departmen
"/campus/overview", "/campus/overview",
summary="校级端:校园整体总览", summary="校级端:校园整体总览",
description="需角色等级≥4;返回学生总数、今日出勤、请假与指示数量等宏观数据。" description="需角色等级≥4;返回学生总数、今日出勤、请假与指示数量等宏观数据。"
, response_model=CampusOverview , response_model=CampusOverview,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
}
) )
async def campus(current_user: dict = Depends(require_role(4))) -> CampusOverview: async def campus(current_user: dict = Depends(require_role(4))) -> CampusOverview:
return await campus_overview(current_user) return await campus_overview(current_user)
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Body
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import List, Dict from typing import List, Dict
from bson import ObjectId from bson import ObjectId
...@@ -30,8 +30,13 @@ class PersonsListResponse(BaseModel): ...@@ -30,8 +30,13 @@ class PersonsListResponse(BaseModel):
summary="批量导入人物档案", summary="批量导入人物档案",
description="导入学生/教师/职员的基础人物信息(person_id/name/type)。", description="导入学生/教师/职员的基础人物信息(person_id/name/type)。",
response_model=BulkInsertResult, response_model=BulkInsertResult,
responses={
400: {"description": "空数据", "content": {"application/json": {"example": {"detail": "空数据"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
},
) )
async def bulk_create(persons: List[PersonCreate], current_user: dict = Depends(require_role(3))) -> BulkInsertResult: async def bulk_create(persons: List[PersonCreate] = Body(..., description="人物档案列表"), current_user: dict = Depends(require_role(3))) -> BulkInsertResult:
docs = [{"person_id": p.person_id, "name": p.name, "type": p.type} for p in persons] docs = [{"person_id": p.person_id, "name": p.name, "type": p.type} for p in persons]
if not docs: if not docs:
raise HTTPException(status_code=400, detail="空数据") raise HTTPException(status_code=400, detail="空数据")
...@@ -54,4 +59,4 @@ async def list_persons(current_user: dict = Depends(require_role(3))) -> Persons ...@@ -54,4 +59,4 @@ async def list_persons(current_user: dict = Depends(require_role(3))) -> Persons
t = d.get("type") t = d.get("type")
if t: if t:
counts[t] = counts.get(t, 0) + 1 counts[t] = counts.get(t, 0) + 1
return {"items": res, "counts_by_type": counts} return {"items": res, "counts_by_type": counts}
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Body
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from bson import ObjectId from bson import ObjectId
...@@ -27,8 +27,13 @@ class ScheduleOut(BaseModel): ...@@ -27,8 +27,13 @@ class ScheduleOut(BaseModel):
summary="为节次设置任课教师人物", summary="为节次设置任课教师人物",
description="将指定 lesson_id 的节次设置为 teacher_person_id(教师人物)。", description="将指定 lesson_id 的节次设置为 teacher_person_id(教师人物)。",
response_model=ActionResult, response_model=ActionResult,
responses={
404: {"description": "节次不存在", "content": {"application/json": {"example": {"detail": "节次不存在"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
},
) )
async def assign_teacher(payload: AssignTeacherPayload, current_user: dict = Depends(require_role(3))) -> ActionResult: async def assign_teacher(payload: AssignTeacherPayload = Body(..., description="节次与教师人物"), current_user: dict = Depends(require_role(3))) -> ActionResult:
res = await db.db.schedules.update_one({"lesson_id": payload.lesson_id}, {"$set": {"teacher_person_id": ObjectId(payload.teacher_person_id)}}) res = await db.db.schedules.update_one({"lesson_id": payload.lesson_id}, {"$set": {"teacher_person_id": ObjectId(payload.teacher_person_id)}})
if res.matched_count == 0: if res.matched_count == 0:
raise HTTPException(status_code=404, detail="节次不存在") raise HTTPException(status_code=404, detail="节次不存在")
...@@ -54,4 +59,4 @@ async def list_schedules(current_user: dict = Depends(require_role(2))) -> List[ ...@@ -54,4 +59,4 @@ async def list_schedules(current_user: dict = Depends(require_role(2))) -> List[
"teacher_person_id": d.get("teacher_person_id"), "teacher_person_id": d.get("teacher_person_id"),
"classes": d.get("classes", []), "classes": d.get("classes", []),
}) })
return res return res
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Path, Body
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from app.api.deps import require_edition_for_mode, require_role from app.api.deps import require_edition_for_mode, require_role
...@@ -18,24 +18,51 @@ class StudentOut(BaseModel): ...@@ -18,24 +18,51 @@ class StudentOut(BaseModel):
name: Optional[str] = None name: Optional[str] = None
class_id: Optional[str] = None class_id: Optional[str] = None
@router.post("/{student_id}/bind", response_model=ActionResult) @router.post(
async def bind(student_id: str, payload: BindPayload, current_user: dict = Depends(require_role(2))) -> ActionResult: "/{student_id}/bind",
response_model=ActionResult,
summary="绑定学生",
description="为指定学生建立主绑定(需角色等级≥2)。",
responses={
400: {"description": "绑定失败或学生不存在", "content": {"application/json": {"example": {"detail": "用户已绑定其他学生或学生不存在"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
)
async def bind(student_id: str = Path(..., description="学生ID"), payload: BindPayload = Body(..., description="绑定参数"), current_user: dict = Depends(require_role(2))) -> ActionResult:
ok = await bind_user_to_student(payload.user_id, student_id) ok = await bind_user_to_student(payload.user_id, student_id)
if not ok: if not ok:
raise HTTPException(status_code=400, detail="用户已绑定其他学生或学生不存在") raise HTTPException(status_code=400, detail="用户已绑定其他学生或学生不存在")
return {"success": True} return {"success": True}
@router.delete("/{student_id}/bind", response_model=ActionResult) @router.delete(
async def unbind(student_id: str, current_user: dict = Depends(require_role(2))) -> ActionResult: "/{student_id}/bind",
response_model=ActionResult,
summary="解除学生绑定",
description="解除当前用户与指定学生的主绑定(需角色等级≥2)。",
responses={
404: {"description": "未找到或未绑定", "content": {"application/json": {"example": {"detail": "学生不存在或未绑定"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
)
async def unbind(student_id: str = Path(..., description="学生ID"), current_user: dict = Depends(require_role(2))) -> ActionResult:
ok = await unbind_user_from_student(student_id) ok = await unbind_user_from_student(student_id)
if not ok: if not ok:
raise HTTPException(status_code=404, detail="学生不存在或未绑定") raise HTTPException(status_code=404, detail="学生不存在或未绑定")
return {"success": True} return {"success": True}
@router.get("/me", response_model=StudentOut) @router.get(
"/me",
response_model=StudentOut,
summary="查询当前绑定学生",
description="返回当前用户绑定的学生信息(需角色等级≥1)。",
responses={
404: {"description": "未绑定学生", "content": {"application/json": {"example": {"detail": "当前用户未绑定学生"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
},
)
async def me(current_user: dict = Depends(require_role(1))) -> StudentOut: async def me(current_user: dict = Depends(require_role(1))) -> StudentOut:
s = await get_student_by_user(str(current_user["_id"])) s = await get_student_by_user(str(current_user["_id"]))
if not s: if not s:
raise HTTPException(status_code=404, detail="当前用户未绑定学生") raise HTTPException(status_code=404, detail="当前用户未绑定学生")
s["_id"] = str(s["_id"]) # 简化返回 s["_id"] = str(s["_id"]) # 简化返回
return s return s
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, Body
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from bson import ObjectId from bson import ObjectId
...@@ -30,8 +30,13 @@ class TeacherOut(BaseModel): ...@@ -30,8 +30,13 @@ class TeacherOut(BaseModel):
summary="批量导入教师实体", summary="批量导入教师实体",
description="导入教师实体(person_id/teacher_id/department/roles),可选绑定 account_id。", description="导入教师实体(person_id/teacher_id/department/roles),可选绑定 account_id。",
response_model=BulkInsertResult, response_model=BulkInsertResult,
responses={
400: {"description": "空数据", "content": {"application/json": {"example": {"detail": "空数据"}}}},
401: {"description": "认证失败", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
},
) )
async def bulk_create(teachers: List[TeacherCreate], current_user: dict = Depends(require_role(3))) -> BulkInsertResult: async def bulk_create(teachers: List[TeacherCreate] = Body(..., description="教师实体列表"), current_user: dict = Depends(require_role(3))) -> BulkInsertResult:
docs = [] docs = []
for t in teachers: for t in teachers:
doc = { doc = {
...@@ -63,4 +68,4 @@ async def list_teachers(current_user: dict = Depends(require_role(3))) -> List[T ...@@ -63,4 +68,4 @@ async def list_teachers(current_user: dict = Depends(require_role(3))) -> List[T
if d.get("account_id"): if d.get("account_id"):
d["account_id"] = str(d["account_id"]) d["account_id"] = str(d["account_id"])
res.append(d) res.append(d)
return res return res
\ No newline at end of file
...@@ -8,6 +8,7 @@ from app.utils.sensitive_word_filter import sensitive_word_filter ...@@ -8,6 +8,7 @@ from app.utils.sensitive_word_filter import sensitive_word_filter
app = FastAPI( app = FastAPI(
title=settings.APP_NAME, title=settings.APP_NAME,
version="1.0.0",
description=( description=(
"LLM 过滤系统后端接口\n\n" "LLM 过滤系统后端接口\n\n"
"角色等级:1 学生、2 班主任、3 中层、4 校级、5 管理员。\n" "角色等级:1 学生、2 班主任、3 中层、4 校级、5 管理员。\n"
...@@ -28,7 +29,16 @@ app = FastAPI( ...@@ -28,7 +29,16 @@ app = FastAPI(
], ],
openapi_url=f"{settings.API_V1_STR}/openapi.json", openapi_url=f"{settings.API_V1_STR}/openapi.json",
docs_url=None, # 禁用默认的/docs docs_url=None, # 禁用默认的/docs
redoc_url=None # 禁用默认的/redoc redoc_url=None, # 禁用默认的/redoc
contact={
"name": "LLM Filter Team",
"url": settings.APP_BASE_URL,
"email": "huangjunbo1107@outlook.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
}
) )
origins_cfg = settings.CORS_ALLOWED_ORIGINS origins_cfg = settings.CORS_ALLOWED_ORIGINS
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment