Files
NextEdu/docs/notifications/channels.md

8.0 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"

// 注意:先在 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()
}
  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。