新增短信功能

This commit is contained in:
阿沐 2022-08-20 14:19:22 +08:00
parent 1ad104e229
commit 51907b646d
32 changed files with 1207 additions and 5 deletions

View File

@ -14,6 +14,18 @@
<artifactId>fast-framework</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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<SmsConfig> list(){
return (List<SmsConfig>)redisUtils.get(SMS_PLATFORM_KEY);
}
public void save(List<SmsConfig> list){
redisUtils.set(SMS_PLATFORM_KEY, list);
}
public void delete(){
redisUtils.delete(SMS_PLATFORM_KEY);
}
}

View File

@ -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<PageResult<SmsLogVO>> page(@Valid SmsLogQuery query){
PageResult<SmsLogVO> page = smsLogService.page(query);
return Result.ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@PreAuthorize("hasAuthority('sms:log')")
public Result<SmsLogVO> get(@PathVariable("id") Long id){
SmsLogEntity entity = smsLogService.getById(id);
return Result.ok(SmsLogConvert.INSTANCE.convert(entity));
}
}

View File

@ -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<PageResult<SmsPlatformVO>> page(@Valid SmsPlatformQuery query){
PageResult<SmsPlatformVO> page = smsPlatformService.page(query);
return Result.ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@PreAuthorize("hasAuthority('sms:platform:info')")
public Result<SmsPlatformVO> 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<String> save(@RequestBody SmsPlatformVO vo){
smsPlatformService.save(vo);
return Result.ok();
}
@PostMapping("send")
@Operation(summary = "发送短信")
@PreAuthorize("hasAuthority('sms:platform:update')")
public Result<String> send(@RequestBody SmsSendVO vo){
SmsPlatformEntity entity = smsPlatformService.getById(vo.getId());
SmsConfig config = SmsPlatformConvert.INSTANCE.convert2(entity);
// 短信参数
Map<String, String> 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<String> update(@RequestBody @Valid SmsPlatformVO vo){
smsPlatformService.update(vo);
return Result.ok();
}
@DeleteMapping
@Operation(summary = "删除")
@PreAuthorize("hasAuthority('sms:platform:delete')")
public Result<String> delete(@RequestBody List<Long> idList){
smsPlatformService.delete(idList);
return Result.ok();
}
}

View File

@ -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<SmsLogVO> convertList(List<SmsLogEntity> list);
}

View File

@ -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<SmsPlatformVO> convertList(List<SmsPlatformEntity> list);
SmsConfig convert2(SmsPlatformEntity entity);
List<SmsConfig> convertList2(List<SmsPlatformEntity> list);
}

View File

@ -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<SmsLogEntity> {
}

View File

@ -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<SmsPlatformEntity> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<SmsLogEntity> {
PageResult<SmsLogVO> page(SmsLogQuery query);
}

View File

@ -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<SmsPlatformEntity> {
PageResult<SmsPlatformVO> page(SmsPlatformQuery query);
/**
* 启用的短信平台列表
*/
List<SmsConfig> listByEnable();
void save(SmsPlatformVO vo);
void update(SmsPlatformVO vo);
void delete(List<Long> idList);
}

View File

@ -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<SmsLogDao, SmsLogEntity> implements SmsLogService {
@Override
public PageResult<SmsLogVO> page(SmsLogQuery query) {
IPage<SmsLogEntity> page = baseMapper.selectPage(getPage(query), getWrapper(query));
return new PageResult<>(SmsLogConvert.INSTANCE.convertList(page.getRecords()), page.getTotal());
}
private LambdaQueryWrapper<SmsLogEntity> getWrapper(SmsLogQuery query){
LambdaQueryWrapper<SmsLogEntity> 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;
}
}

View File

@ -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<SmsPlatformDao, SmsPlatformEntity> implements SmsPlatformService {
private final SmsPlatformCache smsPlatformCache;
@Override
public PageResult<SmsPlatformVO> page(SmsPlatformQuery query) {
IPage<SmsPlatformEntity> page = baseMapper.selectPage(getPage(query), getWrapper(query));
return new PageResult<>(SmsPlatformConvert.INSTANCE.convertList(page.getRecords()), page.getTotal());
}
private LambdaQueryWrapper<SmsPlatformEntity> getWrapper(SmsPlatformQuery query){
LambdaQueryWrapper<SmsPlatformEntity> 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<SmsConfig> listByEnable() {
// 从缓存读取
List<SmsConfig> cacheList = smsPlatformCache.list();
// 如果缓存没有则从DB读取然后保存到缓存里
if(cacheList == null) {
List<SmsPlatformEntity> list = this.list(new LambdaQueryWrapper<SmsPlatformEntity>().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<Long> idList) {
removeByIds(idList);
smsPlatformCache.delete();
}
}

View File

@ -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<String, String> 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);
}
}
}

View File

@ -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<String, String> 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);
}
}
}

View File

@ -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<String, String> params) {
smsStrategy.send(mobile, params);
}
}

View File

@ -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<String, String> params);
}

View File

@ -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;
}

View File

@ -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<String, String> 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<String, String> 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<SmsConfig> platformList = smsPlatformService.listByEnable();
// 是否有可用的短信平台
int count = platformList.size();
if(count == 0) {
throw new FastException("没有可用的短信平台,请先添加");
}
// 采用轮询算法发送短信
long round = smsCacheService.getRoundValue();
return platformList.get((int)round % count);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.maku.message.dao.SmsLogDao">
</mapper>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.maku.message.dao.SmsPlatformDao">
</mapper>

View File

@ -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";
}

View File

@ -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);
}

View File

@ -14,11 +14,11 @@
<artifactId>fast-boot-new</artifactId>
<version>${revision}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>net.maku</groupId>-->
<!-- <artifactId>fast-boot-quartz</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>net.maku</groupId>
<artifactId>fast-boot-quartz</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>net.maku</groupId>
<artifactId>fast-boot-message</artifactId>

12
pom.xml
View File

@ -40,6 +40,8 @@
<captcha.version>1.6.2</captcha.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
<aliyun.oss.version>3.8.0</aliyun.oss.version>
<aliyun.dysmsapi.version>2.0.18</aliyun.dysmsapi.version>
<tencentcloud.sdk.version>3.1.574</tencentcloud.sdk.version>
</properties>
<dependencies>
@ -119,6 +121,16 @@
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${aliyun.dysmsapi.version}</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${tencentcloud.sdk.version}</version>
</dependency>
</dependencies>
</dependencyManagement>