From 3899a2345e07035857ccfa1316f4ce60de37d2f4 Mon Sep 17 00:00:00 2001 From: chengkai3 Date: Tue, 16 Jun 2026 17:44:11 +0800 Subject: [PATCH] =?UTF-8?q?[fix]:[FL-145][=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=A6=81=E6=94=AF=E6=8C=81=E5=88=A0=E9=99=A4]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: multica-agent --- api/app/api/v1/system_messages.py | 15 ++++ api/app/services/system_message_service.py | 10 ++- api/tests/test_system_message_service.py | 39 +++++++++++ memory/2026-06-16.md | 18 +++++ web/src/app/admin/system-messages/page.tsx | 79 ++++++++++++++++++---- 5 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 api/tests/test_system_message_service.py diff --git a/api/app/api/v1/system_messages.py b/api/app/api/v1/system_messages.py index ae72c76..41eb740 100644 --- a/api/app/api/v1/system_messages.py +++ b/api/app/api/v1/system_messages.py @@ -3,6 +3,7 @@ from sqlalchemy.orm import Session from ...core.database import get_db from ...core.dependencies import CurrentUser, get_current_user, require_permission +from ...schemas.auth import MessageResponse from ...schemas.system_message import ( SystemMessageCreateRequest, SystemMessageListResponse, @@ -11,6 +12,7 @@ from ...schemas.system_message import ( ) from ...services.system_message_service import ( create_system_message, + delete_system_message, get_unread_count, list_user_messages, mark_messages_as_read, @@ -72,3 +74,16 @@ def mark_my_messages_read( """标记消息为已读""" affected = mark_messages_as_read(db, current_user.user.id, payload.message_ids) return {"affected": affected} + + +@router.delete("/{message_id}", response_model=MessageResponse) +def remove_system_message( + message_id: str, + _: CurrentUser = Depends(require_permission("admin.system_message")), + db: Session = Depends(get_db), +) -> MessageResponse: + """删除系统消息(需要admin.system_message权限)""" + deleted = delete_system_message(db, message_id) + if not deleted: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="System message not found") + return MessageResponse(message="System message deleted") diff --git a/api/app/services/system_message_service.py b/api/app/services/system_message_service.py index 0ee73fb..16126f6 100644 --- a/api/app/services/system_message_service.py +++ b/api/app/services/system_message_service.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import func, select, update +from sqlalchemy import delete, func, select, update from sqlalchemy.orm import Session from ..models.system_message import SystemMessage @@ -86,6 +86,14 @@ def mark_messages_as_read( return result.rowcount or 0 +def delete_system_message(db: Session, message_id: str) -> bool: + """删除系统消息""" + stmt = delete(SystemMessage).where(SystemMessage.id == message_id) + result = db.execute(stmt) + db.commit() + return (result.rowcount or 0) > 0 + + def get_unread_count(db: Session, user_id: str) -> int: """获取用户未读消息数量""" query = select(func.count()).select_from(SystemMessage).where( diff --git a/api/tests/test_system_message_service.py b/api/tests/test_system_message_service.py new file mode 100644 index 0000000..e7c1386 --- /dev/null +++ b/api/tests/test_system_message_service.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from sqlalchemy import create_engine, select +from sqlalchemy.orm import Session + +from app.core.database import Base +from app.models.system_message import SystemMessage +from app.schemas.system_message import SystemMessageCreateRequest +from app.services.system_message_service import create_system_message, delete_system_message + + +def test_delete_system_message_removes_existing_message() -> None: + engine = create_engine("sqlite+pysqlite:///:memory:") + Base.metadata.create_all(bind=engine, tables=[SystemMessage.__table__]) + + with Session(engine) as db: + message = create_system_message( + db, + SystemMessageCreateRequest( + title="系统通知", + content="测试内容", + message_type="info", + ), + ) + + deleted = delete_system_message(db, message.id) + + assert deleted is True + assert db.scalar(select(SystemMessage).where(SystemMessage.id == message.id)) is None + + +def test_delete_system_message_returns_false_when_missing() -> None: + engine = create_engine("sqlite+pysqlite:///:memory:") + Base.metadata.create_all(bind=engine, tables=[SystemMessage.__table__]) + + with Session(engine) as db: + deleted = delete_system_message(db, "missing-message-id") + + assert deleted is False diff --git a/memory/2026-06-16.md b/memory/2026-06-16.md index 00c60ab..e4e5a2f 100644 --- a/memory/2026-06-16.md +++ b/memory/2026-06-16.md @@ -14,3 +14,21 @@ - 风险与关注点: - 改动仅影响系统消息公开响应 schema 的序列化方式,不改变接口字段、数据库结构或查询逻辑。 + +## Work Log - 系统消息支持删除(FL-145) + +- 背景: + - 系统消息管理页已有创建、查看、标记已读能力,但缺少删除入口。 + +- 本次处理: + - 后端新增 `DELETE /api/v1/system-messages/{message_id}`,复用 `admin.system_message` 权限,删除系统消息记录。 + - 前端系统消息列表新增删除操作,使用二次确认,并在删除后刷新列表与未读统计。 + - 新增系统消息删除服务测试,覆盖存在记录删除与缺失记录返回 `False`。 + +- 验证: + - `UV_CACHE_DIR=/tmp/fquiz-uv-cache uv run --with pytest --with sqlalchemy --with pydantic --with pydantic-settings --with email-validator python -m pytest api/tests/test_system_message_schema.py api/tests/test_system_message_service.py` 通过。 + - `npm --workspace web exec eslint src/app/admin/system-messages/page.tsx` 通过。 + - `npm --workspace web run build` 通过。 + +- 风险与关注点: + - 当前删除为管理端物理删除系统消息记录;广播消息删除后对所有用户不可见。 diff --git a/web/src/app/admin/system-messages/page.tsx b/web/src/app/admin/system-messages/page.tsx index f1a0d56..ab5c358 100644 --- a/web/src/app/admin/system-messages/page.tsx +++ b/web/src/app/admin/system-messages/page.tsx @@ -8,6 +8,7 @@ import { Empty, Form, Input, + Popconfirm, Select, Space, Spin, @@ -72,6 +73,7 @@ export default function AdminSystemMessagesPage() { const [formApi] = Form.useForm(); const [messageTypeFilter, setMessageTypeFilter] = useState("all"); const [unreadOnly, setUnreadOnly] = useState(false); + const [deletingMessageId, setDeletingMessageId] = useState(null); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); @@ -160,6 +162,39 @@ export default function AdminSystemMessagesPage() { }, }); + const deleteMutation = useMutation({ + mutationFn: async (messageId: string) => { + if (!canManage) { + throw new Error("缺少 admin.system_message 权限"); + } + + const response = await fetchWithAuth(`/api/v1/system-messages/${messageId}`, { + method: "DELETE", + }); + if (!response.ok) { + throw new Error(await readApiError(response)); + } + return response.json() as Promise<{ message: string }>; + }, + onMutate: (messageId) => { + setDeletingMessageId(messageId); + setSuccess(""); + setError(""); + }, + onSuccess: async () => { + setError(""); + setSuccess("系统消息已删除"); + await refreshMessages(); + }, + onError: (candidate) => { + setSuccess(""); + setError(candidate instanceof Error ? candidate.message : "删除失败"); + }, + onSettled: () => { + setDeletingMessageId(null); + }, + }); + useToastFeedback({ errorMessage: error, successMessage: success, @@ -233,20 +268,40 @@ export default function AdminSystemMessagesPage() { key: "actions", width: 120, fixed: "right", - render: (_, item) => ( - - ), + render: (_, item) => { + const isDeleting = deletingMessageId === item.id; + + return ( + + + {canManage && ( + deleteMutation.mutate(item.id)} + > + + + )} + + ); + }, }, ], - [markReadMutation], + [canManage, deleteMutation, deletingMessageId, markReadMutation], ); if (initializing) {