This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

240
plans/Plan-b5e97267.plan.md Normal file
View File

@@ -0,0 +1,240 @@
<!-- b5e97267-b1d5-4ef9-9217-ebd91392307c -->
---
todos:
- id: "ddl-mail-tables"
content: "在 slot-notification 新增 mail_configSMTP 直字段)/ mail_template / mail_send_log 表 DDL"
status: pending
- id: "mail-module"
content: "实现 app/mail 模块Model、Entity、Cache、MailService、SmtpMail SDK"
status: pending
- id: "notification-api"
content: "新增 EmailController + MailConfigAdminController 与 route 注册"
status: pending
- id: "sdk-client"
content: "扩展 slot-lib / slot-sdk 的 MailService 客户端方法"
status: pending
- id: "console-migrate"
content: "slot-console CommonController 改调 notification废弃本地 MailService"
status: pending
- id: "admin-ui"
content: "slot-admin 后端 + slot-admin-vue 邮件配置管理页"
status: pending
- id: "data-migration"
content: "编写 mail_config 从 slot-console 配置组到表的幂等迁移脚本"
status: pending
- id: "verify"
content: "跑 verify-slot-backend.sh验证发信/验码/后台 CRUD 链路"
status: pending
isProject: false
---
# slot-notification 邮件配置表化(对齐短信)
## 现状与结论
当前两套通知渠道配置方式不一致:
| 能力 | 短信(已收口) | SMTP 邮件(未收口) |
|---|---|---|
| 配置存储 | [`slot-notification/app/sms/model/SmsConfigModel.php`](slot-notification/app/sms/model/SmsConfigModel.php) → `sms_config` 表 | [`slot-console/app/service/lib/MailService.php`](slot-console/app/service/lib/MailService.php) → `ConfigService::getGroupData('mail_config')` |
| 发送服务 | [`slot-notification/app/sms/services/SmsService.php`](slot-notification/app/sms/services/SmsService.php) | `slot-console` 本地 `MailService` + PHPMailer |
| 验证码 API | `/innerapi/sms/sendVerifyCode` | `slot-console` `CommonController::sendEmailCode` |
| 后台管理 | slot-admin → slot-lib → notification `/innerapi/admin/*` | slot-admin 通用配置页 `email_config`(另一套) |
**结论:应该建表。** 你已选择「完整对齐短信」,建议把 SMTP 邮件作为 slot-notification 的第二条外发渠道,与短信保持同一套「配置表 + 发送日志 + innerapi + 后台管理 + SDK 客户端」模式。
注意slot-notification 里现有 [`MailController`](slot-notification/app/innerapi/controller/MailController.php) / [`EmailService`](slot-notification/app/message/service/EmailService.php) 实际是**站内消息**兼容别名,不是 SMTP。新 SMTP 能力应使用独立命名空间(建议 `app/mail/*`)和独立路由前缀(建议 `/innerapi/email/*`),避免与 `/innerapi/mail/*`(站内消息)冲突。
```mermaid
flowchart LR
subgraph before [当前]
lobby1[Lobby] --> console1[slot-console CommonController]
console1 --> config1["ConfigService mail_config"]
console1 --> mail1[MailService PHPMailer]
end
subgraph after [目标]
lobby2[Lobby] --> console2[slot-console 薄代理]
console2 --> sdk[slot-lib MailService]
sdk --> notif[slot-notification EmailController]
notif --> table["mail_config 表"]
notif --> smtp[SmtpMail PHPMailer]
notif --> log["mail_send_log 表"]
end
```
---
## 表结构设计(邮件用直字段,短信保留 JSON
`s_message`(与 `sms_config` 同库)新增两张表。
### 为什么邮件不用 JSON、短信用 JSON
| 对比 | 短信 `sms_config.config` | 邮件 `mail_config` |
|---|---|---|
| 字段形态 | 各平台差异大Buka 要 appId/senderIdAnt/Chuanglan 字段不同) | SMTP 标准字段固定,截图与代码一致 |
| 后台表单 | 按 `platform` 动态展示不同表单项 | 固定 6~8 个输入框,与现有配置页一一对应 |
| 查询/校验 | 结构不统一,适合 JSON | 可直接列级校验、索引、迁移映射 |
**结论:邮件配置用直接字段更好;不必为了「和短信表长得像」而强行 JSON。**
### 当前邮件配置项(来自截图,仅 SMTP 连接)
截图中的 6 项即为 `mail_config` 配置组现有字段,与 [`MailService`](slot-console/app/service/lib/MailService.php) 读取项一致。**这就是邮件配置表应存的全部内容**
| 配置项 | 列名建议 | 说明 |
|---|---|---|
| smtp host | `smtp_host` | 如 `email-smtp.us-east-1.amazonaws.com` |
| 用户名 | `username` | SMTP 登录账号 |
| 密码 | `password` | SMTP 密码/应用专用密码 |
| 端口 | `port` | 如 `587` |
| 发件人地址 | `from_address` | 原 `from` |
| 发件人名称 | `from_name` | 如 `TOGOO` |
加密方式(`tls`/`ssl`**不单独落库**:发送时按 `port` 推断即可(`587` → STARTTLS`465` → SSL与现网行为一致。
### 邮件模板(独立概念,不属于 mail_config
验证码邮件的标题/正文来自 `email_template` 配置组(`title``binding_email`),属于**发送模板**,不是 SMTP 连接配置。
发送邮件时需要两张表配合:
```mermaid
flowchart LR
sendMail[MailService 发信] --> mailConfig["mail_config\nSMTP 连接"]
sendMail --> mailTemplate["mail_template\n标题/正文模板"]
```
### `mail_config`(仅 SMTP 连接,直字段)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | bigint PK | 主键 |
| `platform` | varchar(32) | 渠道标识,如 `AwsSes``Zoho` |
| `status` | tinyint | 1 启用 / 2 禁用 |
| `num` | int | 排序,降序优先 |
| `smtp_host` | varchar(255) | SMTP 主机 |
| `username` | varchar(128) | SMTP 用户名 |
| `password` | varchar(512) | SMTP 密码 |
| `port` | int | SMTP 端口 |
| `from_address` | varchar(255) | 发件人邮箱 |
| `from_name` | varchar(128) | 发件人显示名 |
| `create_time` / `update_time` | datetime | 审计字段 |
### `mail_template`(邮件发送模板,独立表)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | bigint PK | 主键 |
| `template_code` | varchar(64) | 模板编码,如 `bindingEmail``authEmail` |
| `template_name` | varchar(128) | 模板名称 |
| `title` | varchar(255) | 邮件标题模板 |
| `body` | text | 邮件正文模板,支持 `{code}` 等变量 |
| `status` | tinyint | 1 启用 / 2 禁用 |
| `create_time` / `update_time` | datetime | 审计字段 |
首期迁移:把 `email_template` 配置组中的 `title` + `binding_email` 迁入 `mail_template``template_code=authEmail` 或按业务拆分)。
**注意**`message_template` 是站内消息模板,与 SMTP 外发邮件模板是不同业务,不混用。
### `mail_send_log`(发送日志,放 `s_common`
镜像 [`sms_send_log`](slot-notification/app/sms/model/SmsSendLogModel.php)
- `unique_sn`, `email`, `platform`, `subject`, `content`, `status`, `type`, `retry_num`, `create_time`
---
## 后端实现slot-notification
### 1. 数据层
新建 `app/mail/` 模块,参照 `app/sms/`
- `MailConfigModel` / `MailSendLogModel`
- `MailConfigEntity`(后台 CRUD复用短信 Entity 分页模式)
- 在 [`initDB.php`](slot-notification/app/command/initDB.php) 补充 DDL另提供 `db/mail_config.sql` 便于手工执行
### 2. 发送层
- `composer.json` 增加 `phpmailer/phpmailer`
- `MailService`:从 `mail_config` 取 SMTP 连接,从 `mail_template` 取标题/正文 → PHPMailer 发信 → 写 `mail_send_log`
- `app/mail/services/sdk/BaseMail.php` + `SmtpMail.php`(首期只实现 SMTP结构与 `BaseSms` 一致,便于后续扩展 SendGrid 等)
- `MailCache`:验证码 Redis 缓存(镜像 `SmsCache`key 规则与 slot-console [`EmailCodeLibService`](slot-console/app/service/lib/EmailCodeLibService.php) 保持一致,避免迁移期验证码失效
### 3. API 层
新增路由([`config/route.php`](slot-notification/config/route.php)
- `POST /innerapi/email/send-verify-code`
- `POST /innerapi/email/verify-code`
- `POST /innerapi/admin/mail-config/index|save|update|destroy`
- `POST /innerapi/admin/mail-template/index|save|update|destroy`(模板 CRUD与配置分开
新建 `EmailController`(发送/校验)与 `MailConfigAdminController`(配置 CRUD**不要**复用现有站内消息 `MailController`
### 4. 渠道选择策略
首期与短信一致:按 `status=启用` + `num desc` 取第一条;预留后续 failover注释掉的短信轮询逻辑可一并参考
---
## 跨服务改造
### slot-lib / slot-sdk
在 [`slot-lib/src/services/notification/`](slot-lib/src/services/notification/) 新增 `MailService`(或 `EmailService`
- `sendVerifyCode(email, type)`
- `verifyCode(email, code, type)`
- `getMailConfigPageList` / `saveMailConfig` / `updateMailConfig` / `destroyMailConfig`
同步扩展 [`slot-sdk/src/service/notification/NotificationService.php`](slot-sdk/src/service/notification/NotificationService.php)。
### slot-console调用方改造
[`CommonController::sendEmailCode`](slot-console/app/api/controller/CommonController.php) / `verifyEmailCode`
- 改为调用 slot-lib `MailService`,不再直接依赖本地 `MailService` + `ConfigService`
- 本地 [`MailService`](slot-console/app/service/lib/MailService.php) 标记废弃或删除(确认无其他引用后)
限流、参数校验仍留在 slot-console Controller 层(与现有短信 napi 模式一致)。
### slot-admin + slot-admin-vue
镜像短信后台:
- 后端:[`slot-admin/app/game/controller/SmsController.php`](slot-admin/app/game/controller/SmsController.php) → 新建 `MailController`
- 前端:新建 `mail/config/` 页,仅 6 个 SMTP 字段 + platform/status/num另建 `mail/template/` 页管理标题/正文模板
- 菜单/权限:新增 `mailConfig` 路由与按钮权限
---
## 数据迁移
新增一次性迁移脚本(建议 `slot-notification/app/command/MigrateMailConfig.php`
1.`s_config` 读取 `mail_config` 配置组 → 写入 `mail_config`6 个 SMTP 字段)
2.`s_config` 读取 `email_template` 配置组 → 写入 `mail_template` 表(`title` + `binding_email`
3. 幂等插入(已存在则跳过)
迁移完成后,`slot-console``mail_config` / `email_template` 配置组可保留只读一段时间,确认无回退需求后再清理。
---
## 风险与边界
- **命名冲突**`/innerapi/mail/*` 继续保留给站内消息SMTP 统一走 `/innerapi/email/*`
- **验证码兼容**`MailCache` 的 Redis key 必须与 slot-console 现有 `ShareRedisKeyManagerService::emailCode()` 一致
- **模板范围**:本期只迁移验证码邮件模板(`binding_email`/`title`);充值/提现等 MQ 站内邮件模板(`message_template` 配置组)不在本次范围
- **多 SMTP failover**:表结构支持,首期实现「取第一条启用配置」即可
---
## 验收标准
1. slot-admin 可 CRUD `mail_config`,与短信配置页体验一致
2. Lobby 发/验邮箱验证码链路走 slot-notification功能与迁移前一致
3. 发送成功/失败均写入 `mail_send_log`
4. slot-console 不再读取 `ConfigService::getGroupData('mail_config')`
5. 执行 `verify-slot-backend.sh` 通过