物流项目第六期(短信微服务——对接阿里云第三方短信服务JAVA代码实现、策略模式 + 工厂模式的应用)
前五期:
物流项目第一期(登录业务)-CSDN博客
物流项目第二期(用户端登录与双token三验证)-CSDN博客
物流项目第三期(统一网关、工厂模式运用)-CSDN博客
物流项目第四期(运费模板列表实现)-CSDN博客
物流项目第五期(运费计算实现、责任链设计模式运用)-CSDN博客
注:本章节内容很干,大部分都是代码,我会尽可能的在代码加上详细的注释,帮助理解
需求
短信微服务是一个独立的微服务,主要负责短信的发送,其他微服务可调用此微服务的接口进行短信发送。具体需求如下:
- 提供发送Feign接口,支持单次或批量发送短信
- 支持发送验证码、通知两种类型的短信
- 需要保存发送记录
- 支持多通道发送,并且需要做多通道间的负载均衡
表结构
通道表
CREATE TABLE `sl_sms_third_channel` (`id` bigint NOT NULL COMMENT '主键id',`sms_type` int NOT NULL COMMENT '短信类型,1:验证类型短信,2:通知类型短信',`content_type` int NOT NULL COMMENT '内容类型,1:文字短信,2:语音短信',`sms_code` int NOT NULL COMMENT '短信code,短信微服务发放的code,其他微服务调用时需要传递该参数',`template_code` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台模板code',`send_channel` int NOT NULL COMMENT '第三方短信平台码',`sign_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '签名',`sms_priority` int NOT NULL COMMENT '数字越大优先级越高',`account` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '三方平台对应的账户信息,如:accessKeyId、accessKeySecret等,以json格式存储,使用时自行解析',`status` int NOT NULL COMMENT '通道状态1:使用 中,2:已经停用',`created` datetime NOT NULL COMMENT '创建时间',`updated` datetime NOT NULL COMMENT '更新时间',`is_delete` bit(1) NOT NULL COMMENT '是否删除',PRIMARY KEY (`id`) USING BTREE,KEY `created` (`created`) USING BTREE,KEY `sms_priority` (`sms_priority`),KEY `index_type` (`sms_type`,`content_type`,`sms_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='短信发送通道';
发送记录表
CREATE TABLE `sl_sms_record` (`id` bigint NOT NULL COMMENT '短信发送记录id',`send_channel_id` bigint NOT NULL COMMENT '发送通道id,对应sl_sms_third_channel的主键',`batch_id` bigint NOT NULL COMMENT '发送批次id,用于判断这些数据是同一批次发送的',`app_name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '发起发送请求的微服务名称,如:sl-express-ms-work',`mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',`sms_content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '短信内容,一般为json格式的参数数据,用于填充短信模板中的占位符参数',`status` int NOT NULL COMMENT '发送状态,1:成功,2:失败',`created` datetime NOT NULL COMMENT '创建时间',`updated` datetime NOT NULL COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,KEY `created` (`created`) USING BTREE,KEY `batch_id` (`batch_id`) USING BTREE,KEY `mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='短信发送记录';
Entity
@Data
@TableName("sl_sms_third_channel")
public class SmsThirdChannelEntity extends BaseEntity {/*** 短信类型,1:文字短信,2:语音短信*/private SmsTypeEnum smsType;/*** 内容类型,1:短信验证码,2:营销短信*/private SmsContentTypeEnum contentType;/*** 短信code,短信微服务发放的code,与sms_code是一对多的关系*/private String smsCode;/*** 第三方平台模板code*/private String templateCode;/*** 第三方短信平台码*/private SendChannelEnum sendChannel;/*** 签名*/private String signName;/*** 优先级,数字越大优先级越高*/private Integer smsPriority;/*** 三方平台对应的账户信息,如:accessKeyId、accessKeySecret等,以json格式存储,使用时自行解析*/private String account;/*** 通道状态1:使用 中,2:已经停用*/private Integer status;/*** 是否删除*/private Boolean isDelete;}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sl_sms_record")
public class SmsRecordEntity extends BaseEntity {/*** 发送通道id,对应sl_sms_third_channel的主键*/private Long sendChannelId;/*** 发送批次id,用于判断这些数据是同一批次发送的*/private Long batchId;/*** 发起发送请求的微服务名称,如:sl-express-ms-work*/private String appName;/*** 手机号*/private String mobile;/*** 短信内容,一般为json格式的参数数据,用于填充短信模板中的占位符参数*/private String smsContent;/*** 发送状态,1:成功,2:失败*/private SendStatusEnum status;}
Enum
/*** 三方发送平台枚举*/
public enum SendChannelEnum implements BaseEnum {ALI_YUN(1, "阿里云短信平台,详情:https://www.aliyun.com/product/sms"),CONSOLE(2, "控制台发送短信");//YIMA(2, "yima发送短信");@EnumValue@JsonValueprivate Integer code;private String value;SendChannelEnum(Integer code, String value) {this.code = code;this.value = value;}@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getValue() {return this.value;}public static SendChannelEnum codeOf(Integer code) {return EnumUtil.getBy(SendChannelEnum::getCode, code);}
}
/*** 交易枚举*/
public enum SmsExceptionEnum implements BaseExceptionEnum {SMS_CHANNEL_DOES_NOT_EXIST(1001, "短信通道不存在");private Integer code;private Integer status;private String value;SmsExceptionEnum(Integer code, String value) {this.code = code;this.value = value;this.status = 500;}SmsExceptionEnum(Integer code, Integer status, String value) {this.code = code;this.value = value;this.status = status;}public Integer getCode() {return code;}public String getValue() {return this.value;}@Overridepublic Integer getStatus() {return this.status;}
}
定义 SmsSendHandler
接口
定义
SmsSendHandler
接口及其方法send
是为了抽象化发送短信的逻辑,使得不同的短信服务提供商(如阿里云、腾讯云等)可以以统一的方式被调用。这样做不仅提高了代码的可维护性,还增强了系统的扩展性和灵活性。
/*** SmsSendHandler 接口用于对接第三方短信平台,提供一个标准化的方式来发送短信。* * 设计此接口的主要目的是为了实现解耦和模块化:* - 解耦:通过定义统一的接口,具体的短信发送逻辑可以在不改变调用方代码的情况下进行修改或替换。* - 模块化:允许轻松添加对新的短信服务提供商的支持,只需实现此接口即可。* * 此接口特别适用于以下场景:* 1. 当你需要支持多个短信服务提供商时,每个提供商都有自己的API和请求格式。* 2. 当你希望在运行时动态选择使用哪个短信服务提供商时。* 3. 当你需要测试不同的短信服务提供商时,可以通过简单地切换实现类来完成。* * 方法 send 的功能描述如下:* * @param smsThirdChannelEntity 包含了发送通道的相关信息,例如:* - 短信类型(如验证码、通知等)* - 内容类型(如纯文本、富媒体等)* - 发送渠道(如阿里云、腾讯云等)* - 签名名称* - 第三方账号信息(如 Access Key ID, Secret Key 等)* - 状态(是否启用)* 这些信息对于构建正确的请求至关重要。* * @param smsRecordEntities 待发送的短信记录列表,每个记录包含:* - 手机号码* - 短信内容* - 发送状态(初始为未发送)* - 创建时间* - 更新时间* 在发送成功后,需要更新这些记录的状态为成功,并保存到数据库中。* * 发送短信的具体步骤通常包括:* 1. 根据 smsThirdChannelEntity 获取对应的第三方短信服务配置。* 2. 遍历 smsRecordEntities 列表,构造每个短信的请求参数。* 3. 调用第三方短信服务的 API 发送短信。* 4. 根据返回结果更新 smsRecordEntities 中每条记录的状态。* 5. 将更新后的记录保存到数据库中(假设有一个 DAO 或 Repository 来处理)。* * 注意事项:* - 实现该接口时,应确保异常处理机制完善,避免因个别短信发送失败而导致整个流程中断。* - 如果可能,考虑异步发送短信以提高性能和用户体验。* - 对于高并发场景,考虑使用消息队列(如 RabbitMQ、Kafka)来缓冲短信发送请求。*/
public interface SmsSendHandler {/*** 发送短信,发送成功后需要修改状态为成功状态。** @param smsThirdChannelEntity 发送通道信息* @param smsRecordEntities 待发送列表*/void send(SmsThirdChannelEntity smsThirdChannelEntity, List<SmsRecordEntity> smsRecordEntities);}
对接阿里云第三方短信平台
/*** AliYunSmsSendHandler 是一个实现了 SmsSendHandler 接口的具体类,* 用于通过阿里云短信服务发送短信。该类负责处理短信发送的逻辑,* 包括解析请求参数、构造HTTP请求、发送请求以及处理响应。*/
@Slf4j // Lombok 注解,用于简化日志记录对象的创建
@Component // Spring 注解,标识该类为一个Spring管理的bean
@SendChannel(type = SendChannelEnum.ALI_YUN) // 自定义注解,用于标识该处理器适用于阿里云短信通道
public class AliYunSmsSendHandler implements SmsSendHandler {/*** 定义短信模板,其中包含占位符 {code},用于替换实际验证码。*/private static final String TEMPLATE = "您的验证码是:{code},请勿泄露。";/*** 阿里云短信内容的最大字符限制建议为100以内,超出该长度可能会导致发送失败。*/private static final int MAX_CONTENT_LENGTH = 100;/*** ObjectMapper 实例,用于将Java对象转换为JSON字符串或反之。*/private final ObjectMapper objectMapper = new ObjectMapper();/*** 发送短信的核心方法。** @param smsThirdChannelEntity 包含第三方平台的配置信息,如AppCode、模板ID等。* @param smsRecordEntities 待发送的短信记录列表,每条记录包含手机号码、短信内容等信息。*/@Overridepublic void send(SmsThirdChannelEntity smsThirdChannelEntity, List<SmsRecordEntity> smsRecordEntities) {// 记录开始发送短信的日志,包括渠道配置信息和待发送短信的数量。log.info("开始发送短信,渠道配置:{}", JSONUtil.toJsonStr(smsThirdChannelEntity));log.info("待发送短信数量:{}", smsRecordEntities.size());// 解析第三方平台的账户配置信息,获取必要的参数,如AppCode。JSONObject accountJson = JSONUtil.parseObj(smsThirdChannelEntity.getAccount());String appCode = accountJson.getStr("appCode");// 定义阿里云短信服务的API地址、路径和请求方法。String host = "https://send.market.alicloudapi.com";String path = "/sms/send";String method = "POST";// 构造HTTP请求的公共头部信息,包括授权信息和内容类型。Map<String, String> headers = new HashMap<>();headers.put("Authorization", "APPCODE " + appCode);headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");// 公共查询参数(这里为空)Map<String, String> querys = new HashMap<>();// 遍历每条短信记录进行发送for (SmsRecordEntity record : smsRecordEntities) {try {// 解析原始短信内容(假设是JSON格式字符串),提取出需要替换到模板中的参数。Map<String, String> params = parseContentToMap(record.getSmsContent());// 将提取出的参数转换为JSON字符串,作为最终的短信内容。String contentJson = JSONUtil.toJsonStr(params);// 构造HTTP请求的body部分,包含短信内容、模板ID和接收手机号码。Map<String, String> bodys = new HashMap<>();bodys.put("content", contentJson); // 使用 JSON 格式的 content 字段bodys.put("templateid", smsThirdChannelEntity.getTemplateCode()); // 模板 IDbodys.put("mobile", record.getMobile()); // 手机号// 发起HTTP POST请求,发送短信。HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);// 获取并记录短信平台返回的响应内容。String responseBody = EntityUtils.toString(response.getEntity());log.info("短信平台返回响应:{}", responseBody);// 解析JSON响应,判断短信是否发送成功。JsonNode jsonNode = objectMapper.readTree(responseBody);boolean isSuccess = "OK".equals(jsonNode.path("status").asText());if (isSuccess) {// 如果短信发送成功,则更新记录状态为成功。record.setStatus(SendStatusEnum.SUCCESS);} else {// 如果短信发送失败,则更新记录状态为失败,并记录警告日志。record.setStatus(SendStatusEnum.FAIL);log.warn("短信发送失败,手机号:{},响应内容:{}", record.getMobile(), responseBody);}} catch (Exception e) {// 处理发送过程中可能发生的异常,更新记录状态为失败,并记录错误日志。record.setStatus(SendStatusEnum.FAIL);log.error("短信发送出现异常,手机号:{}", record.getMobile(), e);}// 更新短信记录的状态(这部分假定你有一个 DAO 或 Repository 来处理)。// 注意:在实际应用中,这里应调用相应的持久化方法来保存修改后的记录。// smsRecordRepository.save(record);}}/*** 解析原始短信内容(假设是JSON格式字符串),提取出需要替换到模板中的参数。** @param contentJson 原始短信内容,通常是一个JSON格式的字符串。* @return 包含所有需要替换到模板中的参数的Map对象。*/private Map<String, String> parseContentToMap(String contentJson) {Map<String, String> map = new HashMap<>();try {// 解析JSON字符串,提取出所有的键值对。Map<String, Object> parsed = JSONUtil.parseObj(contentJson).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()));for (Map.Entry<String, Object> entry : parsed.entrySet()) {map.put(entry.getKey(), entry.getValue().toString());}} catch (Exception e) {// 如果解析失败,则记录警告日志,并提供默认值。log.warn("解析短信内容失败:{}", contentJson);map.put("code", "未知");}return map;}
}
实现短信渠道处理工厂类
为什么要设计这样一个 HandlerFactory
?
✅ 1. 统一获取处理器的入口
无论你是通过:
- 枚举(SendChannelEnum)
- 字符串("ALI_YUN")
- 整数编码(1001)
你都可以通过一个工厂类拿到对应的处理类,避免到处写
if-else
或switch-case
来判断渠道。
✅ 2. 实现解耦,增强扩展性
- 业务层只需要知道渠道类型(比如来自数据库或请求参数),无需关心具体是哪个类在干活;
- 新增短信服务商时,只需新增一个
SmsSendHandler
实现类并加上@SendChannel
注解即可;- 不需要修改原有代码,符合开闭原则。
✅ 3. 支持多种参数来源
实际项目中,渠道信息可能来自:
- 数据库字段(int code)
- 接口请求参数(String channelName)
- 内部枚举(SendChannelEnum)
这个工厂类都能兼容,非常灵活。
✅ 4. 提高可维护性和可测试性
- 所有处理器统一管理;
- 方便进行 Mock 测试;
- 逻辑清晰,易于排查问题。
📦 总结一句话:
HandlerFactory
是一个基于自定义注解和 Spring 容器的短信处理器选择工厂,它让系统可以根据不同的短信渠道自动匹配到对应的实现类,实现了解耦、统一调度、高扩展性的架构设计目标。
/*** 短信渠道处理器工厂类** 用于根据短信发送渠道(如阿里云、腾讯云等)动态获取对应的 SmsSendHandler 实现类。* 这是典型的【策略模式】 + 【工厂模式】结合使用,实现了解耦和统一调度。*/
public class HandlerFactory {/*** 私有化构造函数,防止外部实例化此类。* 因为这是一个工具类/工厂类,不需要也不应该被 new 出来。*/private HandlerFactory() {// 空构造方法,仅用于阻止实例化}/*** 根据短信渠道枚举类型获取对应的短信发送处理器** @param sendChannelEnum 指定的短信渠道枚举(如 ALI_YUN, TX_YUN)* @param handler 目标处理器接口类型(如 SmsSendHandler.class)* @return 返回匹配的处理器实例,若无匹配则返回 null*/public static <T> T get(SendChannelEnum sendChannelEnum, Class<T> handler) {// 从 Spring 容器中获取所有实现了指定接口的 BeanMap<String, T> beans = SpringUtil.getBeansOfType(handler);// 遍历所有找到的 Beanfor (Map.Entry<String, T> entry : beans.entrySet()) {// 获取当前 Bean 的类对象Class<?> beanClass = entry.getValue().getClass();// 查看该类上是否有 @SendChannel 注解SendChannel sendChannelAnnotation = beanClass.getAnnotation(SendChannel.class);// 如果存在注解,并且其 type() 值与传入的 sendChannelEnum 匹配,则返回该 Beanif (ObjectUtil.isNotEmpty(sendChannelAnnotation)&& ObjectUtil.equal(sendChannelEnum, sendChannelAnnotation.type())) {return entry.getValue();}}// 没有找到匹配的处理器,返回 nullreturn null;}/*** 通过字符串形式的渠道名称获取短信发送处理器** 示例:"ALI_YUN" -> SendChannelEnum.ALI_YUN** @param payChannel 渠道名称字符串(注意大小写需一致)* @param handler 目标处理器接口类型* @return 对应的处理器实例*/public static <T> T get(String payChannel, Class<T> handler) {// 将字符串转换为枚举后调用主方法return get(SendChannelEnum.valueOf(payChannel), handler);}/*** 通过整数编码获取短信发送处理器** 示例:1001 -> SendChannelEnum.ALI_YUN** @param code 渠道编码(由业务定义,如数据库字段)* @param handler 目标处理器接口类型* @return 对应的处理器实例*/public static <T> T get(Integer code, Class<T> handler) {// 调用 SendChannelEnum.codeOf(code) 方法将编码转为枚举return get(SendChannelEnum.codeOf(code), handler);}}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface SendChannel {SendChannelEnum type();}
短信选择
设计意图与作用
1. 解耦与模块化
- 该方法实现了短信发送渠道的选择逻辑,使得具体的路由逻辑独立于其他业务逻辑,便于维护和扩展。
- 通过接口
RouteService
和其实现类RouteServiceImpl
,实现了良好的模块化设计,方便后续添加新的路由规则或修改现有规则。2. 灵活的选择策略
- 通过设置优先级和随机选择机制,可以在多个符合条件的渠道中灵活选择一个最优或随机的渠道进行短信发送。
- 这种设计特别适合需要负载均衡或多渠道备份的场景。
/*** RouteServiceImpl 是一个服务层实现类,用于根据短信类型、内容类型和短信编码路由到合适的短信发送渠道。* 它扩展了 MyBatis-Plus 提供的 ServiceImpl 基础实现,并实现了自定义的 RouteService 接口。*/
@Service // 标识此类为 Spring 管理的 Bean,可以被自动扫描并注入到其他组件中
public class RouteServiceImpl extends ServiceImpl<SmsThirdChannelMapper, SmsThirdChannelEntity> implements RouteService {/*** 根据给定的短信类型、内容类型和短信编码选择一个合适的短信发送渠道。** @param smsTypeEnum 短信类型(如验证码、通知等)* @param smsContentTypeEnum 短信内容类型(如纯文本、富媒体等)* @param smsCode 短信编码,通常对应某个特定模板或业务场景* @return 匹配的 SmsThirdChannelEntity 实例,如果未找到匹配项则返回 null*/@Overridepublic SmsThirdChannelEntity route(SmsTypeEnum smsTypeEnum, SmsContentTypeEnum smsContentTypeEnum, String smsCode) {// 创建 LambdaQueryWrapper 来构建查询条件,使用 lambda 表达式来避免硬编码字段名LambdaQueryWrapper<SmsThirdChannelEntity> queryWrapper = Wrappers.<SmsThirdChannelEntity>lambdaQuery().eq(SmsThirdChannelEntity::getSmsType, smsTypeEnum) // 短信类型匹配.eq(SmsThirdChannelEntity::getContentType, smsContentTypeEnum) // 内容类型匹配.eq(SmsThirdChannelEntity::getSmsCode, smsCode) // 短信编码匹配.eq(SmsThirdChannelEntity::getStatus, 1) // 只选择启用状态的渠道.orderByDesc(SmsThirdChannelEntity::getSmsPriority) // 按优先级降序排列.last("LIMIT 5"); // 限制结果集大小为5条记录// 执行查询操作,获取符合条件的 SmsThirdChannelEntity 列表List<SmsThirdChannelEntity> smsThirdChannelEntities = super.list(queryWrapper);// 如果没有找到任何符合条件的渠道,则返回 nullif (CollUtil.isEmpty(smsThirdChannelEntities)) {return null;}// 随机选择一个符合条件的渠道(从列表中随机选择一个索引)int index = RandomUtil.randomInt(0, CollUtil.size(smsThirdChannelEntities));// 返回随机选择的渠道实体return CollUtil.get(smsThirdChannelEntities, index);}
}
短信发送业务
这个类的作用与设计意图
✅ 1. 统一短信发送流程
- 从接收请求、路由渠道、组装数据、发送短信、记录日志、持久化数据,整个过程都在一个方法里完成;
- 保证了短信发送的完整性、一致性。
✅ 2. 职责分离清晰
RouteService
负责选路;HandlerFactory
负责找具体实现;SmsSendHandler
负责具体发送;SmsServiceImpl
作为协调者串联各个组件;- 遵循单一职责原则,提高可维护性。
✅ 3. 高扩展性
- 新增短信平台?只需:
- 添加新的
SmsSendHandler
实现类;- 加上
@SendChannel
注解;- 系统自动识别,无需改动已有代码;
✅ 4. 便于监控和追溯
- 每条短信都有唯一批次 ID;
- 所有发送记录都会入库;
- 方便后续做统计分析、失败重试、客户投诉核查等工作。
✅ 5. 容错机制
- 渠道找不到时抛出明确异常;
- 发送失败会记录日志;
- 可结合定时任务进行失败短信重发。
📦 总结一句话:
SmsServiceImpl
是整个短信发送业务的核心协调者,它整合了短信路由、处理器选择、短信记录生成、调用发送接口、持久化等功能,是短信服务模块中最核心的服务类之一。它的存在使得短信发送功能变得结构清晰、易于扩展、便于维护和监控。
/*** SmsServiceImpl 是短信发送服务的具体实现类。** 它继承自 MyBatis-Plus 的 ServiceImpl,具备操作数据库的能力(如保存短信记录),* 并实现了自定义接口 SmsService,对外暴露统一的短信发送方法。*/
@Slf4j // Lombok 提供的日志工具注解,自动创建 log 对象
@Service // 标识为 Spring Bean,可被自动注入使用
public class SmsServiceImpl extends ServiceImpl<SmsRecordMapper, SmsRecordEntity> implements SmsService {/*** 注入路由服务类,用于根据短信类型、内容类型等信息选择合适的短信渠道。*/@Resourceprivate RouteService routeService;/*** 短信发送主入口方法。** @param smsInfoDTO 包含短信发送所需的基本参数,如手机号列表、短信内容、短信类型等* @return 返回发送结果 DTO 列表,包含每个手机号的发送状态等信息*/@Overridepublic List<SendResultDTO> send(SmsInfoDTO smsInfoDTO) {// 1️⃣ 记录开始日志,方便调试和追踪请求log.info("开始路由短信通道,参数:smsType={}, contentType={}, smsCode={}",smsInfoDTO.getSmsType(), smsInfoDTO.getContentType(), smsInfoDTO.getSmsCode());// 2️⃣ 调用路由服务,根据短信类型、内容类型、短信编码获取对应的短信渠道配置SmsThirdChannelEntity smsThirdChannelEntity = routeService.route(smsInfoDTO.getSmsType(),smsInfoDTO.getContentType(),smsInfoDTO.getSmsCode());// 3️⃣ 如果未找到匹配的短信发送渠道,抛出异常并记录错误日志if (ObjectUtil.isEmpty(smsThirdChannelEntity)) {log.error("未能找到短信通道,查询参数为:smsType={}, contentType={}, smsCode={}",smsInfoDTO.getSmsType(), smsInfoDTO.getContentType(), smsInfoDTO.getSmsCode());throw new SLException(SmsExceptionEnum.SMS_CHANNEL_DOES_NOT_EXIST);}// 4️⃣ 找到短信渠道后,记录该渠道信息log.info("找到短信通道实体: {}", smsThirdChannelEntity);// 5️⃣ 使用 HandlerFactory 获取与当前渠道对应的短信发送处理器(如阿里云、腾讯云)SmsSendHandler smsSendHandler = HandlerFactory.get(smsThirdChannelEntity.getSendChannel(), SmsSendHandler.class);// 6️⃣ 如果没有找到对应的处理器,说明系统中没有支持该渠道的实现,抛出异常if (ObjectUtil.isEmpty(smsSendHandler)) {log.info("开始路由短信通道,参数:smsType={}, contentType={}, smsCode={}",smsInfoDTO.getSmsType(), smsInfoDTO.getContentType(), smsInfoDTO.getSmsCode());throw new SLException(SmsExceptionEnum.SMS_CHANNEL_DOES_NOT_EXIST);}// 7️⃣ 生成唯一批次 ID,用于标识本次发送任务(可用于后续日志追踪或查数据)long batchId = IdWorker.getId();// 8️⃣ 构建短信发送记录列表 SmsRecordEntity,每条记录对应一个手机号List<SmsRecordEntity> smsRecordEntities = StreamUtil.of(smsInfoDTO.getMobiles()).map(mobile -> SmsRecordEntity.builder().batchId(batchId) // 批次 ID,同一时间发送的所有短信共用同一个批次号.appName(smsInfoDTO.getAppName()) // 来源应用名,用于区分哪个微服务发起的请求.smsContent(smsInfoDTO.getSmsContent()) // 短信内容,可能是 JSON 字符串.sendChannelId(smsThirdChannelEntity.getId()) // 所使用的短信渠道 ID.mobile(mobile) // 当前处理的手机号.status(SendStatusEnum.FAIL) // 默认发送状态为失败,发送成功后再改为成功.build()).collect(Collectors.toList());// 9️⃣ 调用具体的短信发送处理器执行发送逻辑(比如调用阿里云 API)smsSendHandler.send(smsThirdChannelEntity, smsRecordEntities);// 🔟 将短信发送记录批量写入数据库,用于后期审计、排查、统计等用途super.saveBatch(smsRecordEntities);// 🔪 最后将实体类列表转换为返回值 DTO 列表,屏蔽数据库字段细节return BeanUtil.copyToList(smsRecordEntities, SendResultDTO.class);}
}
相关文章:
物流项目第六期(短信微服务——对接阿里云第三方短信服务JAVA代码实现、策略模式 + 工厂模式的应用)
前五期: 物流项目第一期(登录业务)-CSDN博客 物流项目第二期(用户端登录与双token三验证)-CSDN博客 物流项目第三期(统一网关、工厂模式运用)-CSDN博客 物流项目第四期(运费模板列…...
嵌入式学习的第二十五天-系统编程-文件相关函数-标准I0+文件IO
一、文件的读和写 1.fwrite(读) size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:从指定的stream流对象中获取nmemeb个大小为size字节的数据块到ptr所在的本地内存中。 参数:ptr 要存储数据的本地…...
MySQL 8.0 OCP 1Z0-908 171-180题
Q171.Examine this MySQL client command to connect to a remote database: mysql-h remote-example.org-u root–protocolTCP–ssl-mode Which two–ss1-mode values will ensure that an X.509-compliant certificate will be used to establish the SSL/TLS connection to …...
实现动态增QuartzJob,通过自定义注解调用相应方法
:::tip 动态增加Quartz定时任务,通过自定义注解来实现具体的定时任务方法调用。 ::: 相关依赖如下 <!-- 用来动态创建 Quartz 定时任务 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-start…...
Linux网络 网络基础一
1. 计算机网络背景 1.1 网络发展 独立模式:计算机之间相互独立。 网络互联:多台计算机连接在一起,完成数据共享。 局域网LAN:计算机数量更多了,通过交换机和路由器连接在一起。 广域网WAN:将远隔千里的…...
auto关键字解析
前言 在11标准之前,auto在c中是声明存储器类型的关键字。而在11标准中它的功能变为了类型推导。 对此, 在这里引入Cprimer中的原句: 编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚的知道表达式的类型。然而…...
[实战]用户系统-1-基础功能完善
[实战]用户系统-1 目标响应格式化新建lib-interceptor增加res拦截器新建lib-filter完善异常处理日志处理新建lib-logger新增mongodb的model代码进度目标 我们的用户系统实战,将会实现以下功能,登录,注册,登出,修改用户信息,上传头像,响应的格式化,请求拦截,vip标识。…...
C#SQLServer数据库通用访问类
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; namespace thinger.cn.ADO.NETTeach { /// /// 数据库的通用类 /// public class SQLHelperBase…...
Linux中进程控制(上)
目录 进程创建 写时拷贝 fork常用场景 fork调用失败的原因 进程终止 进程退出场景 退出码 _exit函数 exit函数 进程等待 进程等待必要性 进程等待方法 wait方法 编辑 waitpid方法 获取⼦进程status 阻塞和非阻塞等待 进程创建 在linux中fork函数是⾮常重要的…...
为什么服务器突然变慢?从硬件到软件的排查方法
服务器突然变慢是许多系统管理员和网站运维人员经常遇到的问题。这种情况可能会影响网站性能、用户体验以及整个业务流程。了解服务器变慢的原因并采取相应的排查措施是至关重要的。本文将介绍服务器突然变慢的可能原因,从硬件到软件方面逐一排查,并提供…...
碳交易系统九大构成
碳交易系统九大构成 碳排放权交易系统的核心要素包括覆盖范围、配额总量、配额分配、排放监测、报送与核查,履约考核、抵消机制、交易机制、市场监管及配套的法律法规体系。 图源《中国碳排放权交易市场:从原理到实践》 1、覆盖范围 碳排放权交易体系…...
第9.2讲、Tiny Decoder(带 Mask)详解与实战
自己搭建一个 Tiny Decoder(带 Mask),参考 Transformer Encoder 的结构,并添加 Masked Multi-Head Self-Attention,它是 Decoder 的核心特征之一。 1. 背景与动机 Transformer 架构已成为自然语言处理(NLP…...
Java接口P99含义解析
假设你开了一家奶茶店(接口就是你的奶茶制作流水线),每天要处理100杯订单: 🚀 P99是什么? 平均响应时间:就像说"平均每杯奶茶2分钟做好",但可能有10杯让客人等10分钟P99…...
【Oracle 专栏】清理用户及表空间
Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 今天需要清理一台服务器中之前的库,目前不再使用,以便释放空间。 如:清理 NH_MCRO_COLLECT 用户 2. 实验清理 2.1 查询:清…...
Qt功能区:Ribbon控件
控件 1. 按钮1.1 多选按钮1.2 2. 下拉列表框SARibbonComboBox2.1 简介2.2 代码实现 1. 按钮 1.1 多选按钮 软件功能:用于实现Category的名称居中。 SARibbonCheckBox继承于QCheckBox,使用方法完全相同。 SARibbonCheckBox* checkBox new SARibbonChe…...
eclipse 生成函数说明注释
在Eclipse中生成函数说明注释(JavaDoc风格)可以通过以下方法实现: 快捷键方式: 将光标放在函数上方输入/**后按回车键Eclipse会自动生成包含参数和返回值的注释模板 菜单方式: 选中函数点击菜单栏 Source > Gen…...
【Qt】QImage实战
QImage::Format_Mono, QImage::Format_RGB32, QImage::Format_ARGB32, QImage::Format_ARGB32_Premultiplied, 和 QImage::Format_RGB555 是 Qt 中不同的图像像素格式,它们在存储方式、颜色深度、是否支持透明通道以及适用场景上各有不同。下面是它们的详细对比&…...
tomcat知识点
1. JDK JDK是 Java 语言的软件开发工具包,JDK是整个java开发的核心,它包含JAVA工具还包括完整的 JRE(Java Runtime Environment)Java运行环境,包括了用于产品环境的各种库类,以及给开发人员使用的补充库。 JDK包含了一批用于Java开发的组件,其中包括: javac:编译器,将…...
Linux虚拟文件系统(2)
2.3 目录项-dentry 目录项,即 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。和上一章中超级块和索引节点不同,目录项并不是实际存在于磁盘上的,…...
遥感影像-语义分割数据集:光伏数据集详细介绍及训练样本处理流程
原始数据集详情 简介:数据集包括504张亚米级卫星图片的农业光伏数据集,该数据集用于亚米级影像中的农业光伏提取任务。 KeyValue卫星类型亚米级卫星覆盖区域未知场景未知分辨率0.5m数量504张单张尺寸1024*1024原始影像位深8位标签图片位深8位原始影像通…...
【Java高阶面经:微服务篇】4.大促生存法则:微服务降级实战与高可用架构设计
一、降级决策的核心逻辑:资源博弈下的生存选择 1.1 大促场景的资源极限挑战 在电商大促等极端流量场景下,系统面临的资源瓶颈呈现指数级增长: 流量特征: 峰值QPS可达日常的50倍以上(如某电商大促下单QPS从1万突增至50万)流量毛刺持续时间短(通常2-4小时),但对系统稳…...
工业物联网网关在变电站远程监控中的安全传输解决方案
一、项目背景 随着智能电网的快速发展,对变电站的智能化监控需求日益迫切。传统变电站采用人工巡检和就地监控的方式,存在效率低、实时性差、数据不准确等问题,难以满足现代电力系统对变电站安全、稳定、高效运行的要求。而智能变电站通过引…...
车载诊断架构 --- LIN 节点 ECU 故障设计原则
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
编程技能:字符串函数09,strncmp
专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 (一)WIn32 专栏导航 上一篇:编程技能:字符串函数08,strcmp 回到目录…...
UML 时序图 使用案例
UML 时序图 UML 时序图 (Sequence Diagram)时序图的主要元素消息类型详解时序图示例时序图绘制步骤时序图的应用场景 UML 时序图 (Sequence Diagram) 时序图是UML(统一建模语言)中用于展示对象之间交互行为的动态视图,它特别强调消息的时间顺序。 时序图的主要元素…...
业务逻辑篇水平越权垂直越权未授权访问检测插件SRC 项目
# 逻辑越权 - 检测原理 - 水平 & 垂直 & 未授权 1 、水平越权:同级别的用户之间权限的跨越 2 、垂直越权:低级别用户到高级别用户权限的跨越 3 、未授权访问:通过无级别用户能访问到需验证应用 PHPStudy Metinfo4.0 会员后台中…...
Android开发——不同布局的定位属性 与 通用属性
目录 不同布局的定位属性1. 线性布局(LinearLayout)2. 相对布局(RelativeLayout)3. 约束布局(ConstraintLayout)4. 表格布局(TableLayout)5. 网格布局(GridLayout&#x…...
【DB2】SQL1639N 处理
背景 测试环境21套DB2需要创建只读用户并赋予权限,在20套都成功的情况下,有一套报错了,具体细节为,赋权成功,但是使用被赋权的账户连接失败,报错如下 SQL1639N The database server was unable to perfor…...
禾纳EAT3152AP MOS电源芯片PIN TO PIN替代泰德TDM3307/2307方案
AET3152AP特性 VDS-30V,ID-40A RDS (ON)11mΩ (TYP.)VGS-10V, ID-10A RDS (ON)15mΩ (TYP.)VGS-4.5V, ID-5A 快速切换 l 低电阻 不含卤素和锑,符合Rohs标准 温度范围:-55℃~125℃ 封装:PDFN3030 AET3152AP应用 交换机切换 便携式/台式机中的…...
Python Day28 学习
继续聚类算法的学习 浙大疏锦行 DBSCAN聚类 Q1. 该算法的原理是什么? 总体而言,DBSCAN聚类是一种基于密度的聚类算法,适合发现任意形状的簇和检测噪声点 Q2. 代码实现 import numpy as np import pandas as pd from sklearn.cluster impo…...
企业网站架构部署与优化-Nginx核心功能
目录 #1.1正向代理 1.1.1编译安装Nginx 1.1.2配置正向代理 #2.1反向代理 2.1.1配置nginx七层代理 2.1.2配置nginx四层代理 1.1正向代理 正向代理(Forward Proxy)是一种位于客户端和目标服务器之间的服务器,用于代表客户端向服务器发送请求并…...
Java 多态
文章目录 多态向上转型和向下转型向上转型和重写重写和重载的区别动态绑定和静态绑定用代码来解释什么是多态向下转型 多态的优点 总结 多态 什么是多态?为什么要使用多态? 简单来说是多种形态,具体来说是去完成某个事情,当不同对…...
机器学习中的泛化能力
我们常常提到模型的泛化能力,什么是泛化能力呢? 百度百科这样解释:是指机器学习算法对新鲜样本的适应能力。 学习的目的是学到隐含在数据背后的规律,对具有同一规律的学习集以外的数据,经过训练的网络也能给出合适的输…...
第七章:数据存储策略与状态恢复机制实录
经过状态机、UI交互、逻辑驱动等章节的打磨,前端体系已经具备较强的调度与展示能力。但真正能决定组件在异常情况下能否“满血复活”的关键,落在了“状态恢复”这一关卡。尤其在安卓端环境复杂、网络波动频繁的前提下,若没有稳定的本地数据存…...
digitalworld.local: FALL靶场
digitalworld.local: FALL 来自 <digitalworld.local: FALL ~ VulnHub> 1,将两台虚拟机网络连接都改为NAT模式 2,攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182,靶场IP192.168.23.4 3&…...
Java Collection(集合) 接口
Date: 2025-05-21 20:21:32 author: lijianzhan Java 集合框架提供了一组接口和类,以实现各种数据结构和算法。 以下是关于 Java 集合的核心内容说明: /*** Java Collection Framework 说明:** 在 Java 中,集合(Collec…...
直线型绝对值位移传感器:精准测量的科技利刃
在科技飞速发展的今天,精确测量成为了众多领域不可或缺的关键环节。无论是工业自动化生产线上的精细操作,还是航空航天领域中对零部件位移的严苛把控,亦或是科研实验中对微小位移变化的精准捕捉,都离不开一款高性能的测量设备——…...
Kotlin 极简小炒 P9 - 数组(数组的创建、数组元素的访问与修改、数组遍历、数组操作、多维数组、数组与可变参数)
Kotlin 概述 Kotlin 由 JetBrains 开发,是一种在 JVM(Java 虚拟机)上运行的静态类型编程语言 Kotlin 旨在提高开发者的编码效率和安全性,同时保持与 Java 的高度互操作性 Kotlin 是 Android 应用开发的首选语言,也可…...
Server-Driven UI:Kotlin 如何重塑动态化 Android 应用开发
以下是一篇整合详细代码示例的完整博客,深入探讨Kotlin在Server-Driven UI(SDUI)中的核心作用: Server-Driven UI:Kotlin 如何重塑动态化 Android 应用开发 1. Server-Driven UI 的核心价值 SDUI通过将UI描述与业务逻…...
基于多传感器融合的智能驾驶环境感知系统
摘要 随着自动驾驶技术的发展,单一传感器的局限性日益凸显。本文提出了一种基于多传感器(摄像头、毫米波雷达、激光雷达)融合的环境感知系统,通过深度学习算法实现车辆周围环境的精确感知。文章详细介绍了传感器标定、数据融合、目标检测与跟踪等关键技术,并提供了Python…...
JC/T 2848-2024 玻璃纤维增强石膏(GRG)装饰制品检测
玻璃纤维增强石膏装饰制品是指以玻璃纤维为主要增强材料,高强石膏为主要胶凝材料,适当掺入集料,外加剂的石膏装饰制品,GRG具有防火,隔音,被广泛应用于,墙板,装饰构件等。 JC/T 2848…...
每日算法 -【Swift 算法】寻找字符串中最长回文子串(三种经典解法全解析)
🧩 最长回文子串问题:三种经典解法全解析(含代码注释) 本文将系统讲解“最长回文子串”问题的三种常见解法:中心扩展法、动态规划、马拉车算法(Manacher’s Algorithm),并进行对比与…...
【Java高阶面经:数据库篇】13. MySQL 并发控制秘籍:MVCC 协议与隔离级别深度解析
一、MVCC核心原理:多版本并发控制的基石 1.1 为什么需要MVCC? 在传统锁机制中,读写操作会互相阻塞,导致高并发场景下性能下降。MVCC通过多版本数据快照避免读写阻塞,实现: 读不加锁:快照读(普通SELECT)不阻塞写操作写不阻塞读:写操作生成新版本,读操作访问历史版本…...
分布式集群中的共识算法及其在时序数据库IoTDB中的应用
一、引言 在分布式集群环境中,为了实现海量数据的横向扩展,数据通常被划分为多个子集并分散存储在集群的各个节点上。为了确保数据的高可用性,每个数据子集都会在多个物理节点上存储副本。然而,这种多副本机制也带来了新的挑战&a…...
Java面试实录:从JVM调优到Spring Cloud实践
Java大厂面试:当严肃面试官遇上搞笑程序员 场景设定 面试官:拥有多年行业经验的技术专家,对Java及相关技术栈有着深入的理解。明哥:一位自认为是“水货”的程序员,擅长用幽默化解紧张气氛,但面对复杂问题…...
自定义协议与序列反序列化
目录 引子: 一、再谈 "协议" 二、自定义协议与网络版计算器 1.约定方案一: 2.约定方案二: 3.我们采用的协议 三、网络计算器代码 Log.hpp 日志 Makefile Socket.hpp 套接字封装 Protocol.hpp 协议 序列化反序列化 结构化数据格式规定 TcpSe…...
SAP-ABAP:ABAP异常处理与SAP现代技术融合—— 面向云原生、微服务与低代码场景的创新实践
专题三:ABAP异常处理与SAP现代技术融合 —— 面向云原生、微服务与低代码场景的创新实践 一、SAP技术演进与异常处理的挑战 随着SAP技术栈向云端、微服务化和低代码方向演进,异常处理面临新场景: Fiori UX敏感度:用户期望前端友…...
JavaScript面试题之消息队列
JavaScript消息队列详解:单线程的异步魔法核心 在JavaScript的单线程世界中,消息队列(Message Queue)是实现异步编程的核心机制,它像一位高效的调度员,让代码既能“一心多用”又避免卡顿。本文将深入剖析消…...
【低代码】如何使用明道云调用 Flask 视图函数并传参(POST 方法实践)
在自动化办公或业务流程管理中,明道云提供了强大的 HTTP 请求节点,可以直接调用第三方 API,包括我们常见的 Flask 服务端接口。本文将详细介绍如何使用明道云通过 POST 方法调用 Flask 视图函数并传参,包括配置要点与 Python 后端的参数接收方法。 一、场景介绍 我们希望…...
广州卓远VR受邀参加2025智能体育典型案例调研活动,并入驻国体华为运动健康联合实验室!
近日,“2025年智能体育典型案例调研活动”在东莞松山湖成功举办。本次调研活动由国家体育总局体育科学研究所和中国信息通信研究院联合主办,旨在深入贯彻中央关于培育新型消费的战略部署,通过激活智能健身产品消费潜力,加快运动健…...