feat(system/sms): 短信渠道支持数据字典配置 (#159)

This commit is contained in:
Top2Hub
2025-04-21 16:11:25 +08:00
committed by GitHub
parent c9d5810f7d
commit e4828bf2a3
13 changed files with 220 additions and 21 deletions

View File

@@ -88,5 +88,22 @@ public class CaptchaProperties {
* 模板 ID
*/
private String templateId;
/**
* 供应商渠道
*
* @see top.continew.admin.system.model.resp.SmsConfigResp#supplier
*/
private String supplier;
/**
* 验证码字段模版键名
*/
private String codeKey = "code";
/**
* 失效时间字段模版键名
*/
private String timeKey = "expirationInMinutes";
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.system.config.sms;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import top.continew.admin.system.model.resp.SmsConfigResp;
/**
* 短信配置工具类
*
* @author Top2Hub
* @since 2025/04/21 14:00
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SmsConfigUtil {
private static final TypeReference<Map<String, Object>> CONFIG_MAP_TYPE = new TypeReference<Map<String, Object>>() {};
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
/**
* 将本地配置对象转换为 BaseConfig
*
* @param smsConfig 本地配置对象
* @return SMS4j 配置基类
*/
public static BaseConfig from(SmsConfigResp smsConfig) {
if (Objects.isNull(smsConfig))
return null;
String supplierName = smsConfig.getSupplier();
BaseProviderFactory<?, ?> providerFactory = ProviderFactoryHolder.requireForSupplier(supplierName);
if (Objects.isNull(providerFactory))
return null;
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("configId", smsConfig.getId().toString());
configInfo.put("accessKeyId", smsConfig.getAccessKey());
configInfo.put("accessKeySecret", smsConfig.getSecretKey());
configInfo.put("signature", smsConfig.getSignature());
configInfo.put("templateId", smsConfig.getTemplateId());
if (Objects.nonNull(smsConfig.getWeight()))
configInfo.put("weight", smsConfig.getWeight());
if (Objects.nonNull(smsConfig.getRetryInterval()))
configInfo.put("retryInterval", smsConfig.getRetryInterval());
if (Objects.nonNull(smsConfig.getMaxRetries()))
configInfo.put("maxRetries", smsConfig.getMaxRetries());
if (Objects.nonNull(smsConfig.getSupplierConfig())) {
Map<String, Object> supplierInfo = OBJECT_MAPPER.convertValue(smsConfig
.getSupplierConfig(), CONFIG_MAP_TYPE);
configInfo.putAll(supplierInfo);
}
BaseConfig config = (BaseConfig)OBJECT_MAPPER.convertValue(configInfo, providerFactory.getConfigClass());
return config;
}
}

View File

@@ -18,9 +18,11 @@ package top.continew.admin.system.config.sms;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.core.datainterface.SmsReadConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.springframework.stereotype.Component;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.model.query.SmsConfigQuery;
import top.continew.admin.system.model.resp.SmsConfigResp;
@@ -48,7 +50,7 @@ public class SmsReadConfigDatabaseImpl implements SmsReadConfig {
if (DisEnableStatusEnum.DISABLE.equals(smsConfig.getStatus())) {
return null;
}
return smsConfig.getSupplier().toBaseConfig(smsConfig);
return SmsConfigUtil.from(smsConfig);
}
@Override
@@ -59,6 +61,6 @@ public class SmsReadConfigDatabaseImpl implements SmsReadConfig {
if (CollUtil.isEmpty(list)) {
return List.of();
}
return list.stream().map(smsConfig -> smsConfig.getSupplier().toBaseConfig(smsConfig)).toList();
return list.stream().map(smsConfig -> SmsConfigUtil.from(smsConfig)).toList();
}
}

View File

