本文目录导读:

我来提供一个完整的Java短信群发案例,使用阿里云短信服务作为示例。
项目结构
sms-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/sms/
│ │ │ ├── config/
│ │ │ │ └── SmsConfig.java
│ │ │ ├── controller/
│ │ │ │ └── SmsController.java
│ │ │ ├── service/
│ │ │ │ ├── SmsService.java
│ │ │ │ ├── SmsServiceImpl.java
│ │ │ │ └── BatchSmsService.java
│ │ │ ├── model/
│ │ │ │ ├── SmsRequest.java
│ │ │ │ └── SmsResponse.java
│ │ │ ├── utils/
│ │ │ │ └── SmsTemplate.java
│ │ │ └── SmsApplication.java
│ │ └── resources/
│ │ └── application.yml
│ └── test/
│ └── java/
│ └── com/example/sms/
│ └── SmsServiceTest.java
Maven依赖 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<groupId>com.example</groupId>
<artifactId>sms-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 阿里云短信SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.18</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 数据校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 (application.yml)
server:
port: 8080
spring:
application:
name: sms-demo
# 阿里云短信配置
aliyun:
sms:
# 访问密钥ID(配置为环境变量或使用密钥管理服务)
access-key-id: ${ALIYUN_ACCESS_KEY_ID}
access-key-secret: ${ALIYUN_ACCESS_KEY_SECRET}
# 短信签名
sign-name: "你的短信签名"
# 模板代码
verify-code-template: "SMS_123456789"
notice-template: "SMS_987654321"
# 短信服务Endpoint
endpoint: "dysmsapi.aliyuncs.com"
配置类
// SmsConfig.java
package com.example.sms.config;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.teaopenapi.models.Config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun.sms")
public class SmsConfig {
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String verifyCodeTemplate;
private String noticeTemplate;
private String endpoint;
@Bean
public Client smsClient() throws Exception {
Config config = new Config()
.setAccessKeyId(accessKeyId)
.setAccessKeySecret(accessKeySecret);
config.endpoint = endpoint;
return new Client(config);
}
}
数据模型
// SmsRequest.java
package com.example.sms.model;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.util.List;
import java.util.Map;
@Data
public class SmsRequest {
// 短信签名
private String signName;
// 模板Code
private String templateCode;
// 手机号列表(群发)
@NotEmpty(message = "手机号列表不能为空")
private List<@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String> phoneNumbers;
// 模板参数
private Map<String, String> templateParam;
// 扩展字段
private String extendCode;
// 外部流水扩展字段
private String outId;
}
// SmsResponse.java
package com.example.sms.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SmsResponse {
private boolean success;
private String message;
private String requestId;
private String bizId;
private String code;
public static SmsResponse success(String requestId, String bizId) {
return new SmsResponse(true, "短信发送成功", requestId, bizId, "OK");
}
public static SmsResponse failure(String code, String message) {
return new SmsResponse(false, message, null, null, code);
}
}
// BatchSmsResult.java
package com.example.sms.model;
import lombok.Data;
import java.util.List;
@Data
public class BatchSmsResult {
private int total;
private int successCount;
private int failCount;
private List<String> failedPhones;
private List<String> successPhones;
}
服务层
// SmsService.java
package com.example.sms.service;
import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;
public interface SmsService {
// 发送单个短信
SmsResponse sendSingleSms(String phoneNumber, String templateCode, java.util.Map<String, String> params);
// 发送验证码
SmsResponse sendVerifyCode(String phoneNumber, String code, String businessType);
// 发送通知短信
SmsResponse sendNoticeSms(String phoneNumber, String content);
// 批量发送短信(同步)
com.example.sms.model.BatchSmsResult sendBatchSms(SmsRequest request);
// 批量发送短信(异步)
void sendBatchSmsAsync(SmsRequest request, java.util.concurrent.ExecutorService executor);
}
// SmsServiceImpl.java
package com.example.sms.service;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.example.sms.config.SmsConfig;
import com.example.sms.model.BatchSmsResult;
import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@Service
public class SmsServiceImpl implements SmsService {
@Autowired
private Client smsClient;
@Autowired
private SmsConfig smsConfig;
// 使用线程池进行异步批量发送
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Override
public SmsResponse sendSingleSms(String phoneNumber, String templateCode,
Map<String, String> params) {
try {
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName(smsConfig.getSignName())
.setTemplateCode(templateCode);
// 设置模板参数(如果有)
if (params != null && !params.isEmpty()) {
request.setTemplateParam(JSONObject.toJSONString(params));
}
// 调用阿里云API
SendSmsResponse response = smsClient.sendSms(request);
SendSmsResponseBody body = response.getBody();
if ("OK".equals(body.getCode())) {
log.info("短信发送成功,手机号:{},BizId:{}", phoneNumber, body.getBizId());
return SmsResponse.success(body.getRequestId(), body.getBizId());
} else {
log.error("短信发送失败,手机号:{},code:{}, message:{}",
phoneNumber, body.getCode(), body.getMessage());
return SmsResponse.failure(body.getCode(), body.getMessage());
}
} catch (Exception e) {
log.error("短信发送异常,手机号:{}", phoneNumber, e);
return SmsResponse.failure("SYSTEM_ERROR", "系统异常,发送失败");
}
}
@Override
public SmsResponse sendVerifyCode(String phoneNumber, String code, String businessType) {
Map<String, String> params = new HashMap<>();
params.put("code", code);
// 使用验证码模板
return sendSingleSms(phoneNumber, smsConfig.getVerifyCodeTemplate(), params);
}
@Override
public SmsResponse sendNoticeSms(String phoneNumber, String content) {
Map<String, String> params = new HashMap<>();
params.put("content", content);
// 使用通知模板
return sendSingleSms(phoneNumber, smsConfig.getNoticeTemplate(), params);
}
@Override
public BatchSmsResult sendBatchSms(SmsRequest request) {
BatchSmsResult result = new BatchSmsResult();
result.setTotal(request.getPhoneNumbers().size());
List<String> successPhones = new ArrayList<>();
List<String> failedPhones = new ArrayList<>();
// 同步批量发送
for (String phoneNumber : request.getPhoneNumbers()) {
SmsResponse response = sendSingleSms(
phoneNumber,
request.getTemplateCode(),
request.getTemplateParam()
);
if (response.isSuccess()) {
successPhones.add(phoneNumber);
} else {
failedPhones.add(phoneNumber);
}
}
result.setSuccessCount(successPhones.size());
result.setFailCount(failedPhones.size());
result.setSuccessPhones(successPhones);
result.setFailedPhones(failedPhones);
return result;
}
@Override
public void sendBatchSmsAsync(SmsRequest request, ExecutorService executor) {
// 异步批量发送
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (String phoneNumber : request.getPhoneNumbers()) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
SmsResponse response = sendSingleSms(
phoneNumber,
request.getTemplateCode(),
request.getTemplateParam()
);
if (response.isSuccess()) {
log.info("异步短信发送成功:{}", phoneNumber);
} else {
log.warn("异步短信发送失败:{},错误:{}",
phoneNumber, response.getMessage());
}
} catch (Exception e) {
log.error("异步短信发送异常:{}", phoneNumber, e);
}
}, executor != null ? executor : executorService);
futures.add(future);
}
// 等待所有短信发送完成(可选)
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((v, throwable) -> {
log.info("所有短信异步发送任务执行完成");
});
}
}
// BatchSmsService.java
package com.example.sms.service;
import com.example.sms.model.SmsRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@Service
public class BatchSmsService {
@Autowired
private SmsService smsService;
// 配置线程池
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
/**
* 高性能批量发送(分批次)
*/
public void sendHighPerformanceBatch(SmsRequest request, int batchSize) {
List<String> allPhones = request.getPhoneNumbers();
int totalBatches = (int) Math.ceil((double) allPhones.size() / batchSize);
log.info("开始批量发送短信,总数:{},分{}批", allPhones.size(), totalBatches);
List<CompletableFuture<Void>> batchFutures = new ArrayList<>();
for (int i = 0; i < totalBatches; i++) {
int start = i * batchSize;
int end = Math.min(start + batchSize, allPhones.size());
List<String> batchPhones = allPhones.subList(start, end);
// 每批异步处理
CompletableFuture<Void> batchFuture = CompletableFuture.runAsync(() -> {
SmsRequest batchRequest = new SmsRequest();
batchRequest.setPhoneNumbers(batchPhones);
batchRequest.setTemplateCode(request.getTemplateCode());
batchRequest.setTemplateParam(request.getTemplateParam());
// 同步发送该批次
sendBatchSmsSync(batchRequest);
}, executorService);
batchFutures.add(batchFuture);
}
// 等待所有批次完成
CompletableFuture.allOf(batchFutures.toArray(new CompletableFuture[0]))
.join();
log.info("所有批次短信发送完成");
}
/**
* 同步发送一批短信
*/
private void sendBatchSmsSync(SmsRequest request) {
for (String phoneNumber : request.getPhoneNumbers()) {
try {
var response = smsService.sendSingleSms(
phoneNumber,
request.getTemplateCode(),
request.getTemplateParam()
);
if (response.isSuccess()) {
log.debug("短信发送成功:{}", phoneNumber);
} else {
log.warn("短信发送失败:{},原因:{}", phoneNumber, response.getMessage());
}
} catch (Exception e) {
log.error("短信发送异常:{}", phoneNumber, e);
}
}
}
}
控制器
// SmsController.java
package com.example.sms.controller;
import com.example.sms.model.BatchSmsResult;
import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;
import com.example.sms.service.BatchSmsService;
import com.example.sms.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/sms")
public class SmsController {
@Autowired
private SmsService smsService;
@Autowired
private BatchSmsService batchSmsService;
/**
* 发送验证码
*/
@PostMapping("/code")
public SmsResponse sendVerifyCode(@RequestParam String phoneNumber,
@RequestParam String code,
@RequestParam(required = false) String businessType) {
log.info("发送验证码请求,手机号:{}", phoneNumber);
return smsService.sendVerifyCode(phoneNumber, code, businessType);
}
/**
* 发送单条短信
*/
@PostMapping("/single")
public SmsResponse sendSingle(@RequestParam String phoneNumber,
@RequestParam String content) {
log.info("发送单条短信请求,手机号:{}", phoneNumber);
return smsService.sendNoticeSms(phoneNumber, content);
}
/**
* 批量发送(同步)
*/
@PostMapping("/batch")
public BatchSmsResult sendBatch(@Valid @RequestBody SmsRequest request) {
log.info("批量发送短信请求,手机号数量:{}", request.getPhoneNumbers().size());
return smsService.sendBatchSms(request);
}
/**
* 批量发送(异步)
*/
@PostMapping("/batch-async")
public Map<String, Object> sendBatchAsync(@Valid @RequestBody SmsRequest request) {
log.info("异步批量发送短信请求,手机号数量:{}", request.getPhoneNumbers().size());
smsService.sendBatchSmsAsync(request, null);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "短信已加入发送队列");
result.put("phoneCount", request.getPhoneNumbers().size());
return result;
}
/**
* 高性能分批发送
*/
@PostMapping("/high-performance-batch")
public Map<String, Object> sendHighPerformanceBatch(@Valid @RequestBody SmsRequest request,
@RequestParam(defaultValue = "100") int batchSize) {
log.info("高性能批量发送请求,手机号数量:{},批次大小:{}",
request.getPhoneNumbers().size(), batchSize);
batchSmsService.sendHighPerformanceBatch(request, batchSize);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "批量发送任务执行完成");
result.put("phoneCount", request.getPhoneNumbers().size());
return result;
}
}
工具类
// SmsValidUtil.java
package com.example.sms.utils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class SmsValidUtil {
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
/**
* 校验手机号格式
*/
public static boolean isValidPhone(String phone) {
return PHONE_PATTERN.matcher(phone).matches();
}
/**
* 过滤无效手机号
*/
public static List<String> filterValidPhones(List<String> phones) {
return phones.stream()
.filter(SmsValidUtil::isValidPhone)
.collect(java.util.stream.Collectors.toList());
}
/**
* 去除重复手机号
*/
public static List<String> removeDuplicates(List<String> phones) {
Set<String> uniquePhones = new HashSet<>(phones);
return new ArrayList<>(uniquePhones);
}
/**
* 合并手机号列表(去重)
*/
public static List<String> mergePhones(List<String>... phoneLists) {
Set<String> mergedPhones = new HashSet<>();
for (List<String> phones : phoneLists) {
mergedPhones.addAll(phones);
}
return new ArrayList<>(mergedPhones);
}
}
测试类
// SmsServiceTest.java
package com.example.sms;
import com.example.sms.model.SmsRequest;
import com.example.sms.model.SmsResponse;
import com.example.sms.service.BatchSmsService;
import com.example.sms.service.SmsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
class SmsServiceTest {
@Autowired
private SmsService smsService;
@Autowired
private BatchSmsService batchSmsService;
@Test
void contextLoads() {
}
@Test
void testSendSingleSms() {
String phoneNumber = "13800138000";
String code = "123456";
SmsResponse response = smsService.sendVerifyCode(phoneNumber, code, "TEST");
System.out.println("发送结果:" + response);
}
@Test
void testSendBatchSms() {
List<String> phones = new ArrayList<>();
phones.add("13800138000");
phones.add("13900139000");
phones.add("13700137000");
Map<String, String> params = new HashMap<>();
params.put("content", "这是一条测试通知");
SmsRequest request = new SmsRequest();
request.setPhoneNumbers(phones);
request.setTemplateCode("SMS_987654321");
request.setTemplateParam(params);
var result = smsService.sendBatchSms(request);
System.out.println("批量发送结果:" + result);
}
@Test
void testAsyncBatchSms() {
List<String> phones = new ArrayList<>();
for (int i = 0; i < 10; i++) {
phones.add("1380013" + String.format("%04d", i));
}
Map<String, String> params = new HashMap<>();
params.put("content", "这是一条异步测试通知");
SmsRequest request = new SmsRequest();
request.setPhoneNumbers(phones);
request.setTemplateCode("SMS_987654321");
request.setTemplateParam(params);
smsService.sendBatchSmsAsync(request, null);
System.out.println("已提交异步发送任务");
}
@Test
void testHighPerformanceBatch() {
List<String> phones = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
phones.add("138" + String.format("%08d", i));
}
Map<String, String> params = new HashMap<>();
params.put("content", "这是一条高性能测试通知");
SmsRequest request = new SmsRequest();
request.setPhoneNumbers(phones);
request.setTemplateCode("SMS_987654321");
request.setTemplateParam(params);
long startTime = System.currentTimeMillis();
batchSmsService.sendHighPerformanceBatch(request, 100);
long endTime = System.currentTimeMillis();
System.out.println("高性能批量发送耗时:" + (endTime - startTime) + "ms");
}
}
主启动类
// SmsApplication.java
package com.example.sms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class SmsApplication {
public static void main(String[] args) {
SpringApplication.run(SmsApplication.class, args);
System.out.println("短信服务启动成功!");
}
}
使用示例
// 调用示例
// 1. 发送验证码
Map<String, String> result = restTemplate.postForObject(
"http://localhost:8080/api/sms/code?phoneNumber=13800138000&code=123456",
null,
Map.class
);
// 2. 批量发送
SmsRequest request = new SmsRequest();
request.setPhoneNumbers(Arrays.asList("13800138000", "13900139000"));
request.setTemplateCode("SMS_987654321");
Map<String, String> params = new HashMap<>();
params.put("content", "Hello, 这是一条测试短信");
request.setTemplateParam(params);
Map<String, Object> batchResult = restTemplate.postForObject(
"http://localhost:8080/api/sms/batch",
request,
Map.class
);
注意事项
- 安全配置:API密钥不要硬编码在代码中,应使用环境变量或密钥管理服务
- 限流控制:根据阿里云服务限制,注意发送频率和每日限制
- 错误处理:实现完善的错误处理和重试机制
- 性能优化:使用线程池进行批量异步发送
- 日志记录:记录完整的发送日志,便于排查问题
- 敏感信息加密:对敏感信息进行加密存储和传输
- 成本控制:合理设计短信发送策略,避免不必要的发送费用
这个案例提供了完整的短信群发解决方案,包括单发、批量发送、异步发送和高性能分批发送等功能,可根据实际需求进行调整。