| @ -0,0 +1,239 @@ | |||
| # 短信验证码登录功能使用指南 | |||
| ## 功能概述 | |||
| 在 `AppletLoginServiceImpl` 中实现了完整的短信验证码登录功能,包括: | |||
| - 发送验证码 (`phoneSendCode`) | |||
| - 验证码登录 (`phoneLogin`) | |||
| ## 实现特性 | |||
| ### ✅ 已实现功能 | |||
| 1. **手机号格式验证** | |||
| - 验证11位数字格式 | |||
| - 以1开头的中国大陆手机号 | |||
| 2. **验证码生成与发送** | |||
| - 生成6位随机数字验证码 | |||
| - 通过短信服务发送到用户手机 | |||
| - 验证码5分钟内有效 | |||
| 3. **防刷机制** | |||
| - 同一手机号60秒内只能发送一次验证码 | |||
| - 避免短信轰炸 | |||
| 4. **验证码验证** | |||
| - 登录时验证验证码的正确性 | |||
| - 验证码使用后自动失效 | |||
| - 过期验证码自动清理 | |||
| 5. **安全机制** | |||
| - Redis存储验证码,自动过期 | |||
| - 验证成功后立即删除验证码 | |||
| - 完整的错误处理和日志记录 | |||
| ## API接口 | |||
| ### 1. 发送验证码 | |||
| **接口**: `POST /api/xxx/phoneSendCode` | |||
| **参数**: | |||
| - `phone`: 手机号码 | |||
| **响应示例**: | |||
| ```json | |||
| { | |||
| "success": true, | |||
| "message": "验证码发送成功", | |||
| "code": 200 | |||
| } | |||
| ``` | |||
| ### 2. 验证码登录 | |||
| **接口**: `POST /api/xxx/phoneLogin` | |||
| **参数**: | |||
| - `phone`: 手机号码 | |||
| - `code`: 验证码 | |||
| **响应示例**: | |||
| ```json | |||
| { | |||
| "success": true, | |||
| "message": "登录成功", | |||
| "code": 200, | |||
| "result": { | |||
| "userInfo": { | |||
| "id": "用户ID", | |||
| "phone": "手机号", | |||
| "nickName": "昵称" | |||
| }, | |||
| "token": "JWT令牌" | |||
| } | |||
| } | |||
| ``` | |||
| ## 使用流程 | |||
| ### 前端调用流程 | |||
| 1. **发送验证码** | |||
| ```javascript | |||
| // 发送验证码 | |||
| function sendSmsCode(phone) { | |||
| fetch('/api/xxx/phoneSendCode', { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/x-www-form-urlencoded', | |||
| }, | |||
| body: `phone=${phone}` | |||
| }) | |||
| .then(response => response.json()) | |||
| .then(data => { | |||
| if (data.success) { | |||
| alert('验证码发送成功'); | |||
| // 开始倒计时 | |||
| startCountdown(); | |||
| } else { | |||
| alert(data.message); | |||
| } | |||
| }); | |||
| } | |||
| ``` | |||
| 2. **验证码登录** | |||
| ```javascript | |||
| // 验证码登录 | |||
| function loginWithSms(phone, code) { | |||
| fetch('/api/xxx/phoneLogin', { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/x-www-form-urlencoded', | |||
| }, | |||
| body: `phone=${phone}&code=${code}` | |||
| }) | |||
| .then(response => response.json()) | |||
| .then(data => { | |||
| if (data.success) { | |||
| // 保存用户信息和token | |||
| localStorage.setItem('token', data.result.token); | |||
| localStorage.setItem('userInfo', JSON.stringify(data.result.userInfo)); | |||
| // 跳转到主页 | |||
| window.location.href = '/main.html'; | |||
| } else { | |||
| alert(data.message); | |||
| } | |||
| }); | |||
| } | |||
| ``` | |||
| ## 配置说明 | |||
| ### 短信服务配置 | |||
| 确保在 `application-dev.yml` 中配置了短信服务: | |||
| ```yaml | |||
| ##配置短信服务 | |||
| sms: | |||
| userName: csxinyuan | |||
| clientPassword: HwmY9q4F3fhi | |||
| interfacePassword: pGUKjsiVkaNF | |||
| apiUrl: http://agent.izjun.com:8001/sms/api/sendMessageOne | |||
| ``` | |||
| ### Redis配置 | |||
| 验证码存储依赖Redis,确保Redis服务正常运行。 | |||
| ## 关键常量配置 | |||
| ```java | |||
| // 验证码Redis前缀 | |||
| private static final String SMS_CODE_PREFIX = "SMS_CODE:"; | |||
| // 验证码过期时间(秒) | |||
| private static final int SMS_CODE_EXPIRE_TIME = 300; // 5分钟 | |||
| // 验证码长度 | |||
| private static final int SMS_CODE_LENGTH = 6; | |||
| ``` | |||
| ## 错误处理 | |||
| ### 常见错误码 | |||
| | 错误信息 | 说明 | 解决方案 | | |||
| |---------|------|----------| | |||
| | "手机号格式不正确" | 手机号不符合11位数字格式 | 检查手机号输入 | | |||
| | "发送过于频繁,请稍后再试" | 60秒内重复发送 | 等待60秒后重试 | | |||
| | "验证码已过期,请重新获取" | 验证码超过5分钟 | 重新获取验证码 | | |||
| | "验证码错误" | 输入的验证码不正确 | 检查验证码输入 | | |||
| | "电话号码不存在" | 数据库中没有该手机号用户 | 用户需要先注册 | | |||
| ## 日志监控 | |||
| ### 关键日志 | |||
| ```java | |||
| log.info("验证码发送成功,手机号:{},验证码:{}", phone, verifyCode); | |||
| log.error("验证码发送失败,手机号:{},错误信息:{}", phone, smsResponse.getMessage()); | |||
| log.error("发送验证码异常,手机号:{}", phone, e); | |||
| ``` | |||
| ### 监控指标 | |||
| - 验证码发送成功率 | |||
| - 验证码验证成功率 | |||
| - 短信服务响应时间 | |||
| - 用户登录成功率 | |||
| ## 扩展建议 | |||
| ### 1. 图形验证码 | |||
| 可以添加图形验证码,防止机器人刷验证码: | |||
| ```java | |||
| // 发送前验证图形验证码 | |||
| if (!validateCaptcha(captcha)) { | |||
| return Result.error("图形验证码错误"); | |||
| } | |||
| ``` | |||
| ### 2. IP限制 | |||
| 可以添加IP维度的限制: | |||
| ```java | |||
| // IP限制 | |||
| String ipLimitKey = "SMS_IP_LIMIT:" + clientIp; | |||
| if (redisUtil.get(ipLimitKey) > 10) { | |||
| return Result.error("该IP发送次数过多,请稍后再试"); | |||
| } | |||
| ``` | |||
| ### 3. 短信模板化 | |||
| 可以使用短信模板,支持多种场景: | |||
| ```java | |||
| public enum SmsTemplate { | |||
| LOGIN("您的登录验证码是:%s,%d分钟内有效。"), | |||
| REGISTER("您的注册验证码是:%s,%d分钟内有效。"), | |||
| RESET_PASSWORD("您的密码重置验证码是:%s,%d分钟内有效。"); | |||
| } | |||
| ``` | |||
| ## 注意事项 | |||
| 1. **生产环境安全** | |||
| - 不要在日志中输出真实验证码 | |||
| - 考虑添加更严格的频率限制 | |||
| - 监控异常发送行为 | |||
| 2. **用户体验** | |||
| - 提供清晰的错误提示 | |||
| - 倒计时显示发送间隔 | |||
| - 支持语音验证码备选方案 | |||
| 3. **成本控制** | |||
| - 监控短信发送量 | |||
| - 设置日发送上限 | |||
| - 异常情况下的熔断机制 | |||
| @ -0,0 +1,115 @@ | |||
| # Common 通用工具模块 | |||
| ## 目录结构 | |||
| 本目录用于存放项目中的通用工具和服务组件,按功能模块进行分类组织。 | |||
| ``` | |||
| org.jeecg.common/ | |||
| ├── sms/ # 短信服务模块 | |||
| │ ├── config/ # 配置类 | |||
| │ │ └── SmsConfig.java | |||
| │ ├── entity/ # 实体类/数据对象 | |||
| │ │ ├── SmsMessage.java | |||
| │ │ ├── SmsRequest.java | |||
| │ │ ├── SmsResponse.java | |||
| │ │ └── SmsResponseData.java | |||
| │ ├── service/ # 服务实现类 | |||
| │ │ └── SmsService.java | |||
| │ └── controller/ # REST控制器 | |||
| │ └── SmsController.java | |||
| └── README.md # 说明文档 | |||
| ``` | |||
| ## 现有模块 | |||
| ### 📱 SMS 短信服务模块 | |||
| - **路径**: `org.jeecg.common.sms` | |||
| - **功能**: 提供短信发送服务,支持单发、批量发送、定时发送 | |||
| - **配置**: 在 `application-dev.yml` 中配置短信服务参数 | |||
| - **接口**: `/common/sms/**` | |||
| #### 主要类说明: | |||
| - `SmsConfig`: 短信配置类,读取yml配置 | |||
| - `SmsService`: 短信服务实现,核心业务逻辑 | |||
| - `SmsController`: REST接口控制器 | |||
| - `SmsMessage`: 短信消息实体 | |||
| - `SmsRequest/Response`: 请求响应实体 | |||
| ## 扩展新模块 | |||
| 当需要添加新的通用工具或服务时,请遵循以下规范: | |||
| ### 1. 目录结构规范 | |||
| ``` | |||
| org.jeecg.common.{模块名}/ | |||
| ├── config/ # 配置类(如需要) | |||
| ├── entity/ # 实体类/数据对象 | |||
| ├── service/ # 服务实现类 | |||
| ├── controller/ # REST控制器(如需要) | |||
| ├── util/ # 工具类(如需要) | |||
| └── constants/ # 常量定义(如需要) | |||
| ``` | |||
| ### 2. 命名规范 | |||
| - **包名**: 使用小写字母,多个单词用点分隔 | |||
| - **类名**: 使用驼峰命名,见名知意 | |||
| - **配置类**: 以 `Config` 结尾 | |||
| - **服务类**: 以 `Service` 结尾 | |||
| - **控制器**: 以 `Controller` 结尾 | |||
| - **实体类**: 使用具体业务名称 | |||
| ### 3. 示例:添加邮件服务模块 | |||
| ``` | |||
| org.jeecg.common.email/ | |||
| ├── config/ | |||
| │ └── EmailConfig.java | |||
| ├── entity/ | |||
| │ ├── EmailMessage.java | |||
| │ ├── EmailRequest.java | |||
| │ └── EmailResponse.java | |||
| ├── service/ | |||
| │ └── EmailService.java | |||
| └── controller/ | |||
| └── EmailController.java | |||
| ``` | |||
| ### 4. 配置文件 | |||
| - 在 `application-dev.yml` 中添加相应配置 | |||
| - 使用统一的配置前缀,如:`email:`、`file:`等 | |||
| ### 5. API接口 | |||
| - 统一使用 `/common/{模块名}/**` 作为接口路径 | |||
| - 添加 Swagger 文档注解 | |||
| ## 注意事项 | |||
| 1. **依赖管理**: 确保添加的依赖不与现有项目冲突 | |||
| 2. **异常处理**: 统一使用项目的异常处理机制 | |||
| 3. **日志记录**: 使用 SLF4J 进行日志记录 | |||
| 4. **测试**: 为新模块编写相应的单元测试 | |||
| 5. **文档**: 更新本README文档,说明新模块的用途和使用方法 | |||
| ## 使用示例 | |||
| ### 短信服务使用示例 | |||
| ```java | |||
| @Autowired | |||
| private SmsService smsService; | |||
| // 发送单条短信 | |||
| SmsResponse response = smsService.sendSms("13800138000", "验证码:123456"); | |||
| // 批量发送 | |||
| List<SmsMessage> messages = Arrays.asList( | |||
| new SmsMessage("13800138000", "消息1"), | |||
| new SmsMessage("13800138001", "消息2") | |||
| ); | |||
| SmsResponse response = smsService.sendSmsBatch(messages); | |||
| ``` | |||
| ## 联系方式 | |||
| 如有问题或建议,请联系开发团队。 | |||
| @ -0,0 +1,213 @@ | |||
| # Java 8 兼容性修改说明 | |||
| ## 问题描述 | |||
| 原始的短信服务类使用了 Java 11 的 `java.net.http` 包,导致在 Java 8 环境下编译失败: | |||
| ``` | |||
| java: 程序包java.net.http不存在 | |||
| ``` | |||
| ## 解决方案 | |||
| ### 1. 替换 HTTP 客户端 | |||
| **原始代码(Java 11)**: | |||
| ```java | |||
| import java.net.http.HttpClient; | |||
| import java.net.http.HttpRequest; | |||
| import java.net.http.HttpResponse; | |||
| private final HttpClient httpClient = HttpClient.newHttpClient(); | |||
| private String sendHttpRequest(SmsRequest request) throws IOException, InterruptedException { | |||
| String jsonBody = objectMapper.writeValueAsString(request); | |||
| HttpRequest httpRequest = HttpRequest.newBuilder() | |||
| .uri(URI.create(smsConfig.getApiUrl())) | |||
| .header("Accept", "application/json") | |||
| .header("Content-Type", "application/json;charset=utf-8") | |||
| .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) | |||
| .build(); | |||
| HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); | |||
| if (response.statusCode() != 200) { | |||
| throw new IOException("HTTP请求失败,状态码:" + response.statusCode()); | |||
| } | |||
| return response.body(); | |||
| } | |||
| ``` | |||
| **修改后的代码(Java 8 兼容)**: | |||
| ```java | |||
| import java.io.BufferedReader; | |||
| import java.io.DataOutputStream; | |||
| import java.io.InputStreamReader; | |||
| import java.net.HttpURLConnection; | |||
| import java.net.URL; | |||
| import java.nio.charset.StandardCharsets; | |||
| private String sendHttpRequest(SmsRequest request) throws IOException { | |||
| String jsonBody = objectMapper.writeValueAsString(request); | |||
| URL url = new URL(smsConfig.getApiUrl()); | |||
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | |||
| try { | |||
| // 设置请求方法和属性 | |||
| connection.setRequestMethod("POST"); | |||
| connection.setRequestProperty("Accept", "application/json"); | |||
| connection.setRequestProperty("Content-Type", "application/json;charset=utf-8"); | |||
| connection.setDoOutput(true); | |||
| connection.setDoInput(true); | |||
| connection.setUseCaches(false); | |||
| // 发送请求体 | |||
| try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { | |||
| byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8); | |||
| os.write(input, 0, input.length); | |||
| os.flush(); | |||
| } | |||
| // 检查响应状态码 | |||
| int responseCode = connection.getResponseCode(); | |||
| if (responseCode != 200) { | |||
| throw new IOException("HTTP请求失败,状态码:" + responseCode); | |||
| } | |||
| // 读取响应 | |||
| try (BufferedReader br = new BufferedReader( | |||
| new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { | |||
| StringBuilder response = new StringBuilder(); | |||
| String responseLine; | |||
| while ((responseLine = br.readLine()) != null) { | |||
| response.append(responseLine.trim()); | |||
| } | |||
| return response.toString(); | |||
| } | |||
| } finally { | |||
| connection.disconnect(); | |||
| } | |||
| } | |||
| ``` | |||
| ### 2. 修改异常处理 | |||
| **原始代码**: | |||
| ```java | |||
| } catch (Exception e) { | |||
| logger.error("短信发送失败", e); | |||
| // ... | |||
| } | |||
| ``` | |||
| **修改后**: | |||
| ```java | |||
| } catch (IOException e) { | |||
| logger.error("短信发送网络异常", e); | |||
| SmsResponse errorResponse = new SmsResponse(); | |||
| errorResponse.setCode(-1); | |||
| errorResponse.setMessage("网络异常:" + e.getMessage()); | |||
| return errorResponse; | |||
| } catch (Exception e) { | |||
| logger.error("短信发送失败", e); | |||
| // ... | |||
| } | |||
| ``` | |||
| ## 主要变更 | |||
| ### ✅ 移除的 Java 11 特性 | |||
| 1. `java.net.http.HttpClient` | |||
| 2. `java.net.http.HttpRequest` | |||
| 3. `java.net.http.HttpResponse` | |||
| 4. `InterruptedException` 异常处理 | |||
| ### ✅ 使用的 Java 8 兼容特性 | |||
| 1. `java.net.HttpURLConnection` - 传统的 HTTP 客户端 | |||
| 2. `java.io.*` 类进行流处理 | |||
| 3. `java.nio.charset.StandardCharsets` - UTF-8 编码 | |||
| 4. try-with-resources 语句(Java 7+) | |||
| ## 兼容性说明 | |||
| ### ✅ 支持的 Java 版本 | |||
| - Java 8+ | |||
| - Java 11+ | |||
| - Java 17+ | |||
| ### 📊 性能对比 | |||
| | 特性 | Java 11 HttpClient | Java 8 HttpURLConnection | | |||
| |------|-------------------|---------------------------| | |||
| | **连接池** | 内置 | 需要手动管理 | | |||
| | **异步支持** | 原生支持 | 需要额外实现 | | |||
| | **HTTP/2** | 支持 | 不支持 | | |||
| | **内存使用** | 较高 | 较低 | | |||
| | **兼容性** | Java 11+ | Java 1.1+ | | |||
| ### 🔧 功能保持 | |||
| - ✅ POST 请求发送 | |||
| - ✅ JSON 请求体 | |||
| - ✅ HTTP 头设置 | |||
| - ✅ 响应状态码检查 | |||
| - ✅ 响应内容读取 | |||
| - ✅ 连接管理 | |||
| - ✅ 错误处理 | |||
| ## 测试建议 | |||
| ### 单元测试 | |||
| ```java | |||
| @Test | |||
| public void testSendSms() { | |||
| SmsService smsService = new SmsService(); | |||
| SmsResponse response = smsService.sendSms("13800138000", "测试消息"); | |||
| assertNotNull(response); | |||
| } | |||
| ``` | |||
| ### 集成测试 | |||
| 1. 验证 HTTP 请求格式是否正确 | |||
| 2. 测试网络异常处理 | |||
| 3. 验证响应解析功能 | |||
| ## 后续升级建议 | |||
| 如果将来项目升级到 Java 11+,可以考虑以下优化: | |||
| ### 1. 重新启用 Java 11 HttpClient | |||
| ```java | |||
| // 可选:使用连接池提升性能 | |||
| HttpClient client = HttpClient.newBuilder() | |||
| .connectTimeout(Duration.ofSeconds(10)) | |||
| .build(); | |||
| ``` | |||
| ### 2. 异步发送支持 | |||
| ```java | |||
| // 异步发送短信 | |||
| CompletableFuture<SmsResponse> future = smsService.sendSmsAsync(phone, content); | |||
| ``` | |||
| ### 3. HTTP/2 支持 | |||
| ```java | |||
| // 启用 HTTP/2 | |||
| HttpClient client = HttpClient.newBuilder() | |||
| .version(HttpClient.Version.HTTP_2) | |||
| .build(); | |||
| ``` | |||
| ## 注意事项 | |||
| 1. **连接管理**: 确保在 `finally` 块中调用 `connection.disconnect()` | |||
| 2. **编码处理**: 使用 `StandardCharsets.UTF_8` 确保字符编码正确 | |||
| 3. **资源释放**: 使用 try-with-resources 自动关闭流 | |||
| 4. **超时设置**: 可以通过 `connection.setConnectTimeout()` 设置连接超时 | |||
| ## 总结 | |||
| 通过将 Java 11 的 `java.net.http` 替换为 Java 8 兼容的 `HttpURLConnection`,成功解决了编译错误,同时保持了所有原有功能。修改后的代码在 Java 8+ 环境下都能正常运行。 | |||
| @ -0,0 +1,230 @@ | |||
| # SMS 短信服务模块 | |||
| ## 概述 | |||
| 短信服务模块提供完整的短信发送功能,支持单条发送、批量发送和定时发送。 | |||
| ## 目录结构 | |||
| ``` | |||
| org.jeecg.common.sms/ | |||
| ├── config/ | |||
| │ └── SmsConfig.java # 配置类 | |||
| ├── entity/ | |||
| │ ├── SmsMessage.java # 短信消息实体 | |||
| │ ├── SmsRequest.java # 请求实体 | |||
| │ ├── SmsResponse.java # 响应实体 | |||
| │ └── SmsResponseData.java # 响应数据实体 | |||
| ├── service/ | |||
| │ └── SmsService.java # 服务实现类 | |||
| └── controller/ | |||
| └── SmsController.java # REST控制器 | |||
| ``` | |||
| ## 配置说明 | |||
| 在 `application-dev.yml` 中的配置: | |||
| ```yaml | |||
| ##配置短信服务 | |||
| sms: | |||
| # 短信服务配置 | |||
| userName: csxinyuan | |||
| clientPassword: HwmY9q4F3fhi | |||
| interfacePassword: pGUKjsiVkaNF | |||
| apiUrl: http://agent.izjun.com:8001/sms/api/sendMessageOne | |||
| ``` | |||
| ## 使用方法 | |||
| ### 1. 在其他类中注入服务 | |||
| ```java | |||
| @Autowired | |||
| private SmsService smsService; | |||
| ``` | |||
| ### 2. 发送单条短信 | |||
| ```java | |||
| // 即时发送 | |||
| SmsResponse response = smsService.sendSms("13800138000", "您的验证码是:123456"); | |||
| // 定时发送 | |||
| String sendTime = "2024-12-25 10:30:00"; | |||
| SmsResponse response = smsService.sendSms("13800138000", "圣诞快乐!", sendTime); | |||
| ``` | |||
| ### 3. 批量发送短信 | |||
| ```java | |||
| List<SmsMessage> messageList = Arrays.asList( | |||
| new SmsMessage("13800138000", "消息内容1"), | |||
| new SmsMessage("13800138001", "消息内容2"), | |||
| new SmsMessage("13800138002", "消息内容3") | |||
| ); | |||
| SmsResponse response = smsService.sendSmsBatch(messageList); | |||
| ``` | |||
| ### 4. 参数验证 | |||
| ```java | |||
| // 验证手机号格式 | |||
| boolean isValidPhone = smsService.isValidPhone("13800138000"); | |||
| // 验证短信内容 | |||
| boolean isValidContent = smsService.isValidContent("短信内容"); | |||
| // 格式化时间 | |||
| String timeStr = smsService.formatSendTime(LocalDateTime.now()); | |||
| ``` | |||
| ## REST API接口 | |||
| ### 发送单条短信 | |||
| - **URL**: `POST /common/sms/send` | |||
| - **参数**: | |||
| - `phone`: 手机号 | |||
| - `content`: 短信内容 | |||
| - **示例**: | |||
| ```bash | |||
| curl -X POST "http://localhost:8002/novel-admin/common/sms/send" \ | |||
| -d "phone=13800138000&content=测试短信" | |||
| ``` | |||
| ### 发送定时短信 | |||
| - **URL**: `POST /common/sms/sendScheduled` | |||
| - **参数**: | |||
| - `phone`: 手机号 | |||
| - `content`: 短信内容 | |||
| - `sendTime`: 定时时间 (yyyy-MM-dd HH:mm:ss) | |||
| - **示例**: | |||
| ```bash | |||
| curl -X POST "http://localhost:8002/novel-admin/common/sms/sendScheduled" \ | |||
| -d "phone=13800138000&content=定时短信&sendTime=2024-12-25 10:30:00" | |||
| ``` | |||
| ### 批量发送短信 | |||
| - **URL**: `POST /common/sms/sendBatch` | |||
| - **Content-Type**: `application/json` | |||
| - **Body**: | |||
| ```json | |||
| [ | |||
| { | |||
| "phone": "13800138000", | |||
| "content": "消息内容1" | |||
| }, | |||
| { | |||
| "phone": "13800138001", | |||
| "content": "消息内容2" | |||
| } | |||
| ] | |||
| ``` | |||
| ### 获取当前时间格式 | |||
| - **URL**: `GET /common/sms/currentTime` | |||
| - **返回**: 当前时间的格式化字符串 | |||
| ## 响应格式 | |||
| ### 成功响应 | |||
| ```json | |||
| { | |||
| "success": true, | |||
| "message": "短信发送成功", | |||
| "code": 200, | |||
| "result": { | |||
| "code": 0, | |||
| "message": "成功", | |||
| "data": [ | |||
| { | |||
| "code": 0, | |||
| "message": "成功", | |||
| "phone": "13800138000", | |||
| "msgId": 1234567890, | |||
| "smsCount": 1 | |||
| } | |||
| ], | |||
| "smsCount": 1 | |||
| } | |||
| } | |||
| ``` | |||
| ### 错误响应 | |||
| ```json | |||
| { | |||
| "success": false, | |||
| "message": "短信发送失败:错误描述", | |||
| "code": 500 | |||
| } | |||
| ``` | |||
| ## 注意事项 | |||
| 1. **手机号格式**: 必须是11位数字,以1开头 | |||
| 2. **短信内容**: 不能超过500字符 | |||
| 3. **批量限制**: 批量发送最多支持1000条消息 | |||
| 4. **定时限制**: 定时发送时间限制在15天以内 | |||
| 5. **签名算法**: MD5(userName + timestamp + MD5(password)) | |||
| ## 错误码说明 | |||
| | 错误码 | 说明 | | |||
| |--------|------| | |||
| | 0 | 成功 | | |||
| | -1 | 系统异常 | | |||
| | 其他 | 具体错误信息请参考接口文档 | | |||
| ## 业务集成示例 | |||
| ### 用户注册发送验证码 | |||
| ```java | |||
| @Service | |||
| public class UserService { | |||
| @Autowired | |||
| private SmsService smsService; | |||
| public boolean sendRegistrationCode(String phone) { | |||
| // 生成验证码 | |||
| String code = generateRandomCode(); | |||
| // 构建短信内容 | |||
| String content = String.format("您的注册验证码是:%s,5分钟内有效。", code); | |||
| // 发送短信 | |||
| SmsResponse response = smsService.sendSms(phone, content); | |||
| if (response.isSuccess()) { | |||
| // 保存验证码到缓存 | |||
| saveCodeToCache(phone, code); | |||
| return true; | |||
| } else { | |||
| logger.error("发送注册验证码失败:{}", response.getMessage()); | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| ``` | |||
| ### 系统通知批量发送 | |||
| ```java | |||
| @Service | |||
| public class NotificationService { | |||
| @Autowired | |||
| private SmsService smsService; | |||
| public void sendSystemNotification(List<String> phoneList, String message) { | |||
| List<SmsMessage> messageList = phoneList.stream() | |||
| .map(phone -> new SmsMessage(phone, message)) | |||
| .collect(Collectors.toList()); | |||
| SmsResponse response = smsService.sendSmsBatch(messageList); | |||
| if (response.isSuccess()) { | |||
| logger.info("系统通知发送成功,共发送{}条", response.getSmsCount()); | |||
| } else { | |||
| logger.error("系统通知发送失败:{}", response.getMessage()); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,64 @@ | |||
| package org.jeecg.common.sms.config; | |||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||
| import org.springframework.stereotype.Component; | |||
| /** | |||
| * 短信服务配置类 | |||
| */ | |||
| @Component | |||
| @ConfigurationProperties(prefix = "sms") | |||
| public class SmsConfig { | |||
| /** | |||
| * 用户名 | |||
| */ | |||
| private String userName; | |||
| /** | |||
| * 客户端密码 | |||
| */ | |||
| private String clientPassword; | |||
| /** | |||
| * 接口密码 | |||
| */ | |||
| private String interfacePassword; | |||
| /** | |||
| * API地址 | |||
| */ | |||
| private String apiUrl; | |||
| public String getUserName() { | |||
| return userName; | |||
| } | |||
| public void setUserName(String userName) { | |||
| this.userName = userName; | |||
| } | |||
| public String getClientPassword() { | |||
| return clientPassword; | |||
| } | |||
| public void setClientPassword(String clientPassword) { | |||
| this.clientPassword = clientPassword; | |||
| } | |||
| public String getInterfacePassword() { | |||
| return interfacePassword; | |||
| } | |||
| public void setInterfacePassword(String interfacePassword) { | |||
| this.interfacePassword = interfacePassword; | |||
| } | |||
| public String getApiUrl() { | |||
| return apiUrl; | |||
| } | |||
| public void setApiUrl(String apiUrl) { | |||
| this.apiUrl = apiUrl; | |||
| } | |||
| } | |||
| @ -0,0 +1,152 @@ | |||
| package org.jeecg.common.sms.controller; | |||
| import io.swagger.annotations.Api; | |||
| import io.swagger.annotations.ApiOperation; | |||
| import io.swagger.annotations.ApiParam; | |||
| import org.jeecg.common.sms.entity.SmsMessage; | |||
| import org.jeecg.common.sms.entity.SmsResponse; | |||
| import org.jeecg.common.sms.service.SmsService; | |||
| import org.jeecg.common.api.vo.Result; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import java.time.LocalDateTime; | |||
| import java.util.List; | |||
| /** | |||
| * 短信发送控制器 | |||
| */ | |||
| @RestController | |||
| @RequestMapping("/common/sms") | |||
| @Api(tags = "短信发送接口") | |||
| public class SmsController { | |||
| private static final Logger logger = LoggerFactory.getLogger(SmsController.class); | |||
| @Autowired | |||
| private SmsService smsService; | |||
| /** | |||
| * 发送单条短信 | |||
| */ | |||
| @PostMapping("/send") | |||
| @ApiOperation("发送单条短信") | |||
| public Result<SmsResponse> sendSms( | |||
| @ApiParam("手机号") @RequestParam String phone, | |||
| @ApiParam("短信内容") @RequestParam String content) { | |||
| try { | |||
| // 验证参数 | |||
| if (!smsService.isValidPhone(phone)) { | |||
| return Result.error("手机号格式不正确"); | |||
| } | |||
| if (!smsService.isValidContent(content)) { | |||
| return Result.error("短信内容不能为空且不能超过500字符"); | |||
| } | |||
| // 发送短信 | |||
| SmsResponse response = smsService.sendSms(phone, content); | |||
| if (response.isSuccess()) { | |||
| return Result.OK("短信发送成功", response); | |||
| } else { | |||
| return Result.error("短信发送失败:" + response.getMessage()); | |||
| } | |||
| } catch (Exception e) { | |||
| logger.error("发送短信异常", e); | |||
| return Result.error("发送短信异常:" + e.getMessage()); | |||
| } | |||
| } | |||
| /** | |||
| * 发送定时短信 | |||
| */ | |||
| @PostMapping("/sendScheduled") | |||
| @ApiOperation("发送定时短信") | |||
| public Result<SmsResponse> sendScheduledSms( | |||
| @ApiParam("手机号") @RequestParam String phone, | |||
| @ApiParam("短信内容") @RequestParam String content, | |||
| @ApiParam("定时发送时间,格式:yyyy-MM-dd HH:mm:ss") @RequestParam String sendTime) { | |||
| try { | |||
| // 验证参数 | |||
| if (!smsService.isValidPhone(phone)) { | |||
| return Result.error("手机号格式不正确"); | |||
| } | |||
| if (!smsService.isValidContent(content)) { | |||
| return Result.error("短信内容不能为空且不能超过500字符"); | |||
| } | |||
| // 发送定时短信 | |||
| SmsResponse response = smsService.sendSms(phone, content, sendTime); | |||
| if (response.isSuccess()) { | |||
| return Result.OK("定时短信设置成功", response); | |||
| } else { | |||
| return Result.error("定时短信设置失败:" + response.getMessage()); | |||
| } | |||
| } catch (Exception e) { | |||
| logger.error("发送定时短信异常", e); | |||
| return Result.error("发送定时短信异常:" + e.getMessage()); | |||
| } | |||
| } | |||
| /** | |||
| * 批量发送短信 | |||
| */ | |||
| @PostMapping("/sendBatch") | |||
| @ApiOperation("批量发送短信") | |||
| public Result<SmsResponse> sendBatchSms( | |||
| @ApiParam("短信消息列表") @RequestBody List<SmsMessage> messageList) { | |||
| try { | |||
| // 验证参数 | |||
| if (messageList == null || messageList.isEmpty()) { | |||
| return Result.error("短信消息列表不能为空"); | |||
| } | |||
| if (messageList.size() > 1000) { | |||
| return Result.error("批量发送短信数量不能超过1000条"); | |||
| } | |||
| // 验证每条消息 | |||
| for (SmsMessage message : messageList) { | |||
| if (!smsService.isValidPhone(message.getPhone())) { | |||
| return Result.error("手机号格式不正确:" + message.getPhone()); | |||
| } | |||
| if (!smsService.isValidContent(message.getContent())) { | |||
| return Result.error("短信内容不正确:" + message.getContent()); | |||
| } | |||
| } | |||
| // 批量发送短信 | |||
| SmsResponse response = smsService.sendSmsBatch(messageList); | |||
| if (response.isSuccess()) { | |||
| return Result.OK("批量短信发送成功", response); | |||
| } else { | |||
| return Result.error("批量短信发送失败:" + response.getMessage()); | |||
| } | |||
| } catch (Exception e) { | |||
| logger.error("批量发送短信异常", e); | |||
| return Result.error("批量发送短信异常:" + e.getMessage()); | |||
| } | |||
| } | |||
| /** | |||
| * 获取格式化的当前时间(用于定时发送) | |||
| */ | |||
| @GetMapping("/currentTime") | |||
| @ApiOperation("获取当前时间格式") | |||
| public Result<String> getCurrentTime() { | |||
| String currentTime = smsService.formatSendTime(LocalDateTime.now()); | |||
| return Result.OK("当前时间", currentTime); | |||
| } | |||
| } | |||
| @ -0,0 +1,80 @@ | |||
| package org.jeecg.common.sms.entity; | |||
| import com.fasterxml.jackson.annotation.JsonProperty; | |||
| /** | |||
| * 短信消息对象 | |||
| */ | |||
| public class SmsMessage { | |||
| /** | |||
| * 发送手机号码 | |||
| */ | |||
| @JsonProperty("phone") | |||
| private String phone; | |||
| /** | |||
| * 短信内容 | |||
| */ | |||
| @JsonProperty("content") | |||
| private String content; | |||
| /** | |||
| * 附带通道扩展码(可选) | |||
| */ | |||
| @JsonProperty("extcode") | |||
| private String extcode; | |||
| /** | |||
| * 用户回传数据,最大长度64(可选) | |||
| */ | |||
| @JsonProperty("callData") | |||
| private String callData; | |||
| public SmsMessage() { | |||
| } | |||
| public SmsMessage(String phone, String content) { | |||
| this.phone = phone; | |||
| this.content = content; | |||
| } | |||
| public SmsMessage(String phone, String content, String extcode, String callData) { | |||
| this.phone = phone; | |||
| this.content = content; | |||
| this.extcode = extcode; | |||
| this.callData = callData; | |||
| } | |||
| public String getPhone() { | |||
| return phone; | |||
| } | |||
| public void setPhone(String phone) { | |||
| this.phone = phone; | |||
| } | |||
| public String getContent() { | |||
| return content; | |||
| } | |||
| public void setContent(String content) { | |||
| this.content = content; | |||
| } | |||
| public String getExtcode() { | |||
| return extcode; | |||
| } | |||
| public void setExtcode(String extcode) { | |||
| this.extcode = extcode; | |||
| } | |||
| public String getCallData() { | |||
| return callData; | |||
| } | |||
| public void setCallData(String callData) { | |||
| this.callData = callData; | |||
| } | |||
| } | |||
| @ -0,0 +1,90 @@ | |||
| package org.jeecg.common.sms.entity; | |||
| import com.fasterxml.jackson.annotation.JsonProperty; | |||
| import java.util.List; | |||
| /** | |||
| * 短信发送请求对象 | |||
| */ | |||
| public class SmsRequest { | |||
| /** | |||
| * 帐号用户名 | |||
| */ | |||
| @JsonProperty("userName") | |||
| private String userName; | |||
| /** | |||
| * 短信消息列表 | |||
| */ | |||
| @JsonProperty("messageList") | |||
| private List<SmsMessage> messageList; | |||
| /** | |||
| * 当前时间戳,精确到毫秒 | |||
| */ | |||
| @JsonProperty("timestamp") | |||
| private Long timestamp; | |||
| /** | |||
| * 签名,MD5(userName + timestamp + MD5(password)) | |||
| */ | |||
| @JsonProperty("sign") | |||
| private String sign; | |||
| /** | |||
| * 短信定时发送时间,格式:yyyy-MM-dd HH:mm:ss(可选) | |||
| */ | |||
| @JsonProperty("sendTime") | |||
| private String sendTime; | |||
| public SmsRequest() { | |||
| } | |||
| public SmsRequest(String userName, List<SmsMessage> messageList, Long timestamp, String sign) { | |||
| this.userName = userName; | |||
| this.messageList = messageList; | |||
| this.timestamp = timestamp; | |||
| this.sign = sign; | |||
| } | |||
| public String getUserName() { | |||
| return userName; | |||
| } | |||
| public void setUserName(String userName) { | |||
| this.userName = userName; | |||
| } | |||
| public List<SmsMessage> getMessageList() { | |||
| return messageList; | |||
| } | |||
| public void setMessageList(List<SmsMessage> messageList) { | |||
| this.messageList = messageList; | |||
| } | |||
| public Long getTimestamp() { | |||
| return timestamp; | |||
| } | |||
| public void setTimestamp(Long timestamp) { | |||
| this.timestamp = timestamp; | |||
| } | |||
| public String getSign() { | |||
| return sign; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| } | |||
| public String getSendTime() { | |||
| return sendTime; | |||
| } | |||
| public void setSendTime(String sendTime) { | |||
| this.sendTime = sendTime; | |||
| } | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| package org.jeecg.common.sms.entity; | |||
| import com.fasterxml.jackson.annotation.JsonProperty; | |||
| import java.util.List; | |||
| /** | |||
| * 短信发送响应对象 | |||
| */ | |||
| public class SmsResponse { | |||
| /** | |||
| * 处理结果,0为成功,其他失败 | |||
| */ | |||
| @JsonProperty("code") | |||
| private Integer code; | |||
| /** | |||
| * 处理结果描述 | |||
| */ | |||
| @JsonProperty("message") | |||
| private String message; | |||
| /** | |||
| * 处理结果的数据数组 | |||
| */ | |||
| @JsonProperty("data") | |||
| private List<SmsResponseData> data; | |||
| /** | |||
| * 消耗此次请求的计费总数 | |||
| */ | |||
| @JsonProperty("smsCount") | |||
| private Integer smsCount; | |||
| public SmsResponse() { | |||
| } | |||
| public Integer getCode() { | |||
| return code; | |||
| } | |||
| public void setCode(Integer code) { | |||
| this.code = code; | |||
| } | |||
| public String getMessage() { | |||
| return message; | |||
| } | |||
| public void setMessage(String message) { | |||
| this.message = message; | |||
| } | |||
| public List<SmsResponseData> getData() { | |||
| return data; | |||
| } | |||
| public void setData(List<SmsResponseData> data) { | |||
| this.data = data; | |||
| } | |||
| public Integer getSmsCount() { | |||
| return smsCount; | |||
| } | |||
| public void setSmsCount(Integer smsCount) { | |||
| this.smsCount = smsCount; | |||
| } | |||
| /** | |||
| * 检查响应是否成功 | |||
| */ | |||
| public boolean isSuccess() { | |||
| return code != null && code == 0; | |||
| } | |||
| } | |||
| @ -0,0 +1,82 @@ | |||
| package org.jeecg.common.sms.entity; | |||
| import com.fasterxml.jackson.annotation.JsonProperty; | |||
| /** | |||
| * 短信响应数据对象 | |||
| */ | |||
| public class SmsResponseData { | |||
| /** | |||
| * 处理结果,0为成功,其他失败 | |||
| */ | |||
| @JsonProperty("code") | |||
| private Integer code; | |||
| /** | |||
| * 处理结果描述 | |||
| */ | |||
| @JsonProperty("message") | |||
| private String message; | |||
| /** | |||
| * 发送手机号码 | |||
| */ | |||
| @JsonProperty("phone") | |||
| private String phone; | |||
| /** | |||
| * 唯一消息Id | |||
| */ | |||
| @JsonProperty("msgId") | |||
| private Long msgId; | |||
| /** | |||
| * 此号码的计费数 | |||
| */ | |||
| @JsonProperty("smsCount") | |||
| private Integer smsCount; | |||
| public SmsResponseData() { | |||
| } | |||
| public Integer getCode() { | |||
| return code; | |||
| } | |||
| public void setCode(Integer code) { | |||
| this.code = code; | |||
| } | |||
| public String getMessage() { | |||
| return message; | |||
| } | |||
| public void setMessage(String message) { | |||
| this.message = message; | |||
| } | |||
| public String getPhone() { | |||
| return phone; | |||
| } | |||
| public void setPhone(String phone) { | |||
| this.phone = phone; | |||
| } | |||
| public Long getMsgId() { | |||
| return msgId; | |||
| } | |||
| public void setMsgId(Long msgId) { | |||
| this.msgId = msgId; | |||
| } | |||
| public Integer getSmsCount() { | |||
| return smsCount; | |||
| } | |||
| public void setSmsCount(Integer smsCount) { | |||
| this.smsCount = smsCount; | |||
| } | |||
| } | |||
| @ -0,0 +1,236 @@ | |||
| package org.jeecg.common.sms.service; | |||
| import com.fasterxml.jackson.databind.ObjectMapper; | |||
| import org.jeecg.common.sms.config.SmsConfig; | |||
| import org.jeecg.common.sms.entity.SmsMessage; | |||
| import org.jeecg.common.sms.entity.SmsRequest; | |||
| import org.jeecg.common.sms.entity.SmsResponse; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Service; | |||
| import java.io.BufferedReader; | |||
| import java.io.DataOutputStream; | |||
| import java.io.IOException; | |||
| import java.io.InputStreamReader; | |||
| import java.net.HttpURLConnection; | |||
| import java.net.URL; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.security.MessageDigest; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.time.LocalDateTime; | |||
| import java.time.format.DateTimeFormatter; | |||
| import java.util.Arrays; | |||
| import java.util.List; | |||
| /** | |||
| * 短信发送服务类 | |||
| */ | |||
| @Service | |||
| public class SmsService { | |||
| private static final Logger logger = LoggerFactory.getLogger(SmsService.class); | |||
| @Autowired | |||
| private SmsConfig smsConfig; | |||
| private final ObjectMapper objectMapper = new ObjectMapper(); | |||
| /** | |||
| * 发送单条短信 | |||
| * | |||
| * @param phone 手机号 | |||
| * @param content 短信内容 | |||
| * @return 发送结果 | |||
| */ | |||
| public SmsResponse sendSms(String phone, String content) { | |||
| return sendSms(phone, content, null); | |||
| } | |||
| /** | |||
| * 发送定时短信 | |||
| * | |||
| * @param phone 手机号 | |||
| * @param content 短信内容 | |||
| * @param sendTime 定时发送时间,格式:yyyy-MM-dd HH:mm:ss | |||
| * @return 发送结果 | |||
| */ | |||
| public SmsResponse sendSms(String phone, String content, String sendTime) { | |||
| SmsMessage message = new SmsMessage(phone, content); | |||
| List<SmsMessage> messageList = Arrays.asList(message); | |||
| return sendSmsBatch(messageList, sendTime); | |||
| } | |||
| /** | |||
| * 批量发送短信 | |||
| * | |||
| * @param messageList 短信消息列表 | |||
| * @return 发送结果 | |||
| */ | |||
| public SmsResponse sendSmsBatch(List<SmsMessage> messageList) { | |||
| return sendSmsBatch(messageList, null); | |||
| } | |||
| /** | |||
| * 批量发送定时短信 | |||
| * | |||
| * @param messageList 短信消息列表 | |||
| * @param sendTime 定时发送时间,格式:yyyy-MM-dd HH:mm:ss | |||
| * @return 发送结果 | |||
| */ | |||
| public SmsResponse sendSmsBatch(List<SmsMessage> messageList, String sendTime) { | |||
| try { | |||
| // 构建请求参数 | |||
| SmsRequest request = buildSmsRequest(messageList, sendTime); | |||
| // 发送HTTP请求 | |||
| String responseJson = sendHttpRequest(request); | |||
| // 解析响应 | |||
| SmsResponse response = objectMapper.readValue(responseJson, SmsResponse.class); | |||
| logger.info("短信发送完成,响应:{}", responseJson); | |||
| return response; | |||
| } catch (IOException e) { | |||
| logger.error("短信发送网络异常", e); | |||
| SmsResponse errorResponse = new SmsResponse(); | |||
| errorResponse.setCode(-1); | |||
| errorResponse.setMessage("网络异常:" + e.getMessage()); | |||
| return errorResponse; | |||
| } catch (Exception e) { | |||
| logger.error("短信发送失败", e); | |||
| SmsResponse errorResponse = new SmsResponse(); | |||
| errorResponse.setCode(-1); | |||
| errorResponse.setMessage("短信发送异常:" + e.getMessage()); | |||
| return errorResponse; | |||
| } | |||
| } | |||
| /** | |||
| * 构建短信请求对象 | |||
| */ | |||
| private SmsRequest buildSmsRequest(List<SmsMessage> messageList, String sendTime) { | |||
| long timestamp = System.currentTimeMillis(); | |||
| String sign = generateSign(smsConfig.getUserName(), timestamp, smsConfig.getInterfacePassword()); | |||
| SmsRequest request = new SmsRequest(); | |||
| request.setUserName(smsConfig.getUserName()); | |||
| request.setMessageList(messageList); | |||
| request.setTimestamp(timestamp); | |||
| request.setSign(sign); | |||
| if (sendTime != null && !sendTime.trim().isEmpty()) { | |||
| request.setSendTime(sendTime); | |||
| } | |||
| return request; | |||
| } | |||
| /** | |||
| * 生成签名 | |||
| * 签名规则:MD5(userName + timestamp + MD5(password)) | |||
| */ | |||
| private String generateSign(String userName, long timestamp, String password) { | |||
| try { | |||
| String passwordMd5 = md5(password); | |||
| String signString = userName + timestamp + passwordMd5; | |||
| return md5(signString); | |||
| } catch (Exception e) { | |||
| logger.error("生成签名失败", e); | |||
| throw new RuntimeException("生成签名失败", e); | |||
| } | |||
| } | |||
| /** | |||
| * MD5加密 | |||
| */ | |||
| private String md5(String input) throws NoSuchAlgorithmException { | |||
| MessageDigest md = MessageDigest.getInstance("MD5"); | |||
| byte[] digest = md.digest(input.getBytes()); | |||
| StringBuilder sb = new StringBuilder(); | |||
| for (byte b : digest) { | |||
| sb.append(String.format("%02x", b)); | |||
| } | |||
| return sb.toString(); | |||
| } | |||
| /** | |||
| * 发送HTTP请求 | |||
| */ | |||
| private String sendHttpRequest(SmsRequest request) throws IOException { | |||
| String jsonBody = objectMapper.writeValueAsString(request); | |||
| logger.info("发送短信请求:{}", jsonBody); | |||
| URL url = new URL(smsConfig.getApiUrl()); | |||
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | |||
| try { | |||
| // 设置请求方法和属性 | |||
| connection.setRequestMethod("POST"); | |||
| connection.setRequestProperty("Accept", "application/json"); | |||
| connection.setRequestProperty("Content-Type", "application/json;charset=utf-8"); | |||
| connection.setDoOutput(true); | |||
| connection.setDoInput(true); | |||
| connection.setUseCaches(false); | |||
| // 发送请求体 | |||
| try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { | |||
| byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8); | |||
| os.write(input, 0, input.length); | |||
| os.flush(); | |||
| } | |||
| // 检查响应状态码 | |||
| int responseCode = connection.getResponseCode(); | |||
| if (responseCode != 200) { | |||
| throw new IOException("HTTP请求失败,状态码:" + responseCode); | |||
| } | |||
| // 读取响应 | |||
| try (BufferedReader br = new BufferedReader( | |||
| new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { | |||
| StringBuilder response = new StringBuilder(); | |||
| String responseLine; | |||
| while ((responseLine = br.readLine()) != null) { | |||
| response.append(responseLine.trim()); | |||
| } | |||
| return response.toString(); | |||
| } | |||
| } finally { | |||
| connection.disconnect(); | |||
| } | |||
| } | |||
| /** | |||
| * 验证手机号格式 | |||
| */ | |||
| public boolean isValidPhone(String phone) { | |||
| if (phone == null || phone.trim().isEmpty()) { | |||
| return false; | |||
| } | |||
| // 简单的手机号验证,11位数字,以1开头 | |||
| return phone.matches("^1[0-9]{10}$"); | |||
| } | |||
| /** | |||
| * 验证短信内容 | |||
| */ | |||
| public boolean isValidContent(String content) { | |||
| if (content == null || content.trim().isEmpty()) { | |||
| return false; | |||
| } | |||
| // 内容不能超过500字符 | |||
| return content.length() <= 500; | |||
| } | |||
| /** | |||
| * 格式化定时发送时间 | |||
| */ | |||
| public String formatSendTime(LocalDateTime dateTime) { | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | |||
| return dateTime.format(formatter); | |||
| } | |||
| } | |||