Commit 9968066b authored by honghy's avatar honghy

新增bulk短信客户端

parent dbedbfb6
......@@ -5,10 +5,12 @@ ALTER TABLE system_sms_channel MODIFY api_secret VARCHAR(200) COMMENT '短信 AP
-- 新增数据字典渠道类型
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (3, 'Sendchamp', 'SENDCHAMP', 'system_sms_channel_code', 0, 'primary', '', NULL, '2741', '2024-10-28 09:11:18', '2741', '2024-10-28 09:12:13', b'0', 'Sendchamp');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (4, 'Ycloud', 'YCLOUD', 'system_sms_channel_code', 0, 'primary', '', NULL, '2741', '2024-10-28 09:12:55', '2741', '2024-10-28 09:12:55', b'0', 'Ycloud');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (5, 'Bulk', 'BULK', 'system_sms_channel_code', 0, 'primary', '', NULL, '2741', '2024-10-28 09:12:55', '2741', '2024-10-28 09:12:55', b'0', 'Bulk');
-- 新增渠道
INSERT INTO `jiedao`.`system_sms_channel`(`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES (7, 'sendchamp', 'SENDCHAMP', 0, '', 'ecit@ewchina.net', 'sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO', '', '2741', '2024-10-26 17:06:44', '2741', '2024-10-28 15:16:39', b'0', 'sendchamp');
INSERT INTO `jiedao`.`system_sms_channel`(`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES (8, 'ycloud', 'YCLOUD', 0, NULL, 'ecit@ewchina.net', '9dbd912f56c101e53b23cb7b758ffda8', NULL, '2741', '2024-10-26 17:12:50', '2741', '2024-10-28 15:16:18', b'0', 'ycloud');
INSERT INTO `jiedao`.`system_sms_channel`(`signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES ('sendchamp', 'SENDCHAMP', 0, '', 'ecit@ewchina.net', 'sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO', '', '2741', '2024-10-26 17:06:44', '2741', '2024-10-28 15:16:39', b'0', 'sendchamp');
INSERT INTO `jiedao`.`system_sms_channel`(`signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES ('ycloud', 'YCLOUD', 0, NULL, 'ecit@ewchina.net', '9dbd912f56c101e53b23cb7b758ffda8', NULL, '2741', '2024-10-26 17:12:50', '2741', '2024-10-28 15:16:18', b'0', 'ycloud');
INSERT INTO `jiedao`.`system_sms_channel`(`signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES ('bulk', 'BULK', 0, NULL, 'ecit@ewchina.net', '0z@@vtj1h', NULL, '2741', '2024-10-26 17:12:50', '2741', '2024-10-28 15:16:18', b'0', 'bulk');
-- 短信日志新增字段
alter table system_sms_log add node_id bigint comment '节点id',add node_template_sn int default 1 comment '节点模板序列号',add message_type tinyint comment '发送类型';
......
......@@ -2,55 +2,73 @@ package cn.iocoder.yudao.framework.http.sms;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import cn.iocoder.yudao.framework.http.util.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* BulksmsHttp 类用于处理与 Bulksms 相关的 HTTP 请求。
* 主要功能包括发送短信和设置短信参数。
*
* @author wuxian
* @since 2024-11-27
**/
@Slf4j
public class BulksmsHttp {
// 请求的URL地址
private static final String REQ_URL = "https://portal.nigeriabulksms.com/api/";
public void sendReq(Map<String, Object> param, Map<String, String> header) {
HttpClient client = HttpClientFactory.get(Request.Util.OkHttp);
Request req = Request.create(REQ_URL, Request.Method.POST, param);
req.setParamFormat(Request.ParamFormat.FORM);
try {
Response res = client.execute(req, Request.Option.create(0, 0, header));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Map<String, String> setHeader(String accept, String contentType) {
/**
* 发送请求到 Bulksms 服务器。
*
* @param param 请求参数
* @return 响应对象
*/
public Response sendReq(Map<String, Object> param) {
// 设置请求头
Map<String, String> header = new HashMap<>();
//header.put("Accept","application/json,text/plain,*/*");
//header.put("Content-Type", "application/json");
header.put("Accept", accept);
header.put("Content-Type", contentType);
return header;
header.put("Accept", "text/html,application/xhtml+xml,application/xml");
header.put("Content-Type", "application/json");
// 发送POST请求
return HttpUtils.post(REQ_URL, param, header, Request.ParamFormat.FORM);
}
/**
* 设置发送短信的参数。
*
* @param to 接收短信的手机号
* @param message 短信内容
* @param senderName 发送者名称
* @param username 用户名
* @param password 密码
* @return 参数映射
*/
@NotNull
public Map<String, Object> setParams(String mobiles, String message, String senderName, String username, String password) {
Map<String, Object> param = new HashMap<>();
param.put("username", username);
param.put("password", password);
param.put("mobiles", mobiles);
param.put("message", message);
param.put("sender", senderName);
return param;
}
public static void main(String[] args){
public static void main(String[] args) {
BulksmsHttp test = new BulksmsHttp();
Map<String,String> header = setHeader("text/html,application/xhtml+xml,application/xml","application/json");
String mobiles = "18102810628";
String mobiles = "2347087010202";
String sender = "ECLogistics";
String message = "Good day This is E&C logistics(ship from China to Nigeria),It has been a while since we last worked together,and we would like to reestablish our partnership. Our main service:GROUPAGE/FULL CONTAINER/AIR CARGO/COMPRESSING/CONSOLITAION .We value your business and would appreciate the opportunity to serve you once again.Have a Nice day! My WA :+861 592 035 652 7";
String username = "ecit@ewchina.net";
String password = "0z@@vtj1h";
Map<String, Object> param = new HashMap<>();
param.put("username","ecit@ewchina.net");
param.put("password","0z@@vtj1h");
Map<String, Object> param = test.setParams(mobiles, sender, message, username, password);
test.sendReq(param,header);
test.sendReq(param);
}
}
......@@ -2,9 +2,7 @@ package cn.iocoder.yudao.framework.http.sms;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import lombok.SneakyThrows;
import cn.iocoder.yudao.framework.http.util.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
......@@ -13,44 +11,47 @@ import java.util.Map;
/**
* SendChampHttp类用于处理与SendChamp API相关的HTTP请求,以发送短信和获取短信状态
* 该类包含了构造请求参数和请求头的方法,以及执行HTTP请求的方法
*
* @author wuxian
* @since 2024-10-23
**/
@Slf4j
public class SendChampHttp {
// 定义发送短信的API URL
private static final String SMS_URL = "https://api.sendchamp.com/api/v1/sms/send";
// 定义获取短信状态的API URL前缀
private static final String RECEIVE_URL = "https://api.sendchamp.com/api/v1/sms/status/";
/**
* 发送POST请求以发送短信
*
* @param param 请求参数,包含短信的接收者、消息内容等
* @param header 请求头,包含API密钥等信息
* @return 返回API的响应结果
*/
public Response postReq(Map<String, Object> param, Map<String, String> header) {
HttpClient client = HttpClientFactory.get(Request.Util.OkHttp);
Request req = Request.create(SMS_URL, Request.Method.POST, param);
req.setParamFormat(Request.ParamFormat.JSON);
try {
Response res = client.execute(req, Request.Option.create(0, 0, header));
log.warn("response code ---------------------------->" + res.getCode());
log.warn("response content is --------------------》" + res.getBody());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
return HttpUtils.post(SMS_URL, param, header, Request.ParamFormat.JSON);
}
@SneakyThrows
/**
* 发送GET请求以获取短信发送状态
*
* @param header 请求头,包含API密钥等信息
* @param id 短信发送的唯一标识符
* @return 返回API的响应结果
*/
public Response getReceiveStatus(Map<String, String> header, String id) {
HttpClient client = HttpClientFactory.get(Request.Util.OkHttp);
Request req = Request.create(RECEIVE_URL + id, Request.Method.GET, "");
try {
Response res = client.execute(req, Request.Option.create(0, 0, header));
log.warn("response code ---------------------------->" + res.getCode());
log.warn("response content is --------------------》" + res.getBody());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
return HttpUtils.get(RECEIVE_URL + id, header, "");
}
/**
* 设置HTTP请求的头信息
*
* @param apiKey SendChamp API密钥
* @return 返回构造好的请求头Map对象
*/
@NotNull
public Map<String, String> setHeader(String apiKey) {
Map<String, String> header = new HashMap<>();
......@@ -60,6 +61,14 @@ public class SendChampHttp {
return header;
}
/**
* 设置发送短信的请求参数
*
* @param to 短信接收者的电话号码
* @param message 短信内容
* @param senderName 发送者名称
* @return 返回构造好的请求参数Map对象
*/
@NotNull
public Map<String, Object> setParams(String to, String message, String senderName) {
Map<String, Object> param = new HashMap<>();
......@@ -73,17 +82,15 @@ public class SendChampHttp {
public static void main(String[] args) {
SendChampHttp test = new SendChampHttp();
// String to = "2348140352000";
String to = "8618926674857";
String senderName = "ECLogistics";
//信息如果超过200字符,需要拆成多条短信,多次发送
String message = "Good day!This is E&C logistics(ship from China to Nigeria),It has been a while since we last worked together, and we would like to reestablish our partnership. Our main service: GROUPAGE/FULL CONTAINE";
Map<String, Object> param = test.setParams(to, message, senderName);
String apiKey = "Bearer sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO";
Map<String, String> header = test.setHeader(apiKey);
test.postReq(param, header);
// test.getReceiveStatus(header, "66e6e9df-b454-4df7-a968-af944a535757");
test.postReq(param,header);
}
......
......@@ -49,8 +49,8 @@ public class WhisperClientHttp {
req.setParamFormat(Request.ParamFormat.JSON);
try {
Response res = client.execute(req,Request.Option.create(0,0,header));
log.warn("response code ---------------------------->"+res.getCode());
log.warn("response content is --------------------》"+res.getBody());
log.info("response code ---------------------------->"+res.getCode());
log.info("response content is --------------------》"+res.getBody());
} catch (Exception e) {
throw new RuntimeException(e);
}
......
package cn.iocoder.yudao.framework.http.sms;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import lombok.SneakyThrows;
import cn.iocoder.yudao.framework.http.util.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* YCloudWhatsappHttp类用于处理与 Whatsapp 相关的 HTTP 请求,以发送短信和获取短信状态。
*
* @author wuxian
* @since 2024-10-30
**/
@Slf4j
public class YCloudWhatsappHttp {
private HttpClient client = null;
//请求地址
private static final String WA_URL = "https://api.ycloud.com/v2/whatsapp/messages/sendDirectly";
private static final String RECEIVE_URL = "https://api.ycloud.com/v2/whatsapp/messages/";
public YCloudWhatsappHttp() {
init();
}
private void init() {
client = HttpClientFactory.get(Request.Util.OkHttp);
}
@SneakyThrows
/**
* 发送WhatsApp消息
*
* @param header 请求头,包含认证信息
* @param param 请求参数,包含消息内容
* @return 响应结果
*/
public Response postWhatsapp(Map<String, String> header, Map<String, Object> param) {
Request req = Request.create(WA_URL, Request.Method.POST, param);
req.setParamFormat(Request.ParamFormat.JSON);
Response res = client.execute(req, Request.Option.create(0, 0, header));
log.warn("response code ---------------------------->" + res.getCode());
log.warn("response content is --------------------》" + res.getBody());
return res;
return HttpUtils.post(WA_URL, param, header, Request.ParamFormat.JSON);
}
@SneakyThrows
/**
* 获取消息发送状态
*
* @param header 请求头,包含认证信息
* @param id 消息ID
* @return 响应结果
*/
public Response getReceiveStatus(Map<String, String> header, String id) {
Request req = Request.create(RECEIVE_URL + id, Request.Method.GET, "");
Response res = client.execute(req, Request.Option.create(0, 0, header));
log.warn("response code ---------------------------->" + res.getCode());
log.warn("response content is --------------------》" + res.getBody());
return res;
return HttpUtils.get(RECEIVE_URL + id, header, "");
}
/**
* 设置请求参数
*
* @param code 验证码
* @param templateName 模板名称
* @param lanCode 语言编号
......@@ -61,16 +55,15 @@ public class YCloudWhatsappHttp {
* @return 参数
*/
public Map<String, Object> setParams(String code, String templateName, String lanCode, String to) {
//构建模板参数
Map<String, Object> template = new HashMap<>();
Map<String, String> language = new HashMap<>();
Components components = new Components();
Parameters parameters = new Parameters();
Button button = new Button();
button.setIndex("0");
button.setType("button");
button.setSub_type("url");
//模板包含语言(language)、组件(components),组件包含参数(parameters)
parameters.setType("text");
parameters.setText(code);
......@@ -78,11 +71,9 @@ public class YCloudWhatsappHttp {
list1.add(parameters);
//构建json中的组件
components.setType("body");
components.setParameters(list1);
button.setParameters(list1);
List<Object> list2 = new ArrayList<>();
list2.add(components);
list2.add(new Components().setType("body").setParameters(list1));
list2.add(button);
language.put("code", lanCode);
......@@ -101,8 +92,14 @@ public class YCloudWhatsappHttp {
return param;
}
//构建http请求的头
/**
* 设置请求头
*
* @param apiKey API密钥
* @return 请求头
*/
public Map<String, String> setHeader(String apiKey) {
//构建http请求的头
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json");
header.put("Content-Type", "application/json");
......@@ -113,31 +110,10 @@ public class YCloudWhatsappHttp {
public static void main(String[] args) {
YCloudWhatsappHttp test = new YCloudWhatsappHttp();
String apiKey = "9dbd912f56c101e53b23cb7b758ffda8";
// String code = "8888";
// String templateName = "ec_verification";
// String lanCode = "en";
// test.postWhatsapp(test.setHeader(apiKey), test.setParams(code, templateName, lanCode, "8618926674857"));
Response response = test.getReceiveStatus(test.setHeader(apiKey), "67370e34334f011e030dfd7b");
Map<String, Object> responseObj = JsonUtils.parseObject(response.getBody(), Map.class);
String status = (String) responseObj.get("status");
if (status != null && ("delivered".equals(status) || "read".equals(status))) {
System.out.println(status);
String deliverTime = (String) responseObj.get("deliverTime");
// 定义 ISO 8601 格式的日期时间解析器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
// 将字符串解析为 ZonedDateTime 对象
ZonedDateTime utcDateTime = ZonedDateTime.parse(deliverTime, formatter);
// 获取本地时区
ZoneId localZoneId = ZoneId.systemDefault();
// 将 UTC 时间转换为本地时间
ZonedDateTime localDateTime = utcDateTime.withZoneSameInstant(localZoneId);
// 将 ZonedDateTime 转换为 Date
Date localDate = Date.from(localDateTime.toInstant());
// 输出本地时间
System.out.println("本地时间: " + localDate);
} else {
System.out.println("null");
}
String code = "8888";
String templateName = "ec_verification";
String lanCode = "en";
test.postWhatsapp(test.setHeader(apiKey), test.setParams(code, templateName, lanCode, "8618926674857"));
}
}
......
package cn.iocoder.yudao.framework.http.util;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* Http 工具类
*
* @author Jayden
* @date 2024/11/28
*/
@Slf4j
public class HttpUtils {
// 创建一个HttpClient实例,用于执行HTTP请求
private static final HttpClient HTTP_CLIENT = HttpClientFactory.get(Request.Util.OkHttp);
/**
* 执行POST请求
*
* @param url 请求的URL
* @param params 请求参数,键值对形式
* @param headers 请求头,键值对形式
* @return 返回响应对象,包含响应码、响应内容等信息
* @throws RuntimeException 如果执行请求时发生异常,则抛出运行时异常
*/
public static Response post(String url, Map<String, Object> params, Map<String, String> headers, Request.ParamFormat paramFormat) {
// 创建请求对象,指定URL、请求方法和参数
Request request = Request.create(url, Request.Method.POST, params);
request.setParamFormat(paramFormat);
try {
// 执行请求并获取响应
Response response = HTTP_CLIENT.execute(request, Request.Option.create(0, 0, headers));
// 记录响应码和响应内容
log.info("response code ---------------------------->" + response.getCode());
log.info("response content is --------------------》" + response.getBody());
return response;
} catch (Exception e) {
// 记录错误信息并抛出运行时异常
log.error("Error executing POST request: " + e.getMessage(), e);
throw new RuntimeException("Error executing POST request: " + e.getMessage(), e);
}
}
/**
* 执行GET请求
*
* @param url 请求的URL
* @param headers 请求头,键值对形式
* @return 返回响应对象,包含响应码、响应内容等信息
* @throws RuntimeException 如果执行请求时发生异常,则抛出运行时异常
*/
public static Response get(String url, Map<String, String> headers, String param) {
// 创建请求对象,指定URL和请求方法
Request request = Request.create(url, Request.Method.GET, param);
try {
// 执行请求并获取响应
Response response = HTTP_CLIENT.execute(request, Request.Option.create(0, 0, headers));
// 记录响应码和响应内容
log.info("response code ---------------------------->" + response.getCode());
log.info("response content is --------------------》" + response.getBody());
return response;
} catch (Exception e) {
// 记录错误信息并抛出运行时异常
log.error("Error executing GET request: " + e.getMessage(), e);
throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
}
}
}
package cn.iocoder.yudao.framework.http.util;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
......@@ -68,7 +74,7 @@ public class StrUtils {
*/
public static String autoFill(Integer source, Integer length) {
if (source == null || length == null || (source + "").length() >= length){
if (source == null || length == null || (source + "").length() >= length) {
return source + "";
}
......@@ -107,12 +113,12 @@ public class StrUtils {
// 指定填充字符
if (isBlank(str)){
if (isBlank(str)) {
str = "0";
}
// 指定填充方向
if (isRight == null){
if (isRight == null) {
isRight = false;
}
......@@ -149,7 +155,7 @@ public class StrUtils {
*/
public static boolean hasOnlyNum(String str) {
if (isBlank(str)){
if (isBlank(str)) {
return false;
}
......@@ -277,4 +283,56 @@ public class StrUtils {
return first.toLowerCase() + after;
}
/**
* 处理短信模板内容
* 包括获取模板内容并进行参数替换
*
* @param smsTemplateDTO 短信模板信息
* @param templateParams 模板参数
* @return 替换后的短信内容
*/
public static String processTemplateContent(SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
// 获取短信模板内容
String content = smsTemplateDTO.getContent();
// 如果有模板参数,则进行参数替换
if (StringUtils.isNotBlank(content) && templateParams != null) {
for (KeyValue<String, Object> entry : templateParams) {
// 对键和值进行转义,以确保安全性
String key = escapeKey(entry.getKey());
String value = escapeValue(entry.getValue());
// 构建正则表达式,用于匹配模板中的占位符
String regex = "\\$\\{" + key + "\\}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
// 将匹配到的占位符替换为实际值
content = matcher.replaceAll(value);
}
}
return content;
}
/**
* 转义键
* 对键进行转义,以防止恶意代码注入
*
* @param key 待转义的键
* @return 转义后的键
*/
private static String escapeKey(String key) {
return key.replaceAll("[^a-zA-Z0-9_]", "");
}
/**
* 转义值
* 对值进行转义,以防止恶意代码注入
*
* @param value 待转义的值
* @return 转义后的值
*/
private static String escapeValue(Object value) {
if (value instanceof String) {
return ((String) value).replaceAll("[^a-zA-Z0-9_]", "");
}
return value.toString();
}
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.sms.core.client.impl;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.bulk.BulkSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendChampSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.yCloud.YCloudSmsClient;
......@@ -20,12 +21,13 @@ import java.util.concurrent.ConcurrentMap;
/**
* 短信客户端工厂接口
*
* @author zzf
* @author Jayden
*/
@Validated
@Slf4j
public class SmsClientFactoryImpl implements SmsClientFactory {
/**
* 短信客户端 Map
* key:渠道编号,使用 {@link SmsChannelProperties#getId()}
......@@ -41,28 +43,45 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
*/
private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();
/**
* 构造方法,初始化 channelCodeClients 集合
*/
public SmsClientFactoryImpl() {
// 初始化 channelCodeClients 集合
Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
// 创建一个空的 SmsChannelProperties 对象
SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())
.setApiKey("default").setApiSecret("default");
// 创建 Sms 客户端
AbstractSmsClient smsClient = createSmsClient(properties);
channelCodeClients.put(channel.getCode(), smsClient);
});
}
/**
* 根据渠道编号获取短信客户端
*
* @param channelId 渠道编号
* @return 短信客户端
*/
@Override
public SmsClient getSmsClient(Long channelId) {
return channelIdClients.get(channelId);
}
/**
* 根据渠道编码获取短信客户端
*
* @param channelCode 渠道编码
* @return 短信客户端
*/
@Override
public SmsClient getSmsClient(String channelCode) {
return channelCodeClients.get(channelCode);
}
/**
* 创建或更新短信客户端
*
* @param properties 渠道属性
*/
@Override
public void createOrUpdateSmsClient(SmsChannelProperties properties) {
AbstractSmsClient client = channelIdClients.get(properties.getId());
......@@ -75,6 +94,12 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
}
}
/**
* 根据渠道属性创建短信客户端
*
* @param properties 渠道属性
* @return 短信客户端
*/
private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {
SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode());
Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
......@@ -90,6 +115,8 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
return new SendChampSmsClient(properties);
case YCLOUD:
return new YCloudSmsClient(properties);
case BULK:
return new BulkSmsClient(properties);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
......
package cn.iocoder.yudao.framework.sms.core.client.impl.bulk;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.sms.BulksmsHttp;
import cn.iocoder.yudao.framework.http.util.StrUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* SendChamp客户端
*
* @author jayden
*/
@Slf4j
public class BulkSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final BulksmsHttp bulksmsHttp = new BulksmsHttp();
public BulkSmsClient(SmsChannelProperties properties) {
super(properties, new BulkSmsCodeMapping());
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
}
@Override
protected void doInit() {
}
@Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
return null;
}
/**
* 发送短信服务(版本2)
* 该方法用于发送短信,会根据模板内容和参数动态生成短信内容,并处理发送过程
*
* @param logId 日志ID,用于追踪和记录
* @param mobile 手机号码,短信接收方
* @param smsTemplateDTO 短信模板信息,包括模板内容等
* @param templateParams 模板参数,用于替换模板中的占位符
* @return 返回短信发送结果,包括状态码、消息ID等信息
*/
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
SmsCommonResult<SmsSendRespDTO> smsCommonResult;
// 获取并处理短信模板内容
String content = StrUtils.processTemplateContent(smsTemplateDTO, templateParams);
try {
// 设置发送短信所需的参数
Map<String, Object> param = bulksmsHttp.setParams(mobile, content, "ECLogistics", properties.getApiKey(), properties.getApiSecret());
// 发送POST请求
Response response = bulksmsHttp.sendReq(param);
// 解析响应结果
Map<?, ?> result = JSON.parseObject(response.getBody(), Map.class);
smsCommonResult = SmsCommonResult.build((String) result.get("status"), null, null, null, codeMapping);
} catch (Exception e) {
// 记录发送短信时发生的错误
log.error("Error sending SMS: ", e);
// 返回内部服务器错误结果
return SmsCommonResult.build("500", "Internal Server Error", null, null, codeMapping);
}
// 返回请求结果
return smsCommonResult;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
return smsLogDTO;
}
@Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) {
return null;
}
@Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
return SmsCommonResult.build("OK", "", null, data, codeMapping);
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.bulk;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import org.apache.commons.lang3.StringUtils;
/**
* SendChamp SmsCodeMapping 实现类
*
* @author Jayden
*/
public class BulkSmsCodeMapping implements SmsCodeMapping {
@Override
public ErrorCode apply(String apiCode) {
if (StringUtils.isNotBlank(apiCode) && "OK".equals(apiCode)) {
return GlobalErrorCodeConstants.SUCCESS;
}
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
}
}
......@@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.sms.SendChampHttp;
import cn.iocoder.yudao.framework.http.util.StrUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
......@@ -19,11 +20,10 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* SendChamp客户端
*
* @author jayden
*/
@Slf4j
......@@ -31,7 +31,6 @@ public class SendChampSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final SendChampHttp sendchampHttp = new SendChampHttp();
// 定义发送方名称,此处为固定值
public SendChampSmsClient(SmsChannelProperties properties) {
super(properties, new SendChampSmsCodeMapping());
......@@ -45,7 +44,7 @@ public class SendChampSmsClient extends AbstractSmsClient {
}
@Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
return null;
}
......@@ -62,22 +61,8 @@ public class SendChampSmsClient extends AbstractSmsClient {
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
SmsCommonResult<SmsSendRespDTO> smsCommonResult = SmsCommonResult.build("400", "Bad Request", null, null, codeMapping);
// 获取短信模板内容
String content = smsTemplateDTO.getContent();
// 如果有模板参数,则进行参数替换
if (templateParams != null) {
for (KeyValue<String, Object> entry : templateParams) {
// 对键和值进行转义,以确保安全性
String key = escapeKey(entry.getKey());
String value = escapeValue(entry.getValue());
// 构建正则表达式,用于匹配模板中的占位符
String regex = "\\$\\{" + key + "\\}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
// 将匹配到的占位符替换为实际值
content = matcher.replaceAll(value);
}
}
// 获取并处理短信模板内容
String content = StrUtils.processTemplateContent(smsTemplateDTO, templateParams);
try {
// 如果信息超过200字符,需要拆分成多条短信发送
......@@ -89,11 +74,9 @@ public class SendChampSmsClient extends AbstractSmsClient {
Map<String, String> header = sendchampHttp.setHeader("Bearer " + properties.getApiSecret());
// 发送POST请求
Response response = sendchampHttp.postReq(param, header);
// 获取响应体
String result = response.getBody();
// 解析响应结果
SendChampResult sendChampResult = JSON.parseObject(result, SendChampResult.class);
SendChampResult sendChampResult = JSON.parseObject(response.getBody(), SendChampResult.class);
SendChampResult.Data data = sendChampResult.getData();
// 构建发送结果
String id = data.getBusinessId();
......@@ -121,30 +104,6 @@ public class SendChampSmsClient extends AbstractSmsClient {
return smsLogDTO;
}
/**
* 转义键
* 对键进行转义,以防止恶意代码注入
*
* @param key 待转义的键
* @return 转义后的键
*/
private String escapeKey(String key) {
return key.replaceAll("[^a-zA-Z0-9_]", "");
}
/**
* 转义值
* 对值进行转义,以防止恶意代码注入
*
* @param value 待转义的值
* @return 转义后的值
*/
private String escapeValue(Object value) {
if (value instanceof String) {
return ((String) value).replaceAll("[^a-zA-Z0-9_]", "");
}
return value.toString();
}
/**
* 拆分内容
......
......@@ -17,8 +17,9 @@ public enum SmsChannelEnum {
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
YUN_PIAN("YUN_PIAN", "云片"),
ALIYUN("ALIYUN", "阿里云"),
SENDCHAMP("SENDCHAMP","Sendchamp"),
YCLOUD("YCLOUD","Ycloud")
SENDCHAMP("SENDCHAMP", "Sendchamp"),
YCLOUD("YCLOUD", "Ycloud"),
BULK("BULK", "bulk")
// TENCENT("TENCENT", "腾讯云"),
// HUA_WEI("HUA_WEI", "华为云"),
;
......@@ -34,7 +35,9 @@ public enum SmsChannelEnum {
public static SmsChannelEnum getByCode(String code) {
SmsChannelEnum channelEnum = ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
if (null == channelEnum) { return DEBUG_DING_TALK; }
if (null == channelEnum) {
return DEBUG_DING_TALK;
}
return channelEnum;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment