设计一个消息通知系统(短信/邮件/Push)

1. 概述

1.1 系统背景

消息通知系统是现代应用的核心基础设施,需要支持:

  • 多种通知渠道:短信、邮件、Push推送
  • 高并发发送:支持百万级消息发送
  • 高可用性:7×24小时稳定运行
  • 消息可靠性:消息不丢失、不重复
  • 灵活配置:支持模板、规则配置

1.2 核心挑战

技术挑战

  • 多渠道支持:统一接口,多种实现
  • 高并发处理:百万级消息发送
  • 消息可靠性:保证消息送达
  • 成本控制:短信、邮件成本优化
  • 模板管理:模板配置和管理

1.3 本文内容结构

本文将从以下几个方面全面解析消息通知系统:

  1. 需求分析:功能需求、非功能需求
  2. 架构设计:整体架构、模块设计
  3. 短信系统:短信发送、模板管理
  4. 邮件系统:邮件发送、附件处理
  5. Push推送:iOS、Android推送
  6. 高可用设计:容错、降级、监控
  7. 性能优化:异步处理、批量发送
  8. 实战案例:完整实现方案

2. 需求分析

2.1 功能需求

2.1.1 核心功能

通知发送

  • 短信发送
  • 邮件发送
  • Push推送
  • 多渠道发送

模板管理

  • 模板创建
  • 模板编辑
  • 模板审核
  • 模板使用

消息管理

  • 消息查询
  • 消息统计
  • 发送记录
  • 失败重试

2.1.2 扩展功能

规则配置

  • 发送规则
  • 限流规则
  • 降级规则
  • 黑白名单

统计分析

  • 发送统计
  • 成功率统计
  • 成本统计
  • 渠道对比

2.2 非功能需求

2.2.1 性能需求

响应时间

  • 发送接口:< 100ms(异步)
  • 查询接口:< 200ms

吞吐量

  • 峰值QPS:10万+
  • 平均QPS:5万+

并发用户

  • 同时在线用户:100万+
  • 峰值并发:50万+

2.2.2 可用性需求

可用性指标

  • 系统可用性:99.99%
  • 消息送达率:> 99.9%
  • 故障恢复时间:< 5分钟

容错能力

  • 单点故障不影响服务
  • 自动故障转移
  • 服务降级

2.2.3 可靠性需求

消息可靠性

  • 消息不丢失
  • 消息不重复
  • 消息有序(可选)

重试机制

  • 失败自动重试
  • 重试策略配置
  • 最大重试次数

3. 架构设计

3.1 整体架构

3.1.1 架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
业务系统

通知API(统一接口)

消息队列(Kafka)

通知服务集群

├──→ 短信服务(短信发送)
├──→ 邮件服务(邮件发送)
└──→ Push服务(Push推送)

├──→ 短信服务商(阿里云、腾讯云)
├──→ 邮件服务商(SMTP、SendGrid)
└──→ Push服务商(APNs、FCM)

数据库(MySQL + Redis)

3.1.2 架构说明

接入层

  • 通知API:统一接口,支持多种通知类型
  • 消息队列:异步处理,削峰填谷

服务层

  • 通知服务:消息路由、模板渲染
  • 短信服务:短信发送、状态回调
  • 邮件服务:邮件发送、附件处理
  • Push服务:iOS、Android推送

数据层

  • MySQL:消息记录、模板管理
  • Redis:缓存、限流、去重

3.2 模块设计

3.2.1 通知API

职责

  • 接收通知请求
  • 参数校验
  • 消息入队
  • 快速响应

技术选型

  • Spring Boot
  • Kafka(消息队列)
  • Redis(限流、去重)

3.2.2 通知服务

职责

  • 消息消费
  • 模板渲染
  • 渠道路由
  • 发送调度

技术选型

  • Spring Boot
  • Kafka Consumer
  • 模板引擎(FreeMarker)

3.2.3 短信服务

职责

  • 短信发送
  • 状态回调
  • 模板管理
  • 签名管理

技术选型

  • Spring Boot
  • 阿里云短信SDK
  • 腾讯云短信SDK

3.2.4 邮件服务

职责

  • 邮件发送
  • 附件处理
  • 模板管理
  • 发送统计

技术选型

  • Spring Boot
  • JavaMail
  • SendGrid API

3.2.5 Push服务

职责

  • iOS推送(APNs)
  • Android推送(FCM)
  • 推送统计
  • 设备管理

