短信管理单元测试完善

This commit is contained in:
gaoqr
2025-10-14 09:03:58 +08:00
parent d8592dfce9
commit d6a60de583
42 changed files with 855 additions and 1480 deletions
@@ -3,7 +3,6 @@ package com.cf.imes.module.system.api.sms;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
import com.cf.imes.module.system.service.sms.SmsCodeService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -31,10 +30,4 @@ public class SmsCodeApiImpl implements SmsCodeApi {
return success(true);
}
@Override
public CommonResult<Boolean> validateSmsCode(SmsCodeValidateReqDTO reqDTO) {
smsCodeService.validateSmsCode(reqDTO);
return success(true);
}
}
@@ -1,32 +0,0 @@
package com.cf.imes.module.system.api.sms;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import com.cf.imes.module.system.service.sms.SmsSendService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class SmsSendApiImpl implements SmsSendApi {
@Resource
private SmsSendService smsSendService;
@Override
public CommonResult<Long> sendSingleSmsToAdmin(SmsSendSingleToUserReqDTO reqDTO) {
return success(smsSendService.sendSingleSmsToAdmin(reqDTO.getMobile(), reqDTO.getUserId(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams()));
}
@Override
public CommonResult<Long> sendSingleSmsToMember(SmsSendSingleToUserReqDTO reqDTO) {
return success(smsSendService.sendSingleSmsToMember(reqDTO.getMobile(), reqDTO.getUserId(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams()));
}
}
@@ -1,82 +0,0 @@
package com.cf.imes.module.system.controller.admin.sms;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelRespVO;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelSimpleRespVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
import com.cf.imes.module.system.service.sms.SmsChannelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.Comparator;
import java.util.List;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 短信渠道")
@RestController
@RequestMapping("system/sms-channel")
public class SmsChannelController {
@Resource
private SmsChannelService smsChannelService;
@PostMapping("/create")
@Operation(summary = "创建短信渠道")
@PreAuthorize("@ss.hasPermission('system:sms-channel:create')")
public CommonResult<Long> createSmsChannel(@Valid @RequestBody SmsChannelSaveReqVO createReqVO) {
return success(smsChannelService.createSmsChannel(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新短信渠道")
@PreAuthorize("@ss.hasPermission('system:sms-channel:update')")
public CommonResult<Boolean> updateSmsChannel(@Valid @RequestBody SmsChannelSaveReqVO updateReqVO) {
smsChannelService.updateSmsChannel(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除短信渠道")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:sms-channel:delete')")
public CommonResult<Boolean> deleteSmsChannel(@RequestParam("id") Long id) {
smsChannelService.deleteSmsChannel(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得短信渠道")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:sms-channel:query')")
public CommonResult<SmsChannelRespVO> getSmsChannel(@RequestParam("id") Long id) {
SmsChannelDO channel = smsChannelService.getSmsChannel(id);
return success(BeanUtils.toBean(channel, SmsChannelRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得短信渠道分页")
@PreAuthorize("@ss.hasPermission('system:sms-channel:query')")
public CommonResult<PageResult<SmsChannelRespVO>> getSmsChannelPage(@Valid SmsChannelPageReqVO pageVO) {
PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(pageVO);
return success(BeanUtils.toBean(pageResult, SmsChannelRespVO.class));
}
@GetMapping({"/list-all-simple", "/simple-list"})
@Operation(summary = "获得短信渠道精简列表", description = "包含被禁用的短信渠道")
public CommonResult<List<SmsChannelSimpleRespVO>> getSimpleSmsChannelList() {
List<SmsChannelDO> list = smsChannelService.getSmsChannelList();
list.sort(Comparator.comparing(SmsChannelDO::getId));
return success(BeanUtils.toBean(list, SmsChannelSimpleRespVO.class));
}
}
@@ -1,5 +1,6 @@
package com.cf.imes.module.system.controller.admin.sms;
import com.cf.imes.framework.common.enums.UserTypeEnum;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.pojo.PageParam;
import com.cf.imes.framework.common.pojo.PageResult;
@@ -96,7 +97,7 @@ public class SmsTemplateController {
@Operation(summary = "发送短信")
@PreAuthorize("@ss.hasPermission('system:sms-template:send-sms')")
public CommonResult<Long> sendSms(@Valid @RequestBody SmsTemplateSendReqVO sendReqVO) {
return success(smsSendService.sendSingleSmsToAdmin(sendReqVO.getMobile(), null,
return success(smsSendService.sendSingleSms(sendReqVO.getMobile(), null, UserTypeEnum.ADMIN.getValue(),
sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
}
@@ -1,30 +0,0 @@
package com.cf.imes.module.system.controller.admin.sms.vo.channel;
import com.cf.imes.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 短信渠道分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsChannelPageReqVO extends PageParam {
@Schema(description = "任务状态", example = "1")
private Integer status;
@Schema(description = "短信签名,模糊匹配", example = "晨丰科技")
private String signature;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}
@@ -1,45 +0,0 @@
package com.cf.imes.module.system.controller.admin.sms.vo.channel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 短信渠道 Response VO")
@Data
public class SmsChannelRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "短信签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰科技")
@NotNull(message = "短信签名不能为空")
private String signature;
@Schema(description = "渠道编码,参见 SmsChannelEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "YUN_PIAN")
private String code;
@Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer status;
@Schema(description = "备注", example = "好吃!")
private String remark;
@Schema(description = "短信 API 的账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng")
@NotNull(message = "短信 API 的账号不能为空")
private String apiKey;
@Schema(description = "短信 API 的密钥", example = "yuanma")
private String apiSecret;
@Schema(description = "短信发送回调 URL", example = "https://www.cf.com")
@URL(message = "回调 URL 格式不正确")
private String callbackUrl;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -1,42 +0,0 @@
package com.cf.imes.module.system.controller.admin.sms.vo.channel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - 短信渠道创建/修改 Request VO")
@Data
public class SmsChannelSaveReqVO {
@Schema(description = "编号", example = "1024")
private Long id;
@Schema(description = "短信签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰科技")
@NotNull(message = "短信签名不能为空")
private String signature;
@Schema(description = "渠道编码,参见 SmsChannelEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "YUN_PIAN")
@NotNull(message = "渠道编码不能为空")
private String code;
@Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer status;
@Schema(description = "备注", example = "好吃!")
private String remark;
@Schema(description = "短信 API 的账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng")
@NotNull(message = "短信 API 的账号不能为空")
private String apiKey;
@Schema(description = "短信 API 的密钥", example = "yuanma")
private String apiSecret;
@Schema(description = "短信发送回调 URL", example = "http://www.cf.com")
@URL(message = "回调 URL 格式不正确")
private String callbackUrl;
}
@@ -1,19 +0,0 @@
package com.cf.imes.module.system.controller.admin.sms.vo.channel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 短信渠道精简 Response VO")
@Data
public class SmsChannelSimpleRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "短信签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰科技")
private String signature;
@Schema(description = "渠道编码,参见 SmsChannelEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "YUN_PIAN")
private String code;
}
@@ -3,8 +3,8 @@ package com.cf.imes.module.system.controller.admin.sms.vo.template;
import com.cf.imes.module.system.validation.common.CommonStatus;
import com.cf.imes.module.system.validation.sms.SmsTemplateTypeValid;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import jakarta.validation.constraints.NotNull;
@@ -27,26 +27,26 @@ public class SmsTemplateSaveReqVO {
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "模板编码不能为空")
@Length(min = 1, max = 64, message = "模板编码长度不能超过64个字符")
@Size(min = 1, max = 64, message = "模板编码长度不能超过64个字符")
private String code;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng")
@NotNull(message = "模板名称不能为空")
@Length(min = 1, max = 64, message = "模板名称长度不能超过64个字符")
@Size(min = 1, max = 64, message = "模板名称长度不能超过64个字符")
private String name;
@Schema(description = "模板内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,{name}。你长的太{like}啦!")
@NotNull(message = "模板内容不能为空")
@Length(min = 1, max = 255, message = "模板内容长度不能超过255个字符")
@Size(min = 1, max = 255, message = "模板内容长度不能超过255个字符")
private String content;
@Schema(description = "备注", example = "哈哈哈")
@Length(max = 255, message = "备注长度不能超过255个字符")
@Size(max = 255, message = "备注长度不能超过255个字符")
private String remark;
@Schema(description = "短信 API 的模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4383920")
@NotNull(message = "短信 API 的模板编号不能为空")
@Length(min = 1, max = 64, message = "短信 API 的模板编号长度不能超过64个字符")
@Size(min = 1, max = 64, message = "短信 API 的模板编号长度不能超过64个字符")
private String apiTemplateId;
}
@@ -1,62 +0,0 @@
package com.cf.imes.module.system.dal.dataobject.sms;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import com.cf.imes.module.system.framework.sms.core.enums.SmsChannelEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 短信渠道 DO
*
* @author zzf
* @since 2021-01-25
*/
@TableName(value = "system_sms_channel", autoResultMap = true)
@KeySequence("system_sms_channel_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsChannelDO extends BaseDO {
/**
* 渠道编号
*/
private Long id;
/**
* 短信签名
*/
private String signature;
/**
* 渠道编码
*
* 枚举 {@link SmsChannelEnum}
*/
private String code;
/**
* 启用状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 短信 API 的账号
*/
private String apiKey;
/**
* 短信 API 的密钥
*/
private String apiSecret;
/**
* 短信发送回调 URL
*/
private String callbackUrl;
}
@@ -1,65 +0,0 @@
package com.cf.imes.module.system.dal.dataobject.sms;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
* 手机验证码 DO
*
* idx_mobile 索引:基于 {@link #mobile} 字段
*
* @author 晨丰科技
*/
@TableName("system_sms_code")
@KeySequence("system_sms_code_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SmsCodeDO extends BaseDO {
/**
* 编号
*/
private Long id;
/**
* 手机号
*/
private String mobile;
/**
* 验证码
*/
private String code;
/**
* 发送场景
*
* 枚举 {@link SmsCodeDO}
*/
private Integer scene;
/**
* 创建 IP
*/
private String createIp;
/**
* 今日发送的第几条
*/
private Integer todayIndex;
/**
* 是否使用
*/
private Boolean used;
/**
* 使用时间
*/
private LocalDateTime usedTime;
/**
* 使用 IP
*/
private String usedIp;
}
@@ -41,13 +41,11 @@ public class SmsLogDO extends BaseDO {
/**
* 短信渠道编号
*
* 关联 {@link SmsChannelDO#getId()}
*/
private Long channelId;
/**
* 短信渠道编码
*
* 冗余 {@link SmsChannelDO#getCode()}
*/
private String channelCode;
@@ -80,13 +80,11 @@ public class SmsTemplateDO extends BaseDO {
/**
* 短信渠道编号
*
* 关联 {@link SmsChannelDO#getId()}
*/
private Long channelId;
/**
* 短信渠道编码
*
* 冗余 {@link SmsChannelDO#getCode()}
*/
private String channelCode;
@@ -1,25 +0,0 @@
package com.cf.imes.module.system.dal.mysql.sms;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {
default PageResult<SmsChannelDO> selectPage(SmsChannelPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SmsChannelDO>()
.likeIfPresent(SmsChannelDO::getSignature, reqVO.getSignature())
.eqIfPresent(SmsChannelDO::getStatus, reqVO.getStatus())
.betweenIfPresent(SmsChannelDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(SmsChannelDO::getId));
}
default SmsChannelDO selectByCode(String code) {
return selectOne(SmsChannelDO::getCode, code);
}
}
@@ -1,28 +0,0 @@
package com.cf.imes.module.system.dal.mysql.sms;
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
import com.cf.imes.framework.mybatis.core.query.QueryWrapperX;
import com.cf.imes.module.system.dal.dataobject.sms.SmsCodeDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SmsCodeMapper extends BaseMapperX<SmsCodeDO> {
/**
* 获得手机号的最后一个手机验证码
*
* @param mobile 手机号
* @param scene 发送场景,选填
* @param code 验证码 选填
* @return 手机验证码
*/
default SmsCodeDO selectLastByMobile(String mobile, String code, Integer scene) {
return selectOne(new QueryWrapperX<SmsCodeDO>()
.eq("mobile", mobile)
.eqIfPresent("scene", scene)
.eqIfPresent("code", code)
.orderByDesc("id")
.limitN(1));
}
}
@@ -9,14 +9,6 @@ import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
* @since 2021/1/28 14:01
*/
public interface SmsClientFactory {
/**
* 获得短信 Client
*
* @param channelCode 渠道编码
* @return 短信 Client
*/
SmsClient getSmsClient(String channelCode);
/**
* 获得短信 Client
*
@@ -55,11 +55,6 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
return client;
}
@Override
public SmsClient getSmsClient(String channelCode) {
return channelCodeClients.get(channelCode);
}
private AbstractSmsClient createSmsClient(SmsProperties smsProperties) {
String channel = smsProperties.getChannel();
SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(channel);
@@ -1,13 +1,6 @@
package com.cf.imes.module.system.service.sms;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* 短信渠道 Service 接口
@@ -17,67 +10,6 @@ import java.util.List;
*/
public interface SmsChannelService {
/**
* 创建短信渠道
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSmsChannel(@Valid SmsChannelSaveReqVO createReqVO);
/**
* 更新短信渠道
*
* @param updateReqVO 更新信息
*/
void updateSmsChannel(@Valid SmsChannelSaveReqVO updateReqVO);
/**
* 删除短信渠道
*
* @param id 编号
*/
void deleteSmsChannel(Long id);
/**
* 获得短信渠道
*
* @param id 编号
* @return 短信渠道
*/
SmsChannelDO getSmsChannel(Long id);
/**
* 获得所有短信渠道列表
*
* @return 短信渠道列表
*/
List<SmsChannelDO> getSmsChannelList();
/**
* 获得短信渠道分页
*
* @param pageReqVO 分页查询
* @return 短信渠道分页
*/
PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO);
/**
* 获得短信客户端
*
* @param id 编号
* @return 短信客户端
*/
SmsClient getSmsClient(Long id);
/**
* 获得短信客户端
*
* @param code 编码
* @return 短信客户端
*/
SmsClient getSmsClient(String code);
/**
* 根据配置获得短信客户端
*
@@ -1,28 +1,13 @@
package com.cf.imes.module.system.service.sms;
import cn.hutool.core.text.CharSequenceUtil;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
import com.cf.imes.module.system.framework.sms.core.client.SmsClientFactory;
import com.cf.imes.module.system.framework.sms.core.property.SmsProperties;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import com.cf.imes.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
import com.cf.imes.module.system.dal.mysql.sms.SmsChannelMapper;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.util.List;
import static com.cf.imes.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
/**
* 短信渠道 Service 实现类
@@ -36,123 +21,9 @@ public class SmsChannelServiceImpl implements SmsChannelService {
@Resource
private SmsClientFactory smsClientFactory;
@Resource
private SmsChannelMapper smsChannelMapper;
@Resource
private SmsTemplateService smsTemplateService;
@Resource
private SmsProperties smsProperties;
/**
* {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
*/
@Getter
private final LoadingCache<Long, SmsClient> idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
new CacheLoader<>() {
@Override
public SmsClient load(Long id) {
return smsClientFactory.getSmsClient(smsProperties);
}
});
/**
* {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory
*/
@Getter
private final LoadingCache<String, SmsClient> codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L),
new CacheLoader<>() {
@Override
public SmsClient load(String code) {
return smsClientFactory.getSmsClient(smsProperties);
}
});
@Override
public Long createSmsChannel(SmsChannelSaveReqVO createReqVO) {
SmsChannelDO channel = BeanUtils.toBean(createReqVO, SmsChannelDO.class);
smsChannelMapper.insert(channel);
return channel.getId();
}
@Override
public void updateSmsChannel(SmsChannelSaveReqVO updateReqVO) {
// 校验存在
SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId());
// 更新
SmsChannelDO updateObj = BeanUtils.toBean(updateReqVO, SmsChannelDO.class);
smsChannelMapper.updateById(updateObj);
// 清空缓存
clearCache(updateReqVO.getId(), channel.getCode());
}
@Override
public void deleteSmsChannel(Long id) {
// 校验存在
SmsChannelDO channel = validateSmsChannelExists(id);
// 校验是否有在使用该账号的模版
if (smsTemplateService.getSmsTemplateCountByChannelId(id) > 0) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN);
}
// 删除
smsChannelMapper.deleteById(id);
// 清空缓存
clearCache(id, channel.getCode());
}
/**
* 清空指定渠道编号的缓存
*
* @param id 渠道编号
* @param code 渠道编码
*/
private void clearCache(Long id, String code) {
idClientCache.invalidate(id);
if (CharSequenceUtil.isNotEmpty(code)) {
codeClientCache.invalidate(code);
}
}
private SmsChannelDO validateSmsChannelExists(Long id) {
SmsChannelDO channel = smsChannelMapper.selectById(id);
if (channel == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS);
}
return channel;
}
@Override
public SmsChannelDO getSmsChannel(Long id) {
return smsChannelMapper.selectById(id);
}
@Override
public List<SmsChannelDO> getSmsChannelList() {
return smsChannelMapper.selectList();
}
@Override
public PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO) {
return smsChannelMapper.selectPage(pageReqVO);
}
@Override
public SmsClient getSmsClient(Long id) {
return idClientCache.getUnchecked(id);
}
@Override
public SmsClient getSmsClient(String code) {
return smsClientFactory.getSmsClient(smsProperties);
}
@Override
public SmsClient getSmsClient() {
return smsClientFactory.getSmsClient(smsProperties);
@@ -3,7 +3,6 @@ package com.cf.imes.module.system.service.sms;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO;
import jakarta.validation.Valid;
@@ -31,13 +30,6 @@ public interface SmsCodeService {
*/
void useSmsCode(@Valid SmsCodeUseReqDTO reqDTO);
/**
* 检查验证码是否有效
*
* @param reqDTO 校验请求
*/
void validateSmsCode(@Valid SmsCodeValidateReqDTO reqDTO);
/**
* 检查验证码是否有效
*
@@ -1,6 +1,5 @@
package com.cf.imes.module.system.service.sms;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -10,11 +9,8 @@ import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import com.cf.imes.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsCodeDO;
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
import com.cf.imes.module.system.dal.mysql.sms.SmsCodeMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.enums.sms.SmsSceneEnum;
import com.cf.imes.module.system.framework.sms.config.SmsCodeProperties;
@@ -27,7 +23,6 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@@ -45,9 +40,6 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Resource
private SmsCodeProperties smsCodeProperties;
@Resource
private SmsCodeMapper smsCodeMapper;
@Resource
private SmsSendService smsSendService;
@@ -215,28 +207,4 @@ public class SmsCodeServiceImpl implements SmsCodeService {
}
}
@Override
public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {
validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
}
private SmsCodeDO validateSmsCode0(String mobile, String code, Integer scene) {
// 校验验证码
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_NOT_FOUND);
}
// 超过时间
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_EXPIRED);
}
// 判断验证码是否已被使用
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CODE_USED);
}
return lastSmsCode;
}
}
@@ -32,8 +32,7 @@ public class SmsLogServiceImpl implements SmsLogService {
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder();
// 根据是否要发送,设置状态
logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus()
: SmsSendStatusEnum.IGNORE.getStatus());
logBuilder.sendStatus(SmsSendStatusEnum.INIT.getStatus());
// 设置手机相关字段
logBuilder.mobile(mobile).userId(userId).userType(userType);
// 设置模板相关字段
@@ -11,35 +11,6 @@ import java.util.Map;
* @author 晨丰科技
*/
public interface SmsSendService {
/**
* 发送单条短信给管理后台的用户
*
* 在 mobile 为空时,使用 userId 加载对应管理员的手机号
*
* @param mobile 手机号
* @param userId 用户编号
* @param templateCode 短信模板编号
* @param templateParams 短信模板参数
* @return 发送日志编号
*/
Long sendSingleSmsToAdmin(String mobile, Long userId,
String templateCode, Map<String, Object> templateParams);
/**
* 发送单条短信给用户 APP 的用户
*
* 在 mobile 为空时,使用 userId 加载对应会员的手机号
*
* @param mobile 手机号
* @param userId 用户编号
* @param templateCode 短信模板编号
* @param templateParams 短信模板参数
* @return 发送日志编号
*/
Long sendSingleSmsToMember(String mobile, Long userId,
String templateCode, Map<String, Object> templateParams);
/**
* 发送单条短信给用户
*
@@ -5,22 +5,20 @@ import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import com.cf.imes.framework.common.core.KeyValue;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.enums.UserTypeEnum;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.framework.sms.config.SmsCodeProperties;
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
import com.cf.imes.module.system.mq.producer.sms.SmsProducer;
import com.cf.imes.module.system.service.member.MemberService;
import com.cf.imes.module.system.service.sms.handler.SmsSendAfterSendHandler;
import com.cf.imes.module.system.service.user.AdminUserService;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@@ -37,10 +35,6 @@ import java.util.stream.Collectors;
@Slf4j
public class SmsSendServiceImpl implements SmsSendService {
@Resource
private AdminUserService adminUserService;
@Resource
private MemberService memberService;
@Resource
private SmsChannelService smsChannelService;
@Resource
@@ -52,30 +46,10 @@ public class SmsSendServiceImpl implements SmsSendService {
private SmsProducer smsProducer;
@Resource
private List<SmsSendAfterSendHandler> smsSendAfterSendHandlers;
private RedisTemplate redisTemplate;
@Override
public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
// 如果 mobile 为空,则加载用户编号对应的手机号
if (StringUtils.isEmpty(mobile)) {
AdminUserDO user = adminUserService.getUser(userId);
if (user != null) {
mobile = user.getMobile();
}
}
// 执行发送
return sendSingleSms(mobile, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
// 如果 mobile 为空,则加载用户编号对应的手机号
if (StringUtils.isEmpty(mobile)) {
mobile = memberService.getMemberUserMobile(userId);
}
// 执行发送
return sendSingleSms(mobile, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
}
@Resource
private SmsCodeProperties smsCodeProperties;
@Override
public Long sendSingleSms(String mobile, Long userId, Integer userType,
@@ -95,7 +69,9 @@ public class SmsSendServiceImpl implements SmsSendService {
// 发送 MQ 消息,异步执行发送短信
if (isSend) {
smsProducer.sendSmsSendMessage(sendLogId, mobile, template, newTemplateParams);
SmsSendMessage message = new SmsSendMessage().setLogId(sendLogId).setMobile(mobile);
message.setChannelId(template.getChannelId()).setApiTemplateId(template.getApiTemplateId()).setTemplateType(template.getType()).setTemplateParams(newTemplateParams);
doSendSms(message);
}
return sendLogId;
}
@@ -153,9 +129,11 @@ public class SmsSendServiceImpl implements SmsSendService {
message.getApiTemplateId(), templateParams);
channelCode = sendResponse.getChannelCode();
// 发送成功后操作
for (SmsSendAfterSendHandler afterSendHandler : smsSendAfterSendHandlers) {
if (afterSendHandler.checkTemplateType(message.getTemplateType())) {
afterSendHandler.afterSend(sendResponse, templateParams);
if (sendResponse.getSuccess()) {
for (KeyValue<String, Object> keyValue : templateParams) {
if ("code".equals(keyValue.getKey())) {
redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
}
}
}
smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
@@ -171,7 +149,7 @@ public class SmsSendServiceImpl implements SmsSendService {
@Override
public void receiveSmsStatus(String channelCode, String text) throws Throwable {
// 获得渠道对应的 SmsClient 客户端
SmsClient smsClient = smsChannelService.getSmsClient(channelCode);
SmsClient smsClient = smsChannelService.getSmsClient();
Assert.notNull(smsClient, "短信客户端({}) 不存在", channelCode);
// 解析内容
List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);
@@ -5,7 +5,6 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
@@ -14,7 +13,6 @@ import com.cf.imes.module.system.framework.sms.core.client.dto.SmsTemplateRespDT
import com.cf.imes.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import com.cf.imes.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import com.cf.imes.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsChannelDO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.cf.imes.module.system.dal.mysql.sms.SmsTemplateMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
@@ -122,18 +120,6 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
return smsTemplateMapper.selectCountByChannelId(channelId);
}
@VisibleForTesting
public SmsChannelDO validateSmsChannel(Long channelId) {
SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);
if (channelDO == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS);
}
if (CommonStatusEnum.isDisable(channelDO.getStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.SMS_CHANNEL_DISABLE);
}
return channelDO;
}
@VisibleForTesting
public void validateSmsTemplateCodeDuplicate(Long id, String code) {
SmsTemplateDO template = smsTemplateMapper.selectByCode(code);
@@ -154,6 +140,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
*
* @param apiTemplateId API 模板编号
*/
@VisibleForTesting
void validateApiTemplate(String apiTemplateId) {
// 获得短信模板
SmsClient smsClient = smsChannelService.getSmsClient();
@@ -1,25 +0,0 @@
package com.cf.imes.module.system.service.sms.handler;
import com.cf.imes.framework.common.core.KeyValue;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import java.util.List;
/**
* 短信发送后处理器接口
*
* @author Gqr
* @since 2024/8/12 18:42
*/
public interface SmsSendAfterSendHandler {
/**
* 校验消息模板类型
*
* @param templateType
* @return
*/
boolean checkTemplateType(Integer templateType);
void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params);
}
@@ -1,46 +0,0 @@
package com.cf.imes.module.system.service.sms.handler.impl;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.core.KeyValue;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.enums.sms.SmsTemplateTypeEnum;
import com.cf.imes.module.system.framework.sms.config.SmsCodeProperties;
import com.cf.imes.module.system.service.sms.handler.SmsSendAfterSendHandler;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.util.List;
/**
* @author Gqr
* @since 2024/8/12 18:49
*/
@Service
public class SmsSendAfterSendHandlerImpl implements SmsSendAfterSendHandler {
@Resource
private RedisTemplate redisTemplate;
@Resource
private SmsCodeProperties smsCodeProperties;
@Override
public boolean checkTemplateType(Integer templateType) {
SmsTemplateTypeEnum smsTemplateTypeEnum = SmsTemplateTypeEnum.valueOf(templateType);
return ObjectUtil.isNotNull(smsTemplateTypeEnum);
}
@Override
public void afterSend(SmsSendRespDTO sendResponse, List<KeyValue<String, Object>> params) {
// 发送成功把验证码存入redis
if (sendResponse.getSuccess()) {
for (KeyValue<String, Object> keyValue : params) {
if ("code".equals(keyValue.getKey())) {
redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
}
}
}
}
}
@@ -133,7 +133,6 @@ logging:
# 配置自己写的 MyBatis Mapper 打印日志
com.cf.imes.module.system.dal.mysql: debug
com.cf.imes.module.system.dal.mysql.sensitiveword.SensitiveWordMapper: INFO # 配置 SensitiveWordMapper 的日志级别为 info
com.cf.imes.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO :先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
--- #################### 微信公众号、小程序相关配置 ####################