Files
NextEdu/docs/notifications/channels.md
SpecialX 6585e10c6f feat(P2): 实现质量保障类5项功能(无障碍/视觉回归/通知渠道/漏洞扫描/灾备)
## 新增功能

### 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 警告
2026-06-17 20:18:29 +08:00

7.8 KiB
Raw Blame History

通知渠道集成文档

本模块(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 ActionssendNotificationAction, 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_tokenGET 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

模板数据映射

  • keyword1payload.title
  • keyword2payload.content
  • keyword3payload.type

可通过 payload.metadata.wechatKeywords 自定义覆盖。

注意:当前 users 表无 wechat_open_id 字段,微信渠道暂不会实际触发。扩展 schema 后在 data-access.tsgetUserContactInfo 中补充查询即可。

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

生产环境配置

阿里云短信示例

SMS_PROVIDER=aliyun
SMS_ACCESS_KEY_ID=LTAI5tXXXXXXXXXXXX
SMS_ACCESS_KEY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXX
SMS_SIGN_NAME=智慧教务
SMS_TEMPLATE_CODE=SMS_123456789

微信公众号示例

WECHAT_APP_ID=wx1234567890abcdef
WECHAT_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
WECHAT_TEMPLATE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

邮件 SMTP 示例

EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=notification@example.com
EMAIL_PASS=xxxxxxxx
EMAIL_FROM=智慧教务 <notification@example.com>

使用方式

1. 通过 Server Action 调用

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. 直接调用分发器(服务端)

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 接口:
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()
}
  1. types.tsNotificationChannel 类型中添加新渠道:
export type NotificationChannel = "in_app" | "email" | "sms" | "wechat" | "your_channel"
  1. dispatcher.tsSenderRegistryselectChannels 中注册新渠道。

  2. 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。