技术选型

  • Spring Boot
  • APNs SDK
  • FCM SDK

4. 短信系统

4.1 短信发送

4.1.1 短信服务商

主流服务商

  • 阿里云短信:稳定可靠,价格适中
  • 腾讯云短信:功能丰富,价格优惠
  • 华为云短信:性能优秀,价格合理

选型建议

  • 主服务商:阿里云
  • 备用服务商:腾讯云
  • 多服务商切换,保障可用性

4.1.2 短信发送实现

阿里云短信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
public class SmsService {

@Autowired
private IAcsClient acsClient;

public void sendSms(String phone, String templateCode, Map<String, String> params) {
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(phone);
request.setSignName("公司名称");
request.setTemplateCode(templateCode);
request.setTemplateParam(JSON.toJSONString(params));

try {
SendSmsResponse response = acsClient.getAcsResponse(request);
if ("OK".equals(response.getCode())) {
// 发送成功
log.info("短信发送成功: {}", response.getRequestId());
} else {
// 发送失败
log.error("短信发送失败: {}", response.getMessage());
throw new BusinessException("短信发送失败");
}
} catch (Exception e) {
log.error("短信发送异常", e);
throw new BusinessException("短信发送异常");
}
}
}

腾讯云短信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class TencentSmsService {

@Autowired
private SmsClient smsClient;

public void sendSms(String phone, String templateId, String[] params) {
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumberSet(new String[]{phone});
request.setTemplateID(templateId);
request.setTemplateParamSet(params);
request.setSmsSdkAppid("appId");
request.setSign("签名");

try {
SendSmsResponse response = smsClient.SendSms(request);
if (response.getSendStatusSet()[0].getCode().equals("Ok")) {
log.info("短信发送成功");
} else {
log.error("短信发送失败: {}", response.getSendStatusSet()[0].getMessage());
throw new BusinessException("短信发送失败");
}
} catch (Exception e) {
log.error("短信发送异常", e);
throw new BusinessException("短信发送异常");
}
}
}

4.1.3 多服务商切换

服务商管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class SmsProviderManager {

@Autowired
private SmsService aliyunSmsService;

@Autowired
private TencentSmsService tencentSmsService;

public void sendSms(String phone, String templateCode, Map<String, String> params) {
// 优先使用主服务商
try {
aliyunSmsService.sendSms(phone, templateCode, params);
} catch (Exception e) {
log.warn("主服务商发送失败,切换到备用服务商", e);
// 切换到备用服务商
tencentSmsService.sendSms(phone, templateCode, params);
}
}
}

4.2 模板管理

4.2.1 模板配置

模板数据结构

1
2
3
4
5
6
7
8
9
public class SmsTemplate {
private String templateCode;
private String templateName;
private String content;
private String provider; // 服务商
private String status; // 状态:审核中、已通过、已拒绝
private Date createTime;
private Date updateTime;
}

模板管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class SmsTemplateService {

@Autowired
private SmsTemplateMapper templateMapper;

public void createTemplate(SmsTemplate template) {
// 1. 保存模板
templateMapper.insert(template);

// 2. 提交审核(异步)
templateAuditService.submitAudit(template);
}

public SmsTemplate getTemplate(String templateCode) {
return templateMapper.selectByCode(templateCode);
}
}

4.3 状态回调

4.3.1 回调处理

回调接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/api/sms/callback")
public class SmsCallbackController {

@PostMapping("/aliyun")
public void aliyunCallback(@RequestBody String body) {
// 解析回调数据
Map<String, String> data = parseCallback(body);

// 更新发送状态
smsRecordService.updateStatus(
data.get("bizId"),
data.get("status"),
data.get("errCode")
);
}

@PostMapping("/tencent")
public void tencentCallback(@RequestBody String body) {
// 解析回调数据
Map<String, String> data = parseCallback(body);

// 更新发送状态
smsRecordService.updateStatus(
data.get("serialNo"),
data.get("status"),
data.get("errMsg")
);
}
}

5. 邮件系统

5.1 邮件发送

5.1.1 SMTP发送

JavaMail实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Service
public class EmailService {

@Value("${mail.smtp.host}")
private String smtpHost;

@Value("${mail.smtp.port}")
private int smtpPort;

@Value("${mail.username}")
private String username;

@Value("${mail.password}")
private String password;

public void sendEmail(String to, String subject, String content) {
Properties props = new Properties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.port", smtpPort);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");

Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});

