|
|
@@ -0,0 +1,198 @@
|
|
|
+package cn.iocoder.byzs.module.system.framework.sms.core.client.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.lang.Assert;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.crypto.digest.DigestUtil;
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import cn.iocoder.byzs.framework.common.core.KeyValue;
|
|
|
+import cn.iocoder.byzs.framework.common.util.http.HttpUtils;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.TreeMap;
|
|
|
+import cn.iocoder.byzs.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
|
|
+import cn.iocoder.byzs.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
|
|
+import cn.iocoder.byzs.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
|
|
+import cn.iocoder.byzs.module.system.framework.sms.core.property.SmsChannelProperties;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+public class SubmailSmsClient extends AbstractSmsClient {
|
|
|
+
|
|
|
+ private static final String API_URL_XSEND = "https://api.mysubmail.com/message/xsend";
|
|
|
+ private static final String API_URL_QUERY = "https://api.mysubmail.com/message/query";
|
|
|
+ private static final String API_URL_TEMPLATE_GET = "https://api-v4.mysubmail.com/sms/template";
|
|
|
+ private static final String TIMESTAMP_URL = "https://api-v4.mysubmail.com/service/timestamp";
|
|
|
+ private static final String SIGN_TYPE_MD5 = "md5";
|
|
|
+ private static final String NORMAL = "normal";
|
|
|
+
|
|
|
+ public SubmailSmsClient(SmsChannelProperties properties) {
|
|
|
+ super(properties);
|
|
|
+ Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
|
|
+ Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
|
|
+ List<KeyValue<String, Object>> templateParams) throws Throwable {
|
|
|
+ StringBuilder postData = new StringBuilder();
|
|
|
+ postData.append("appid=").append(URLEncoder.encode(properties.getApiKey(), StandardCharsets.UTF_8));
|
|
|
+ postData.append("&signature=").append(URLEncoder.encode(properties.getApiSecret(), StandardCharsets.UTF_8));
|
|
|
+ postData.append("&project=").append(URLEncoder.encode(apiTemplateId, StandardCharsets.UTF_8));
|
|
|
+ postData.append("&to=").append(URLEncoder.encode(mobile, StandardCharsets.UTF_8));
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotEmpty(properties.getSignature())) {
|
|
|
+ postData.append("&sms_signature=").append(URLEncoder.encode(properties.getSignature(), StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotEmpty(templateParams)) {
|
|
|
+ Map<String, Object> vars = new LinkedHashMap<>();
|
|
|
+ templateParams.forEach(param -> vars.put(param.getKey(), param.getValue()));
|
|
|
+ postData.append("&vars=").append(URLEncoder.encode(JSONUtil.toJsonStr(vars), StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ java.util.Map<String, String> headers = new LinkedHashMap<>();
|
|
|
+ headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
|
+ headers.put("Accept", "application/json");
|
|
|
+
|
|
|
+ String responseBody = HttpUtils.post(API_URL_XSEND, headers, postData.toString());
|
|
|
+ JSONObject response = JSONUtil.parseObj(responseBody);
|
|
|
+
|
|
|
+ String status = response.getStr("status");
|
|
|
+ if ("success".equals(status)) {
|
|
|
+ return new SmsSendRespDTO()
|
|
|
+ .setSuccess(true)
|
|
|
+ .setSerialNo(response.getStr("send_id"));
|
|
|
+ }
|
|
|
+ return new SmsSendRespDTO()
|
|
|
+ .setSuccess(false)
|
|
|
+ .setApiCode(response.getStr("code"))
|
|
|
+ .setApiMsg(response.getStr("msg"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
|
|
|
+ List<SmsReceiveRespDTO> results = new ArrayList<>();
|
|
|
+ JSONObject status = JSONUtil.parseObj(text);
|
|
|
+
|
|
|
+ String statusStr = status.getStr("status");
|
|
|
+ if ("success".equals(statusStr)) {
|
|
|
+ JSONObject data = status.getJSONObject("data");
|
|
|
+ if (ObjectUtil.isNotEmpty(data)) {
|
|
|
+ SmsReceiveRespDTO result = new SmsReceiveRespDTO()
|
|
|
+ .setSuccess("success".equals(data.getStr("status")))
|
|
|
+ .setErrorMsg(data.getStr("status"))
|
|
|
+ .setMobile(data.getStr("phone"))
|
|
|
+ .setSerialNo(data.getStr("send_id"));
|
|
|
+ results.add(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return results;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
|
|
+ TreeMap<String, String> requestData = new TreeMap<>();
|
|
|
+ requestData.put("appid", properties.getApiKey());
|
|
|
+ requestData.put("template_id", apiTemplateId);
|
|
|
+
|
|
|
+ requestData = buildSignature(properties.getApiKey(), properties.getApiSecret(), NORMAL, requestData);
|
|
|
+
|
|
|
+ StringBuilder urlBuilder = new StringBuilder(API_URL_TEMPLATE_GET);
|
|
|
+ urlBuilder.append("?");
|
|
|
+ for (Map.Entry<String, String> entry : requestData.entrySet()) {
|
|
|
+ String key = entry.getKey();
|
|
|
+ String value = entry.getValue();
|
|
|
+ urlBuilder.append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8)).append("&");
|
|
|
+ }
|
|
|
+ String url = urlBuilder.substring(0, urlBuilder.length() - 1);
|
|
|
+
|
|
|
+ java.util.Map<String, String> headers = new LinkedHashMap<>();
|
|
|
+ headers.put("Accept", "application/json");
|
|
|
+
|
|
|
+ String responseBody;
|
|
|
+ try {
|
|
|
+ responseBody = HttpUtils.get(url, headers);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[getSmsTemplate][模板编号({}) 请求异常: {}]", apiTemplateId, e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ObjectUtil.isEmpty(responseBody)) {
|
|
|
+ log.warn("[getSmsTemplate][模板编号({}) 响应为空]", apiTemplateId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String trimmedBody = responseBody.trim();
|
|
|
+ if (!trimmedBody.startsWith("{")) {
|
|
|
+ log.warn("[getSmsTemplate][模板编号({}) 响应不是有效的 JSON,响应内容: {}", apiTemplateId, trimmedBody);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONObject response;
|
|
|
+ try {
|
|
|
+ response = JSONUtil.parseObj(responseBody);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[getSmsTemplate][模板编号({}) 响应 JSON 解析失败,错误: {},响应内容: {}", apiTemplateId, e.getMessage(), responseBody);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.containsKey("status") && "success".equals(response.getStr("status"))) {
|
|
|
+ JSONObject data = response.getJSONObject("template");
|
|
|
+ if (ObjectUtil.isNotEmpty(data)) {
|
|
|
+ return new SmsTemplateRespDTO()
|
|
|
+ .setId(data.getStr("template_id"))
|
|
|
+ .setContent(data.getStr("sms_content"))
|
|
|
+ .setAuditStatus(Integer.valueOf(data.getStr("template_status")));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("[getSmsTemplate][模板编号({}) 查询失败,响应: {}", apiTemplateId, responseBody);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getTimestamp() {
|
|
|
+ try {
|
|
|
+ String responseBody = HttpUtils.get(TIMESTAMP_URL, new LinkedHashMap<>());
|
|
|
+ if (ObjectUtil.isNotEmpty(responseBody)) {
|
|
|
+ JSONObject json = JSONUtil.parseObj(responseBody);
|
|
|
+ return json.getStr("timestamp");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[getTimestamp][获取时间戳异常: {}]", e.getMessage());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TreeMap<String, String> buildSignature(String appid, String appkey, String signType, TreeMap<String, String> requestData) {
|
|
|
+ String timestamp = getTimestamp();
|
|
|
+ if (StrUtil.isEmpty(timestamp)) {
|
|
|
+ log.warn("[buildSignature][获取时间戳失败]");
|
|
|
+ return requestData;
|
|
|
+ }
|
|
|
+ requestData.put("timestamp", timestamp);
|
|
|
+ requestData.put("sign_type", signType);
|
|
|
+
|
|
|
+ StringBuilder signStr = new StringBuilder();
|
|
|
+ signStr.append(appid).append(appkey);
|
|
|
+ for (Map.Entry<String, String> entry : requestData.entrySet()) {
|
|
|
+ signStr.append(entry.getKey()).append("=").append(entry.getValue());
|
|
|
+ }
|
|
|
+ signStr.append(appid).append(appkey);
|
|
|
+
|
|
|
+ String signature;
|
|
|
+ if (SIGN_TYPE_MD5.equals(signType)) {
|
|
|
+ signature = DigestUtil.md5Hex(signStr.toString(), StandardCharsets.UTF_8);
|
|
|
+ } else {
|
|
|
+ signature = appkey;
|
|
|
+ }
|
|
|
+ requestData.put("signature", signature);
|
|
|
+ return requestData;
|
|
|
+ }
|
|
|
+}
|