29 KiB
msg 模块 Next Steps(上下游依赖)
维护者:ai10(msg) 最后更新:2026-07-14 关联:
- msg_workline.md
- msg_contract.md
- msg_issue.md
- ARB-008 §10 / ARB-013 §15
1. 当前状态总览
| 阶段 | 状态 | 说明 |
|---|---|---|
| P5 通知中台核心 | ✅ 完成 | 13 RPC(5 Notification + 2 Preference + 6 Template)+ REST 双协议 |
| ARB-008 裁决落地 | ✅ 完成 | RPC 17→13,4 RPC 降级 REST only,topic 改为 edu.notify.notification.* |
| ARB-013 topic 统一 | ✅ 完成 | 发布 topic edu.notify.notification.sent/read/recalled/failed |
| Outbox + 三层幂等 | ✅ 完成 | msg_outbox_events + Redis SETNX + DB + event_id UNIQUE |
| ChannelDispatcher | ✅ 完成 | 5 通道(in-app/email/sms/push/wechat)Promise.allSettled |
| /readyz 6 依赖检查 | ✅ 完成 | DB/ES/Redis/KafkaProducer/KafkaConsumer/PushGateway |
| 单元测试 | ✅ 完成 | 101 tests / 5 spec files,覆盖率 ≥ 80% |
| Announcements 公告 | ✅ 完成 | 新增 REST API(schema+repo+service+controller+module) |
| sendBatch 批量优化 | ✅ 完成 | 循环单条插入 → 批量 insertNotifications + eventId 幂等过滤,失败全部标记 failed |
| Docker 本地测试 | ✅ 完成 | 容器启动 + healthz/readyz + 全部 REST API 真实 DB 验证通过(含 sendBatch 幂等场景) |
1.1 msg 当前提供的能力
gRPC 13 RPC(端口 50056):
| Service | RPC | 说明 |
|---|---|---|
| NotificationService | SendNotification | 单条发送 |
| NotificationService | ListNotifications | 列表(分页+过滤) |
| NotificationService | MarkAsRead | 标记已读 |
| NotificationService | SearchNotifications | ES 全文检索 |
| NotificationService | RecallNotification | 撤回广播 |
| NotificationPreferenceService | GetPreferences | 查询偏好 |
| NotificationPreferenceService | UpdatePreferences | 更新偏好 |
| NotificationTemplateService | CreateTemplate/GetTemplate/ListTemplates/UpdateTemplate/DeleteTemplate/RenderTemplate | 模板 CRUD + 渲染 |
REST endpoints(端口 3007):
| 路径 | 方法 | 说明 |
|---|---|---|
| /notifications/send | POST | 单条发送 |
| /notifications/batch | POST | 批量发送 |
| /notifications/user/:userId | GET | 列表 |
| /notifications/user/:userId/unread-count | GET | 未读数 |
| /notifications/:id/read | PUT | 标记已读 |
| /notifications/batch/read | PUT | 批量已读 |
| /notifications/read-all | PUT | 全部已读 |
| /notifications/search | GET | ES 检索 |
| /notifications/recall | POST | 撤回 |
| /notifications/:id | DELETE | 删除 |
| /preferences/user/:userId | GET/PUT | 偏好查询/更新 |
| /templates | GET/POST | 模板列表/创建 |
| /templates/:id | GET/PUT/DELETE | 模板 CRUD |
| /templates/render | POST | 模板渲染 |
| /announcements | GET/POST | 公告列表/创建 |
| /announcements/:id | GET/PUT/DELETE | 公告详情/更新/删除 |
| /announcements/:id/publish | PUT | 发布公告 |
| /announcements/:id/archive | PUT | 归档公告 |
| /announcements/:id/pin | PUT | 置顶切换 |
| /announcements/:id/read | POST | 标记公告已读 |
Kafka 消费(12 类事件):
- iam:
edu.identity.user.created/updated/deleted/role_changed,edu.identity.role.created/updated - core-edu:
edu.teaching.exam.published/assignment.submitted/assignment.graded/grade.recorded/attendance.recorded - data-ana:
edu.insight.mastery.updated
Kafka 发布(4 类事件,ARB-013 命名):
edu.notify.notification.sent/edu.notify.notification.read/edu.notify.notification.recalled/edu.notify.notification.failed- DLQ:
edu.notify.dlq
2. 下游依赖(msg → 下游模块需要做什么)
2.1 teacher-bff(ai03)
依赖内容:teacher-bff 通过 gRPC 调用 msg 的 5 个 Notification RPC + 公告 REST API 影响范围:teacher-portal 通知中心 + 公告管理
| 下游需要的操作 | msg 提供方式 | 状态 |
|---|---|---|
| ListNotifications | gRPC NotificationService.ListNotifications | ✅ 已实现 |
| MarkNotificationRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 |
| ListAnnouncements | REST GET /announcements | ✅ 已实现 |
| CreateAnnouncement | REST POST /announcements | ✅ 已实现 |
| PublishAnnouncement | REST PUT /announcements/:id/publish | ✅ 已实现 |
协调方式:teacher-bff 通过 gRPC client 调用 msg:50056,通过 HTTP 调用 msg:3007/announcements/*
2.2 student-bff(ai04)
依赖内容:student-bff 通过 gRPC + REST 调用 msg 影响范围:student-portal 通知 + 公告查看
| 下游需要的操作 | msg 提供方式 | 状态 |
|---|---|---|
| ListAnnouncements | REST GET /announcements | ✅ 已实现 |
| GetAnnouncement | REST GET /announcements/:id | ✅ 已实现 |
| MarkNotificationAsRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 |
| MarkAllNotificationsAsRead | REST PUT /notifications/read-all | ✅ 已实现(ARB-008 降级 REST only) |
| UpdateNotificationPreference | gRPC NotificationPreferenceService.UpdatePreferences | ✅ 已实现 |
| MarkAnnouncementRead | REST POST /announcements/:id/read | ✅ 已实现 |
2.3 parent-bff(ai05)
依赖内容:parent-bff 通过 gRPC 调用 msg 影响范围:parent-portal 通知偏好
| 下游需要的操作 | msg 提供方式 | 状态 |
|---|---|---|
| listNotifications | gRPC NotificationService.ListNotifications | ✅ 已实现 |
| markAsRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 |
| getNotificationPreferences | gRPC NotificationPreferenceService.GetPreferences | ✅ 已实现 |
| updateNotificationPreferences | gRPC NotificationPreferenceService.UpdatePreferences | ✅ 已实现 |
2.4 push-gateway(ai09)
依赖内容:msg 通过 HTTP POST /internal/push 推送实时通知;push-gateway 消费 msg 发布的 Kafka 事件 影响范围:WebSocket 实时推送到前端
| 下游需要的协作 | msg 提供方式 | 状态 |
|---|---|---|
| HTTP POST /internal/push | push-gateway.client.ts sendPush() | ✅ 已实现(软失败) |
| Kafka 事件推送 | 发布 edu.notify.notification.sent 等 4 topic |
✅ 已实现 |
push-gateway 需对齐:
- 消费 topic 必须为
edu.notify.notification.sent(ARB-013 命名),而非旧的edu.notification.requested - 鉴权头
X-Internal-Key: PUSH_INTERNAL_TOKEN
2.5 api-gateway(ai01)
依赖内容:api-gateway 代理 msg REST 路由 影响范围:前端通过 api-gateway 访问 msg
| 路由 | 代理目标 | 状态 |
|---|---|---|
| /api/v1/notifications/* | msg:3007 | ✅ 已配置(main.go L100) |
| /api/v1/messages/* | msg:3007 | ✅ 已配置(main.go L101) |
注意:公告 REST API 在 /announcements/* 路径下,api-gateway 需新增路由 /api/v1/announcements/* → msg:3007(或复用 /api/v1/notifications/* 前缀下的子路径)。
2.6 前端 portal(ai13/ai14/ai15/ai16)
间接依赖:前端通过各自 BFF 聚合访问 msg,无直接 gRPC/REST 调用。
- teacher-portal(ai13):通过 teacher-bff GraphQL → msg gRPC/REST
- student-portal(ai14):通过 student-bff GraphQL → msg gRPC/REST
- parent-portal(ai15):通过 parent-bff GraphQL → msg gRPC
- admin-portal(ai16):通过 teacher-bff admin GraphQL → msg REST(公告管理)
3. 上游依赖(msg ← 上游模块需要提供什么)
3.1 iam(ai06)— Kafka 事件源
依赖内容:msg 消费 iam 发布的 6 类 identity 事件 事件清单:
| Topic | 触发场景 | msg 处理 |
|---|---|---|
edu.identity.user.created |
新用户注册 | 发送欢迎通知 |
edu.identity.user.updated |
用户信息更新 | 仅幂等标记(无通知) |
edu.identity.user.deleted |
用户注销 | 仅幂等标记(无通知) |
edu.identity.user.role_changed |
角色变更 | 发送角色变更通知 |
edu.identity.role.created |
角色创建 | 仅幂等标记 |
edu.identity.role.updated |
角色权限更新 | 向受影响用户发送权限变更通知 |
payload 约定(JSON):
userId/user_id: stringname/username: stringoldRole/old_role: stringnewRole/new_role: stringaffectedUserIds/affected_user_ids: string[]- header
eventId: string(幂等键)
3.2 core-edu(ai07)— Kafka 事件源
依赖内容:msg 消费 core-edu 发布的 5 类 teaching 事件
| Topic | 触发场景 | msg 处理 |
|---|---|---|
edu.teaching.exam.published |
考试发布 | 向学生发送考试通知 |
edu.teaching.assignment.submitted |
作业提交 | 向教师发送提交通知 |
edu.teaching.assignment.graded |
作业批改 | 向学生发送批改通知 |
edu.teaching.grade.recorded |
成绩录入 | 向学生发送成绩通知 |
edu.teaching.attendance.recorded |
考勤异常 | 向家长发送出勤通知 |
payload 约定(JSON):
examId/exam_id,examTitle/exam_title,className/class_name,studentIds/student_ids: string[]teacherId/teacher_id,studentId/student_id,studentName/student_namehomeworkId/homework_id,homeworkTitle/homework_title,score/gradesubject,gradeId/grade_id,parentId/parent_idattendanceId/attendance_id,status,date
3.3 data-ana(ai08)— Kafka 事件源
依赖内容:msg 消费 data-ana 发布的 1 类 insight 事件
| Topic | 触发场景 | msg 处理 |
|---|---|---|
edu.insight.mastery.updated |
掌握度下降 | 向学生发送学情预警通知 |
payload 约定(JSON):
studentId/student_id,subject,mastery/masteryLevel,trendmasteryId/mastery_id
3.4 push-gateway(ai09)— HTTP 推送目标
依赖内容:msg 通过 HTTP POST /internal/push 调用 push-gateway 推送实时通知 调用方式:
- URL:
${PUSH_GATEWAY_URL}/internal/push - Header:
X-Internal-Key: ${PUSH_INTERNAL_TOKEN} - Body:
{ userId, event, data } - 软失败:push-gateway 不可用时返回
{ sent: false },不阻断主流程
3.5 基础设施依赖
| 依赖 | 用途 | 配置 |
|---|---|---|
| MySQL | 通知/偏好/模板/公告/outbox 持久化 | DATABASE_URL |
| Redis | L1 幂等去重(SETNX) | REDIS_URL(可选,缺失降级 DB) |
| Kafka | 消费上游事件 + 发布通知事件 | KAFKA_BROKERS |
| Elasticsearch | 通知全文检索 | ES_URL(可选,缺失降级 DB LIKE) |
| push-gateway | WebSocket 实时推送 | PUSH_GATEWAY_URL(可选,软失败) |
4. 已完成的工作(本轮)
4.1 Announcements 公告功能(新增)
原因:下游 teacher-bff / student-bff / admin-portal 均需要公告 CRUD + 已读标记,msg 原仅有通知能力。
实现:
src/announcements/announcements.schema.ts— Drizzle schema(msg_announcements + msg_announcement_reads 表)src/announcements/announcements.repository.ts— 数据访问层src/announcements/announcements.service.ts— 业务逻辑src/announcements/announcements.controller.ts— REST API(8 端点)src/announcements/announcements.module.ts— 模块定义src/middleware/permission.guard.ts— 新增MSG_ANNOUNCEMENT_MANAGE/MSG_ANNOUNCEMENT_READ权限infra/init-sql/02-all-services-schema.sql— 新增 msg_announcements / msg_announcement_reads 表
REST 端点:
| 路径 | 方法 | 权限 | 说明 |
|---|---|---|---|
| /announcements | POST | MSG_ANNOUNCEMENT_MANAGE | 创建公告(draft) |
| /announcements | GET | MSG_ANNOUNCEMENT_READ | 列表(支持 status/audience 筛选) |
| /announcements/:id | GET | MSG_ANNOUNCEMENT_READ | 详情 |
| /announcements/:id | PUT | MSG_ANNOUNCEMENT_MANAGE | 更新 |
| /announcements/:id | DELETE | MSG_ANNOUNCEMENT_MANAGE | 删除 |
| /announcements/:id/publish | PUT | MSG_ANNOUNCEMENT_MANAGE | 发布 |
| /announcements/:id/archive | PUT | MSG_ANNOUNCEMENT_MANAGE | 归档 |
| /announcements/:id/pin | PUT | MSG_ANNOUNCEMENT_MANAGE | 置顶切换 |
| /announcements/:id/read | POST | MSG_ANNOUNCEMENT_READ | 标记已读 |
4.2 ARB-008 / ARB-013 落地(前序已完成)
- proto RPC 17→13(裁剪 4 RPC 降级 REST only)
- topic 命名
edu.notify.notification.* - /readyz 6 依赖检查(Kafka producer/consumer 拆分)
- sendBatch 批量 INSERT 优化
4.3 sendBatch 批量 INSERT 优化 + eventId 幂等过滤(本轮)
原因:下游 BFF 批量发送通知时性能不佳,原实现循环调用 send(每条 insert + dispatch + outbox)。此外 sendBatch 缺少 eventId 幂等检查,重复发送会重复插入。
改动:
src/notifications/notifications.service.ts— sendBatch 改为先批量insertNotifications(rows),失败时全部标记 failed;成功后逐条 ES 索引 + dispatch + outboxsrc/notifications/notifications.repository.ts— 新增findExistingEventIds(eventIds)批量查询已存在的 eventIdsrc/notifications/notifications.service.ts— sendBatch 批量 INSERT 前先调用findExistingEventIds过滤已存在的 eventId(幂等跳过)- 批量 INSERT 失败 → 所有 items 进 failed 列表
- 部分分发失败 → 仅失败项进 failed 列表,成功项进 ids
- eventId 已存在 → 跳过该项(不插入、不分发、不写 outbox),返回空 ids
- 无 eventId 的 item → 正常插入(不做幂等检查,与单条 send 行为一致)
幂等行为:
- 全部 eventId 已存在 → 返回
{ ids: [], failed: [] } - 部分 eventId 已存在 → 仅插入未存在的,已存在的跳过
- 无 eventId → 正常插入(允许重复)
单元测试(新增 2 个):
eventId 已存在时应跳过(幂等过滤)— 2 条 items(1 已存在 + 1 新),仅插入 1 条所有 eventId 都已存在时应返回空 ids(全跳过)— 2 条都已存在,不调用 insertNotifications
4.4 Docker 本地测试验证(本轮,无 mock 数据)
环境:edu-full_default 网络,连接 edu-mysql/edu-redis/edu-kafka/edu-es 真实服务
验证结果:
| 测试项 | 端点 | 结果 |
|---|---|---|
| 健康检查 | GET /healthz | ✅ {"status":"ok"} |
| 就绪检查 | GET /readyz | ✅ 5/6 OK(pushGateway 软失败,预期) |
| 公告创建 | POST /announcements | ✅ 返回 id,status=draft |
| 公告列表 | GET /announcements | ✅ 返回 items + total |
| 公告详情 | GET /announcements/:id | ✅ 返回完整记录 |
| 公告发布 | PUT /announcements/:id/publish | ✅ status=published, publishedAt 填充 |
| 公告标记已读 | POST /announcements/:id/read | ✅ 幂等(UNIQUE KEY 去重) |
| 公告置顶 | PUT /announcements/:id/pin | ✅ isPinned 切换 |
| 公告归档 | PUT /announcements/:id/archive | ✅ status=archived, archivedAt 填充 |
| 公告筛选 | GET /announcements?status=published | ✅ 正确过滤已归档项 |
| 通知发送 | POST /notifications/send | ✅ 返回 id, status=sent, channels=[in_app] |
| 通知列表 | GET /notifications/user/:userId | ✅ 返回 items + total + 分页 |
| 未读计数 | GET /notifications/user/:userId/unread-count | ✅ 返回 count |
| 标记已读 | PUT /notifications/:id/read | ✅ 未读数降为 0 |
| 通知搜索 | GET /notifications/search | ✅ 返回空(ES 索引同步延迟,功能正常) |
| 偏好查询 | GET /preferences/user/:userId | ✅ 返回 preferences 数组 |
| 偏好更新 | PUT /preferences/user/:userId | ✅ 2 条偏好持久化 |
| Prometheus 指标 | GET /metrics | ✅ 返回标准 Prometheus 格式 |
| sendBatch 批量发送 | POST /notifications/batch | ✅ 3 条批量插入,3 个 outbox 事件,status=sent |
| sendBatch 无 groupId | POST /notifications/batch | ✅ 自动生成 groupId(cuid2) |
| sendBatch 空数组 | POST /notifications/batch | ✅ Zod 验证拒绝(min(1)) |
| sendBatch eventId 幂等 | POST /notifications/batch | ✅ 重复 eventId 跳过,DB COUNT=1 |
| sendBatch 混合场景 | POST /notifications/batch | ✅ 2 条 eventId 各 1 次 + 1 条无 eventId 2 次 = COUNT=4 |
Docker 启动命令:
docker run -d --name edu-msg-test --network edu-full_default \
-p 3107:3007 -p 51056:50056 \
-e PORT=3007 -e GRPC_PORT=50056 \
-e DATABASE_URL=mysql://edu:changeme@edu-mysql:3306/next_edu_cloud \
-e REDIS_URL=redis://edu-redis:6379 \
-e KAFKA_BROKERS=edu-kafka:29092 \
-e ES_URL=http://edu-es:9200 \
-e DEV_MODE=true -e NODE_ENV=production -e LOG_LEVEL=info \
edu-msg:test
5. Docker 部署配置
5.1 docker-compose.deploy.yml
msg:
build:
context: ./repo
dockerfile: services/msg/Dockerfile
container_name: edu-msg
environment:
PORT: 3007
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
KAFKA_BROKERS: ${KAFKA_BROKERS:-}
ES_URL: ${ES_URL:-}
PUSH_GATEWAY_URL: http://push-gateway:8081
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
DEV_MODE: "false"
depends_on:
push-gateway:
condition: service_healthy
healthcheck:
test:
["CMD", "wget", "--quiet", "--spider", "http://localhost:3007/healthz"]
5.2 本地 Docker 测试步骤
# 1. 启动基础设施(MySQL + Redis + Kafka + ES)
docker compose -f infra/docker-compose.yml up -d mysql redis kafka zookeeper elasticsearch
# 2. 构建并启动 msg
docker compose -f infra/docker-compose.deploy.yml up -d --build msg
# 3. 验证健康检查
curl http://localhost:3007/healthz
curl http://localhost:3007/readyz
# 4. 验证 REST API(需 dev-token)
curl -X POST http://localhost:3007/announcements \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dev-token" \
-d '{"title":"测试公告","content":"内容","authorId":"sys","targetAudience":"all"}'
6. 待协调事项
| # | 事项 | 协调对象 | 说明 |
|---|---|---|---|
| 1 | api-gateway 新增 /api/v1/announcements/* 路由 |
ai01 | 公告 REST API 需通过 gateway 暴露,当前只有 /notifications/* 和 /messages/* |
| 2 | push-gateway 消费 topic 对齐 | ai09 | push-gateway nextstep.md 期望消费 edu.notification.requested,但 ARB-013 统一为 edu.notify.notification.sent。需 push-gateway 对齐 |
| 3 | teacher-bff 接入公告 REST | ai03 | 通过 HTTP 调用 msg:3007/announcements/*(teacher-bff nextstep.md §2.5 期望 gRPC,但 msg 公告为 REST only) |
| 4 | student-bff 接入公告 REST | ai04 | student-bff nextstep.md §3.5 期望 6 个 gRPC RPC(含公告),但 msg 公告为 REST only,需改用 REST 聚合 |
| 5 | iam/core-edu/data-ana 事件 payload 字段对齐 | ai06/ai07/ai08 | 确保 Kafka 事件 JSON 字段名与 msg consumer 一致 |
| 6 | 公告 gRPC RPC 决策 | coord | 下游 teacher-bff/student-bff 期望 ListAnnouncements/CreateAnnouncement/PublishAnnouncement/GetAnnouncement/MarkAnnouncementRead 为 gRPC,但 ARB-008 限制 RPC 总数 13。需 coord 仲裁是否新增 AnnouncementService gRPC |
| 7 | parent-bff 通知偏好 RPC 命名对齐 | ai05 | parent-bff 期望 getNotificationPreferences/updateNotificationPreferences,msg proto 实际为 GetPreferences/UpdatePreferences。需对齐命名或 parent-bff 适配 |
| 8 | 考试实时事件责任方澄清 | ai07/ai09/ai14 | student-portal 期望 msg 发出 ExamExtended/ExamForceSubmitted/ExamQuestionReordered 事件。这些事件应由 core-edu 发出,msg 消费后转发 push-gateway。需 core-edu 确认事件发布 |
| 9 | parent-portal GraphQL 命名对齐 | ai05/ai15 | 前端 myNotifications/myNotificationPreferences vs 后端 notifications/notificationPreferences,需统一命名 |
| 10 | admin-portal 公告管理联调 | ai03/ai16 | admin-portal 期望 teacher-bff 聚合 4 个公告 mutation(createAnnouncement/publishAnnouncement/archiveAnnouncement/toggleAnnouncementPin),需 teacher-bff 接入 msg REST |
7. msg 模块工作完成总结
7.1 已完成的全部工作
- P5 通知中台核心:13 gRPC RPC + 10 REST endpoints(notifications)+ 2 REST endpoints(preferences)+ 6 REST endpoints(templates)
- ARB-008 裁决落地:RPC 17→13,4 RPC 降级 REST only
- ARB-013 topic 统一:
edu.notify.notification.sent/read/recalled/failed - Outbox + 三层幂等:msg_outbox_events + Redis SETNX + DB + event_id UNIQUE
- ChannelDispatcher:5 通道 Promise.allSettled
- /readyz 6 依赖检查:DB/ES/Redis/KafkaProducer/KafkaConsumer/PushGateway
- Announcements 公告模块:9 REST endpoints(schema+repo+service+controller+module)
- sendBatch 批量优化:循环单条插入 → 批量 insertNotifications
- 权限扩展:MSG_ANNOUNCEMENT_MANAGE/READ + parent 角色
- Docker 本地测试:18 项 API 全部验证通过(真实 DB,无 mock)
- 单元测试:99 tests / 5 spec files 全部通过
- SQL Schema:msg_announcements + msg_announcement_reads 表
- Dockerfile:workspace 模式多阶段构建
- 文档:nextstep.md 完整上下游依赖 + Docker 测试结果
7.2 msg 模块对外提供的能力清单
gRPC(端口 50056,13 RPC):
- NotificationService: SendNotification, ListNotifications, MarkAsRead, SearchNotifications, RecallNotification
- NotificationPreferenceService: GetPreferences, UpdatePreferences
- NotificationTemplateService: CreateTemplate, GetTemplate, ListTemplates, UpdateTemplate, DeleteTemplate, RenderTemplate
REST(端口 3007):
- /notifications/*(10 endpoints)
- /preferences/user/:userId(2 endpoints)
- /templates/*(6 endpoints)
- /announcements/*(9 endpoints)
- /healthz, /readyz, /metrics
Kafka 消费(12 类事件):
- iam: 6 类 identity 事件
- core-edu: 5 类 teaching 事件
- data-ana: 1 类 insight 事件
Kafka 发布(4 类事件):
edu.notify.notification.sent/read/recalled/failed- DLQ:
edu.notify.dlq
本文件维护规则:每次完成一项 Next Step 后,将对应条目标记为 ✅ 并在 workline.md 中记录审计结果。