diff --git a/fast-boot-module/fast-boot-message/pom.xml b/fast-boot-module/fast-boot-message/pom.xml
index 44324e3..0f4aef8 100644
--- a/fast-boot-module/fast-boot-message/pom.xml
+++ b/fast-boot-module/fast-boot-message/pom.xml
@@ -14,6 +14,18 @@
fast-framework
${revision}
+
+ com.aliyun
+ dysmsapi20170525
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java
+
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/cache/SmsPlatformCache.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/cache/SmsPlatformCache.java
new file mode 100644
index 0000000..d3c3630
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/cache/SmsPlatformCache.java
@@ -0,0 +1,48 @@
+package net.maku.message.cache;
+
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.utils.RedisUtils;
+import net.maku.message.sms.config.SmsConfig;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 短信平台 Cache
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Service
+@AllArgsConstructor
+public class SmsPlatformCache {
+ private final RedisUtils redisUtils;
+
+ /**
+ * 短信平台轮询KEY
+ */
+ private final String SMS_ROUND_KEY = "message:sms:round";
+
+ /**
+ * 短信平台列表KEY
+ */
+ private final String SMS_PLATFORM_KEY = "message:sms:platform";
+
+ /**
+ * 获取短信轮询值
+ */
+ public Long getRoundValue() {
+ return redisUtils.increment(SMS_ROUND_KEY);
+ }
+
+ public List list(){
+ return (List)redisUtils.get(SMS_PLATFORM_KEY);
+ }
+
+ public void save(List list){
+ redisUtils.set(SMS_PLATFORM_KEY, list);
+ }
+
+ public void delete(){
+ redisUtils.delete(SMS_PLATFORM_KEY);
+ }
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsLogController.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsLogController.java
new file mode 100644
index 0000000..309a4e8
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsLogController.java
@@ -0,0 +1,48 @@
+package net.maku.message.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.utils.Result;
+import net.maku.message.convert.SmsLogConvert;
+import net.maku.message.entity.SmsLogEntity;
+import net.maku.message.query.SmsLogQuery;
+import net.maku.message.service.SmsLogService;
+import net.maku.message.vo.SmsLogVO;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+* 短信日志
+*
+* @author 阿沐 babamu@126.com
+*/
+@RestController
+@RequestMapping("message/sms/log")
+@Tag(name="短信日志")
+@AllArgsConstructor
+public class SmsLogController {
+ private final SmsLogService smsLogService;
+
+ @GetMapping("page")
+ @Operation(summary = "分页")
+ @PreAuthorize("hasAuthority('sms:log')")
+ public Result> page(@Valid SmsLogQuery query){
+ PageResult page = smsLogService.page(query);
+
+ return Result.ok(page);
+ }
+
+ @GetMapping("{id}")
+ @Operation(summary = "信息")
+ @PreAuthorize("hasAuthority('sms:log')")
+ public Result get(@PathVariable("id") Long id){
+ SmsLogEntity entity = smsLogService.getById(id);
+
+ return Result.ok(SmsLogConvert.INSTANCE.convert(entity));
+ }
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsPlatformController.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsPlatformController.java
new file mode 100644
index 0000000..c451e3e
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/controller/SmsPlatformController.java
@@ -0,0 +1,113 @@
+package net.maku.message.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.utils.ExceptionUtils;
+import net.maku.framework.common.utils.Result;
+import net.maku.message.convert.SmsPlatformConvert;
+import net.maku.message.entity.SmsPlatformEntity;
+import net.maku.message.query.SmsPlatformQuery;
+import net.maku.message.service.SmsPlatformService;
+import net.maku.message.sms.SmsContext;
+import net.maku.message.sms.config.SmsConfig;
+import net.maku.message.sms.service.SmsService;
+import net.maku.message.vo.SmsPlatformVO;
+import net.maku.message.vo.SmsSendVO;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+* 短信平台
+*
+* @author 阿沐 babamu@126.com
+*/
+@RestController
+@RequestMapping("message/sms/platform")
+@Tag(name="短信平台")
+@AllArgsConstructor
+public class SmsPlatformController {
+ private final SmsPlatformService smsPlatformService;
+ private final SmsService smsService;
+
+ @GetMapping("page")
+ @Operation(summary = "分页")
+ @PreAuthorize("hasAuthority('sms:platform:page')")
+ public Result> page(@Valid SmsPlatformQuery query){
+ PageResult page = smsPlatformService.page(query);
+
+ return Result.ok(page);
+ }
+
+ @GetMapping("{id}")
+ @Operation(summary = "信息")
+ @PreAuthorize("hasAuthority('sms:platform:info')")
+ public Result get(@PathVariable("id") Long id){
+ SmsPlatformEntity entity = smsPlatformService.getById(id);
+
+ return Result.ok(SmsPlatformConvert.INSTANCE.convert(entity));
+ }
+
+ @PostMapping
+ @Operation(summary = "保存")
+ @PreAuthorize("hasAuthority('sms:platform:save')")
+ public Result save(@RequestBody SmsPlatformVO vo){
+ smsPlatformService.save(vo);
+
+ return Result.ok();
+ }
+
+ @PostMapping("send")
+ @Operation(summary = "发送短信")
+ @PreAuthorize("hasAuthority('sms:platform:update')")
+ public Result send(@RequestBody SmsSendVO vo){
+ SmsPlatformEntity entity = smsPlatformService.getById(vo.getId());
+ SmsConfig config = SmsPlatformConvert.INSTANCE.convert2(entity);
+
+ // 短信参数
+ Map params = new HashMap<>();
+ if(!StringUtils.isAnyBlank(vo.getParamKey(), vo.getParamValue())) {
+ params.put(vo.getParamKey(), vo.getParamValue());
+ }
+
+ try {
+ // 发送短信
+ new SmsContext(config).send(vo.getMobile(), params);
+
+ // 保存日志
+ smsService.saveLog(config, vo.getMobile(), params, null);
+
+ return Result.ok();
+ }catch (Exception e) {
+ // 保存日志
+ smsService.saveLog(config, vo.getMobile(), params, e);
+
+ return Result.error(ExceptionUtils.getExceptionMessage(e));
+ }
+ }
+
+ @PutMapping
+ @Operation(summary = "修改")
+ @PreAuthorize("hasAuthority('sms:platform:update')")
+ public Result update(@RequestBody @Valid SmsPlatformVO vo){
+ smsPlatformService.update(vo);
+
+ return Result.ok();
+ }
+
+ @DeleteMapping
+ @Operation(summary = "删除")
+ @PreAuthorize("hasAuthority('sms:platform:delete')")
+ public Result delete(@RequestBody List idList){
+ smsPlatformService.delete(idList);
+
+ return Result.ok();
+ }
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsLogConvert.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsLogConvert.java
new file mode 100644
index 0000000..fb38ff7
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsLogConvert.java
@@ -0,0 +1,23 @@
+package net.maku.message.convert;
+
+import net.maku.message.entity.SmsLogEntity;
+import net.maku.message.vo.SmsLogVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 短信日志
+*
+* @author 阿沐 babamu@126.com
+*/
+@Mapper
+public interface SmsLogConvert {
+ SmsLogConvert INSTANCE = Mappers.getMapper(SmsLogConvert.class);
+
+ SmsLogVO convert(SmsLogEntity entity);
+
+ List convertList(List list);
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsPlatformConvert.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsPlatformConvert.java
new file mode 100644
index 0000000..1c8fd71
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/convert/SmsPlatformConvert.java
@@ -0,0 +1,30 @@
+package net.maku.message.convert;
+
+import net.maku.message.entity.SmsPlatformEntity;
+import net.maku.message.sms.config.SmsConfig;
+import net.maku.message.vo.SmsPlatformVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 短信平台
+*
+* @author 阿沐 babamu@126.com
+*/
+@Mapper
+public interface SmsPlatformConvert {
+ SmsPlatformConvert INSTANCE = Mappers.getMapper(SmsPlatformConvert.class);
+
+ SmsPlatformEntity convert(SmsPlatformVO vo);
+
+ SmsPlatformVO convert(SmsPlatformEntity entity);
+
+ List convertList(List list);
+
+ SmsConfig convert2(SmsPlatformEntity entity);
+
+ List convertList2(List list);
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsLogDao.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsLogDao.java
new file mode 100644
index 0000000..01a9968
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsLogDao.java
@@ -0,0 +1,15 @@
+package net.maku.message.dao;
+
+import net.maku.framework.common.dao.BaseDao;
+import net.maku.message.entity.SmsLogEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* 短信日志
+*
+* @author 阿沐 babamu@126.com
+*/
+@Mapper
+public interface SmsLogDao extends BaseDao {
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsPlatformDao.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsPlatformDao.java
new file mode 100644
index 0000000..33c6643
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/dao/SmsPlatformDao.java
@@ -0,0 +1,15 @@
+package net.maku.message.dao;
+
+import net.maku.framework.common.dao.BaseDao;
+import net.maku.message.entity.SmsPlatformEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* 短信平台
+*
+* @author 阿沐 babamu@126.com
+*/
+@Mapper
+public interface SmsPlatformDao extends BaseDao {
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsLogEntity.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsLogEntity.java
new file mode 100644
index 0000000..9bcd179
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsLogEntity.java
@@ -0,0 +1,63 @@
+package net.maku.message.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 短信日志
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Data
+@EqualsAndHashCode(callSuper=false)
+@TableName("sms_log")
+public class SmsLogEntity {
+ /**
+ * id
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 平台ID
+ */
+ private Long platformId;
+
+ /**
+ * 平台类型
+ */
+ private Integer platform;
+
+ /**
+ * 手机号
+ */
+ private String mobile;
+
+ /**
+ * 状态 0:失败 1:成功
+ */
+ private Integer status;
+
+ /**
+ * 参数
+ */
+ private String params;
+
+ /**
+ * 异常信息
+ */
+ private String error;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsPlatformEntity.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsPlatformEntity.java
new file mode 100644
index 0000000..d6c6f6b
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/entity/SmsPlatformEntity.java
@@ -0,0 +1,57 @@
+package net.maku.message.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import net.maku.framework.common.entity.BaseEntity;
+
+/**
+ * 短信平台
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Data
+@EqualsAndHashCode(callSuper=false)
+@TableName("sms_platform")
+public class SmsPlatformEntity extends BaseEntity {
+ /**
+ * 平台类型 0:阿里云 1:腾讯云
+ */
+ private Integer platform;
+
+ /**
+ * 短信签名
+ */
+ private String signName;
+
+ /**
+ * 短信模板
+ */
+ private String templateId;
+
+ /**
+ * 短信应用的ID,如:腾讯云等
+ */
+ private String appId;
+
+ /**
+ * 腾讯云国际短信、华为云等需要
+ */
+ private String senderId;
+
+ /**
+ * AccessKey
+ */
+ private String accessKey;
+
+ /**
+ * SecretKey
+ */
+ private String secretKey;
+
+ /**
+ * 状态 0:禁用 1:启用
+ */
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/enums/SmsPlatformEnum.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/enums/SmsPlatformEnum.java
new file mode 100644
index 0000000..df3305a
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/enums/SmsPlatformEnum.java
@@ -0,0 +1,25 @@
+package net.maku.message.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 短信平台枚举
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsPlatformEnum {
+ /**
+ * 阿里云
+ */
+ ALIYUN(0),
+ /**
+ * 腾讯云
+ */
+ QCLOUD(1);
+
+ private final int value;
+
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsLogQuery.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsLogQuery.java
new file mode 100644
index 0000000..c39453a
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsLogQuery.java
@@ -0,0 +1,23 @@
+package net.maku.message.query;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import net.maku.framework.common.query.Query;
+
+/**
+* 短信日志查询
+*
+* @author 阿沐 babamu@126.com
+*/
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "短信日志查询")
+public class SmsLogQuery extends Query {
+ @Schema(description = "平台ID")
+ private Long platformId;
+
+ @Schema(description = "平台类型")
+ private Integer platform;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsPlatformQuery.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsPlatformQuery.java
new file mode 100644
index 0000000..c7e2d88
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/query/SmsPlatformQuery.java
@@ -0,0 +1,23 @@
+package net.maku.message.query;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import net.maku.framework.common.query.Query;
+
+/**
+* 短信平台查询
+*
+* @author 阿沐 babamu@126.com
+*/
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "短信平台查询")
+public class SmsPlatformQuery extends Query {
+ @Schema(description = "平台类型 0:阿里云 1:腾讯云")
+ private Integer platform;
+
+ @Schema(description = "短信签名")
+ private String signName;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsLogService.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsLogService.java
new file mode 100644
index 0000000..9332abe
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsLogService.java
@@ -0,0 +1,18 @@
+package net.maku.message.service;
+
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.service.BaseService;
+import net.maku.message.entity.SmsLogEntity;
+import net.maku.message.query.SmsLogQuery;
+import net.maku.message.vo.SmsLogVO;
+
+/**
+ * 短信日志
+ *
+ * @author 阿沐 babamu@126.com
+ */
+public interface SmsLogService extends BaseService {
+
+ PageResult page(SmsLogQuery query);
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsPlatformService.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsPlatformService.java
new file mode 100644
index 0000000..a1a64fa
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/SmsPlatformService.java
@@ -0,0 +1,32 @@
+package net.maku.message.service;
+
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.service.BaseService;
+import net.maku.message.entity.SmsPlatformEntity;
+import net.maku.message.query.SmsPlatformQuery;
+import net.maku.message.sms.config.SmsConfig;
+import net.maku.message.vo.SmsPlatformVO;
+
+import java.util.List;
+
+/**
+ * 短信平台
+ *
+ * @author 阿沐 babamu@126.com
+ */
+public interface SmsPlatformService extends BaseService {
+
+ PageResult page(SmsPlatformQuery query);
+
+ /**
+ * 启用的短信平台列表
+ */
+ List listByEnable();
+
+ void save(SmsPlatformVO vo);
+
+ void update(SmsPlatformVO vo);
+
+ void delete(List idList);
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsLogServiceImpl.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsLogServiceImpl.java
new file mode 100644
index 0000000..efae8d8
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsLogServiceImpl.java
@@ -0,0 +1,41 @@
+package net.maku.message.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.service.impl.BaseServiceImpl;
+import net.maku.message.convert.SmsLogConvert;
+import net.maku.message.dao.SmsLogDao;
+import net.maku.message.entity.SmsLogEntity;
+import net.maku.message.query.SmsLogQuery;
+import net.maku.message.service.SmsLogService;
+import net.maku.message.vo.SmsLogVO;
+import org.springframework.stereotype.Service;
+
+/**
+ * 短信日志
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Service
+@AllArgsConstructor
+public class SmsLogServiceImpl extends BaseServiceImpl implements SmsLogService {
+
+ @Override
+ public PageResult page(SmsLogQuery query) {
+ IPage page = baseMapper.selectPage(getPage(query), getWrapper(query));
+
+ return new PageResult<>(SmsLogConvert.INSTANCE.convertList(page.getRecords()), page.getTotal());
+ }
+
+ private LambdaQueryWrapper getWrapper(SmsLogQuery query){
+ LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
+ wrapper.eq(query.getPlatform() != null, SmsLogEntity::getPlatform, query.getPlatform());
+ wrapper.like(query.getPlatformId() != null, SmsLogEntity::getPlatformId, query.getPlatformId());
+ wrapper.orderByDesc(SmsLogEntity::getId);
+ return wrapper;
+ }
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsPlatformServiceImpl.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsPlatformServiceImpl.java
new file mode 100644
index 0000000..28c5c87
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/service/impl/SmsPlatformServiceImpl.java
@@ -0,0 +1,90 @@
+package net.maku.message.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.constant.Constant;
+import net.maku.framework.common.page.PageResult;
+import net.maku.framework.common.service.impl.BaseServiceImpl;
+import net.maku.message.cache.SmsPlatformCache;
+import net.maku.message.convert.SmsPlatformConvert;
+import net.maku.message.dao.SmsPlatformDao;
+import net.maku.message.entity.SmsPlatformEntity;
+import net.maku.message.query.SmsPlatformQuery;
+import net.maku.message.service.SmsPlatformService;
+import net.maku.message.sms.config.SmsConfig;
+import net.maku.message.vo.SmsPlatformVO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 短信平台
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Service
+@AllArgsConstructor
+public class SmsPlatformServiceImpl extends BaseServiceImpl implements SmsPlatformService {
+ private final SmsPlatformCache smsPlatformCache;
+
+ @Override
+ public PageResult page(SmsPlatformQuery query) {
+ IPage page = baseMapper.selectPage(getPage(query), getWrapper(query));
+
+ return new PageResult<>(SmsPlatformConvert.INSTANCE.convertList(page.getRecords()), page.getTotal());
+ }
+
+ private LambdaQueryWrapper getWrapper(SmsPlatformQuery query){
+ LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
+ wrapper.eq(query.getPlatform() != null, SmsPlatformEntity::getPlatform, query.getPlatform());
+ wrapper.like(StrUtil.isNotBlank(query.getSignName()), SmsPlatformEntity::getSignName, query.getSignName());
+ return wrapper;
+ }
+
+ @Override
+ public List listByEnable() {
+ // 从缓存读取
+ List cacheList = smsPlatformCache.list();
+
+ // 如果缓存没有,则从DB读取,然后保存到缓存里
+ if(cacheList == null) {
+ List list = this.list(new LambdaQueryWrapper().in(SmsPlatformEntity::getStatus, Constant.ENABLE));
+
+ cacheList = SmsPlatformConvert.INSTANCE.convertList2(list);
+ smsPlatformCache.save(cacheList);
+ }
+
+ return cacheList;
+ }
+
+ @Override
+ public void save(SmsPlatformVO vo) {
+ SmsPlatformEntity entity = SmsPlatformConvert.INSTANCE.convert(vo);
+
+ baseMapper.insert(entity);
+
+ smsPlatformCache.delete();
+ }
+
+ @Override
+ public void update(SmsPlatformVO vo) {
+ SmsPlatformEntity entity = SmsPlatformConvert.INSTANCE.convert(vo);
+
+ updateById(entity);
+
+ smsPlatformCache.delete();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void delete(List idList) {
+ removeByIds(idList);
+
+ smsPlatformCache.delete();
+ }
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/AliyunSmsStrategy.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/AliyunSmsStrategy.java
new file mode 100644
index 0000000..a87d6c3
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/AliyunSmsStrategy.java
@@ -0,0 +1,62 @@
+package net.maku.message.sms;
+
+import cn.hutool.core.map.MapUtil;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.*;
+import com.aliyun.teaopenapi.models.*;
+import lombok.extern.slf4j.Slf4j;
+import net.maku.framework.common.constant.Constant;
+import net.maku.framework.common.exception.FastException;
+import net.maku.framework.common.utils.JsonUtils;
+import net.maku.message.sms.config.SmsConfig;
+
+import java.util.Map;
+
+/**
+ * 阿里云短信
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Slf4j
+public class AliyunSmsStrategy implements SmsStrategy {
+ private final Client client;
+ private final SmsConfig smsConfig;
+ public AliyunSmsStrategy(SmsConfig smsConfig) {
+ this.smsConfig = smsConfig;
+
+ try {
+ Config config = new Config();
+ config.setAccessKeyId(smsConfig.getAccessKey());
+ config.setAccessKeySecret(smsConfig.getSecretKey());
+ config.endpoint = "dysmsapi.aliyuncs.com";
+
+ this.client = new Client(config);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void send(String mobile, Map params) {
+ SendSmsRequest request = new SendSmsRequest();
+ request.setSignName(smsConfig.getSignName());
+ request.setTemplateCode(smsConfig.getTemplateId());
+ request.setPhoneNumbers(mobile);
+// request.setTemplateParam("{\"code\":\"1234\"}");
+ if(MapUtil.isNotEmpty(params)){
+ request.setTemplateParam(JsonUtils.toJsonString(params));
+ }
+
+ try {
+ // 发送短信
+ SendSmsResponse response = client.sendSms(request);
+
+ // 发送失败
+ if(!Constant.OK.equalsIgnoreCase(response.getBody().getCode())) {
+ throw new FastException(response.getBody().getMessage());
+ }
+ } catch (Exception e) {
+ throw new FastException("短信发送失败:", e);
+ }
+ }
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/QcloudSmsStrategy.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/QcloudSmsStrategy.java
new file mode 100644
index 0000000..4faffb3
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/QcloudSmsStrategy.java
@@ -0,0 +1,76 @@
+package net.maku.message.sms;
+
+import cn.hutool.core.map.MapUtil;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.common.profile.HttpProfile;
+import com.tencentcloudapi.sms.v20210111.SmsClient;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
+import com.tencentcloudapi.sms.v20210111.models.SendStatus;
+import net.maku.framework.common.constant.Constant;
+import net.maku.framework.common.exception.FastException;
+import net.maku.message.sms.config.SmsConfig;
+
+import java.util.Map;
+
+/**
+ * 腾讯云短信
+ *
+ * @author 阿沐 babamu@126.com
+ */
+public class QcloudSmsStrategy implements SmsStrategy {
+ private final SmsConfig smsConfig;
+ private SmsClient client;
+
+ public QcloudSmsStrategy(SmsConfig smsConfig){
+ this.smsConfig = smsConfig;
+
+ try {
+ HttpProfile httpProfile = new HttpProfile();
+ httpProfile.setReqMethod("POST");
+ httpProfile.setEndpoint("sms.tencentcloudapi.com");
+
+ ClientProfile clientProfile = new ClientProfile();
+ clientProfile.setHttpProfile(httpProfile);
+
+ Credential cred = new Credential(smsConfig.getAccessKey(), smsConfig.getSecretKey());
+ this.client = new SmsClient(cred, "ap-guangzhou", clientProfile);
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void send(String mobile, Map params) {
+ SendSmsRequest request = new SendSmsRequest();
+ request.setSmsSdkAppId(smsConfig.getAppId());
+ request.setSignName(smsConfig.getSignName());
+ request.setTemplateId(smsConfig.getTemplateId());
+
+ // 有参数则设置
+ if(MapUtil.isNotEmpty(params)){
+ request.setTemplateParamSet(params.values().toArray(new String[0]));
+ }
+
+ // 手机号
+ String[] phoneNumberSet = { "+86" + mobile };
+ request.setPhoneNumberSet(phoneNumberSet);
+
+ // 国际、港澳台短信,需要添加SenderId,国内短信填空,默认未开通
+ request.setSenderId(smsConfig.getSenderId());
+
+ try {
+ // 发送短信
+ SendSmsResponse response = client.SendSms(request);
+ SendStatus sendStatus = response.getSendStatusSet()[0];
+
+ // 发送失败
+ if(!Constant.OK.equalsIgnoreCase(sendStatus.getCode())) {
+ throw new FastException(sendStatus.getMessage());
+ }
+ } catch (Exception e) {
+ throw new FastException("短信发送失败:", e);
+ }
+ }
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsContext.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsContext.java
new file mode 100644
index 0000000..de083a0
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsContext.java
@@ -0,0 +1,30 @@
+package net.maku.message.sms;
+
+import net.maku.framework.common.exception.FastException;
+import net.maku.message.enums.SmsPlatformEnum;
+import net.maku.message.sms.config.SmsConfig;
+
+import java.util.Map;
+
+/**
+ * 短信 Context
+ *
+ * @author 阿沐 babamu@126.com
+ */
+public class SmsContext {
+ private final SmsStrategy smsStrategy;
+
+ public SmsContext(SmsConfig config) {
+ if(config.getPlatform() == SmsPlatformEnum.ALIYUN.getValue()) {
+ this.smsStrategy = new AliyunSmsStrategy(config);
+ }else if(config.getPlatform() == SmsPlatformEnum.QCLOUD.getValue()) {
+ this.smsStrategy = new QcloudSmsStrategy(config);
+ }else {
+ throw new FastException("未知的短信平台");
+ }
+ }
+
+ public void send(String mobile, Map params) {
+ smsStrategy.send(mobile, params);
+ }
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsStrategy.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsStrategy.java
new file mode 100644
index 0000000..ef372b7
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/SmsStrategy.java
@@ -0,0 +1,18 @@
+package net.maku.message.sms;
+
+import java.util.Map;
+
+/**
+ * 短信
+ *
+ * @author 阿沐 babamu@126.com
+ */
+public interface SmsStrategy {
+
+ /**
+ * 发送短信
+ * @param mobile 手机号
+ * @param params 参数
+ */
+ void send(String mobile, Map params);
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/config/SmsConfig.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/config/SmsConfig.java
new file mode 100644
index 0000000..8cb17bf
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/config/SmsConfig.java
@@ -0,0 +1,52 @@
+package net.maku.message.sms.config;
+
+import lombok.Data;
+
+/**
+ * 短信配置项
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Data
+public class SmsConfig {
+ /**
+ * 平台ID
+ */
+ private Long id;
+
+ /**
+ * 平台类型
+ */
+ private Integer platform;
+
+ /**
+ * 短信签名
+ */
+ private String signName;
+
+ /**
+ * 短信模板
+ */
+ private String templateId;
+
+ /**
+ * 短信应用的ID,如:腾讯云等
+ */
+ private String appId;
+
+ /**
+ * 腾讯云国际短信、华为云等需要
+ */
+ private String senderId;
+
+ /**
+ * AccessKey
+ */
+ private String accessKey;
+
+ /**
+ * SecretKey
+ */
+ private String secretKey;
+
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/service/SmsService.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/service/SmsService.java
new file mode 100644
index 0000000..280470a
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/sms/service/SmsService.java
@@ -0,0 +1,107 @@
+package net.maku.message.sms.service;
+
+import cn.hutool.core.map.MapUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.maku.framework.common.constant.Constant;
+import net.maku.framework.common.exception.FastException;
+import net.maku.framework.common.utils.ExceptionUtils;
+import net.maku.framework.common.utils.JsonUtils;
+import net.maku.message.entity.SmsLogEntity;
+import net.maku.message.service.SmsLogService;
+import net.maku.message.service.SmsPlatformService;
+import net.maku.message.cache.SmsPlatformCache;
+import net.maku.message.sms.SmsContext;
+import net.maku.message.sms.config.SmsConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 短信服务
+ *
+ * @author 阿沐 babamu@126.com
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class SmsService {
+ private final SmsPlatformService smsPlatformService;
+ private final SmsLogService smsLogService;
+ private final SmsPlatformCache smsCacheService;
+
+ /**
+ * 发送短信
+ * @param mobile 手机号
+ * @return 是否发送成功
+ */
+ public boolean send(String mobile){
+ return this.send(mobile, MapUtil.newHashMap());
+ }
+
+ /**
+ * 发送短信
+ * @param mobile 手机号
+ * @param params 参数
+ * @return 是否发送成功
+ */
+ public boolean send(String mobile, Map params){
+ SmsConfig config = roundSmsConfig();;
+
+ try {
+ // 发送短信
+ new SmsContext(config).send(mobile, params);
+
+ saveLog(config, mobile, params, null);
+ return true;
+ }catch (Exception e) {
+ log.error("短信发送失败,手机号:{}", mobile, e);
+
+ saveLog(config, mobile, params, e);
+
+ return false;
+ }
+ }
+
+ /**
+ * 保存短信日志
+ */
+ public void saveLog(SmsConfig config, String mobile, Map params, Exception e) {
+ SmsLogEntity logEntity = new SmsLogEntity();
+ logEntity.setPlatform(config.getPlatform());
+ logEntity.setPlatformId(config.getId());
+ logEntity.setMobile(mobile);
+ logEntity.setParams(JsonUtils.toJsonString(params));
+
+ if(e != null) {
+ String error = StringUtils.substring(ExceptionUtils.getExceptionMessage(e), 0, 2000);
+ logEntity.setStatus(Constant.FAIL);
+ logEntity.setError(error);
+ }else {
+ logEntity.setStatus(Constant.SUCCESS);
+ }
+
+ smsLogService.save(logEntity);
+ }
+
+ /**
+ * 通过轮询算法,获取短信平台的配置
+ */
+ private SmsConfig roundSmsConfig() {
+ List platformList = smsPlatformService.listByEnable();
+
+ // 是否有可用的短信平台
+ int count = platformList.size();
+ if(count == 0) {
+ throw new FastException("没有可用的短信平台,请先添加");
+ }
+
+ // 采用轮询算法,发送短信
+ long round = smsCacheService.getRoundValue();
+
+ return platformList.get((int)round % count);
+ }
+
+}
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsLogVO.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsLogVO.java
new file mode 100644
index 0000000..09fc84a
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsLogVO.java
@@ -0,0 +1,46 @@
+package net.maku.message.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import net.maku.framework.common.utils.DateUtils;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 短信日志
+*
+* @author 阿沐 babamu@126.com
+*/
+@Data
+@Schema(description = "短信日志")
+public class SmsLogVO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "id")
+ private Long id;
+
+ @Schema(description = "平台ID")
+ private Long platformId;
+
+ @Schema(description = "平台类型")
+ private Integer platform;
+
+ @Schema(description = "手机号")
+ private String mobile;
+
+ @Schema(description = "状态 0:失败 1:成功")
+ private Integer status;
+
+ @Schema(description = "参数")
+ private String params;
+
+ @Schema(description = "异常信息")
+ private String error;
+
+ @Schema(description = "创建时间")
+ @JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
+ private Date createTime;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsPlatformVO.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsPlatformVO.java
new file mode 100644
index 0000000..4015ea3
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsPlatformVO.java
@@ -0,0 +1,52 @@
+package net.maku.message.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import net.maku.framework.common.utils.DateUtils;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 短信平台
+*
+* @author 阿沐 babamu@126.com
+*/
+@Data
+@Schema(description = "短信平台")
+public class SmsPlatformVO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "id")
+ private Long id;
+
+ @Schema(description = "平台类型 0:阿里云 1:腾讯云")
+ private Integer platform;
+
+ @Schema(description = "短信签名")
+ private String signName;
+
+ @Schema(description = "短信模板")
+ private String templateId;
+
+ @Schema(description = "短信应用的ID,如:腾讯云等")
+ private String appId;
+
+ @Schema(description = "腾讯云国际短信、华为云等需要")
+ private String senderId;
+
+ @Schema(description = "AccessKey")
+ private String accessKey;
+
+ @Schema(description = "SecretKey")
+ private String secretKey;
+
+ @Schema(description = "状态 0:禁用 1:启用")
+ private Integer status;
+
+ @Schema(description = "创建时间")
+ @JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
+ private Date createTime;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsSendVO.java b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsSendVO.java
new file mode 100644
index 0000000..15b51b5
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/java/net/maku/message/vo/SmsSendVO.java
@@ -0,0 +1,30 @@
+package net.maku.message.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+* 短信发送
+*
+* @author 阿沐 babamu@126.com
+*/
+@Data
+@Schema(description = "短信发送")
+public class SmsSendVO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "id")
+ private Long id;
+
+ @Schema(description = "手机号")
+ private String mobile;
+
+ @Schema(description = "参数Key")
+ private String paramKey;
+
+ @Schema(description = "参数Value")
+ private String paramValue;
+
+}
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsLogDao.xml b/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsLogDao.xml
new file mode 100644
index 0000000..4fbe9d4
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsLogDao.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsPlatformDao.xml b/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsPlatformDao.xml
new file mode 100644
index 0000000..c0beb27
--- /dev/null
+++ b/fast-boot-module/fast-boot-message/src/main/resources/mapper/SmsPlatformDao.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fast-framework/src/main/java/net/maku/framework/common/constant/Constant.java b/fast-framework/src/main/java/net/maku/framework/common/constant/Constant.java
index 91b95bd..4fc7226 100644
--- a/fast-framework/src/main/java/net/maku/framework/common/constant/Constant.java
+++ b/fast-framework/src/main/java/net/maku/framework/common/constant/Constant.java
@@ -22,4 +22,25 @@ public interface Constant {
* 超级管理员
*/
Integer SUPER_ADMIN = 1;
+ /**
+ * 禁用
+ */
+ Integer DISABLE = 0;
+ /**
+ * 启用
+ */
+ Integer ENABLE = 1;
+ /**
+ * 失败
+ */
+ Integer FAIL = 0;
+ /**
+ * 成功
+ */
+ Integer SUCCESS = 1;
+ /**
+ * OK
+ */
+ String OK = "OK";
+
}
\ No newline at end of file
diff --git a/fast-framework/src/main/java/net/maku/framework/common/utils/RedisUtils.java b/fast-framework/src/main/java/net/maku/framework/common/utils/RedisUtils.java
index 6cb0a0a..cbcec14 100644
--- a/fast-framework/src/main/java/net/maku/framework/common/utils/RedisUtils.java
+++ b/fast-framework/src/main/java/net/maku/framework/common/utils/RedisUtils.java
@@ -51,6 +51,14 @@ public class RedisUtils {
return get(key, NOT_EXPIRE);
}
+ public Long increment(String key){
+ return redisTemplate.opsForValue().increment(key);
+ }
+
+ public Boolean hasKey(String key){
+ return redisTemplate.hasKey(key);
+ }
+
public void delete(String key) {
redisTemplate.delete(key);
}
diff --git a/fast-server/pom.xml b/fast-server/pom.xml
index 81f944e..fd946b5 100644
--- a/fast-server/pom.xml
+++ b/fast-server/pom.xml
@@ -14,11 +14,11 @@
fast-boot-new
${revision}
-
-
-
-
-
+
+ net.maku
+ fast-boot-quartz
+ ${revision}
+
net.maku
fast-boot-message
diff --git a/pom.xml b/pom.xml
index 72ce6dd..30c4a7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,8 @@
1.6.2
1.4.2.Final
3.8.0
+ 2.0.18
+ 3.1.574
@@ -119,6 +121,16 @@
aliyun-sdk-oss
${aliyun.oss.version}
+
+ com.aliyun
+ dysmsapi20170525
+ ${aliyun.dysmsapi.version}
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java
+ ${tencentcloud.sdk.version}
+