Java实现短信群发案例

wen java案例 1

本文目录导读:

Java实现短信群发案例

  1. 项目结构
  2. Maven依赖 (pom.xml)
  3. 配置文件 (application.yml)
  4. 配置类
  5. 数据模型
  6. 服务层
  7. 控制器
  8. 工具类
  9. 测试类
  10. 主启动类
  11. 使用示例
  12. 注意事项

我来提供一个完整的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
);

注意事项

  1. 安全配置:API密钥不要硬编码在代码中,应使用环境变量或密钥管理服务
  2. 限流控制:根据阿里云服务限制,注意发送频率和每日限制
  3. 错误处理:实现完善的错误处理和重试机制
  4. 性能优化:使用线程池进行批量异步发送
  5. 日志记录:记录完整的发送日志,便于排查问题
  6. 敏感信息加密:对敏感信息进行加密存储和传输
  7. 成本控制:合理设计短信发送策略,避免不必要的发送费用

这个案例提供了完整的短信群发解决方案,包括单发、批量发送、异步发送和高性能分批发送等功能,可根据实际需求进行调整。

抱歉,评论功能暂时关闭!