try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
message.setContent(content, "text/html;charset=UTF-8");

Transport.send(message);
log.info("邮件发送成功: {}", to);
} catch (Exception e) {
log.error("邮件发送失败", e);
throw new BusinessException("邮件发送失败");
}
}
}

5.1.2 SendGrid发送

SendGrid API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class SendGridEmailService {

@Autowired
private SendGrid sendGrid;

public void sendEmail(String to, String subject, String content) {
Email from = new Email("noreply@example.com");
Email toEmail = new Email(to);
Content emailContent = new Content("text/html", content);

Mail mail = new Mail(from, subject, toEmail, emailContent);

Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());

Response response = sendGrid.api(request);
if (response.getStatusCode() == 202) {
log.info("邮件发送成功: {}", to);
} else {
log.error("邮件发送失败: {}", response.getBody());
throw new BusinessException("邮件发送失败");
}
} catch (Exception e) {
log.error("邮件发送异常", e);
throw new BusinessException("邮件发送异常");
}
}
}

5.2 附件处理

5.2.1 附件发送

附件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void sendEmailWithAttachment(String to, String subject, String content, 
List<Attachment> attachments) {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);

// 创建多部分消息
Multipart multipart = new MimeMultipart();

// 正文
MimeBodyPart textPart = new MimeBodyPart();
textPart.setContent(content, "text/html;charset=UTF-8");
multipart.addBodyPart(textPart);

// 附件
for (Attachment attachment : attachments) {
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(attachment.getFile());
attachmentPart.setFileName(attachment.getFileName());
multipart.addBodyPart(attachmentPart);
}

message.setContent(multipart);
Transport.send(message);
}

5.3 模板管理

5.3.1 邮件模板

模板引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class EmailTemplateService {

@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;

public String renderTemplate(String templateName, Map<String, Object> data) {
try {
Template template = freeMarkerConfigurer.getConfiguration()
.getTemplate(templateName + ".ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, data);
} catch (Exception e) {
log.error("模板渲染失败", e);
throw new BusinessException("模板渲染失败");
}
}
}

模板示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- order-confirm.ftl -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>订单确认</title>
</head>
<body>
<h1>订单确认</h1>
<p>尊敬的${userName},您的订单已确认:</p>
<ul>
<li>订单号:${orderId}</li>
<li>订单金额:${amount}</li>
<li>订单时间:${orderTime}</li>
</ul>
</body>
</html>

6. Push推送

6.1 iOS推送(APNs)

6.1.1 APNs配置

证书配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class ApnsConfig {

@Bean
public ApnsClient apnsClient() throws Exception {
// 加载证书
InputStream certStream = new FileInputStream("apns-cert.p12");
String password = "cert-password";

// 创建APNs客户端
return new ApnsClientBuilder()
.setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
.setClientCredentials(certStream, password)
.build();
}
}

6.1.2 iOS推送实现

推送实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Service
public class IosPushService {

@Autowired
private ApnsClient apnsClient;

public void sendPush(String deviceToken, String title, String body, Map<String, Object> data) {
// 构建推送消息
ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
payloadBuilder.setAlertTitle(title);
payloadBuilder.setAlertBody(body);
payloadBuilder.setSound("default");
payloadBuilder.setBadge(1);

// 自定义数据
if (data != null) {
payloadBuilder.addCustomProperty("data", data);
}

String payload = payloadBuilder.buildWithDefaultMaximumLength();

// 发送推送
PushNotificationResponse response = apnsClient.sendNotification(
PushNotification.newBuilder()
.setToken(deviceToken)
.setPayload(payload)
.build()
);

if (response.isAccepted()) {
log.info("iOS推送成功: {}", deviceToken);
} else {
log.error("iOS推送失败: {}", response.getRejectionReason());
throw new BusinessException("iOS推送失败");
}
}
}

6.2 Android推送(FCM)

6.2.1 FCM配置

FCM配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class FcmConfig {

@Value("${fcm.server-key}")
private String serverKey;

@Bean
public FirebaseMessaging firebaseMessaging() {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(
new FileInputStream("firebase-service-account.json")))
.build();

FirebaseApp.initializeApp(options);
return FirebaseMessaging.getInstance();
}
}

6.2.2 Android推送实现

