dev
parent
1501cfdd23
commit
68259d221c
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.qinan.safetyeval.start.sms;
|
||||||
|
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台测试控制器
|
||||||
|
*
|
||||||
|
* @author safety-eval
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "助通短信测试接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/test/sms")
|
||||||
|
public class SmsTestController {
|
||||||
|
|
||||||
|
private final ZtSmsService ztSmsService;
|
||||||
|
|
||||||
|
public SmsTestController(ZtSmsService ztSmsService) {
|
||||||
|
this.ztSmsService = ztSmsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码短信测试接口
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
@ApiOperation("发送验证码短信")
|
||||||
|
@PostMapping("/sendCode")
|
||||||
|
public Map<String, Object> sendVerificationCode(@RequestParam String mobile) {
|
||||||
|
log.info("收到验证码发送请求,手机号: {}", mobile);
|
||||||
|
|
||||||
|
// 生成6位随机验证码
|
||||||
|
String validCode = ztSmsService.generateVerificationCode();
|
||||||
|
|
||||||
|
// 发送验证码短信
|
||||||
|
Map<String, Object> result = ztSmsService.sendVerificationCode(mobile, validCode);
|
||||||
|
|
||||||
|
// 返回验证码供测试验证
|
||||||
|
result.put("validCode", validCode);
|
||||||
|
result.put("templateId", "793059");
|
||||||
|
result.put("templateName", "安全评价监管服务系统注册验证码");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送自定义内容短信测试接口
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param content 短信内容
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
@ApiOperation("发送自定义短信")
|
||||||
|
@PostMapping("/sendCustom")
|
||||||
|
public Map<String, Object> sendCustomSms(@RequestParam String mobile, @RequestParam String content) {
|
||||||
|
log.info("收到自定义短信发送请求,手机号: {}, 内容: {}", mobile, content);
|
||||||
|
return ztSmsService.sendSms(mobile, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取短信配置信息
|
||||||
|
*
|
||||||
|
* @return 配置信息
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取短信配置信息")
|
||||||
|
@GetMapping("/config")
|
||||||
|
public Map<String, Object> getConfig() {
|
||||||
|
Map<String, Object> config = new java.util.HashMap<>();
|
||||||
|
config.put("username", "qhdzyhy");
|
||||||
|
config.put("templateId", "793059");
|
||||||
|
config.put("templateName", "安全评价监管服务系统注册验证码");
|
||||||
|
config.put("templateContent", "[秦安安全]您正在注册本系统,验证码:{valid_code},5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。");
|
||||||
|
config.put("templateVariable", "valid_code");
|
||||||
|
config.put("apiUrl", "http://www.ztsms.cn/sendNSms.do");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
package org.qinan.safetyeval.start.sms;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台(融合云 v2 接口)main 方法测试类
|
||||||
|
* 直接运行无需启动 Spring Boot 容器
|
||||||
|
*
|
||||||
|
* 接口说明:
|
||||||
|
* - 融合云 v2 接口,Content-Type: application/json
|
||||||
|
* - tKey:Unix 时间戳(秒)
|
||||||
|
* - password:md5(原始密码 + tKey),单次 md5 小写
|
||||||
|
*
|
||||||
|
* @author safety-eval
|
||||||
|
*/
|
||||||
|
public class ZtSmsMainTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台配置信息
|
||||||
|
*/
|
||||||
|
private static final String USERNAME = "qhdzyhy";
|
||||||
|
private static final String PASSWORD = "Zcloud@88888";
|
||||||
|
private static final String API_URL = "https://api-shss.zthysms.com/v2/sendSms";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试手机号 - 请替换为实际接收短信的手机号
|
||||||
|
*/
|
||||||
|
private static final String TEST_MOBILE = "19122631081";
|
||||||
|
// private static final String TEST_MOBILE = "18375715233";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("========== 助通短信平台测试开始 ==========");
|
||||||
|
System.out.println("用户名: " + USERNAME);
|
||||||
|
System.out.println("接口地址: " + API_URL);
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// 生成6位随机验证码
|
||||||
|
String validCode = generateVerificationCode();
|
||||||
|
System.out.println("生成验证码: " + validCode);
|
||||||
|
|
||||||
|
// 构建短信内容(与模板一致:模板ID 793059)
|
||||||
|
// 注意:融合云签名必须使用全角【】,不能使用半角[]
|
||||||
|
String content = "【秦安安全】您正在注册本系统,验证码:" + validCode + ",5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。";
|
||||||
|
System.out.println("短信内容: " + content);
|
||||||
|
System.out.println("内容字数: " + content.length());
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
Map<String, Object> result = sendSms(TEST_MOBILE, content);
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("========== 发送结果 ==========");
|
||||||
|
for (Map.Entry<String, Object> entry : result.entrySet()) {
|
||||||
|
System.out.println(entry.getKey() + ": " + entry.getValue());
|
||||||
|
}
|
||||||
|
System.out.println("========== 助通短信平台测试结束 ==========");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信(融合云 v2 接口,使用 JDK 原生 HttpURLConnection,无需任何依赖)
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param content 短信内容
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> sendSms(String mobile, String content) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
// 融合云 v2 tKey:当前 Unix 时间戳(秒,10位)
|
||||||
|
String tKey = String.valueOf(System.currentTimeMillis() / 1000);
|
||||||
|
|
||||||
|
// 融合云 v2 password:md5(md5(原始密码) + tKey),两次 md5 小写
|
||||||
|
String md5Password = md5(PASSWORD);
|
||||||
|
String finalPassword = md5(md5Password + tKey);
|
||||||
|
|
||||||
|
System.out.println("tKey(时间戳): " + tKey);
|
||||||
|
System.out.println("md5(password): " + md5Password);
|
||||||
|
System.out.println("最终 password: " + finalPassword);
|
||||||
|
|
||||||
|
// 构建融合云 v2 JSON 请求体(字段名:username / password / tKey / mobile / content)
|
||||||
|
// content 为明文内容,无需 base64 编码
|
||||||
|
String jsonBody = "{"
|
||||||
|
+ "\"username\":\"" + USERNAME + "\","
|
||||||
|
+ "\"password\":\"" + finalPassword + "\","
|
||||||
|
+ "\"tKey\":\"" + tKey + "\","
|
||||||
|
+ "\"mobile\":\"" + mobile + "\","
|
||||||
|
+ "\"content\":\"" + escapeJson(content) + "\""
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
System.out.println("JSON 请求体: " + jsonBody);
|
||||||
|
|
||||||
|
// 创建连接
|
||||||
|
java.net.URL url = new java.net.URL(API_URL);
|
||||||
|
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
|
||||||
|
conn.setRequestProperty("Accept-Charset", "UTF-8");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(30000);
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
try (java.io.OutputStream os = conn.getOutputStream()) {
|
||||||
|
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
int responseCode = conn.getResponseCode();
|
||||||
|
System.out.println("HTTP 响应码: " + responseCode);
|
||||||
|
|
||||||
|
java.io.InputStream inputStream;
|
||||||
|
if (responseCode >= 400) {
|
||||||
|
inputStream = conn.getErrorStream();
|
||||||
|
} else {
|
||||||
|
inputStream = conn.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
try (java.io.BufferedReader reader = new java.io.BufferedReader(
|
||||||
|
new java.io.InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
response.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String responseBody = response.toString();
|
||||||
|
System.out.println("响应内容: " + responseBody);
|
||||||
|
|
||||||
|
result.put("success", responseCode == 200);
|
||||||
|
result.put("responseCode", responseCode);
|
||||||
|
result.put("response", responseBody);
|
||||||
|
result.put("message", responseCode == 200 ? "短信发送成功" : "短信发送失败");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("短信发送异常: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "短信发送异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5 加密(小写)
|
||||||
|
*
|
||||||
|
* @param input 待加密字符串
|
||||||
|
* @return 32位小写 MD5 字符串
|
||||||
|
*/
|
||||||
|
private static String md5(String input) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : digest) {
|
||||||
|
sb.append(String.format("%02x", b & 0xff));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("MD5加密失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位随机验证码
|
||||||
|
*
|
||||||
|
* @return 6位验证码字符串
|
||||||
|
*/
|
||||||
|
private static String generateVerificationCode() {
|
||||||
|
return String.format("%06d", (int) (Math.random() * 1000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 字符串转义(处理 " \ \n \r \t 等)
|
||||||
|
*
|
||||||
|
* @param input 原始字符串
|
||||||
|
* @return 转义后的字符串
|
||||||
|
*/
|
||||||
|
private static String escapeJson(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < input.length(); i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
sb.append("\\\"");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
sb.append("\\\\");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
sb.append("\\n");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
sb.append("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
sb.append("\\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.qinan.safetyeval.start.sms;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台配置属性
|
||||||
|
*
|
||||||
|
* @author safety-eval
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "zt.sms")
|
||||||
|
public class ZtSmsProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台用户名
|
||||||
|
*/
|
||||||
|
private String username = "qhdzyhy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台密码
|
||||||
|
*/
|
||||||
|
private String password = "Zcloud@88888";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台接口地址
|
||||||
|
*/
|
||||||
|
private String apiUrl = "http://www.ztsms.cn/sendNSms.do";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信模板ID
|
||||||
|
*/
|
||||||
|
private String templateId = "793059";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信签名
|
||||||
|
*/
|
||||||
|
private String signature = "【秦安安全】";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package org.qinan.safetyeval.start.sms;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助通短信平台服务
|
||||||
|
* API文档参考: http://www.ztsms.cn
|
||||||
|
*
|
||||||
|
* @author safety-eval
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class ZtSmsService {
|
||||||
|
|
||||||
|
private final ZtSmsProperties ztSmsProperties;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public ZtSmsService(ZtSmsProperties ztSmsProperties) {
|
||||||
|
this.ztSmsProperties = ztSmsProperties;
|
||||||
|
this.restTemplate = new RestTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码短信
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param validCode 验证码
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
public Map<String, Object> sendVerificationCode(String mobile, String validCode) {
|
||||||
|
// 构建短信内容:[秦安安全]您正在注册本系统,验证码:{valid_code},5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。
|
||||||
|
String content = ztSmsProperties.getSignature() + "您正在注册本系统,验证码:" + validCode + ",5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。";
|
||||||
|
return sendSms(mobile, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信
|
||||||
|
*
|
||||||
|
* @param mobile 手机号
|
||||||
|
* @param content 短信内容
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
public Map<String, Object> sendSms(String mobile, String content) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
// 生成tkey:当前时间,格式yyyyMMddHHmmss
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||||
|
String tkey = sdf.format(new Date());
|
||||||
|
|
||||||
|
// 生成password:md5(md5(password) + tkey)
|
||||||
|
String md5Password = md5(ztSmsProperties.getPassword());
|
||||||
|
String finalPassword = md5(md5Password + tkey);
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||||
|
params.add("username", ztSmsProperties.getUsername());
|
||||||
|
params.add("tkey", tkey);
|
||||||
|
params.add("password", finalPassword);
|
||||||
|
params.add("mobile", mobile);
|
||||||
|
params.add("content", content);
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
|
||||||
|
|
||||||
|
log.info("助通短信发送请求 - 手机号: {}, 内容: {}", mobile, content);
|
||||||
|
log.info("助通短信发送请求 - tkey: {}, password: {}", tkey, finalPassword);
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(
|
||||||
|
ztSmsProperties.getApiUrl(),
|
||||||
|
requestEntity,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
String responseBody = response.getBody();
|
||||||
|
log.info("助通短信发送响应: {}", responseBody);
|
||||||
|
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("mobile", mobile);
|
||||||
|
result.put("response", responseBody);
|
||||||
|
result.put("message", "短信发送成功");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("助通短信发送失败", e);
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("mobile", mobile);
|
||||||
|
result.put("message", "短信发送失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5加密
|
||||||
|
*
|
||||||
|
* @param input 待加密字符串
|
||||||
|
* @return 加密后的小写字符串
|
||||||
|
*/
|
||||||
|
private String md5(String input) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : digest) {
|
||||||
|
sb.append(String.format("%02x", b & 0xff));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("MD5加密失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位随机验证码
|
||||||
|
*
|
||||||
|
* @return 6位验证码
|
||||||
|
*/
|
||||||
|
public String generateVerificationCode() {
|
||||||
|
return String.format("%06d", (int) (Math.random() * 1000000));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue