Commit 56d736c3 authored by honghy's avatar honghy

短信功能实作

parent dea7eb34
This diff is collapsed.
......@@ -162,7 +162,7 @@ public class PhoneUtil {
try {
List<DictDataRespDTO> dtos = DictFrameworkUtils.listDictDatasFromCache("phone_number_rule");
if (CollUtil.isNotEmpty(dtos)) {
DictDataRespDTO dto = dtos.stream().filter(d -> d.getValue().trim().equals(code.trim())).findFirst().orElseGet(null);
DictDataRespDTO dto = dtos.stream().filter(d -> d.getValue().trim().equals(code.trim())).findFirst().orElse(null);
if (Objects.nonNull(dto)) {
// 优先获取中文
rule = dto.getLabel();
......
......@@ -4,6 +4,7 @@ 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 org.jetbrains.annotations.NotNull;
import java.util.HashMap;
......@@ -16,52 +17,70 @@ import java.util.Map;
**/
public class SendchampHttp {
private static final String SMS_URL="https://api.sendchamp.com/api/v1/sms/send";
private static final String SMS_URL = "https://api.sendchamp.com/api/v1/sms/send";
public void postReq(Map<String,Object> param,Map<String, String> header){
private static final String RECEIVE_URL = "https://api.sendchamp.com/api/v1/sms/status/";
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);
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));
System.out.println("response code ---------------------------->"+res.getCode());
System.out.println("response content is --------------------》"+res.getBody());
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());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SneakyThrows
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));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@NotNull
private static Map<String , String> setHeader(String apiKey){
Map<String,String> header = new HashMap<>();
header.put("Accept","application/json,text/plain,*/*");
public static Map<String, String> setHeader(String apiKey) {
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json,text/plain,*/*");
header.put("Content-Type", "application/json");
header.put("Authorization", apiKey);
return header;
}
@NotNull
private static Map<String, Object> setParams(String to,String message,String senderName) {
Map<String,Object> param = new HashMap<>();
param.put("to",to);
param.put("message",message);
param.put("sender_name",senderName);
param.put("route","dnd");
public static Map<String, Object> setParams(String to, String message, String senderName) {
Map<String, Object> param = new HashMap<>();
param.put("to", to);
param.put("message", message);
param.put("sender_name", senderName);
param.put("route", "dnd");
return param;
}
public static void main(String[] args){
public static void main(String[] args) {
SendchampHttp test = new SendchampHttp();
String to = "2348140352000";
// String to = "2348140352000";
String to = "8618926674857";
String senderName = "Sendchamp";
//信息如果超过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 = setParams(to,message,senderName);
Map<String, Object> param = setParams(to, message, senderName);
String apiKey = "Bearer sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO";
Map<String,String> header = setHeader(apiKey);
test.postReq(param,header);
Map<String, String> header = setHeader(apiKey);
test.getReceiveStatus(header, "66e6e9df-b454-4df7-a968-af944a535757");
}
......
package cn.iocoder.yudao.framework.http.example;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* @author wuxian
* @since 2024-10-30
* @since 2024-10-30
**/
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 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(){
private void init() {
client = HttpClientFactory.get(Request.Util.OkHttp);
}
@SneakyThrows
public void postWhatsapp(Map<String, String> header,Map<String,Object> param){
Request req = Request.create(WA_URL,Request.Method.POST,param);
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));
System.out.println("response code ---------------------------->"+res.getCode());
System.out.println("response content is --------------------》"+res.getBody());
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());
return res;
}
@SneakyThrows
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));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
}
/**
*
* @param code 验证码
* @param code 验证码
* @param templateName 模板名称
* @param lanCode 语言编号
* @param lanCode 语言编号
* @param to 接收手机号
* @return 参数
*/
private Map<String,Object> setParams(String code,String templateName,String lanCode){
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();
......@@ -69,44 +83,65 @@ public class YCloudWhatsappHttp {
list2.add(components);
list2.add(button);
language.put("code",lanCode);
language.put("policy","deterministic");
language.put("code", lanCode);
language.put("policy", "deterministic");
template.put("language",language);
template.put("name",templateName);
template.put("components",list2);
template.put("language", language);
template.put("name", templateName);
template.put("components", list2);
//构建最终requestBody的json
Map<String, Object> param = new HashMap<>();
param.put("from","8618022485824");
param.put("to","8618102810628");
param.put("type","template");
param.put("template",template);
param.put("from", "8618022485824");
param.put("to", to);
param.put("type", "template");
param.put("template", template);
return param;
}
//构建http请求的头
private Map<String,String> setHeader(String apiKey){
Map<String,String> header = new HashMap<>();
header.put("Accept","application/json");
public Map<String, String> setHeader(String apiKey) {
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json");
header.put("Content-Type", "application/json");
header.put("X-API-Key", apiKey);
return header;
}
public static void main(String[] args){
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));
// 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");
}
}
}
@lombok.Setter
@lombok.Getter
class Parameters{
class Parameters {
private String type;
private String text;
......@@ -122,10 +157,10 @@ class Components {
@lombok.Setter
@lombok.Getter
class Button {
private String type;
private String sub_type;
private String index;
private List<Object> parameters= null;
private String type;
private String sub_type;
private String index;
private List<Object> parameters = null;
}
......
package cn.iocoder.yudao.framework.sms.core.client;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import java.util.List;
......@@ -25,15 +23,27 @@ public interface SmsClient {
/**
* 发送消息
*
* @param logId 日志编号
* @param mobile 手机号
* @param apiTemplateId 短信 API 的模板编号
* @param logId 日志编号
* @param mobile 手机号
* @param apiTemplateId 短信 API 的模板编号
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
* @return 短信发送结果
*/
SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId,
List<KeyValue<String, Object>> templateParams);
/**
* 发送消息
*
* @param logId 日志编号
* @param mobile 手机号
* @param smsTemplateDTO 模板
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
* @return 短信发送结果
*/
SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO,
List<KeyValue<String, Object>> templateParams);
/**
* 解析接收短信的接收结果
*
......@@ -51,4 +61,11 @@ public interface SmsClient {
*/
SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId);
/**
* 查询短信发送状态
*
* @param smsLogDO
* @return
*/
SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO);
}
package cn.iocoder.yudao.framework.sms.core.client.dto;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
import java.util.Map;
/**
* 日志dto
*
* @author Jayden
* @date 2024/11/6
*/
@Data
@ToString(callSuper = true)
public class SmsLogDTO {
/**
* 自增编号
*/
private Long id;
// ========= 渠道相关字段 =========
/**
* 短信渠道编号
* <p>
*/
private Long channelId;
/**
* 短信渠道编码
* <p>
*/
private String channelCode;
// ========= 模板相关字段 =========
/**
* 模板编号
* <p>
*/
private Long templateId;
/**
* 模板编码
* <p>
*/
private String templateCode;
/**
* 短信类型
* <p>
*/
private Integer templateType;
/**
*
*/
private String templateContent;
/**
*
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> templateParams;
/**
* 短信 API 的模板编号
* <p>
*/
private String apiTemplateId;
// ========= 手机相关字段 =========
/**
* 手机号
*/
private String mobile;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
* <p>
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
// ========= 发送相关字段 =========
/**
* 发送状态
* <p>
*/
private Integer sendStatus;
/**
* 发送时间
*/
private Date sendTime;
/**
* 发送结果的编码
* <p>
* 枚举 {@link SmsFrameworkErrorCodeConstants}
*/
private Integer sendCode;
/**
* 发送结果的提示
* <p>
* 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
* 异常情况下,通过格式化 Exception 的提示存储
*/
private String sendMsg;
/**
* 短信 API 发送结果的编码
* <p>
* 由于第三方的错误码可能是字符串,所以使用 String 类型
*/
private String apiSendCode;
/**
* 短信 API 发送失败的提示
*/
private String apiSendMsg;
/**
* 短信 API 发送返回的唯一请求 ID
* <p>
* 用于和短信 API 进行定位于排错
*/
private String apiRequestId;
/**
* 短信 API 发送返回的序号
* <p>
* 用于和短信 API 平台的发送记录关联
*/
private String apiSerialNo;
// ========= 接收相关字段 =========
/**
* 接收状态
* <p>
*/
private Integer receiveStatus;
/**
* 接收时间
*/
private Date receiveTime;
/**
* 短信 API 接收结果的编码
*/
private String apiReceiveCode;
/**
* 短信 API 接收结果的提示
*/
private String apiReceiveMsg;
/**
* 重发的短信日志id
*/
private Long smsLogId;
/**
* 重发次数
*/
private Integer num;
/**
* 短信节点表id
*/
private Long nodeId;
/**
* 节点模板序列号
*/
private Integer nodeTemplateSn;
/**
* 发送类型
*/
private Integer messageType;
}
package cn.iocoder.yudao.framework.sms.core.client.dto;
import lombok.Data;
import lombok.ToString;
/**
* 模板dto
*
* @author Jayden
* @date 2024/11/6
*/
@Data
@ToString(callSuper = true)
public class SmsTemplateDTO {
/**
* 自增编号
*/
private Long id;
// ========= 模板相关字段 =========
/**
* 短信类型
*/
private Integer type;
/**
* 启用状态
*/
private Integer status;
/**
* 模板编码,保证唯一
*/
private String code;
/**
* 模板名称
*/
private String name;
/**
* 模板内容
*/
private String content;
/**
* 备注
*/
private String remark;
/**
* 短信 API 的模板编号
*/
private String apiTemplateId;
// ========= 渠道相关字段 =========
/**
* 短信渠道编号
*/
private Long channelId;
/**
* 短信渠道编码
*/
private String channelCode;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式
*/
private Long transportId;
/**
* 发送类型
*/
private Integer messageType;
/**
* 语言
*/
private String language;
}
......@@ -4,6 +4,8 @@ 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.debug.DebugDingTalkSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.yCloud.YCloudSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -33,7 +35,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
/**
* 短信客户端 Map
* key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()}
*
* <p>
* 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
* 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients}
*/
......@@ -78,9 +80,16 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
// 创建客户端
switch (channelEnum) {
case ALIYUN: return new AliyunSmsClient(properties);
case YUN_PIAN: return new YunpianSmsClient(properties);
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
case ALIYUN:
return new AliyunSmsClient(properties);
case YUN_PIAN:
return new YunpianSmsClient(properties);
case DEBUG_DING_TALK:
return new DebugDingTalkSmsClient(properties);
case SENDCHAMP:
return new SendchampSmsClient(properties);
case YCLOUD:
return new YCloudSmsClient(properties);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
......
......@@ -3,20 +3,21 @@ package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
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 cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.exceptions.ClientException;
......@@ -25,9 +26,11 @@ import com.aliyuncs.profile.IClientProfile;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import io.undertow.util.DateUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
......@@ -56,6 +59,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
*/
private volatile IAcsClient client;
private static final String DELIVERED_STATUS = "DELIVERED";
public AliyunSmsClient(SmsChannelProperties properties) {
super(properties, new AliyunSmsCodeMapping());
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
......@@ -74,9 +79,9 @@ public class AliyunSmsClient extends AbstractSmsClient {
// 构建参数
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(mobile);
if(mobile.startsWith("0086")||mobile.startsWith("86")||mobile.startsWith("+86")||mobile.startsWith("+0086")) {
if (mobile.startsWith("0086") || mobile.startsWith("86") || mobile.startsWith("+86") || mobile.startsWith("+0086")) {
request.setSignName(properties.getSignature());
}else{
} else {
request.setSignName(properties.getSignatureEn());
}
request.setTemplateCode(apiTemplateId);
......@@ -116,10 +121,14 @@ public class AliyunSmsClient extends AbstractSmsClient {
@VisibleForTesting
Integer convertSmsTemplateAuditStatus(Integer templateStatus) {
switch (templateStatus) {
case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
case 0:
return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
case 1:
return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
case 2:
return SmsTemplateAuditStatusEnum.FAIL.getStatus();
default:
throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
}
}
......@@ -150,9 +159,96 @@ public class AliyunSmsClient extends AbstractSmsClient {
return ex.getErrMsg() + " => " + ex.getErrorDescription();
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long sendLogId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
// 构建参数
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(mobile);
String apiTemplateId = smsTemplateDTO.getApiTemplateId();
request.setSignName(properties.getSignature());
request.setTemplateCode(apiTemplateId);
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
request.setOutId(String.valueOf(sendLogId));
// 执行请求
return invoke(request, response -> new SmsSendRespDTO().setSerialNo(response.getBizId()));
}
/**
* 短信接收状态
* 获取短信日志的接收状态
* 通过调用阿里云短信服务的查询接口,更新短信日志的接收状态
*
* @param smsLogDO 短信日志实体,包含查询所需的参数,如手机号、发送时间等
* @return 更新了接收状态的短信日志实体
*/
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
// 创建查询发送详情的请求
QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
// 设置查询参数,包括手机号(去掉国际拨号)、页大小、当前页、发送日期和业务ID
querySendDetailsRequest.setPhoneNumber(removeCountryCode(smsLogDO.getMobile()));
querySendDetailsRequest.setPageSize(1L);
querySendDetailsRequest.setCurrentPage(1L);
querySendDetailsRequest.setSendDate(formatDate(smsLogDO.getSendTime()));
querySendDetailsRequest.setBizId(smsLogDO.getApiSerialNo());
// 初始化查询响应对象
QuerySendDetailsResponse acsResponse = null;
try {
// 发起查询请求并获取响应
acsResponse = client.getAcsResponse(querySendDetailsRequest);
} catch (ClientException e) {
// 如果查询出错,记录日志并返回初始化的短信日志DTO
log.error("Error querying send details: ", e);
return smsLogDTO;
}
// 如果查询成功且有查询结果,更新短信日志的接收状态
if (acsResponse != null && !acsResponse.getSmsSendDetailDTOs().isEmpty()) {
// 获取第一条查询结果
QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO = acsResponse.getSmsSendDetailDTOs().get(0);
// 如果短信发送成功,更新接收状态为已接收到,并设置接收时间
if (DELIVERED_STATUS.equals(smsSendDetailDTO.getErrCode())) {
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
String receiveDate = smsSendDetailDTO.getReceiveDate();
if (receiveDate != null) {
smsLogDTO.setReceiveTime(DateUtils.parseDate(receiveDate));
}
}
}
// 返回更新后的短信日志DTO
return smsLogDTO;
}
/**
* 去掉手机号开头的国家代码 "86"
*
* @param phoneNumber 包含国家代码的手机号
* @return 去掉国家代码后的手机号
*/
public static String removeCountryCode(String phoneNumber) {
// 使用 replaceFirst 方法去掉开头的 "86"
return phoneNumber.replaceFirst("^86", "");
}
/**
* 将 Date 对象格式化为指定格式的字符串
*
* @param date 需要格式化的 Date 对象
* @return 格式化后的字符串,格式为 "yyyyMMdd"
*/
public static String formatDate(Date date) {
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyyMMdd");
return outputFormat.format(date);
}
/**
* 短信接收状态
* <p>
* 参见 https://help.aliyun.com/document_detail/101867.html 文档
*
* @author 捷道源码
......@@ -198,14 +294,14 @@ public class AliyunSmsClient extends AbstractSmsClient {
private String bizId;
/**
* 用户序列号
*
* <p>
* 这里我们传递的是 SysSmsLogDO 的日志编号
*/
@JsonProperty("out_id")
private String outId;
/**
* 短信长度,例如说 1、2、3
*
* <p>
* 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
*/
@JsonProperty("sms_size")
......
......@@ -9,9 +9,7 @@ import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
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.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -93,4 +91,13 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
return SmsCommonResult.build("0", "success", null, data, codeMapping);
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
return null;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
return null;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.example.SendchampHttp;
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.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Sendchamp客户端
*/
@Slf4j
public class SendchampSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final SendchampHttp sendchampHttp = new SendchampHttp();
// 定义发送方名称,此处为固定值
private final String senderName = "Sendchamp";
public SendchampSmsClient(SmsChannelProperties properties) {
super(properties, new SendchampSmsCodeMapping());
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) throws Throwable {
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 = 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);
}
}
try {
// 如果信息超过200字符,需要拆分成多条短信发送
List<String> messages = splitContent(content, 200);
for (String message : messages) {
// 设置发送短信所需的参数
Map<String, Object> param = sendchampHttp.setParams(mobile, message, senderName);
// 设置请求头,包含认证信息
Map<String, String> header = sendchampHttp.setHeader("Bearer " + properties.getApiSecret());
// 发送POST请求
Response response = sendchampHttp.postReq(param, header);
// 获取响应体
String result = response.getBody();
// 解析响应结果
Map<?, ?> responseObj = JsonUtils.parseObject(result, Map.class);
Map<String, Object> data = (Map<String, Object>) responseObj.get("data");
// 构建发送结果
smsCommonResult = SmsCommonResult.build(String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("message")), String.valueOf(data.get("business_id")), null, codeMapping);
smsCommonResult.setData(new SmsSendRespDTO().setSerialNo(String.valueOf(data.get("business_id"))));
}
} 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;
}
/**
* 转义键
* 对键进行转义,以防止恶意代码注入
*
* @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();
}
/**
* 拆分内容
* 将内容拆分成多个部分,每个部分不超过 maxLength 字符
*
* @param content 待拆分的内容
* @param maxLength 每个部分的最大长度
* @return 拆分后的内容列表
*/
private List<String> splitContent(String content, int maxLength) {
List<String> messages = new ArrayList<>();
int start = 0;
while (start < content.length()) {
int end = Math.min(start + maxLength, content.length());
messages.add(content.substring(start, end));
start = end;
}
return messages;
}
@Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
return null;
}
@Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
return SmsCommonResult.build("accepted", "accepted", null, data, codeMapping);
}
/**
* 短信接收状态
* <p>
* @author 捷道源码
*/
@Data
public static class SmsReceiveStatus {
/**
* module
*/
@JsonProperty("module")
private String module;
/**
* reference
*/
@JsonProperty("reference")
private String reference;
/**
* 状态
*/
@JsonProperty("status")
private String status;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp;
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;
/**
* sendchamp SmsCodeMapping 实现类
*/
public class SendchampSmsCodeMapping implements SmsCodeMapping {
@Override
public ErrorCode apply(String apiCode) {
switch (apiCode) {
case "accepted": return GlobalErrorCodeConstants.SUCCESS;
}
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.yCloud;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.example.YCloudWhatsappHttp;
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.client.impl.sendchamp.SendchampSmsCodeMapping;
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 lombok.extern.slf4j.Slf4j;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* ycloud客户端
*
* @author Jayden
* @date 2024/11/7
*/
@Slf4j
public class YCloudSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final YCloudWhatsappHttp yCloudWhatsappHttp = new YCloudWhatsappHttp();
public YCloudSmsClient(SmsChannelProperties properties) {
super(properties, new SendchampSmsCodeMapping());
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) throws Throwable {
return null;
}
/**
* 发送短信V2版本
* 该方法用于发送短信,根据提供的日志ID、手机号码、短信模板和模板参数进行短信发送
* 主要逻辑包括处理模板参数、调用HTTP工具发送短信以及解析响应结果
*
* @param logId 日志ID,用于追踪和记录发送短信的日志
* @param mobile 手机号码,指定短信接收者的电话号码
* @param smsTemplateDTO 短信模板数据传输对象,包含短信模板的相关信息
* @param templateParams 模板参数列表,包含短信模板中的变量和对应的值
* @return 返回一个通用的结果对象,包含短信发送的相关信息和结果状态
*/
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
// 默认验证码为空字符串
String code = "";
// 如果模板参数不为空,则遍历模板参数以获取验证码
if (templateParams != null) {
for (KeyValue<String, Object> entry : templateParams) {
code = (String) entry.getValue();
}
}
// 发送短信并获取响应
Response response = yCloudWhatsappHttp.postWhatsapp(yCloudWhatsappHttp.setHeader(properties.getApiSecret()),
yCloudWhatsappHttp.setParams(code, smsTemplateDTO.getApiTemplateId(), smsTemplateDTO.getLanguage(), mobile));
// 解析结果
Map<String, Object> responseObj = JsonUtils.parseObject(response.getBody(), Map.class);
// 构建短信发送结果对象
SmsCommonResult<SmsSendRespDTO> smsCommonResult = SmsCommonResult.build(String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("id")), null, codeMapping);
// 设置短信发送结果的详细信息
smsCommonResult.setData(new SmsSendRespDTO().setSerialNo(String.valueOf(responseObj.get("id"))));
// 返回短信发送结果
return smsCommonResult;
}
/**
* 获取短信接收状态
* 本方法通过调用第三方API来获取短信的实际接收状态,并更新短信日志DTO中的相关信息
*
* @param smsLogDO 短信日志DTO,包含需要查询接收状态的短信的相关信息
* @return 返回更新了接收状态和接收时间的短信日志DTO
*/
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
try {
// 发送短信并获取响应
Response response = yCloudWhatsappHttp.getReceiveStatus(yCloudWhatsappHttp.setHeader(properties.getApiSecret()), smsLogDO.getApiSerialNo());
// 解析响应内容为Map对象,以便后续处理
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))) {
// 获取送达时间
String deliverTime = (String) responseObj.get("deliverTime");
// 解析并转换送达时间为本地时间
Date localDate = parseAndConvertToLocalDate(deliverTime);
// 更新短信日志DTO的接收时间和接收状态
smsLogDTO.setReceiveTime(localDate);
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
}
} catch (Exception e) {
// 记录异常信息
log.error("Error occurred while getting receive status: ", e);
// 保持默认的接收状态为未接收到
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
}
// 返回更新后的短信日志DTO
return smsLogDTO;
}
/**
* 解析并转换时间为本地时间
* 本方法将给定的ISO 8601格式的日期时间字符串解析为日期对象,并转换为本地时间
*
* @param deliverTime ISO 8601格式的日期时间字符串
* @return 返回转换后的本地时间日期对象
*/
private Date parseAndConvertToLocalDate(String 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
return Date.from(localDateTime.toInstant());
}
@Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) {
throw new UnsupportedOperationException("无回调");
}
@Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
return SmsCommonResult.build("accepted", "accepted", null, data, codeMapping);
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.yCloud;
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;
/**
* ycloud SmsCodeMapping 实现类
*
* @author Jayden
* @date 2024/11/7
*/
public class YCloudSmsCodeMapping implements SmsCodeMapping {
@Override
public ErrorCode apply(String apiCode) {
switch (apiCode) {
case "success": return GlobalErrorCodeConstants.SUCCESS;
}
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
}
}
......@@ -6,9 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
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.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -148,6 +146,16 @@ public class YunpianSmsClient extends AbstractSmsClient {
return sendResult.getMsg() + " => " + sendResult.getDetail();
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
return null;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
return null;
}
/**
* 短信接收状态
*
......
package cn.iocoder.yudao.framework.sms.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 短信接收状态
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum ReceiveStatusEnum {
RECEIVE_STATUS_0("等待结果", 0),
Receive_STATUS_10("接收成功", 10),
Receive_STATUS_20("接收失败", 20);
private String name;
private Integer value;
}
......@@ -17,6 +17,8 @@ public enum SmsChannelEnum {
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
YUN_PIAN("YUN_PIAN", "云片"),
ALIYUN("ALIYUN", "阿里云"),
SENDCHAMP("SENDCHAMP","Sendchamp"),
YCLOUD("YCLOUD","Ycloud")
// TENCENT("TENCENT", "腾讯云"),
// HUA_WEI("HUA_WEI", "华为云"),
;
......
package cn.iocoder.yudao.framework.apollo.core.constants;
/**
* 缓存的key 常量
*
* @author Jayden
* @date 2024/11/6
*/
public class CacheConstants {
public static final String COLON = ":";
/**
* 短信节点表缓存
*/
public static final String SYSTEM_SMS_NODE_KEY = "system_sms_node:";
/**
* 短信节点表缓存
*/
public static final String SYSTEM_SMS_TEMPLATE_KEY = "system_sms_template:";
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.enums;
import java.util.stream.Stream;
public enum TransportTypeEnum {
OTHER(0, "其他"),
OCEAN_LCL(1, "海运拼柜"),
SPECIAL_LINE_AIR_FREIGHT(3, "专线空运");
......@@ -22,6 +23,7 @@ public enum TransportTypeEnum {
public String getName() {
return name;
}
public static TransportTypeEnum parseByValue(int value) {
return Stream.of(values()).filter(e -> e.getValue() == value).findAny().orElse(null);
}
......
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
......@@ -36,9 +33,8 @@ public class AppAuthCheckCodeReqVO {
@Pattern(regexp = "^[0-9]+$", message = "{app.auth.captcha.pattern}")
private String code;
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 SmsSceneEnum 枚举")
@ApiModelProperty(value = "节点")
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
private String nodeValue;
}
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -25,12 +22,27 @@ public class AppAuthSendSmsReqVO {
// @Mobile(message = "incorrect mobile number")
private String mobile;
@ApiModelProperty(value = "发送场景(0注册1登录2修改手机3忘记密码)", example = "1", notes = "对应 SmsSceneEnum 枚举")
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
@ApiModelProperty(value = "业务节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "区号", required = true)
@NotNull(message = "区号不能为空")
private String areaCode;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@ApiModelProperty(value = "运输方式", required = true)
@NotNull(message = "运输方式不能空")
private Integer transportId;
@ApiModelProperty(value = "是否多订单不能为空", required = true)
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@ApiModelProperty(value = "发送类型不能为空", required = true)
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
/**
* @author Administrator
......
......@@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
......@@ -32,8 +32,8 @@ public interface AuthConvert {
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO);
SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthRegReqVO reqVO, Integer scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsNodeEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, String nodeValue, String usedIp);
SmsCodeUseReqDTO convert(AppAuthRegReqVO reqVO, String nodeValue, String usedIp);
}
......@@ -2,11 +2,12 @@ package cn.iocoder.yudao.module.member.service.auth;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.validation.PhoneUtil;
import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
......@@ -31,7 +32,7 @@ import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateSendReqVO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
......@@ -113,7 +114,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Override
public String reg(@Valid AppAuthRegReqVO reqVO, String userIp, String userAgent, Boolean isAutoLogin) {
// 校验验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_REG.getScene(), userIp));
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_REG.getNodeValue(), userIp));
MemberUserDO userByMobile = userService.checkPhoneUnique(reqVO.getAreaCode(), reqVO.getMobile());
if (userByMobile != null) {
......@@ -164,7 +165,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Transactional
public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
// 校验验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_LOGIN.getNodeValue(), userIp));
// 获得获得注册用户
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), reqVO.getAreaCode(), userIp, reqVO.getLoginPlatform());
......@@ -388,7 +389,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());
// 使用验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_FORGET_PASSWORD,
getClientIP()));
// 更新密码
......@@ -430,7 +431,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());
// 使用验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_FORGET_PASSWORD,
getClientIP()));
// 更新密码
......@@ -451,10 +452,10 @@ public class MemberAuthServiceImpl implements MemberAuthService {
public void sendSmsCode(Long userId, AppAuthSendSmsReqVO reqVO) {
String mobileCode = reqVO.getAreaCode() + StrUtil.COLON + reqVO.getMobile();
paramValidatorApi.validatorMobile(mobileCode);
// String mobile = PhoneUtil.formatPhone(mobileCode);
// if (StringUtils.isNotBlank(mobile)) {
// reqVO.setMobile(mobile);
// }
String mobile = PhoneUtil.formatPhone(mobileCode);
if (StringUtils.isNotBlank(mobile)) {
reqVO.setMobile(mobile);
}
// TODO 要根据不同的场景,校验是否有用户
smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
}
......
......@@ -40,7 +40,7 @@ import cn.iocoder.yudao.module.member.vo.userOperationLog.UserOperationLogCreate
import cn.iocoder.yudao.module.product.service.coupon.CouponService;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.excel.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
......@@ -333,7 +333,7 @@ public class MemberUserServiceImpl implements MemberUserService {
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));*/
// 使用新验证码
smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
.setNodeValue(SmsNodeEnum.MEMBER_UPDATE_MOBILE.getNodeValue()).setUsedIp(getClientIP()));
// 更新用户手机
memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).areaCode(reqVO.getAreaCode()).build());
......
......@@ -42,7 +42,7 @@ import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.system.framework.ue.UeProperties;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
......@@ -154,7 +154,7 @@ public class OrderCargoControlServiceImpl extends AbstractService<OrderCargoCont
if (StringUtils.isNotBlank(createReqVO.getCode()) && (StringUtils.isBlank(createReqVO.getControlPassword()) || isPasswordError || isPasswordNull)) {
SmsCodeUseReqDTO smsCodeSendReqDTO = new SmsCodeUseReqDTO();
smsCodeSendReqDTO.setUsedIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.TRANSFER_CONTROL_GOODS.getScene());
smsCodeSendReqDTO.setNodeValue(SmsNodeEnum.TRANSFER_CONTROL_GOODS.getNodeValue());
smsCodeSendReqDTO.setCode(String.valueOf(createReqVO.getCode()));
smsCodeSendReqDTO.setMobile(orgOrderCargoControlDO.getPhone());
smsCodeApi.useSmsCode(smsCodeSendReqDTO);
......@@ -294,7 +294,12 @@ public class OrderCargoControlServiceImpl extends AbstractService<OrderCargoCont
public void sendSmsCode(Long loginUserId, OrderSendSmsReqVO reqVO, MemberUserDO memberUserDO) {
SmsCodeSendReqDTO smsCodeSendReqDTO = new SmsCodeSendReqDTO();
smsCodeSendReqDTO.setCreateIp(getClientIP());
smsCodeSendReqDTO.setScene(reqVO.getScene());
smsCodeSendReqDTO.setNodeValue(reqVO.getNodeValue());
smsCodeSendReqDTO.setIsTransport(reqVO.getIsTransport());
smsCodeSendReqDTO.setTransportId(reqVO.getTransportId());
smsCodeSendReqDTO.setIsOrders(reqVO.getIsOrders());
smsCodeSendReqDTO.setMessageType(reqVO.getMessageType());
OrderCargoControlDO orderCargoControlDO = this.getOne(new LambdaQueryWrapper<OrderCargoControlDO>()
.eq(OrderCargoControlDO::getOrderId, reqVO.getOrderId())
.eq(OrderCargoControlDO::getIsActual, Boolean.TRUE)
......
package cn.iocoder.yudao.module.order.vo.order;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -16,9 +14,21 @@ public class OrderSendSmsReqVO {
@NotNull(message = "{order.id.not.null}")
private Long orderId;
@ApiModelProperty(value = "发送场景(4 控货全转移 5 放货验证短信 6 放货成功通知)", example = "1", notes = "对应 SmsSceneEnum 枚举", required = true)
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
/**
* 发送场景
*/
@NotNull(message = "业务节点不能为空")
private String nodeValue;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
......@@ -57,7 +57,6 @@ public interface RedeemRewardApi {
/**
* 兑换礼品发送验证码
* @param memberId
*/
void sendSmsCode(Long memberId);
void sendSmsCode(RedeemRewardRespDTO reqVO);
}
......@@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("管理后台 - 礼品兑换 Response VO")
public class RedeemRewardRespDTO {
......@@ -14,4 +16,23 @@ public class RedeemRewardRespDTO {
private String msgZh;
@ApiModelProperty(value = "兑换操作详情英文")
private String msgEn;
@ApiModelProperty(value = "会员id")
@NotNull(message = "会员id不能为空")
private Long memberId;
@NotNull(message = "业务节点不能为空")
private String nodeValue;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
......@@ -25,7 +25,7 @@ import cn.iocoder.yudao.module.reward.util.RewardGenCodeUtils;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
......@@ -86,7 +86,7 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
smsCodeUseReqDTO.setMobile(memberUser.getMobile());
smsCodeUseReqDTO.setCode(appRedeemRewardReqVO.getCode());
smsCodeUseReqDTO.setUsedIp(getClientIP());
smsCodeUseReqDTO.setScene(SmsSceneEnum.MEMBER_REDEEM_REWARD.getScene());
smsCodeUseReqDTO.setNodeValue(SmsNodeEnum.MEMBER_REDEEM_REWARD.getNodeValue());
smsCodeApi.useSmsCode(smsCodeUseReqDTO);
}
// 校验收货人信息
......@@ -282,7 +282,8 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
}
@Override
public void sendSmsCode(Long memberId) {
public void sendSmsCode(RedeemRewardRespDTO reqVO) {
Long memberId = reqVO.getMemberId();
UserRespDTO memberUserApiUser = memberUserApi.getUser(memberId);
if (memberUserApiUser == null) {
throw exception(USER_NOT_EXISTS);
......@@ -291,8 +292,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
smsCodeSendReqDTO.setMobile(memberUserApiUser.getMobile());
smsCodeSendReqDTO.setAreaCode(memberUserApiUser.getAreaCode());
smsCodeSendReqDTO.setCreateIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.MEMBER_REDEEM_REWARD.getScene());
smsCodeSendReqDTO.setNodeValue(reqVO.getNodeValue());
smsCodeSendReqDTO.setIsTransport(reqVO.getIsTransport());
smsCodeSendReqDTO.setTransportId(reqVO.getTransportId());
smsCodeSendReqDTO.setIsOrders(reqVO.getIsOrders());
smsCodeSendReqDTO.setMessageType(reqVO.getMessageType());
smsCodeApi.sendSmsCode(smsCodeSendReqDTO);
}
......
......@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.reward.api.reward.RedeemRewardApi;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardRespDTO;
import cn.iocoder.yudao.module.reward.controller.app.redeem.vo.*;
import cn.iocoder.yudao.module.reward.service.redeem.RewardRedeemService;
import cn.iocoder.yudao.module.reward.vo.reward.RewardRedeemPageRespVO;
......@@ -74,8 +75,8 @@ public class AppRedeemRewardController {
@ApiOperation(value = "兑换礼品发送手机验证码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
@Idempotent(timeout = 5)
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppRewardRedeemReqVO reqVO) {
redeemRewardApi.sendSmsCode(reqVO.getMemberId());
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid RedeemRewardRespDTO reqVO) {
redeemRewardApi.sendSmsCode(reqVO);
return success(true);
}
}
package cn.iocoder.yudao.module.shipment.dal.dataobject;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 出货短信记录 DO
......@@ -20,7 +20,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
public class BoxOrderSmsLogDO extends BaseDO {
/**
*
*
*/
@TableId
private Long id;
......@@ -47,7 +47,7 @@ public class BoxOrderSmsLogDO extends BaseDO {
/**
* 场景编码
*/
private Integer scene;
private String nodeValue;
/**
* 说明
*/
......
package cn.iocoder.yudao.module.shipment.dal.mysql;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxOrderSmsLogDO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxOrderSmsLogDO;
import cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog.BoxOrderSmsLogQueryVO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog.*;
import java.util.List;
/**
* 出货短信记录 Mapper
......@@ -25,7 +26,7 @@ public interface BoxOrderSmsLogMapper extends AbstractMapper<BoxOrderSmsLogDO> {
.eqIfPresent(BoxOrderSmsLogDO::getOrderNo, vo.getOrderNo())
.eqIfPresent(BoxOrderSmsLogDO::getMarks, vo.getMarks())
.eqIfPresent(BoxOrderSmsLogDO::getSelfNo, vo.getSelfNo())
.eqIfPresent(BoxOrderSmsLogDO::getScene, vo.getScene())
.eqIfPresent(BoxOrderSmsLogDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(BoxOrderSmsLogDO::getTemplateCode, vo.getTemplateCode())
.eqIfPresent(BoxOrderSmsLogDO::getDescription, vo.getDescription())
.betweenIfPresent(BoxOrderSmsLogDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
......@@ -45,7 +46,7 @@ public interface BoxOrderSmsLogMapper extends AbstractMapper<BoxOrderSmsLogDO> {
.eqIfPresent(BoxOrderSmsLogDO::getOrderNo, vo.getOrderNo())
.eqIfPresent(BoxOrderSmsLogDO::getMarks, vo.getMarks())
.eqIfPresent(BoxOrderSmsLogDO::getSelfNo, vo.getSelfNo())
.eqIfPresent(BoxOrderSmsLogDO::getScene, vo.getScene())
.eqIfPresent(BoxOrderSmsLogDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(BoxOrderSmsLogDO::getTemplateCode, vo.getTemplateCode())
.eqIfPresent(BoxOrderSmsLogDO::getDescription, vo.getDescription())
.betweenIfPresent(BoxOrderSmsLogDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
......
......@@ -35,7 +35,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxPreloadSection.BoxLoadSectionBackV
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxGuanlianOrderBackVO;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxOrderLocationUpdateReq;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyBackVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.springframework.context.annotation.Description;
import javax.validation.Valid;
......@@ -141,11 +141,11 @@ public interface BoxService extends IService<BoxDO> {
* 发送短信
*
* @param shipment 出货单
* @param smsSceneEnum 场景
* @param smsNodeEnum 场景
*/
void sendSms(Long shipment, SmsSceneEnum smsSceneEnum);
void sendSms(Long shipment, SmsNodeEnum smsNodeEnum);
void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsSceneEnum smsSceneEnum);
void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsNodeEnum smsNodeEnum);
/**
* 新增销售日志
......@@ -809,4 +809,4 @@ public interface BoxService extends IService<BoxDO> {
* @param shipmentId 出货单ID
*/
void clearUnloadCabinet(Long shipmentId);
}
\ No newline at end of file
}
......@@ -33,7 +33,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutCreateRe
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutPDAVO;
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutUpdateReqVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.BeanUtils;
......@@ -74,7 +74,7 @@ public class BoxAirCheckoutServiceImpl extends AbstractService<BoxAirCheckoutMap
@Resource
private WarehouseService warehouseService;
@Resource
@Lazy
private BoxPreloadGoodsService boxPreloadGoodsService;
......@@ -168,7 +168,7 @@ public class BoxAirCheckoutServiceImpl extends AbstractService<BoxAirCheckoutMap
//出仓添加业绩日志
boxService.addTargetLog(shipmentId, TargetLogEnum.LOADING,checkoutTime);
// 出仓短信通知
boxService.sendSms(shipmentId, SmsSceneEnum.AIR_SHIPMENT_WAREHOUSE);
boxService.sendSms(shipmentId, SmsNodeEnum.AIR_SHIPMENT_WAREHOUSE);
//插入操作日志
boxOpLogService.createBoxOpLog(boxDO.getId(), checkout.getStatus(), checkout.getText(), checkout.getTextEn(), null);
}
......
package cn.iocoder.yudao.module.shipment.service.boxArrival;
import java.util.*;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
import cn.iocoder.yudao.module.shipment.convert.BoxArrivalConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxArrivalDO;
......@@ -16,15 +16,17 @@ import cn.iocoder.yudao.module.shipment.service.boxOpLog.BoxOpLogService;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalCreateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalUpdateReqVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -101,11 +103,7 @@ public class BoxArrivalServiceImpl extends AbstractService<BoxArrivalMapper, Box
//确认到港
//发送短信通知
if (StringUtils.equals("3", boxDO.getTransportType())) {
boxService.sendSms(shipmentId, SmsSceneEnum.AIR_SHIPMENT_ARRIVAL);
} else {
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_ARRIVAL);
}
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_ARRIVAL);
//修改订单状态为已到港
......
package cn.iocoder.yudao.module.shipment.service.boxArrivalAir;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.order.dal.dataobject.order.OrderDO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
import cn.iocoder.yudao.module.order.service.order.OrderQueryService;
......@@ -13,7 +12,6 @@ import cn.iocoder.yudao.module.shipment.convert.BoxArrivalAirConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxArrivalAirDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.boxArrivalOrder.BoxArrivalOrderDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.boxClearanceOrder.BoxClearanceOrderDO;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxArrivalAirMapper;
import cn.iocoder.yudao.module.shipment.enums.*;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
......@@ -25,6 +23,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxArrivalAir.BoxArrivalAirQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalAir.BoxArrivalAirUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalOrder.BoxArrivalOrderAllCreateVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalOrder.BoxArrivalOrderCreateVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
......@@ -32,9 +31,10 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -152,6 +152,8 @@ public class BoxArrivalAirServiceImpl extends AbstractService<BoxArrivalAirMappe
.eq(BoxArrivalOrderDO::getShipmentId, shipmentId)
);
if (arrivalCount == arrivalOrders.size()) {
//发送短信通知
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_ARRIVAL);
SapStatueEnum sapStatueEnum = SapStatueEnum.HAS_ARRIVE;
boxDO.setSapStatus(sapStatueEnum.getSapStatus());
boxDO.setDgDate(actTime);
......
......@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.shipment.service.boxCabinet;
import java.util.*;
import javax.annotation.Resource;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.order.enums.TargetLogEnum;
......@@ -11,7 +10,6 @@ import cn.iocoder.yudao.module.shipment.convert.BoxCabinetConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxBookSeaDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxCabinetDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxPreloadGoodsDO;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxBookSeaMapper;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxCabinetMapper;
import cn.iocoder.yudao.module.shipment.enums.*;
......@@ -20,8 +18,7 @@ import cn.iocoder.yudao.module.shipment.service.boxOpLog.BoxOpLogService;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetCreateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxPreloadGoods.BoxPreloadGoodsQueryVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
......@@ -166,7 +163,7 @@ public class BoxCabinetServiceImpl extends AbstractService<BoxCabinetMapper, Box
boxService.addTargetLog(shipmentId, TargetLogEnum.LOADING, ldBoxTime);
//发送短信
try {
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_CABINET);
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_CABINET);
} catch (Exception e) {
log.error(e.getMessage());
}
......
package cn.iocoder.yudao.module.shipment.service.boxClearance;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.bpm.enums.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.order.dal.dataobject.order.OrderDO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
......@@ -28,7 +27,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxClearance.BoxClearanceQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearance.BoxClearanceUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearanceOrder.BoxClearanceOrderAllCreateVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearanceOrder.BoxClearanceOrderCreateVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.base.Joiner;
import org.apache.commons.collections4.CollectionUtils;
......@@ -36,9 +35,10 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -118,7 +118,7 @@ public class BoxClearanceServiceImpl extends AbstractService<BoxClearanceMapper,
if (clClearTime != null) {
boxOpLogService.createBoxOpLog(shipmentId, ClStatueEnum.HAS_CLEARANCE.getClStatus(), "已清关,待到仓", "", null);
//发送清关短信
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_CUSTOMS_CLEARANCE);
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_CUSTOMS_CLEARANCE);
}
//修改订单状态为已清关
......@@ -274,7 +274,7 @@ public class BoxClearanceServiceImpl extends AbstractService<BoxClearanceMapper,
if (clearTime != null) {
boxOpLogService.createBoxOpLog(shipmentId, ClStatueEnum.HAS_CLEARANCE.getClStatus(), "已清关,待到仓", "", null);
//发送清关短信
boxService.sendSms(shipmentId, clearanceOrders, SmsSceneEnum.AIR_SHIPMENT_CUSTOMS_CLEARANCE);
boxService.sendSms(shipmentId, clearanceOrders, SmsNodeEnum.SHIPMENT_CUSTOMS_CLEARANCE);
}
//修改订单状态为已清关
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
......@@ -40,9 +42,9 @@ public class BoxOrderSmsLogBackVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ExcelProperty("场景编码")
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ExcelProperty("节点")
@ApiModelProperty(value = "节点")
private String nodeValue;
@ExcelProperty("说明")
@ApiModelProperty(value = "说明")
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 出货短信记录 Base VO,提供给添加、修改、详细的子 VO 使用
......@@ -27,8 +25,8 @@ public class BoxOrderSmsLogBaseVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "说明")
private String templateCode;
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
......@@ -26,8 +28,8 @@ public class BoxOrderSmsLogQueryVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "说明")
private String templateCode;
......
package cn.iocoder.yudao.module.shipment.controller.admin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.framework.limiter.dynamic.DynamicRateLimiter;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.module.order.vo.order.OrderBackPageVO;
import cn.iocoder.yudao.module.order.vo.orderSplit.OrderSplitBackVO;
import cn.iocoder.yudao.module.shipment.convert.BoxConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
import cn.iocoder.yudao.module.shipment.vo.box.*;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxGuanlianOrderBackVO;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxOrderLocationUpdateReq;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyBackVO;
import cn.iocoder.yudao.module.system.api.file.FileMakeApi;
import cn.iocoder.yudao.module.system.api.file.dto.FileMakeReqDTO;
import cn.iocoder.yudao.module.system.enums.download.DownloadTypeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.io.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.module.shipment.controller.admin.constant.Constant.BOX_UPDATE_KEY;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_UPDATE_REPEAT_COMMIT;
import cn.iocoder.yudao.module.shipment.vo.box.*;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
@Validated
@RestController
@Api(tags = "管理后台 - 出货")
......@@ -323,8 +321,8 @@ public class BoxController {
@ApiImplicitParam(name = "shipmentId", value = "出货单ID", required = true, example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "scene", value = "场景编号", required = true, example = "20", dataTypeClass = String.class)
})
public CommonResult<Boolean> shipmentSmsSend(@RequestParam("shipmentId") Long shipmentId, @RequestParam("scene") Integer scene) {
boxService.sendSms(shipmentId, SmsSceneEnum.getCodeByScene(scene));
public CommonResult<Boolean> shipmentSmsSend(@RequestParam("shipmentId") Long shipmentId, @RequestParam("scene") String scene) {
boxService.sendSms(shipmentId, SmsNodeEnum.getCodeByScene(scene));
return success(true);
}
......
package cn.iocoder.yudao.module.system.api.sms;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import javax.validation.Valid;
......@@ -21,6 +22,16 @@ public interface SmsSendApi {
*/
Long sendSingleSmsToAdmin(@Valid SmsSendSingleToUserReqDTO reqDTO);
/**
* 发送单条短信给 Admin 用户
*
* 在 mobile 为空时,使用 userId 加载对应 Admin 的手机号
*
* @param reqDTO 发送请求
* @return 发送日志编号
*/
Long sendSingleSmsToAdminV2(@Valid SmsSendSingleToUserReqDTOV2 reqDTO);
/**
* 发送单条短信给 Member 用户
*
......
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -26,8 +24,7 @@ public class SmsCodeCheckReqDTO {
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
private String nodeValue;
/**
* 验证码
*/
......
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -25,9 +23,8 @@ public class SmsCodeSendReqDTO {
/**
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
@NotNull(message = "业务节点不能为空")
private String nodeValue;
/**
* 发送 IP
*/
......@@ -36,4 +33,16 @@ public class SmsCodeSendReqDTO {
@NotNull(message = "国家区号不能为空")
private String areaCode;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -26,8 +24,7 @@ public class SmsCodeUseReqDTO {
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
private String nodeValue;
/**
* 验证码
*/
......
package cn.iocoder.yudao.module.system.api.sms.dto.send;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Map;
/**
* 短信发送给 Admin 或者 Member 用户
*
* @author 捷道源码
*/
@Data
public class SmsSendSingleToUserReqDTOV2 {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
@NotNull(message = "手机号不能为空")
private String mobile;
@NotNull(message = "节点不能为空")
private String nodeValue;
@NotNull(message = "区号不能为空")
private String areaCode;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
/**
* 短信模板参数
*/
private Map<String, Object> templateParams;
}
......@@ -89,6 +89,9 @@ public interface ErrorCodeConstants {
ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002013001, "模板参数({})缺失");
ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002013002, "短信模板不存在");
// ========== 短信节点 1002013100 ==========
ErrorCode SMS_NODE_NOT_EXISTS = new ErrorCode(1002013100, "短信节点不存在");
// ========== 短信验证码 1002014000 ==========
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002014000, "验证码不存在");
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002014001, "验证码已过期");
......
package cn.iocoder.yudao.module.system.enums.sms;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.util.json.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 国家编号枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsCountryCodeEnum {
SMS_COUNTRY_CODE_0("全部", "0"),
SMS_COUNTRY_CODE_1("其他", "1");
private String name;
private String value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 是否多订单枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsIsOrdersEnum {
SMS_ORDERS_0("否", 0),
SMS_ORDERS_1("是", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 是否匹配运输方式枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsIsTransportEnum {
SMS_ORDERS_0("否", 0),
SMS_ORDERS_1("是", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 发送类型枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsMessageTypeEnum {
SMS_MESSAGE_TYPE_1("短信", 1),
SMS_MESSAGE_TYPE_2("whatsapp", 2),
SMS_MESSAGE_TYPE_3("email", 3);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户短信验证码发送场景的枚举
*
* @author 捷道源码
*/
@Getter
@AllArgsConstructor
public enum SmsNodeEnum {
MEMBER_REG("user-sms-reg", "user-sms-reg", "会员用户 - 手机号注册"),//测了
MEMBER_LOGIN("user-sms-login", "user-sms-login", "会员用户 - 手机号登陆"),//测了
MEMBER_UPDATE_MOBILE("user-sms-update-mobile", "user-sms-update-mobile", "会员用户 - 解绑手机"),//测了
MEMBER_FORGET_PASSWORD("user-sms-reset-password", "user-sms-reset-password", "会员用户 - 忘记密码"),//测了
TRANSFER_CONTROL_GOODS("transfer-control-goods", "transfer-control-goods", "订单 - 控货权转移"),//测了
DELIVERY_VERIFICATION_SMS("delivery-verification-sms", "delivery-verification-sms", "订单 - 放货验证短信"),//测了
NOTIFICATION_SUCCESS_DELIVERY("notification-successful-delivery", "notification-successful-delivery", "订单 - 放货成功通知"),//测了
NOTIFICATION_SUCCESS_CANCEL_DELIVERY("notification-successful-cancel-delivery", "notification-successful-cancel-delivery", "订单 - 取消放货通知"),//测了
// NOTIFICATION_SUCCESS_SEASONING_CONDIMENTS("notification-successful-seasoning-condiments", "notification-successful-seasoning-condiments", "订单 - 调货通知"),//暂时没发现调用
MEMBER_REDEEM_REWARD("user-sms-redeem-reward", "user-sms-redeem-reward", "会员用户 - 兑换礼品"),
// SHIPMENT_ARRIVALS("shipment-arrivals", "shipment-arrivals", "出货 - 到港通知(多订单)"),//
// SHIPMENT_CUSTOMS_CLEARANCES("shipment-customs-clearances", "shipment-customs-clearances", "出货 - 清关通知(多订单)"),//
// SHIPMENT_CABINETS("shipment-cabinets", "shipment-cabinets", "出货 - 装柜通知(多订单)"),//
SHIPMENT_ARRIVAL("shipment-arrival", "shipment-arrival", "出货 - 到港通知"),//h
SHIPMENT_CUSTOMS_CLEARANCE("shipment-customs-clearance", "shipment-customs-clearance", "出货 - 清关通知"),//h
SHIPMENT_CABINET("shipment-cabinet", "shipment-cabinet", "出货 - 装柜通知"),//h
SHIPMENT_CLOSE_CONTAINER("shipment-close-container", "shipment-close-container", "出货 - 封柜反审"),//好像没发信息
WAREHOUSE_IN_CONTROL("warehouse-in-control", "warehouse-in-control", "入仓控货通知"),//h
WAREHOUSE_IN("warehouse-in", "warehouse-in", "入仓通知"),//h
WAREHOUSE_IN_APPEND("warehouse-in-append", "warehouse-in-append", "追加通知"),//k
WAREHOUSE_IN_CONTROL_COLLECT_OF_GOODS("warehouse-in-control-collect-of-goods", "warehouse-in-control-collect-of-goods", "入仓-控货代收货款"),//无此功能
WAREHOUSE_IN_COLLECT_OF_GOODS("warehouse-in-collect-of-goods", "warehouse-in-collect-of-goods", "入仓-非控货代收货款"),//h
/** 空运新增模板 **/
// AIR_WAREHOUSE_IN_CONTROL("air-warehouse-in-control", "air-warehouse-in-control", "入仓控货通知"),//x
// AIR_WAREHOUSE_IN("air-warehouse-in", "air-warehouse-in", "入仓通知"),//x
// AIR_SHIPMENT_ARRIVAL("air-shipment-arrival", "air-shipment-arrival", "出货 - 到港通知"),//x
// AIR_SHIPMENT_ARRIVALS("air-shipment-arrivals", "air-shipment-arrivals", "出货 - 到港通知(多订单)"),//x
// AIR_SHIPMENT_CUSTOMS_CLEARANCE("air-shipment-customs-clearance", "air-shipment-customs-clearance", "出货 - 清关通知"),//
// AIR_SHIPMENT_CUSTOMS_CLEARANCES("air-shipment-customs-clearances", "air-shipment-customs-clearances", "出货 - 清关通知(多订单)"),//
AIR_SHIPMENT_WAREHOUSE("air-shipment-warehouse", "air-shipment-warehouse", "出货 - 出仓通知"),//
// AIR_SHIPMENT_WAREHOUSES("air-shipment-warehouses", "air-shipment-warehouses", "出货 - 出仓通知(多订单)"),//
// AIR_WAREHOUSE_IN_COLLECT_OF_GOODS("air-warehouse-in-collect-of-goods", "air-warehouse-in-collect-of-goods", "入仓-非控货代收货款"),//
;
/**
* 验证场景的编号
*/
private final String nodeValue;
/**
* 模版编码
*/
private final String templateCode;
/**
* 描述
*/
private final String description;
public static SmsNodeEnum getCodeByScene(String scene) {
return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getNodeValue().equals(scene),
values());
}
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 系统状态枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SystemStatusEnum {
SYSTEM_STATUS_0("开启", 0),
SYSTEM_STATUS_1("关闭", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.api.sms;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
......@@ -25,6 +26,13 @@ public class SmsSendApiImpl implements SmsSendApi {
reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAreaCode());
}
@Override
public Long sendSingleSmsToAdminV2(SmsSendSingleToUserReqDTOV2 reqDTO) {
return smsSendService.sendSingleSmsToAdminV2(reqDTO.getMobile(), reqDTO.getUserId(),
reqDTO.getNodeValue(), reqDTO.getAreaCode(), reqDTO.getIsOrders(), reqDTO.getIsTransport(),
reqDTO.getTransportId(), reqDTO.getMessageType(), reqDTO.getTemplateParams());
}
@Override
public Long sendSingleSmsToMember(SmsSendSingleToUserReqDTO reqDTO) {
return smsSendService.sendSingleSmsToMember(reqDTO.getMobile(), reqDTO.getUserId(),
......
......@@ -2,13 +2,17 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
import cn.iocoder.yudao.module.system.service.sms.SmsLogService;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
......@@ -27,6 +31,9 @@ public class SmsCallbackController {
@Resource
private SmsSendService smsSendService;
@Autowired
private SmsLogService smsLogService;
@PostMapping("/yunpian")
@ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档")
@ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = String.class)
......@@ -46,4 +53,13 @@ public class SmsCallbackController {
return success(true);
}
@PostMapping("/sendchamp")
@ApiOperation(value = "sendchamp短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
@OperateLog(enable = false)
public CommonResult<Boolean> receiveSendchampSmsStatus(HttpServletRequest request) throws Throwable {
String text = ServletUtil.getBody(request);
SendchampSmsClient.SmsReceiveStatus smsReceiveStatus = JsonUtils.parseObject(text, SendchampSmsClient.SmsReceiveStatus.class);
smsLogService.updateSendchampReceive(smsReceiveStatus);
return success(true);
}
}
package cn.iocoder.yudao.module.system.controller.admin.sms;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.system.convert.sms.SmsNodeConvert;
import cn.iocoder.yudao.module.system.service.sms.SmsNodeService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
@Validated
@RestController
@Api(tags = "管理后台 - 短信节点")
@RequestMapping("/system/sms-node")
public class SmsNodeController {
@Resource
private SmsNodeService smsNodeService;
@PostMapping("/create")
@ApiOperation("创建短信节点")
@PreAuthorize("@ss.hasPermission('system:sms-node:create')")
public CommonResult<Long> createSmsNode(@Valid @RequestBody SmsNodeCreateReqVO createReqVO) {
return success(smsNodeService.createSmsNode(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新短信节点")
@PreAuthorize("@ss.hasPermission('system:sms-node:update')")
public CommonResult<Boolean> updateSmsNode(@Valid @RequestBody SmsNodeUpdateReqVO updateReqVO) {
smsNodeService.updateSmsNode(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除短信节点")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:delete')")
public CommonResult<Boolean> deleteSmsNode(@RequestParam("id") Long id) {
smsNodeService.deleteSmsNode(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得短信节点")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<SmsNodeBackVO> getSmsNode(@RequestParam("id") Long id) {
SmsNodeDO smsNode = smsNodeService.getSmsNode(id);
return success(SmsNodeConvert.INSTANCE.convert(smsNode));
}
@GetMapping("/list")
@ApiOperation("获得短信节点列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<List<SmsNodeBackVO>> getSmsNodeList(@RequestParam("ids") Collection<Long> ids) {
List<SmsNodeDO> list = smsNodeService.getSmsNodeList(ids);
return success(SmsNodeConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得短信节点分页")
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<PageResult<SmsNodeBackVO>> getSmsNodePage(@Valid SmsNodeQueryVO query, PageVO page) {
PageResult<SmsNodeDO> pageResult = smsNodeService.getSmsNodePage(query, page);
return success(SmsNodeConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@ApiOperation("导出短信节点 Excel")
@PreAuthorize("@ss.hasPermission('system:sms-node:export')")
@OperateLog(type = EXPORT)
public void exportSmsNodeExcel(@Valid SmsNodeQueryVO query,
HttpServletResponse response) throws IOException {
List<SmsNodeDO> list = smsNodeService.getSmsNodeList(query);
// 导出 Excel
List<SmsNodeBackVO> datas = SmsNodeConvert.INSTANCE.convertList(list);
ExcelUtils.write(response, "短信节点.xls", "数据", SmsNodeBackVO.class, datas);
}
}
......@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.excel.convert.DictConvert;
import cn.iocoder.yudao.framework.excel.convert.JsonConvert;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
......@@ -107,4 +108,23 @@ public class SmsLogExcelVO {
*/
@ExcelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ExcelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ExcelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ExcelProperty(value = "发送类型")
private Integer messageType;
}
......@@ -55,4 +55,24 @@ public class SmsLogExportReqVO {
@ApiModelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "短信 API 发送返回的序号")
private String apiSerialNo;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.log;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -62,4 +63,21 @@ public class SmsLogPageReqVO extends PageParam {
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
}
......@@ -97,4 +97,22 @@ public class SmsLogRespVO {
@ApiModelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.convert.DictConvert;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 短信节点 Response VO
*
* @author jayden
*/
@Data
@ApiModel("管理后台 - 短信节点 Response VO")
public class SmsNodeBackVO {
@ExcelProperty("编号")
@ApiModelProperty(value = "编号", required = true)
private Long id;
@ExcelProperty("节点")
@ApiModelProperty(value = "节点", required = true)
private String nodeValue;
@ExcelProperty(value = "运输方式", converter = DictConvert.class)
@DictFormat("transport_type") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
@ApiModelProperty(value = "运输方式", required = true)
private Long transportId;
@ExcelProperty("国家区号")
@ApiModelProperty(value = "国家区号", required = true)
private Long countryId;
@ExcelProperty("国家区号")
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致", required = true)
private String countryCode;
@ExcelProperty("启用状态")
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)", required = true)
private Integer status;
@ExcelProperty("模板1")
@ApiModelProperty(value = "模板1", required = true)
private Long templateIdOne;
@ExcelProperty("模板2")
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ExcelProperty("模板3")
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ExcelProperty("模板4")
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@ExcelProperty("创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ExcelProperty("是否匹配运输方式")
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ExcelProperty("多订单")
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
/**
* 短信节点 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SmsNodeBaseVO {
@ApiModelProperty(value = "节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "运输方式ID", required = true)
@NotNull(message = "运输方式ID不能为空")
private Long transportId;
@ApiModelProperty(value = "国家区号id", required = true)
@NotNull(message = "国家区号id不能为空")
private Long countryId;
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致", required = true)
@NotNull(message = "国家区号,和订单中国家区号保持一致不能为空")
private String countryCode;
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)", required = true)
@NotNull(message = "启用状态(0:启用,1:关闭)不能为空")
private Integer status;
@ApiModelProperty(value = "模板1", required = true)
@NotNull(message = "模板1不能为空")
private Long templateIdOne;
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel("管理后台 - 短信节点创建 Request VO")
public class SmsNodeCreateReqVO extends SmsNodeBaseVO {
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
@ApiModel("管理后台 - 短信节点查询 VO")
public class SmsNodeQueryVO {
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式ID")
private Long transportId;
@ApiModelProperty(value = "国家区号id")
private Long countryId;
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致")
private String countryCode;
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)")
private Integer status;
@ApiModelProperty(value = "模板1")
private Long templateIdOne;
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 短信节点更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsNodeUpdateReqVO extends SmsNodeBaseVO {
@ApiModelProperty(value = "编号", required = true)
@NotNull(message = "编号不能为空")
private Long id;
}
......@@ -6,9 +6,9 @@ import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 短信模板 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
* 短信模板 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SmsTemplateBaseVO {
......@@ -43,11 +43,25 @@ public class SmsTemplateBaseVO {
@NotNull(message = "短信渠道编号不能为空")
private Long channelId;
@ApiModelProperty(value = "短信 API 的英文模板编号", required = true, example = "4383920")
@NotNull(message = "短信 API 的英文模板编号不能为空")
private String apiTemplateIdEn;
// @ApiModelProperty(value = "短信 API 的英文模板编号", required = true, example = "4383920")
// @NotNull(message = "短信 API 的英文模板编号不能为空")
// private String apiTemplateIdEn;
//
// @ApiModelProperty(value = "英文模板内容", required = true, example = "你好,{name}。你长的太{like}啦!")
// @NotNull(message = "英文模板内容不能为空")
// private String contentEn;
@ApiModelProperty(value = "英文模板内容", required = true, example = "你好,{name}。你长的太{like}啦!")
@NotNull(message = "英文模板内容不能为空")
private String contentEn;
@ApiModelProperty(value = "节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "运输方式", required = true)
@NotNull(message = "运输方式不能为空")
private Long transportId;
@ApiModelProperty(value = "发送类型", required = true)
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
......@@ -52,9 +52,21 @@ public class SmsTemplateExcelVO {
@ExcelProperty("创建时间")
private Date createTime;
@ExcelProperty("英文模板内容")
private String contentEn;
// @ExcelProperty("英文模板内容")
// private String contentEn;
//
// @ExcelProperty("短信 API 的英文模板编号")
// private String apiTemplateIdEn;
@ExcelProperty("短信 API 的英文模板编号")
private String apiTemplateIdEn;
@ExcelProperty(value = "节点")
private String nodeValue;
@ExcelProperty(value = "运输方式")
private Long transportId;
@ExcelProperty(value = "发送类型")
private Integer messageType;
@ExcelProperty(value = "语言")
private String language;
}
......@@ -44,4 +44,16 @@ public class SmsTemplateExportReqVO {
@ApiModelProperty(value = "短信 API 的英文模板编号", example = "4383920", notes = "模糊匹配")
private String apiTemplateIdEn;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式")
private Long transportId;
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
......@@ -49,4 +49,16 @@ public class SmsTemplatePageReqVO extends PageParam {
@ApiModelProperty(value = "短信 API 的英文模板编号", example = "4383920", notes = "模糊匹配")
private String apiTemplateIdEn;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式")
private Long transportId;
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
package cn.iocoder.yudao.module.system.convert.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsLogDTO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
......@@ -21,6 +22,8 @@ public interface SmsLogConvert {
SmsLogRespVO convert(SmsLogDO bean);
SmsLogDTO toDto(SmsLogDO smsLogDO);
List<SmsLogRespVO> convertList(List<SmsLogDO> list);
PageResult<SmsLogRespVO> convertPage(PageResult<SmsLogDO> page);
......
package cn.iocoder.yudao.module.system.convert.sms;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeBackVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 短信节点 Convert
* @author jayden
*/
@Mapper
public interface SmsNodeConvert {
/*****转换MapStruct*****/
SmsNodeConvert INSTANCE = Mappers.getMapper(SmsNodeConvert.class);
/***
* 创建VO转实体
* @param bean
* @return
*/
SmsNodeDO convert(SmsNodeCreateReqVO bean);
/***
* 修改VO转实体
* @param bean
* @return
*/
SmsNodeDO convert(SmsNodeUpdateReqVO bean);
/***
* 实体转返回VO
* @param bean
* @return
*/
SmsNodeBackVO convert(SmsNodeDO bean);
/***
* 实体列表转返回VO列表
* @param list
* @return
*/
List<SmsNodeBackVO> convertList(List<SmsNodeDO> list);
/***
* 实体分页转返回分页
* @param page
* @return
*/
PageResult<SmsNodeBackVO> convertPage(PageResult<SmsNodeDO> page);
}
package cn.iocoder.yudao.module.system.convert.sms;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateRespVO;
......@@ -20,6 +21,8 @@ public interface SmsTemplateConvert {
SmsTemplateDO convert(SmsTemplateUpdateReqVO bean);
SmsTemplateDTO toDto(SmsTemplateDO smsTemplateDO);
SmsTemplateRespVO convert(SmsTemplateDO bean);
List<SmsTemplateRespVO> convertList(List<SmsTemplateDO> list);
......
......@@ -34,11 +34,10 @@ public class SmsCodeDO extends BaseDO {
*/
private String code;
/**
* 发送场景
* 业务节点
*
* 枚举 {@link SmsCodeDO}
*/
private Integer scene;
private String nodeValue;
/**
* 创建 IP
*/
......
......@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import java.util.Date;
......@@ -37,13 +38,13 @@ public class SmsLogDO extends BaseDO {
/**
* 短信渠道编号
*
* <p>
* 关联 {@link SmsChannelDO#getId()}
*/
private Long channelId;
/**
* 短信渠道编码
*
* <p>
* 冗余 {@link SmsChannelDO#getCode()}
*/
private String channelCode;
......@@ -52,19 +53,19 @@ public class SmsLogDO extends BaseDO {
/**
* 模板编号
*
* <p>
* 关联 {@link SmsTemplateDO#getId()}
*/
private Long templateId;
/**
* 模板编码
*
* <p>
* 冗余 {@link SmsTemplateDO#getCode()}
*/
private String templateCode;
/**
* 短信类型
*
* <p>
* 冗余 {@link SmsTemplateDO#getType()}
*/
private Integer templateType;
......@@ -79,7 +80,7 @@ public class SmsLogDO extends BaseDO {
private Map<String, Object> templateParams;
/**
* 短信 API 的模板编号
*
* <p>
* 冗余 {@link SmsTemplateDO#getApiTemplateId()}
*/
private String apiTemplateId;
......@@ -96,7 +97,7 @@ public class SmsLogDO extends BaseDO {
private Long userId;
/**
* 用户类型
*
* <p>
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
......@@ -105,7 +106,7 @@ public class SmsLogDO extends BaseDO {
/**
* 发送状态
*
* <p>
* 枚举 {@link SmsSendStatusEnum}
*/
private Integer sendStatus;
......@@ -115,20 +116,20 @@ public class SmsLogDO extends BaseDO {
private Date sendTime;
/**
* 发送结果的编码
*
* <p>
* 枚举 {@link SmsFrameworkErrorCodeConstants}
*/
private Integer sendCode;
/**
* 发送结果的提示
*
* <p>
* 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
* 异常情况下,通过格式化 Exception 的提示存储
*/
private String sendMsg;
/**
* 短信 API 发送结果的编码
*
* <p>
* 由于第三方的错误码可能是字符串,所以使用 String 类型
*/
private String apiSendCode;
......@@ -138,13 +139,13 @@ public class SmsLogDO extends BaseDO {
private String apiSendMsg;
/**
* 短信 API 发送返回的唯一请求 ID
*
* <p>
* 用于和短信 API 进行定位于排错
*/
private String apiRequestId;
/**
* 短信 API 发送返回的序号
*
* <p>
* 用于和短信 API 平台的发送记录关联
*/
private String apiSerialNo;
......@@ -153,7 +154,7 @@ public class SmsLogDO extends BaseDO {
/**
* 接收状态
*
* <p>
* 枚举 {@link SmsReceiveStatusEnum}
*/
private Integer receiveStatus;
......@@ -179,5 +180,19 @@ public class SmsLogDO extends BaseDO {
*/
private Integer num;
/**
* 短信节点表id
*/
private Long nodeId;
/**
* 节点模板序列号
*/
private Integer nodeTemplateSn;
/**
* 发送类型
*/
private Integer messageType;
}
package cn.iocoder.yudao.module.system.dal.dataobject.sms;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 短信节点 DO
*
* @author jayden
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("system_sms_node")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsNodeDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式ID
*
*/
private Integer transportId;
/**
* 国家区号id
*/
private Long countryId;
/**
* 国家区号,和订单中国家区号保持一致
*/
private String countryCode;
/**
* 启用状态(0:启用,1:关闭)
*/
private Integer status;
/**
* 模板1
*/
private Long templateIdOne;
/**
* 模板2
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdTwo;
/**
* 模板3
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdThree;
/**
* 模板4
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdFour;
/**
* 是否匹配运输方式(0:否,1:是)
*/
private Integer isTransport;
/**
* 多订单(0:否,1:是)
*/
private Integer isOrders;
}
......@@ -95,5 +95,20 @@ public class SmsTemplateDO extends BaseDO {
* 短信 API 的英文模板编号
*/
private String apiTemplateIdEn;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式
*/
private Long transportId;
/**
* 发送类型
*/
private Integer messageType;
/**
* 语言
*/
private String language;
}
......@@ -16,13 +16,13 @@ public interface SmsCodeMapper extends BaseMapperX<SmsCodeDO> {
* 获得手机号的最后一个手机验证码
*
* @param mobile 手机号
* @param scene 发送场景,选填
* @param nodeValue 发送场景,选填
* @param code 验证码 选填
* @return 手机验证码
*/
default SmsCodeDO selectLastByMobile(String mobile, String code, Integer scene) {
default SmsCodeDO selectLastByMobile(String mobile, String code, String nodeValue) {
return selectOne(new QueryWrapperX<SmsCodeDO>()
.eq("mobile", mobile).eqIfPresent("scene", scene).eqIfPresent("code", code)
.eq("mobile", mobile).eqIfPresent("node_value", nodeValue).eqIfPresent("code", code)
.between("create_time", DateUtil.beginOfDay(new Date()), DateUtil.endOfDay(new Date()))
.orderByDesc("id").last(SqlConstants.LIMIT1));
}
......
package cn.iocoder.yudao.module.system.dal.mysql.sms;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
......@@ -18,6 +19,8 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
return selectPage(reqVO, new QueryWrapperX<SmsLogDO>()
.eqIfPresent("channel_id", reqVO.getChannelId())
.eqIfPresent("template_id", reqVO.getTemplateId())
.eqIfPresent("node_id", reqVO.getNodeId())
.eqIfPresent("node_template_sn", reqVO.getNodeTemplateSn())
.likeIfPresent("mobile", reqVO.getMobile())
.eqIfPresent("send_status", reqVO.getSendStatus())
.betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
......@@ -30,11 +33,14 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
return selectList(new QueryWrapperX<SmsLogDO>()
.eqIfPresent("channel_id", reqVO.getChannelId())
.eqIfPresent("template_id", reqVO.getTemplateId())
.eqIfPresent("node_id", reqVO.getNodeId())
.eqIfPresent("node_template_sn", reqVO.getNodeTemplateSn())
.likeIfPresent("mobile", reqVO.getMobile())
.eqIfPresent("send_status", reqVO.getSendStatus())
.betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
.eqIfPresent("receive_status", reqVO.getReceiveStatus())
.betweenIfPresent("receive_time", reqVO.getBeginReceiveTime(), reqVO.getEndReceiveTime())
.eq(StringUtils.isNotBlank(reqVO.getApiSerialNo()),"api_serial_no",reqVO.getApiSerialNo())
.orderByDesc("id"));
}
......@@ -42,4 +48,11 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
"DELETE FROM system_sms_log WHERE date(create_time) <= date(date_sub(now(), interval 90 day))"
})
int clearLog(Integer day);
default List<SmsLogDO> selectReceiveList() {
return selectList(new QueryWrapperX<SmsLogDO>()
.apply("create_time >= NOW() - INTERVAL 20 HOUR")
.eq("template_type", 2)
.lt("num", 1)
.ne("receive_status", 10));
}
}
package cn.iocoder.yudao.module.system.dal.mysql.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeQueryVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 短信节点 Mapper
*
* @author jayden
*/
@Mapper
public interface SmsNodeMapper extends AbstractMapper<SmsNodeDO> {
@Override
default PageResult<SmsNodeDO> selectPage(PageVO page, Object object) {
if (object instanceof SmsNodeQueryVO) {
SmsNodeQueryVO vo = (SmsNodeQueryVO) object;
return selectPage(page, new LambdaQuery<SmsNodeDO>()
.eq(StringUtils.isNotBlank(vo.getNodeValue()), SmsNodeDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(SmsNodeDO::getTransportId, vo.getTransportId())
.eqIfPresent(SmsNodeDO::getCountryId, vo.getCountryId())
.eqIfPresent(SmsNodeDO::getCountryCode, vo.getCountryCode())
.eqIfPresent(SmsNodeDO::getIsTransport, vo.getIsTransport())
.eqIfPresent(SmsNodeDO::getIsOrders, vo.getIsOrders())
.eqIfPresent(SmsNodeDO::getStatus, vo.getStatus())
.eqIfPresent(SmsNodeDO::getTemplateIdOne, vo.getTemplateIdOne())
.eqIfPresent(SmsNodeDO::getTemplateIdTwo, vo.getTemplateIdTwo())
.eqIfPresent(SmsNodeDO::getTemplateIdThree, vo.getTemplateIdThree())
.eqIfPresent(SmsNodeDO::getTemplateIdFour, vo.getTemplateIdFour())
.betweenIfPresent(SmsNodeDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
.orderByDesc(SmsNodeDO::getId));
}
return null;
}
@Override
default List<SmsNodeDO> selectList(Object object) {
if (object instanceof SmsNodeQueryVO) {
SmsNodeQueryVO vo = (SmsNodeQueryVO) object;
return selectList(new LambdaQuery<SmsNodeDO>()
.eq(StringUtils.isNotBlank(vo.getNodeValue()), SmsNodeDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(SmsNodeDO::getTransportId, vo.getTransportId())
.eqIfPresent(SmsNodeDO::getCountryId, vo.getCountryId())
.eqIfPresent(SmsNodeDO::getCountryCode, vo.getCountryCode())
.eqIfPresent(SmsNodeDO::getIsTransport, vo.getIsTransport())
.eqIfPresent(SmsNodeDO::getIsOrders, vo.getIsOrders())
.eqIfPresent(SmsNodeDO::getStatus, vo.getStatus())
.eqIfPresent(SmsNodeDO::getTemplateIdOne, vo.getTemplateIdOne())
.eqIfPresent(SmsNodeDO::getTemplateIdTwo, vo.getTemplateIdTwo())
.eqIfPresent(SmsNodeDO::getTemplateIdThree, vo.getTemplateIdThree())
.eqIfPresent(SmsNodeDO::getTemplateIdFour, vo.getTemplateIdFour())
.betweenIfPresent(SmsNodeDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
.orderByDesc(SmsNodeDO::getId));
}
return null;
}
}
......@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
......@@ -26,6 +27,10 @@ public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
return selectPage(reqVO, new LambdaQueryWrapperX<SmsTemplateDO>()
.eqIfPresent(SmsTemplateDO::getType, reqVO.getType())
.eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus())
.eq(StringUtils.isNotBlank(reqVO.getNodeValue()), SmsTemplateDO::getNodeValue, reqVO.getNodeValue())
.eqIfPresent(SmsTemplateDO::getTransportId, reqVO.getTransportId())
.eqIfPresent(SmsTemplateDO::getMessageType, reqVO.getMessageType())
.eqIfPresent(SmsTemplateDO::getLanguage, reqVO.getLanguage())
.likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode())
.likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent())
.likeIfPresent(SmsTemplateDO::getContentEn, reqVO.getContentEn())
......@@ -40,6 +45,10 @@ public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
return selectList(new LambdaQueryWrapperX<SmsTemplateDO>()
.eqIfPresent(SmsTemplateDO::getType, reqVO.getType())
.eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus())
.eq(StringUtils.isNotBlank(reqVO.getNodeValue()), SmsTemplateDO::getNodeValue, reqVO.getNodeValue())
.eqIfPresent(SmsTemplateDO::getTransportId, reqVO.getTransportId())
.eqIfPresent(SmsTemplateDO::getMessageType, reqVO.getMessageType())
.eqIfPresent(SmsTemplateDO::getLanguage, reqVO.getLanguage())
.likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode())
.likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent())
.likeIfPresent(SmsTemplateDO::getApiTemplateId, reqVO.getApiTemplateId())
......
package cn.iocoder.yudao.module.system.mq.consumer.sms;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SmsSendMessage} 的消费者
*
* @author zzf
*/
@Component
@Slf4j
public class SmsSendConsumer extends AbstractStreamMessageListener<SmsSendMessage> {
@Resource
private SmsSendService smsSendService;
@Override
public void onMessage(SmsSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
smsSendService.doSendSms(message);
}
}
//package cn.iocoder.yudao.module.system.mq.consumer.sms;
//
//import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
//import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
//import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.stereotype.Component;
//
//import javax.annotation.Resource;
//
///**
// * 针对 {@link SmsSendMessage} 的消费者
// *
// * @author zzf
// */
//@Component
//@Slf4j
//public class SmsSendConsumer extends AbstractStreamMessageListener<SmsSendMessage> {
//
// @Resource
// private SmsSendService smsSendService;
//
// @Override
// public void onMessage(SmsSendMessage message) {
// log.info("[onMessage][消息内容({})]", message);
// smsSendService.doSendSms(message);
// }
//
//}
package cn.iocoder.yudao.module.system.mq.consumer.sms;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SmsSendMessage} 的消费者
*
* @author zzf
*/
@Component
@Slf4j
public class SmsSendConsumerV2 extends AbstractStreamMessageListener<SmsSendMessageV2> {
@Resource
private SmsSendService smsSendService;
@Override
public void onMessage(SmsSendMessageV2 message) {
log.info("[onMessageV2][消息内容({})]", message);
smsSendService.doSendSmsV2(message);
}
}
package cn.iocoder.yudao.module.system.mq.message.sms;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 短信发送消息
*
* @author 捷道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsSendMessageV2 extends AbstractStreamMessage {
/**
* 短信日志编号
*/
@NotNull(message = "短信日志编号不能为空")
private Long logId;
/**
* 手机号
*/
@NotNull(message = "手机号不能为空")
private String mobile;
/**
* 模板
*/
@NotNull(message = "模板不能为空")
private SmsTemplateDTO smsTemplateDTO;
/**
* 短信模板参数
*/
private List<KeyValue<String, Object>> templateParams;
@Override
public String getStreamKey() {
return "system.sms.send";
}
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.mq.producer.sms;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import lombok.extern.slf4j.Slf4j;
......@@ -56,4 +57,7 @@ public class SmsProducer {
redisMQTemplate.send(message);
}
public void sendSmsSendMessageV2(SmsSendMessageV2 message) {
redisMQTemplate.send(message);
}
}
package cn.iocoder.yudao.module.system.service.mail.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
import cn.iocoder.yudao.module.system.service.sms.SmsCodeService;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* 短信验证码 Service 实现类
*
* @author 捷道源码
*/
@Service
@Validated
public class EmailCodeServiceImpl implements EmailCodeService {
@Resource
private SmsCodeProperties smsCodeProperties;
@Resource
private SmsCodeMapper smsCodeMapper;
@Resource
private SmsSendService smsSendService;
@Override
public void sendEmailCode(SmsCodeSendReqDTO reqDTO) {
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// 创建验证码
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
// 发送验证码
smsSendService.sendSingleSms(reqDTO.getAreaCode()+reqDTO.getMobile(), null, null,
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
}
private String createSmsCode(String mobile, Integer scene, String ip) {
// 校验是否可以发送验证码,不用筛选场景
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿:提升,每个 IP 每天可发送数量
// TODO 芋艿:提升,每个 IP 每小时可发送数量
}
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
}
@Override
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
// 检测验证码是否有效
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// 使用验证码
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
}
@Override
public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
}
public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
// 校验验证码
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
}
// 超过时间
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
throw ServiceExceptionUtil.exception(SMS_CODE_EXPIRED);
}
// 判断验证码是否已被使用
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
throw ServiceExceptionUtil.exception(SMS_CODE_USED);
}
return lastSmsCode;
}
}
//package cn.iocoder.yudao.module.system.service.mail.impl;
//
//import cn.hutool.core.lang.Assert;
//import cn.hutool.core.map.MapUtil;
//import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
//import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
//import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
//import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
//import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
//import cn.iocoder.yudao.module.system.service.sms.SmsCodeService;
//import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
//import org.springframework.stereotype.Service;
//import org.springframework.validation.annotation.Validated;
//
//import javax.annotation.Resource;
//import java.util.Date;
//
//import static cn.hutool.core.util.RandomUtil.randomInt;
//import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
//
///**
// * 短信验证码 Service 实现类
// *
// * @author 捷道源码
// */
//@Service
//@Validated
//public class EmailCodeServiceImpl implements EmailCodeService {
//
// @Resource
// private SmsCodeProperties smsCodeProperties;
//
// @Resource
// private SmsCodeMapper smsCodeMapper;
//
// @Resource
// private SmsSendService smsSendService;
//
// @Override
// public void sendEmailCode(SmsCodeSendReqDTO reqDTO) {
// SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
// Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// // 创建验证码
// String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
// // 发送验证码
// smsSendService.sendSingleSms(reqDTO.getAreaCode()+reqDTO.getMobile(), null, null,
// sceneEnum.getTemplateCode(), MapUtil.of("code", code));
// }
//
// private String createSmsCode(String mobile, Integer scene, String ip) {
// // 校验是否可以发送验证码,不用筛选场景
// SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
// if (lastSmsCode != null) {
// if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
// throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
// }
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
// throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
// }
// // TODO 芋艿:提升,每个 IP 每天可发送数量
// // TODO 芋艿:提升,每个 IP 每小时可发送数量
// }
//
// // 创建验证码记录
// String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
// SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
// .scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
// .createIp(ip).used(false).build();
// smsCodeMapper.insert(newSmsCode);
// return code;
// }
//
// @Override
// public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
// // 检测验证码是否有效
// SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// // 使用验证码
// smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
// .used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
// }
//
// @Override
// public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
// checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// }
//
// public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
// // 校验验证码
// SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// // 若验证码不存在,抛出异常
// if (lastSmsCode == null) {
// throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
// }
// // 超过时间
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
// throw ServiceExceptionUtil.exception(SMS_CODE_EXPIRED);
// }
// // 判断验证码是否已被使用
// if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
// throw ServiceExceptionUtil.exception(SMS_CODE_USED);
// }
// return lastSmsCode;
// }
//
//}
package cn.iocoder.yudao.module.system.service.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* @author Jayden
* @date 2024/11/12
*/
public class QuerySendDetails {
/**
* <b>description</b> :
* <p>使用AK&amp;SK初始化账号Client</p>
*
* @return Client
* @throws Exception
*/
public static IAcsClient createClient() throws Exception {
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。
String key = "LTAIh5ooNeMRzsuz";
String secret = "jdqgtCP2Z0nZllEcxMiX8pv8qUDOTq";
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", key, secret);
IAcsClient defaultAcsClient = new DefaultAcsClient(profile);
return defaultAcsClient;
}
public static void main(String[] args_) throws Exception {
String key = "LTAIh5ooNeMRzsuz";
String secret = "jdqgtCP2Z0nZllEcxMiX8pv8qUDOTq";
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", key, secret);
IAcsClient client = new DefaultAcsClient(profile);
QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
querySendDetailsRequest.setPhoneNumber("233591972886");
querySendDetailsRequest.setPageSize(1L);
querySendDetailsRequest.setCurrentPage(1L);
querySendDetailsRequest.setSendDate("20240905");
querySendDetailsRequest.setBizId("373707825513736449^0");
QuerySendDetailsResponse acsResponse = client.getAcsResponse(querySendDetailsRequest);
List<QuerySendDetailsResponse.SmsSendDetailDTO> smsSendDetailDTOs = acsResponse.getSmsSendDetailDTOs();
String receiveDate = smsSendDetailDTOs.get(0).getReceiveDate();
System.out.println(convertDateFormat(receiveDate));
}
/**
* 将日期字符串从 "yyyy-MM-dd HH:mm:ss" 格式转换为 "yyyyMMdd" 格式
*
* @param inputDateStr 输入的日期字符串,格式为 "yyyy-MM-dd HH:mm:ss"
* @return 转换后的日期字符串,格式为 "yyyyMMdd"
*/
public static String convertDateFormat(String inputDateStr) {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyyMMdd");
try {
// 解析输入的日期字符串
Date date = inputFormat.parse(inputDateStr);
// 格式化日期为输出格式
return outputFormat.format(date);
} catch (ParseException e) {
// 处理解析异常
e.printStackTrace();
return null;
}
}
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.web.config.BusinessProperties;
......@@ -9,14 +8,12 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
......@@ -44,38 +41,38 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()){
// SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
// Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// // TODO debug业务模式下不需要生成发送验证码,固定使用9999
// return;
// }
// 创建验证码
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getNodeValue(), reqDTO.getCreateIp());
// 发送验证码
smsSendService.sendSingleSms(reqDTO.getAreaCode() + reqDTO.getMobile(), null, null,
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
smsSendService.sendSingleSmsV2(reqDTO.getAreaCode() + reqDTO.getMobile(), null, null, reqDTO.getNodeValue(), reqDTO.getAreaCode(), reqDTO.getIsOrders(), reqDTO.getIsTransport(),
reqDTO.getTransportId(), reqDTO.getMessageType(), MapUtil.of("code", code), null, 1);
}
private String createSmsCode(String mobile, Integer scene, String ip) {
private String createSmsCode(String mobile, String nodeValue, String ip) {
// 校验是否可以发送验证码,不用筛选场景
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿:提升,每个 IP 每天可发送数量
// TODO 芋艿:提升,每个 IP 每小时可发送数量
}
// if (lastSmsCode != null) {
// if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
// throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
// }
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
// throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
// }
// // TODO 芋艿:提升,每个 IP 每天可发送数量
// // TODO 芋艿:提升,每个 IP 每小时可发送数量
// }
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.nodeValue(nodeValue).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
......@@ -84,20 +81,20 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
if (Objects.equals(reqDTO.getCode(), "9999")) {
return;
}
if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// TODO debug业务模式下不需要生成发送验证码,固定使用9999
if (Objects.equals(reqDTO.getCode(), "9999")) {
return;
} else {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
}
}
// if (Objects.equals(reqDTO.getCode(), "9999")) {
// return;
// }
//
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// // TODO debug业务模式下不需要生成发送验证码,固定使用9999
// if (Objects.equals(reqDTO.getCode(), "9999")) {
// return;
// } else {
// throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
// }
// }
// 检测验证码是否有效
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getNodeValue());
// 使用验证码
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
......@@ -105,12 +102,12 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getNodeValue());
}
public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
public SmsCodeDO checkSmsCode0(String mobile, String code, String nodeValue) {
// 校验验证码
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, nodeValue);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
......
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
......@@ -36,6 +37,27 @@ public interface SmsLogService {
Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId);
/**
* 创建短信日志
*
* @param mobile 手机号
* @param userId 用户编号
* @param userType 用户类型
* @param isSend 是否发送
* @param template 短信模板
* @param templateContent 短信内容
* @param templateParams 短信参数
* @param apiTemplateId 短信 API 的模板编号
* @param smsLogId 重发短信时的原短信日志id
* @param nodeId 节点id
* @param nodeTemplateSn 节点模板序列号
* @param messageType 发送类型
* @return 发送日志编号
*/
Long createSmsLogV2(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId,
Long nodeId, Integer nodeTemplateSn, Integer messageType);
/**
* 更新日志的发送结果
*
......@@ -90,4 +112,11 @@ public interface SmsLogService {
void updateBatchSmsLog(List<SmsLogDO> resendSmsLogList);
List<SmsLogDO> selectReceiveList();
/**
* 更新sendchamp回调状态
* @param smsReceiveStatus
*/
void updateSendchampReceive(SendchampSmsClient.SmsReceiveStatus smsReceiveStatus);
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
......@@ -55,6 +57,57 @@ public class SmsLogServiceImpl implements SmsLogService {
return logDO.getId();
}
/**
* 创建短信日志
*
* @param mobile 手机号
* @param userId 用户编号
* @param userType 用户类型
* @param isSend 是否发送
* @param template 短信模板
* @param templateContent 短信内容
* @param templateParams 短信参数
* @param apiTemplateId 短信 API 的模板编号
* @param smsLogId 重发短信时的原短信日志id
* @param nodeId 节点id
* @param nodeTemplateSn 节点模板序列号
* @param messageType 发送类型
* @return 发送日志编号
*/
@Override
public Long createSmsLogV2(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId,
Long nodeId, Integer nodeTemplateSn, Integer messageType) {
SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder();
// 根据是否要发送,设置状态
logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus()
: SmsSendStatusEnum.IGNORE.getStatus());
// 设置手机相关字段
logBuilder.mobile(mobile).userId(userId).userType(userType);
// 设置模板相关字段
logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());
logBuilder.templateContent(templateContent).templateParams(templateParams)
.apiTemplateId(apiTemplateId);
// 设置渠道相关字段
logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());
// 设置接收相关字段
logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus());
// 重发短信的原短信日志id
logBuilder.smsLogId(smsLogId);
// 默认重发次数是0
logBuilder.num(0);
// 节点id
logBuilder.nodeId(nodeId);
// 节点模板序列号
logBuilder.nodeTemplateSn(nodeTemplateSn);
// 默认重发次数是0
logBuilder.messageType(messageType);
// 发送类型
SmsLogDO logDO = logBuilder.build();
smsLogMapper.insert(logDO);
return logDO.getId();
}
@Override
public void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
String apiSendCode, String apiSendMsg,
......@@ -100,4 +153,22 @@ public class SmsLogServiceImpl implements SmsLogService {
public void updateBatchSmsLog(List<SmsLogDO> resendSmsLogList) {
smsLogMapper.updateBatch(resendSmsLogList);
}
@Override
public List<SmsLogDO> selectReceiveList() {
return smsLogMapper.selectReceiveList();
}
@Override
public void updateSendchampReceive(SendchampSmsClient.SmsReceiveStatus smsReceiveStatus) {
if ("delivered".equals(smsReceiveStatus.getStatus())) {
List<SmsLogDO> smsLogDOS = smsLogMapper.selectList(new SmsLogExportReqVO().setApiSerialNo(smsReceiveStatus.getReference()));
if (smsLogDOS != null) {
SmsLogDO smsLogDO = smsLogDOS.get(0);
smsLogDO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
smsLogDO.setReceiveTime(new Date());
smsLogMapper.updateById(smsLogDO);
}
}
}
}
package cn.iocoder.yudao.module.system.service.sms;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
/**
* 短信节点 Service 接口
*
* @author jayden
*/
public interface SmsNodeService extends IService<SmsNodeDO> {
/**
* 创建短信节点
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSmsNode(@Valid SmsNodeCreateReqVO createReqVO);
/**
* 更新短信节点
*
* @param updateReqVO 更新信息
*/
void updateSmsNode(@Valid SmsNodeUpdateReqVO updateReqVO);
/**
* 删除短信节点
*
* @param id 编号
*/
void deleteSmsNode(Long id);
/**
* 获得短信节点
*
* @param id 编号
* @return 短信节点
*/
SmsNodeDO getSmsNode(Long id);
/**
* 获得短信节点列表
*
* @param ids 编号
* @return 短信节点列表
*/
List<SmsNodeDO> getSmsNodeList(Collection<Long> ids);
/**
* 获得短信节点分页
*
* @param page 分页查询
* @param query 查询
* @return 短信节点分页
*/
PageResult<SmsNodeDO> getSmsNodePage(SmsNodeQueryVO query, PageVO page);
/**
* 获得短信节点列表, 用于 Excel 导出
*
* @param query 查询
* @return 短信节点列表
*/
List<SmsNodeDO> getSmsNodeList(SmsNodeQueryVO query);
/**
* 构建缓存键
* 根据SmsNodeDO对象的属性生成一个唯一的缓存键
* 这是为了确保每个配置项在缓存中都有一个唯一的标识
*
* @param smsNodeDO SmsNodeDO 对象
* @return 缓存键
*/
String buildCacheKey(SmsNodeDO smsNodeDO);
}
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