统一对内提供 HTTP API,将不同外部供应商的通知请求异步、可靠地投递到目标 HTTP(S) 端点。
本项目的功能代码由 AI 生成 系统设计上的演进路线等方面 AI 给了建议。
- 解决的问题
- 提供单一入口
POST /api/notifications,业务系统只需提交目标 URL、方法、Headers、Body。 - 接收请求后写入内部可靠存储(H2,可替换为生产级数据库/队列),异步派发。
- 提供投递结果查询
GET /api/notifications/{id}。 - 统一重试策略、失败处理(重试 + 死信跟踪)。
- 提供单一入口
- 不解决的问题
- 不保证外部 API 的业务成功,仅保证请求已送达并获得 HTTP 响应。
- 不负责事件编排/过滤,业务逻辑仍由各系统负责。
- 不提供供应商侧的 ACK/回调处理,因为需求明确“不关心返回值”,只需稳定送达。
- 不提供功能查询的图形界面
- 通知语义:至少一次(At-least-once)。写入数据库成功即视为接收,后台工作线程直到收到 2xx 才判定成功。
- 失败策略
- 使用指数退避(10s 起步,翻倍至 15 分钟上限)进行重试。
- 超过
maxAttempts(默认 5 次)进入DEAD_LETTER,供运维人工介入或脚本重放。 - RestTemplate 超时/连接异常、非 2xx 状态都视为失败,记录
lastError。 - 外部系统长期不可用 → 任务保持
FAILED并持续重试,直到成功或死信;系统不会阻塞其他任务。 — 如果考虑后期人工介入与感知等方面,可以增加检查脚本,判断尝试次数大于特定值并且未成功的记录,通过邮件等形式提醒人介入,也可以通过图形界面展示成功失败比例等方式,可以做为未来的易用性方面的内容。
AI工具在首版时即建议引入消息队列,避免通过扫描表的方式进行。个人判断在MVP阶段,直接引入消息队列比较重,需要考虑部署以及运维等情况,可以在后续功能验证之后再演进。 后续在消息队列引入后,可以通过多个分区,主题等形式,来支撑更高的流量与并发情况。
- 当前版本
- Spring Boot + JPA + H2:快速验证、内存数据库便于演示。
- 单进程调度器(@Scheduled),适合首版低流量。
- 通过数据库行锁保证同一任务只被一个工作线程处理。
- 未来演进
- 流量增大:将存储升级为 PostgreSQL/MySQL 或直接切换到消息队列(Kafka、RabbitMQ)。
- 数据存储方面可以通过分库分表等形式,支撑更多的数据存储。或者冷热数据,将超过一定时间的数据offload到外部对象存储等。
- 调度框架:使用分布式 job(Quartz、Spring Cloud Task)或托管任务系统,允许多实例并行。
- 死信处理:引入 Dead Letter Queue + 自动重放 API。
- 观测性:接入 Prometheus/Grafana,记录投递延迟、成功率、供应商 SLA。
- 中间件选择
- 首版直接用数据库,主要考虑部署简单、可快速落地。
- 可替代方案:使用 Kafka 作为持久化队列,消费端负责调用外部 API;或利用云厂商的消息通知服务(例如 AWS SNS + Lambda),减少自建运维成本。
业务系统 → REST API (NotificationController) → NotificationService
↓ ↓
请求被持久化 (NotificationRepository / H2) ← 定时工作线程 NotificationDispatcher
↓
RestTemplate 调用外部供应商
mvn spring-boot:runcurl -X POST http://localhost:8080/api/notifications \
-H 'Content-Type: application/json' \
-d '{
"targetUrl": "https://example.com/callback",
"httpMethod": "POST",
"headers": {"X-Source": "CRM"},
"body": "{\"userId\":123}",
"maxAttempts": 3
}'返回:
{
"id": "8c9f0c07-1bfc-4835-9f81-6b4dd0aa0a30",
"status": "PENDING"
}使用 GET /api/notifications/{id} 查询状态(SUCCEEDED / FAILED / DEAD_LETTER)。*** End Patch