@@ -32,7 +32,10 @@ import top.continew.starter.core.enums.BaseEnum;
* @author luoqiz
* @author Charles7c
* @since 2025/03/15 22:15
*
* @deprecated 使用数据字典`sms_supplier_type`动态维护
*/
@Deprecated
@Getter
@RequiredArgsConstructor
public enum SmsSupplierEnum implements BaseEnum<String> {

View File

@@ -20,7 +20,6 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.model.entity.BaseDO;
import top.continew.admin.system.enums.SmsSupplierEnum;
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import java.io.Serial;
@@ -47,7 +46,7 @@ public class SmsConfigDO extends BaseDO {
/**
* 厂商
*/
private SmsSupplierEnum supplier;
private String supplier;
/**
* Access Key

View File

@@ -19,7 +19,6 @@ package top.continew.admin.system.model.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.enums.SmsSupplierEnum;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
@@ -52,7 +51,7 @@ public class SmsConfigQuery implements Serializable {
*/
@Schema(description = "厂商", example = "cloopen")
@Query
private SmsSupplierEnum supplier;
private String supplier;
/**
* Access Key

View File

@@ -24,7 +24,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.system.enums.SmsSupplierEnum;
import java.io.Serial;
import java.io.Serializable;
@@ -56,7 +55,7 @@ public class SmsConfigReq implements Serializable {
*/
@Schema(description = "厂商", example = "cloopen")
@NotNull(message = "厂商无效")
private SmsSupplierEnum supplier;
private String supplier;
/**
* Access Key

View File

@@ -20,9 +20,10 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.common.config.excel.DictExcelProperty;
import top.continew.admin.common.config.excel.ExcelDictConverter;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.model.resp.BaseDetailResp;
import top.continew.admin.system.enums.SmsSupplierEnum;
import top.continew.starter.file.excel.converter.ExcelBaseEnumConverter;
import top.continew.starter.security.mask.annotation.JsonMask;
@@ -52,10 +53,14 @@ public class SmsConfigResp extends BaseDetailResp {
/**
* 厂商
* 对齐 sms4j 短信服务商常量类
*
* @see org.dromara.sms4j.comm.constant.SupplierConstant
*/
@Schema(description = "厂商", example = "cloopen")
@ExcelProperty(value = "厂商")
private SmsSupplierEnum supplier;
@ExcelProperty(value = "厂商", converter = ExcelDictConverter.class)
@DictExcelProperty("sms_supplier_type")
private String supplier;
/**
* Access Key

View File

@@ -16,12 +16,13 @@
package top.continew.admin.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.springframework.stereotype.Service;
import top.continew.admin.system.enums.SmsSupplierEnum;
import top.continew.admin.system.config.sms.SmsConfigUtil;
import top.continew.admin.system.mapper.SmsConfigMapper;
import top.continew.admin.system.model.entity.SmsConfigDO;
import top.continew.admin.system.model.query.SmsConfigQuery;
@@ -31,6 +32,7 @@ import top.continew.admin.system.service.SmsConfigService;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import java.util.List;
import java.util.Objects;
/**
* 短信配置业务实现
@@ -69,9 +71,11 @@ public class SmsConfigServiceImpl extends BaseServiceImpl<SmsConfigMapper, SmsCo
* @param entity 配置信息
*/
private void load(SmsConfigDO entity) {
SmsSupplierEnum supplier = entity.getSupplier();
BaseConfig config = supplier.toBaseConfig(BeanUtil.toBean(entity, SmsConfigResp.class));
SmsFactory.createSmsBlend(config);
SmsConfigResp smsConfig = this.get(entity.getId());
BaseConfig config = SmsConfigUtil.from(smsConfig);
if (Objects.nonNull(config))
SmsFactory.createSmsBlend(config);
}
/**

View File

@@ -38,7 +38,6 @@ import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.validation.annotation.Validated;
@@ -204,12 +203,16 @@ public class CaptchaController {
String captcha = RandomUtil.randomNumbers(captchaSms.getLength());
// 发送验证码
Long expirationInMinutes = captchaSms.getExpirationInMinutes();
SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.CLOOPEN);
SmsBlend smsBlend = SmsFactory.getBySupplier(captchaSms.getSupplier());
Map<String, String> messageMap = MapUtil.newHashMap(2, true);
messageMap.put("captcha", captcha);
messageMap.put("expirationInMinutes", String.valueOf(expirationInMinutes));
messageMap.put(captchaSms.getCodeKey(), captcha);
messageMap.put(captchaSms.getTimeKey(), String.valueOf(expirationInMinutes));
SmsResponse smsResponse = smsBlend.sendMessage(phone, captchaSms
.getTemplateId(), (LinkedHashMap<String, String>)messageMap);
CheckUtils.throwIf(!smsResponse.isSuccess(), "验证码发送失败");
// 保存验证码
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone;

View File

@@ -11,6 +11,8 @@ databaseChangeLog:
file: db/changelog/mysql/plugin/plugin_open.sql
- include:
file: db/changelog/mysql/plugin/plugin_generator.sql
- include:
file: db/changelog/mysql/sms_dict_data.sql
# PostgreSQL
# - include:
# file: db/changelog/postgresql/main_table.sql
@@ -23,4 +25,6 @@ databaseChangeLog:
# - include:
# file: db/changelog/postgresql/plugin/plugin_open.sql
# - include:
# file: db/changelog/postgresql/plugin/plugin_generator.sql
# file: db/changelog/postgresql/plugin/plugin_generator.sql
# - include:
# file: db/changelog/postgresql/sms_dict_data.sql

View File

@@ -0,0 +1,38 @@
-- liquibase formatted sql
-- changeset TopHub:2
-- comment 新增 短信厂商 字典
-- 新增 短信厂商 字典数据
INSERT INTO `sys_dict`
(`id`, `name`, `code`, `description`, `is_system`, `create_user`, `create_time`)
VALUES
(4, '短信厂商', 'sms_supplier_type', NULL, b'1', 1, NOW());
-- 新增 短信厂商 字典项
INSERT INTO `sys_dict_item`
(`id`, `label`, `value`, `color`, `sort`, `description`, `status`, `dict_id`, `create_user`, `create_time`)
VALUES
(8, '阿里云', 'alibaba', 'primary', 1, NULL, 1, 4, 1, NOW()),
(9, '容联云', 'cloopen', 'success', 2, NULL, 1, 4, 1, NOW()),
(10, '天翼云', 'ctyun', 'warning', 3, NULL, 1, 4, 1, NOW()),
(11, '亿美软通', 'emay', 'primary', 4, NULL, 1, 4, 1, NOW()),
(12, '华为', 'huawei', 'success', 5, NULL, 1, 4, 1, NOW()),
(13, '京东', 'jdcloud', 'warning', 6, NULL, 1, 4, 1, NOW()),
(14, '网易', 'netease', 'primary', 7, NULL, 1, 4, 1, NOW()),
(15, '腾讯', 'tencent', 'success', 8, NULL, 1, 4, 1, NOW()),
(16, '合一', 'unisms', 'warning', 9, NULL, 1, 4, 1, NOW()),
(17, '云片', 'yunpian', 'primary', 10, NULL, 1, 4, 1, NOW()),
(18, '助通', 'zhutong', 'success', 11, NULL, 1, 4, 1, NOW()),
(19, '联麓', 'lianlu', 'warning', 12, NULL, 1, 4, 1, NOW()),
(20, '鼎众', 'dingzhong', 'primary', 13, NULL, 1, 4, 1, NOW()),
(21, '七牛云', 'qiniu', 'success', 14, NULL, 1, 4, 1, NOW()),
(22, '创蓝', 'chuanglan', 'warning', 15, NULL, 1, 4, 1, NOW()),
(23, '极光', 'jiguang', 'primary', 16, NULL, 1, 4, 1, NOW()),
(24, '布丁云V2', 'buding_v2', 'success', 17, NULL, 1, 4, 1, NOW()),
(25, '中国移动 云MAS', 'mas', 'warning', 18, NULL, 1, 4, 1, NOW()),
(26, '百度云', 'baidu', 'primary', 19, NULL, 1, 4, 1, NOW()),
(27, '螺丝帽', 'luosimao', 'success', 20, NULL, 1, 4, 1, NOW()),
(28, 'SUBMAIL短信', 'mysubmail', 'success', 21, NULL, 1, 4, 1, NOW()),
(29, '单米科技短信', 'danmi', 'success', 22, NULL, 1, 4, 1, NOW()),
(30, '联通一信通', 'yixintong', 'success', 23, NULL, 1, 4, 1, NOW());

View File

@@ -0,0 +1,38 @@
-- liquibase formatted sql
-- changeset TopHub:2
-- comment 新增 短信厂商 字典
-- 新增 短信厂商 字典数据
INSERT INTO "sys_dict"
("id", "name", "code", "description", "is_system", "create_user", "create_time")
VALUES
(4, '短信厂商', 'sms_supplier_type', NULL, true, 1, NOW());
-- 新增 短信厂商 字典项
INSERT INTO "sys_dict_item"
("id", "label", "value", "color", "sort", "description", "status", "dict_id", "create_user", "create_time")
VALUES
(8, '阿里云', 'alibaba', 'primary', 1, NULL, 1, 4, 1, NOW()),
(9, '容联云', 'cloopen', 'success', 2, NULL, 1, 4, 1, NOW()),
(10, '天翼云', 'ctyun', 'warning', 3, NULL, 1, 4, 1, NOW()),
(11, '亿美软通', 'emay', 'primary', 4, NULL, 1, 4, 1, NOW()),
(12, '华为', 'huawei', 'success', 5, NULL, 1, 4, 1, NOW()),
(13, '京东', 'jdcloud', 'warning', 6, NULL, 1, 4, 1, NOW()),
(14, '网易', 'netease', 'primary', 7, NULL, 1, 4, 1, NOW()),
(15, '腾讯', 'tencent', 'success', 8, NULL, 1, 4, 1, NOW()),
(16, '合一', 'unisms', 'warning', 9, NULL, 1, 4, 1, NOW()),
(17, '云片', 'yunpian', 'primary', 10, NULL, 1, 4, 1, NOW()),
(18, '助通', 'zhutong', 'success', 11, NULL, 1, 4, 1, NOW()),
(19, '联麓', 'lianlu', 'warning', 12, NULL, 1, 4, 1, NOW()),
(20, '鼎众', 'dingzhong', 'primary', 13, NULL, 1, 4, 1, NOW()),
(21, '七牛云', 'qiniu', 'success', 14, NULL, 1, 4, 1, NOW()),
(22, '创蓝', 'chuanglan', 'warning', 15, NULL, 1, 4, 1, NOW()),
(23, '极光', 'jiguang', 'primary', 16, NULL, 1, 4, 1, NOW()),
(24, '布丁云V2', 'buding_v2', 'success', 17, NULL, 1, 4, 1, NOW()),
(25, '中国移动 云MAS', 'mas', 'warning', 18, NULL, 1, 4, 1, NOW()),
(26, '百度云', 'baidu', 'primary', 19, NULL, 1, 4, 1, NOW()),
(27, '螺丝帽', 'luosimao', 'success', 20, NULL, 1, 4, 1, NOW()),
(28, 'SUBMAIL短信', 'mysubmail', 'success', 21, NULL, 1, 4, 1, NOW()),
(29, '单米科技短信', 'danmi', 'success', 22, NULL, 1, 4, 1, NOW()),
(30, '联通一信通', 'yixintong', 'success', 23, NULL, 1, 4, 1, NOW());