推送实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
public class AndroidPushService {

@Autowired
private FirebaseMessaging firebaseMessaging;

public void sendPush(String deviceToken, String title, String body, Map<String, String> data) {
// 构建推送消息
Message message = Message.builder()
.setToken(deviceToken)
.setNotification(Notification.builder()
.setTitle(title)
.setBody(body)
.build())
.putAllData(data)
.setAndroidConfig(AndroidConfig.builder()
.setPriority(AndroidConfig.Priority.HIGH)
.build())
.build();

try {
String response = firebaseMessaging.send(message);
log.info("Android推送成功: {}", response);
} catch (Exception e) {
log.error("Android推送失败", e);
throw new BusinessException("Android推送失败");
}
}
}

6.3 统一推送接口

6.3.1 推送服务

统一接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class PushService {

@Autowired
private IosPushService iosPushService;

@Autowired
private AndroidPushService androidPushService;

public void sendPush(PushRequest request) {
String platform = request.getPlatform();
String deviceToken = request.getDeviceToken();
String title = request.getTitle();
String body = request.getBody();
Map<String, Object> data = request.getData();

if ("ios".equals(platform)) {
iosPushService.sendPush(deviceToken, title, body, data);
} else if ("android".equals(platform)) {
androidPushService.sendPush(deviceToken, title, body, data);
} else {
throw new BusinessException("不支持的平台: " + platform);
}
}
}

7. 高可用设计

7.1 异步处理

7.1.1 消息队列

Kafka消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
public class NotificationService {

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

public void sendNotification(NotificationRequest request) {
// 快速响应
String notificationId = generateNotificationId();

// 发送到消息队列
kafkaTemplate.send("notification", notificationId, JSON.toJSONString(request));

return notificationId;
}
}

@Component
public class NotificationConsumer {

@KafkaListener(topics = "notification", groupId = "notification-processor")
public void processNotification(String notificationId, String message) {
NotificationRequest request = JSON.parseObject(message, NotificationRequest.class);

try {
// 根据类型路由到不同服务
switch (request.getType()) {
case "sms":
smsService.sendSms(request);
break;
case "email":
emailService.sendEmail(request);
break;
case "push":
pushService.sendPush(request);
break;
}

// 更新状态
notificationRecordService.updateStatus(notificationId, "SUCCESS");
} catch (Exception e) {
log.error("通知发送失败", e);
notificationRecordService.updateStatus(notificationId, "FAILED");
// 重试机制
retryService.retry(notificationId, request);
}
}
}

7.2 重试机制

7.2.1 重试策略

重试实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class RetryService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

public void retry(String notificationId, NotificationRequest request) {
String retryKey = "retry:notification:" + notificationId;
String retryCount = redisTemplate.opsForValue().get(retryKey);

int count = retryCount == null ? 0 : Integer.parseInt(retryCount);

if (count < 3) { // 最多重试3次
// 延迟重试
scheduleRetry(notificationId, request, count + 1);
redisTemplate.opsForValue().set(retryKey, String.valueOf(count + 1), 1, TimeUnit.HOURS);
} else {
// 超过重试次数,记录失败
notificationRecordService.updateStatus(notificationId, "FAILED");
log.error("通知发送失败,已超过最大重试次数: {}", notificationId);
}
}

@Scheduled(fixedDelay = 5000) // 每5秒检查一次
public void processRetry() {
// 处理重试队列
Set<String> retryKeys = redisTemplate.keys("retry:notification:*");
for (String key : retryKeys) {
// 处理重试
}
}
}

7.3 降级策略

7.3.1 服务降级

降级实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class NotificationService {

@HystrixCommand(fallbackMethod = "sendNotificationFallback")
public void sendNotification(NotificationRequest request) {
doSendNotification(request);
}

public void sendNotificationFallback(NotificationRequest request) {
// 降级处理:记录到数据库,后续异步处理
notificationRecordService.saveFailedNotification(request);
log.warn("通知发送降级,已记录到数据库: {}", request);
}
}

8. 性能优化

8.1 批量发送

8.1.1 批量处理

批量发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Service
public class BatchNotificationService {

@Scheduled(fixedDelay = 1000) // 每秒处理一次
public void processBatch() {
// 批量获取消息
List<NotificationRequest> requests = getBatchNotifications(100);

if (!requests.isEmpty()) {
// 批量发送
batchSend(requests);
}
}

private void batchSend(List<NotificationRequest> requests) {
// 按类型分组
Map<String, List<NotificationRequest>> grouped = requests.stream()
.collect(Collectors.groupingBy(NotificationRequest::getType));

// 批量发送短信
if (grouped.containsKey("sms")) {
smsService.batchSend(grouped.get("sms"));
}

// 批量发送邮件
if (grouped.containsKey("email")) {
emailService.batchSend(grouped.get("email"));
}

// 批量发送Push
if (grouped.containsKey("push")) {
pushService.batchSend(grouped.get("push"));
}
}
}

