luotaiqian 2026-07-06 16:45:03 +08:00
parent 1501cfdd23
commit 68259d221c
4 changed files with 476 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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
* - tKeyUnix
* - passwordmd5( + 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 passwordmd5(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();
}
}

View File

@ -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 = "【秦安安全】";
}

View File

@ -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());
// 生成passwordmd5(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));
}
}