## 新增功能 ### 1. 屏幕阅读器兼容性增强(a11y) - 无障碍工具库:src/shared/lib/a11y.ts - aria-live Hook:src/shared/hooks/use-aria-live.ts - a11y 组件:skip-link/visually-hidden/focus-trap/aria-status - 增强 UI:table.tsx 系统性 ARIA role,dialog.tsx aria-modal - 审计文档:docs/accessibility/a11y-audit.md(WCAG 2.1 AA 清单) ### 2. 视觉回归测试 - 测试套件:tests/visual/(homepage + 3 个 dashboard) - 3 视口(desktop/tablet/mobile)× 2 主题(light/dark) - 动态元素遮罩,避免误报 - playwright.config.ts 新增 visual-chromium 项目 - 文档:docs/testing/visual-regression.md ### 3. 短信/微信推送渠道集成 - 新模块:src/modules/notifications/ - 4 个渠道:SMS(阿里云/腾讯云)、WeChat(公众号)、Email(SMTP)、In-App - 分发器按用户偏好并行多渠道发送 - 外部 SDK 动态 import,Mock 模式开发可用 - 文档:docs/notifications/channels.md ### 4. 漏洞扫描 CI 集成 - CI security-scan job:npm audit + Snyk + Trivy FS + OWASP ZAP - 独立工作流 security.yml:每周一深度扫描 + 容器镜像扫描 - 配置:suppressions.json + .trivyignore - 本地脚本:security-scan.sh/ps1 - 文档:docs/security/scanning.md(SLA 分级) ### 5. 灾备方案 - 脚本:backup-verify/backup-offsite-sync/dr-drill/failover/health-check - CI 增强:备份后校验+异地同步,每周灾备演练 - 独立工作流 dr-drill.yml:每周一凌晨 4 点自动演练 - 文档:docs/dr/dr-plan.md(RTO 4h/RPO 24h)+ dr-runbook.md(6 故障场景) ## 验证 - npx tsc --noEmit:0 错误 - npm run lint:0 错误 0 警告
239 lines
7.8 KiB
Markdown
239 lines
7.8 KiB
Markdown
# 通知渠道集成文档
|
||
|
||
本模块(`src/modules/notifications`)为系统提供多渠道通知发送能力,支持站内消息、短信、微信公众号模板消息和邮件四种渠道。
|
||
|
||
## 架构概览
|
||
|
||
```
|
||
调用方 (Server Action / 其他模块)
|
||
│
|
||
▼
|
||
dispatcher.ts ── 读取用户通知偏好 (notification_preferences)
|
||
│ ── 读取用户联系方式 (users.phone / users.email)
|
||
│
|
||
├── in_app (站内消息,总是启用)
|
||
├── sms (短信,smsEnabled && phone)
|
||
├── wechat (微信模板消息,pushEnabled && openId)
|
||
└── email (邮件,emailEnabled && email)
|
||
│
|
||
▼
|
||
data-access.ts ── logNotificationSend (console 日志)
|
||
```
|
||
|
||
## 模块结构
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `types.ts` | 通知渠道类型定义(NotificationPayload, ChannelSendResult 等) |
|
||
| `channels/types.ts` | 渠道发送者接口(NotificationChannelSender, ChannelRecipient) |
|
||
| `channels/sms-channel.ts` | 短信渠道(阿里云/腾讯云/Mock) |
|
||
| `channels/wechat-channel.ts` | 微信公众号模板消息渠道 |
|
||
| `channels/email-channel.ts` | 邮件渠道(Nodemailer SMTP) |
|
||
| `channels/in-app-channel.ts` | 站内消息渠道(复用 messaging data-access) |
|
||
| `dispatcher.ts` | 通知分发器(按偏好选择渠道、并行发送) |
|
||
| `data-access.ts` | 通知数据访问(偏好查询、联系方式查询、日志记录) |
|
||
| `actions.ts` | Server Actions(sendNotificationAction, sendClassNotificationAction) |
|
||
| `index.ts` | 模块统一导出 |
|
||
|
||
## 渠道配置
|
||
|
||
### 1. 站内消息(in_app)
|
||
|
||
- **默认启用**,无需配置。
|
||
- 复用现有 `messaging` 模块的 `createNotification`,写入 `message_notifications` 表。
|
||
- 用户可在站内通知中心查看。
|
||
|
||
### 2. 短信(SMS)
|
||
|
||
支持三种 Provider,通过 `SMS_PROVIDER` 环境变量选择:
|
||
|
||
| Provider | 说明 | 环境变量 |
|
||
|----------|------|----------|
|
||
| `mock`(默认) | 开发环境模拟,仅记录日志 | 无需其他配置 |
|
||
| `aliyun` | 阿里云短信 | `SMS_ACCESS_KEY_ID`, `SMS_ACCESS_KEY_SECRET`, `SMS_SIGN_NAME`, `SMS_TEMPLATE_CODE` |
|
||
| `tencent` | 腾讯云短信 | 同上(复用相同变量名) |
|
||
|
||
**模板变量替换**:将 `payload.title` / `payload.content` 填入模板变量 `title` / `content`。
|
||
|
||
### 3. 微信公众号模板消息(wechat)
|
||
|
||
通过微信公众号 API 发送模板消息:
|
||
|
||
1. **获取 access_token**:`GET https://api.weixin.qq.com/cgi-bin/token`(带缓存,提前 5 分钟刷新)
|
||
2. **发送模板消息**:`POST https://api.weixin.qq.com/cgi-bin/message/template/send`
|
||
|
||
**环境变量**:
|
||
|
||
| 变量 | 说明 |
|
||
|------|------|
|
||
| `WECHAT_APP_ID` | 公众号 AppID |
|
||
| `WECHAT_APP_SECRET` | 公众号 AppSecret |
|
||
| `WECHAT_TEMPLATE_ID` | 模板消息 ID |
|
||
|
||
**模板数据映射**:
|
||
|
||
- `keyword1` ← `payload.title`
|
||
- `keyword2` ← `payload.content`
|
||
- `keyword3` ← `payload.type`
|
||
|
||
可通过 `payload.metadata.wechatKeywords` 自定义覆盖。
|
||
|
||
> **注意**:当前 `users` 表无 `wechat_open_id` 字段,微信渠道暂不会实际触发。扩展 schema 后在 `data-access.ts` 的 `getUserContactInfo` 中补充查询即可。
|
||
|
||
### 4. 邮件(email)
|
||
|
||
使用 Nodemailer 通过 SMTP 发送,支持 HTML 邮件模板(根据通知类型显示不同颜色)。
|
||
|
||
**环境变量**:
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `EMAIL_HOST` | - | SMTP 主机(配置后启用真实发送) |
|
||
| `EMAIL_PORT` | `587` | SMTP 端口(465 使用 SSL) |
|
||
| `EMAIL_USER` | - | SMTP 用户名 |
|
||
| `EMAIL_PASS` | - | SMTP 密码 |
|
||
| `EMAIL_FROM` | `noreply@example.com` | 发件人地址 |
|
||
|
||
## Mock 模式(开发环境)
|
||
|
||
所有渠道均提供 Mock 实现,**无需任何外部服务即可运行**:
|
||
|
||
- SMS: `SMS_PROVIDER=mock`(默认)→ 仅 `console.info` 记录
|
||
- WeChat: 未配置 `WECHAT_APP_ID` 等 → 自动使用 Mock
|
||
- Email: 未配置 `EMAIL_HOST` → 自动使用 Mock
|
||
- 站内消息: 始终真实写入数据库(无 Mock)
|
||
|
||
## 生产环境配置
|
||
|
||
### 阿里云短信示例
|
||
|
||
```env
|
||
SMS_PROVIDER=aliyun
|
||
SMS_ACCESS_KEY_ID=LTAI5tXXXXXXXXXXXX
|
||
SMS_ACCESS_KEY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXX
|
||
SMS_SIGN_NAME=智慧教务
|
||
SMS_TEMPLATE_CODE=SMS_123456789
|
||
```
|
||
|
||
### 微信公众号示例
|
||
|
||
```env
|
||
WECHAT_APP_ID=wx1234567890abcdef
|
||
WECHAT_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
WECHAT_TEMPLATE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
### 邮件 SMTP 示例
|
||
|
||
```env
|
||
EMAIL_HOST=smtp.example.com
|
||
EMAIL_PORT=587
|
||
EMAIL_USER=notification@example.com
|
||
EMAIL_PASS=xxxxxxxx
|
||
EMAIL_FROM=智慧教务 <notification@example.com>
|
||
```
|
||
|
||
## 使用方式
|
||
|
||
### 1. 通过 Server Action 调用
|
||
|
||
```ts
|
||
import { sendNotificationAction, sendClassNotificationAction } from "@/modules/notifications/actions"
|
||
|
||
// 发送给单个用户
|
||
await sendNotificationAction({
|
||
userId: "user-xxx",
|
||
title: "作业提醒",
|
||
content: "您有一份新作业待提交",
|
||
type: "info",
|
||
actionUrl: "/homework/123",
|
||
})
|
||
|
||
// 发送给班级所有学生(教师权限)
|
||
await sendClassNotificationAction("class-xxx", {
|
||
title: "考试通知",
|
||
content: "明天下午 2 点期中考试",
|
||
type: "warning",
|
||
actionUrl: "/exams/456",
|
||
})
|
||
```
|
||
|
||
### 2. 直接调用分发器(服务端)
|
||
|
||
```ts
|
||
import { sendNotification } from "@/modules/notifications"
|
||
|
||
await sendNotification({
|
||
userId: "user-xxx",
|
||
title: "成绩发布",
|
||
content: "您的数学成绩为 95 分",
|
||
type: "success",
|
||
})
|
||
```
|
||
|
||
## 渠道选择逻辑
|
||
|
||
分发器根据用户通知偏好(`notification_preferences` 表)和联系方式决定启用渠道:
|
||
|
||
| 渠道 | 启用条件 |
|
||
|------|----------|
|
||
| in_app | `pushEnabled`(默认 true),总是兜底 |
|
||
| sms | `smsEnabled` && 用户有手机号 |
|
||
| email | `emailEnabled` && 用户有邮箱 |
|
||
| wechat | `pushEnabled` && 用户有 wechatOpenId |
|
||
|
||
> 通知偏好中的 `homeworkNotifications` / `gradeNotifications` 等按通知类别控制,由调用方在构造 payload 前决定是否调用发送。
|
||
|
||
## 扩展新渠道
|
||
|
||
1. 在 `channels/` 下创建新文件,实现 `NotificationChannelSender` 接口:
|
||
|
||
```ts
|
||
import "server-only"
|
||
import type { NotificationChannelSender, ChannelRecipient } from "./types"
|
||
import type { NotificationPayload, ChannelSendResult, NotificationChannel } from "../types"
|
||
|
||
const channel: NotificationChannel = "your_channel" as NotificationChannel
|
||
|
||
class YourChannelSender implements NotificationChannelSender {
|
||
readonly channel = channel
|
||
async send(payload: NotificationPayload, recipient: ChannelRecipient): Promise<ChannelSendResult> {
|
||
// 实现发送逻辑
|
||
}
|
||
async sendBatch(items) { /* ... */ }
|
||
}
|
||
|
||
export function createYourSender(): NotificationChannelSender {
|
||
return new YourChannelSender()
|
||
}
|
||
```
|
||
|
||
2. 在 `types.ts` 的 `NotificationChannel` 类型中添加新渠道:
|
||
|
||
```ts
|
||
export type NotificationChannel = "in_app" | "email" | "sms" | "wechat" | "your_channel"
|
||
```
|
||
|
||
3. 在 `dispatcher.ts` 的 `SenderRegistry` 和 `selectChannels` 中注册新渠道。
|
||
|
||
4. 在 `index.ts` 中导出新的发送器工厂。
|
||
|
||
## 权限说明
|
||
|
||
- `sendNotificationAction`: 需要 `MESSAGE_SEND` 权限
|
||
- `sendClassNotificationAction`: 需要 `MESSAGE_SEND` 权限,且教师只能给自己所教班级发送
|
||
|
||
> 项目无独立 `NOTIFICATION_SEND` 权限点,复用 `MESSAGE_SEND`(教师/管理员/年级主任均拥有)。
|
||
|
||
## 外部 SDK 依赖
|
||
|
||
所有外部 SDK 均使用**动态 import**,避免增加构建体积:
|
||
|
||
| 渠道 | SDK | 安装命令 |
|
||
|------|-----|----------|
|
||
| 阿里云短信 | `@alicloud/dysmsapi20170525`, `@alicloud/openapi-client`, `@alicloud/credentials` | `npm i @alicloud/dysmsapi20170525 @alicloud/openapi-client @alicloud/credentials` |
|
||
| 腾讯云短信 | `tencentcloud-sdk-nodejs` | `npm i tencentcloud-sdk-nodejs` |
|
||
| 邮件 | `nodemailer` | `npm i nodemailer @types/nodemailer` |
|
||
|
||
> **Mock 模式无需安装任何 SDK**,开发环境开箱即用。生产环境按需安装对应 SDK。
|