Files
NextEdu/docs/notifications/channels.md

245 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 通知渠道集成文档
本模块(`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_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"
// 注意:先在 types.ts 的 NotificationChannel 联合类型中添加 "your_channel"
const channel: NotificationChannel = "your_channel"
class YourChannelSender implements NotificationChannelSender {
readonly channel = channel
async send(payload: NotificationPayload, recipient: ChannelRecipient): Promise<ChannelSendResult> {
// 实现发送逻辑
}
async sendBatch(
items: Array<{ payload: NotificationPayload; recipient: ChannelRecipient }>
): Promise<ChannelSendResult[]> {
// 实现批量发送逻辑
return []
}
}
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。