dev
parent
8ca81101ab
commit
2829d082ae
|
|
@ -1,7 +1,6 @@
|
|||
package org.qinan.safetyeval.adapter.web;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.jjb.saas.message.client.message.facede.MessageFacade;
|
||||
import com.jjb.saas.message.client.message.request.MessageSendCmd;
|
||||
import com.jjb.saas.message.client.message.request.MessageTargetCmd;
|
||||
import io.swagger.annotations.Api;
|
||||
|
|
@ -9,11 +8,11 @@ import io.swagger.annotations.ApiOperation;
|
|||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.qinan.safetyeval.client.api.AccountApi;
|
||||
import org.qinan.safetyeval.client.co.AccountCO;
|
||||
import org.qinan.safetyeval.client.dto.*;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.infrastructure.remote.SmsRemote;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -39,11 +38,9 @@ public class AccountController {
|
|||
@Resource
|
||||
private AccountApi accountApi;
|
||||
|
||||
@DubboReference
|
||||
private MessageFacade messageFacade;
|
||||
@Resource
|
||||
private SmsRemote smsRemote;
|
||||
|
||||
@Value("${sms.sign.sourceCode:}")
|
||||
private String sourceCode;
|
||||
|
||||
@ApiOperation("新增用户")
|
||||
@PostMapping("/save")
|
||||
|
|
@ -78,27 +75,7 @@ public class AccountController {
|
|||
@ApiOperation("发送短信")
|
||||
@PostMapping("/sendMessage")
|
||||
public com.alibaba.cola.dto.SingleResponse<Boolean> sendMessage(@Validated @RequestBody SendMessageCmd cmd) {
|
||||
if (StringUtils.isBlank(sourceCode)) {
|
||||
throw new BizException(SMS_SOURCE_CODE_NOT_EXIST);
|
||||
}
|
||||
|
||||
MessageSendCmd messageSendCmd = new MessageSendCmd();
|
||||
messageSendCmd.setBusinessId(UUID.randomUUID().toString().replace("-", ""));
|
||||
|
||||
MessageTargetCmd messageTargetCmd = new MessageTargetCmd();
|
||||
messageTargetCmd.setMobile(cmd.getMobile());
|
||||
messageSendCmd.setTargetCmd(messageTargetCmd);
|
||||
messageSendCmd.setNeedTokenEnum(false);
|
||||
|
||||
messageSendCmd.setSourceCode(sourceCode);
|
||||
Map<String, Object> codeMap = new HashMap<>();
|
||||
codeMap.put("valid_code", "123456");
|
||||
messageSendCmd.setParams(codeMap);
|
||||
|
||||
log.info("发送短信参数: messageSendCmd: {}", JSONUtil.toJsonStr(messageSendCmd));
|
||||
com.alibaba.cola.dto.SingleResponse<Boolean> result = messageFacade.send(messageSendCmd);
|
||||
log.info("发送短信结果: result: {}", JSONUtil.toJsonStr(result));
|
||||
return result;
|
||||
return smsRemote.sign(cmd.getMobile());
|
||||
}
|
||||
|
||||
@ApiOperation("用户同步gbs")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package org.qinan.safetyeval.infrastructure.remote;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 助通短信平台(融合云 v2)配置属性
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "zt.sms")
|
||||
public class SmsProperties {
|
||||
|
||||
/**
|
||||
* 助通融合云用户名
|
||||
*/
|
||||
private String username = "qhdzyhy";
|
||||
|
||||
/**
|
||||
* 助通融合云密码(明文,调用时二次 md5 加密)
|
||||
*/
|
||||
private String password = "Zcloud@88888";
|
||||
|
||||
/**
|
||||
* 融合云 v2 短信发送接口地址
|
||||
*/
|
||||
private String apiUrl = "https://api-shss.zthysms.com/v2/sendSms";
|
||||
|
||||
/**
|
||||
* 短信签名(必须使用全角【】,需与平台报备一致)
|
||||
*/
|
||||
private String signature = "【秦安安全】";
|
||||
|
||||
/**
|
||||
* 连接超时(毫秒)
|
||||
*/
|
||||
private int connectTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 读取超时(毫秒)
|
||||
*/
|
||||
private int readTimeout = 30000;
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
package org.qinan.safetyeval.infrastructure.remote;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.cola.dto.SingleResponse;
|
||||
import com.jjb.saas.message.client.message.facede.MessageFacade;
|
||||
import com.jjb.saas.message.client.message.request.MessageSendCmd;
|
||||
import com.jjb.saas.message.client.message.request.MessageTargetCmd;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.qinan.safetyeval.client.dto.SendMessageCmd;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.qinan.safetyeval.domain.exception.ErrorCode.SMS_SOURCE_CODE_NOT_EXIST;
|
||||
|
||||
/**
|
||||
* 助通短信平台(融合云 v2)远程调用
|
||||
* <p>
|
||||
* 接口说明:
|
||||
* - 地址:https://api-shss.zthysms.com/v2/sendSms
|
||||
* - 方式:POST,Content-Type: application/json
|
||||
* - 鉴权:tKey = 当前 Unix 秒级时间戳;password = md5(md5(明文密码) + tKey)
|
||||
* - 错误码:200 成功;4023 签名错误;4001 用户名错误;4009 密码错误 等
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SmsRemote {
|
||||
|
||||
private final SmsProperties smsProperties;
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@DubboReference
|
||||
private MessageFacade messageFacade;
|
||||
|
||||
|
||||
@Value("${sms.sign.sourceCode:}")
|
||||
private String sourceCode;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(smsProperties.getConnectTimeout());
|
||||
factory.setReadTimeout(smsProperties.getReadTimeout());
|
||||
this.restTemplate = new RestTemplate(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送注册验证码短信(模板ID 793059)
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param validCode 验证码
|
||||
* @return 调用结果
|
||||
*/
|
||||
public SingleResponse<String> sendRegisterVerificationCode(String mobile, String validCode) {
|
||||
// 模板内容:[秦安安全]您正在注册本系统,验证码:{valid_code},5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。
|
||||
String content = smsProperties.getSignature()
|
||||
+ "您正在注册本系统,验证码:" + validCode
|
||||
+ ",5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。";
|
||||
return sendSms(mobile, content);
|
||||
}
|
||||
|
||||
public SingleResponse<Boolean> sign(String mobile) {
|
||||
String validCode = generateVerificationCode();
|
||||
String content = "【秦安安全】您正在注册本系统,验证码:" + validCode + ",5分钟内有效,请勿泄露给他人,如非本人操作请忽略本条短信。";
|
||||
sendSms(mobile, content);
|
||||
return SingleResponse.of(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信(融合云 v2 接口,明文内容)
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param content 短信内容(需包含全角【】签名)
|
||||
* @return 调用结果
|
||||
*/
|
||||
public SingleResponse<String> sendSms(String mobile, String content) {
|
||||
log.info("助通短信发送请求 - mobile={}, content={}", mobile, content);
|
||||
SingleResponse<String> response;
|
||||
try {
|
||||
String jsonBody = buildRequestBody(mobile, content);
|
||||
log.info("助通短信发送请求体: {}", jsonBody);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
|
||||
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
|
||||
|
||||
ResponseEntity<String> resp =
|
||||
restTemplate.postForEntity(smsProperties.getApiUrl(), requestEntity, String.class);
|
||||
|
||||
String body = resp.getBody();
|
||||
log.info("助通短信发送响应: status={}, body={}", resp.getStatusCodeValue(), body);
|
||||
|
||||
// 融合云成功返回 HTTP 200,且响应 JSON 中 code=200
|
||||
if (resp.getStatusCodeValue() == 200) {
|
||||
response = SingleResponse.of(body);
|
||||
} else {
|
||||
log.warn("助通短信发送失败 - mobile={}, body={}", mobile, body);
|
||||
throw new BizException("SMS_SEND_FAIL", "短信发送失败: " + body);
|
||||
}
|
||||
} catch (BizException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("助通短信发送异常 - mobile={}", mobile, e);
|
||||
throw new BizException("SMS_SEND_ERROR", "短信发送异常: " + e.getMessage());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 6 位随机验证码
|
||||
*
|
||||
* @return 6位验证码字符串
|
||||
*/
|
||||
public String generateVerificationCode() {
|
||||
return String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
/**
|
||||
* 构建融合云 v2 JSON 请求体
|
||||
*/
|
||||
private String buildRequestBody(String mobile, String content) {
|
||||
String tKey = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String finalPassword = md5(md5(smsProperties.getPassword()) + tKey);
|
||||
|
||||
return "{"
|
||||
+ "\"username\":\"" + smsProperties.getUsername() + "\","
|
||||
+ "\"password\":\"" + finalPassword + "\","
|
||||
+ "\"tKey\":\"" + tKey + "\","
|
||||
+ "\"mobile\":\"" + mobile + "\","
|
||||
+ "\"content\":\"" + escapeJson(content) + "\""
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5 加密(小写)
|
||||
*/
|
||||
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 BizException("SMS_MD5_ERROR", "MD5加密失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 字符串转义
|
||||
*/
|
||||
private 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();
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation("发送短信")
|
||||
@PostMapping("/sendMessage")
|
||||
public com.alibaba.cola.dto.SingleResponse<Boolean> sendMessage(@Validated @RequestBody SendMessageCmd cmd) {
|
||||
if (StringUtils.isBlank(sourceCode)) {
|
||||
throw new BizException(SMS_SOURCE_CODE_NOT_EXIST);
|
||||
}
|
||||
|
||||
MessageSendCmd messageSendCmd = new MessageSendCmd();
|
||||
messageSendCmd.setBusinessId(UUID.randomUUID().toString().replace("-", ""));
|
||||
|
||||
MessageTargetCmd messageTargetCmd = new MessageTargetCmd();
|
||||
messageTargetCmd.setMobile(cmd.getMobile());
|
||||
messageSendCmd.setTargetCmd(messageTargetCmd);
|
||||
messageSendCmd.setNeedTokenEnum(false);
|
||||
|
||||
messageSendCmd.setSourceCode(sourceCode);
|
||||
Map<String, Object> codeMap = new HashMap<>();
|
||||
codeMap.put("valid_code", "123456");
|
||||
messageSendCmd.setParams(codeMap);
|
||||
|
||||
log.info("发送短信参数: messageSendCmd: {}", JSONUtil.toJsonStr(messageSendCmd));
|
||||
com.alibaba.cola.dto.SingleResponse<Boolean> result = messageFacade.send(messageSendCmd);
|
||||
log.info("发送短信结果: result: {}", JSONUtil.toJsonStr(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.qinan.safetyeval.start.sms;
|
||||
package org.qinan.safetyeval.infrastructure.remote;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
Loading…
Reference in New Issue