mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	feat(system/sms): 短信渠道支持数据字典配置 (#159)
This commit is contained in:
		| @@ -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"; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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> { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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,8 +71,10 @@ 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)); | ||||
|         SmsConfigResp smsConfig = this.get(entity.getId()); | ||||
|  | ||||
|         BaseConfig config = SmsConfigUtil.from(smsConfig); | ||||
|         if (Objects.nonNull(config)) | ||||
|             SmsFactory.createSmsBlend(config); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 | ||||
| @@ -24,3 +26,5 @@ databaseChangeLog: | ||||
| #      file: db/changelog/postgresql/plugin/plugin_open.sql | ||||
| #  - include: | ||||
| #      file: db/changelog/postgresql/plugin/plugin_generator.sql | ||||
| #   - include: | ||||
| #       file: db/changelog/postgresql/sms_dict_data.sql | ||||
| @@ -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()); | ||||
| @@ -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()); | ||||
		Reference in New Issue
	
	Block a user
	 Top2Hub
					Top2Hub