8.2 限流控制

8.2.1 限流策略

限流实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class NotificationService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

public boolean checkRateLimit(String userId, String type) {
String key = "rate:limit:" + type + ":" + userId;
Long count = redisTemplate.opsForValue().increment(key);

if (count == 1) {
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}

// 短信:每分钟最多5条
if ("sms".equals(type)) {
return count <= 5;
}
// 邮件:每分钟最多10条
else if ("email".equals(type)) {
return count <= 10;
}
// Push:每分钟最多20条
else if ("push".equals(type)) {
return count <= 20;
}

return true;
}
}

8.3 缓存优化

8.3.1 模板缓存

模板缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class TemplateService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

public String getTemplate(String templateCode) {
// 查询缓存
String cacheKey = "template:" + templateCode;
String template = redisTemplate.opsForValue().get(cacheKey);

if (template != null) {
return template;
}

// 查询数据库
template = templateMapper.selectByCode(templateCode).getContent();

// 写入缓存
redisTemplate.opsForValue().set(cacheKey, template, 1, TimeUnit.HOURS);

return template;
}
}

9. 实战案例

9.1 统一通知接口

9.1.1 API设计

通知接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/api/notification")
public class NotificationController {

@Autowired
private NotificationService notificationService;

@PostMapping("/send")
public Response<String> sendNotification(@RequestBody NotificationRequest request) {
try {
// 参数校验
validateRequest(request);

// 限流检查
if (!notificationService.checkRateLimit(request.getUserId(), request.getType())) {
return Response.error("请求过于频繁,请稍后再试");
}

// 发送通知
String notificationId = notificationService.sendNotification(request);

return Response.success(notificationId);
} catch (BusinessException e) {
return Response.error(e.getMessage());
} catch (Exception e) {
log.error("发送通知失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

9.1.2 通知请求

请求模型

1
2
3
4
5
6
7
8
9
10
public class NotificationRequest {
private String type; // 类型:sms、email、push
private String userId; // 用户ID
private String templateCode; // 模板代码
private Map<String, Object> params; // 模板参数
private String phone; // 手机号(短信)
private String email; // 邮箱(邮件)
private String deviceToken; // 设备Token(Push)
private String platform; // 平台:ios、android(Push)
}

9.2 通知服务实现

9.2.1 服务实现

通知服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class NotificationService {

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

@Autowired
private TemplateService templateService;

public String sendNotification(NotificationRequest request) {
// 1. 生成通知ID
String notificationId = generateNotificationId();

// 2. 渲染模板
String template = templateService.getTemplate(request.getTemplateCode());
String content = renderTemplate(template, request.getParams());
request.setContent(content);

// 3. 发送到消息队列
kafkaTemplate.send("notification", notificationId, JSON.toJSONString(request));

// 4. 记录通知
notificationRecordService.saveNotification(notificationId, request);

return notificationId;
}

private String renderTemplate(String template, Map<String, Object> params) {
// 使用FreeMarker渲染模板
return templateService.renderTemplate(template, params);
}
}

10. 总结

10.1 核心要点

  1. 架构设计:统一接口、异步处理、多渠道支持
  2. 短信系统:多服务商、模板管理、状态回调
  3. 邮件系统:SMTP、SendGrid、附件处理
  4. Push推送:iOS、Android、统一接口
  5. 高可用设计:异步处理、重试机制、降级策略
  6. 性能优化:批量发送、限流控制、缓存优化

10.2 关键设计

  1. 统一接口:统一的通知接口,支持多种类型
  2. 异步处理:消息队列异步处理,提高性能
  3. 多渠道支持:短信、邮件、Push统一管理
  4. 模板管理:模板配置和渲染
  5. 重试机制:失败自动重试,保障可靠性

10.3 最佳实践

  1. 异步处理:消息队列削峰填谷
  2. 批量发送:批量处理提高效率
  3. 限流控制:防止滥用,控制成本
  4. 模板缓存:缓存模板,提高性能
  5. 监控告警:实时监控,及时告警

相关文章