提交 2bd40cfb authored 作者: YunaiV's avatar YunaiV

新增 `yudao-spring-boot-starter-biz-error-code` 错误码组件

上级 ca28d791
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-framework</artifactId>
<groupId>cn.iocoder.cloud</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
错误码 ErrorCode 的自动配置功能,提供如下功能:
1. 远程读取:项目启动时,从 system-server 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提水可配置;
2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-server 服务加载最新的 ErrorCode 错误码;
3. 自动写入:项目启动时,将项目本地的错误码写到 system-server 服务中,方便管理员在管理后台编辑;
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-rpc</artifactId>
<optional>true</optional>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
<version>${revision}</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<scope>provided</scope> <!-- 设置为 provided,主要是 ErrorCodeProperties 使用到 -->
</dependency>
</dependencies>
</project>
package cn.iocoder.yudao.module.system.framework.errorcode.config;
package cn.iocoder.yudao.framework.errorcode.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
......
package cn.iocoder.yudao.module.system.framework.errorcode.config;
package cn.iocoder.yudao.framework.errorcode.config;
import cn.iocoder.yudao.module.system.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
import cn.iocoder.yudao.module.system.framework.errorcode.core.loader.ErrorCodeLoader;
import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
import cn.iocoder.yudao.module.system.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
import cn.iocoder.yudao.module.system.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoader;
import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
// TODO 芋艿:貌似放的位置有问题
/**
* 错误码配置类
*
* @author 芋道源码
*/
@Configuration
@EnableConfigurationProperties(ErrorCodeProperties.class)
@EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
public class ErrorCodeConfiguration {
public class YudaoErrorCodeAutoConfiguration {
@Bean
public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
ErrorCodeProperties errorCodeProperties,
ErrorCodeFrameworkService errorCodeFrameworkService) {
return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(),
errorCodeFrameworkService);
ErrorCodeApi errorCodeApi) {
return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(), errorCodeApi);
}
@Bean
public ErrorCodeLoader errorCodeLoader(@Value("${spring.application.name}") String applicationName,
ErrorCodeFrameworkService errorCodeFrameworkService) {
return new ErrorCodeLoaderImpl(applicationName, errorCodeFrameworkService);
ErrorCodeApi errorCodeApi) {
return new ErrorCodeLoaderImpl(applicationName, errorCodeApi);
}
}
package cn.iocoder.yudao.framework.errorcode.config;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* 错误码用到 Feign 的配置项
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = ErrorCodeApi.class) // 主要是引入相关的 API 服务
public class YudaoErrorCodeRpcAutoConfiguration {
}
package cn.iocoder.yudao.module.system.framework.errorcode.core.generator;
package cn.iocoder.yudao.framework.errorcode.core.generator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.module.system.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeAutoGenerateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
......@@ -36,9 +36,9 @@ public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
*/
private final List<String> constantsClassList;
/**
* 错误码 Service
* 错误码 Api
*/
private final ErrorCodeFrameworkService errorCodeService;
private final ErrorCodeApi errorCodeApi;
@Override
@EventListener(ApplicationReadyEvent.class)
......@@ -49,7 +49,7 @@ public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
// 第二步,写入到 system 服务
errorCodeService.autoGenerateErrorCodes(autoGenerateDTOs);
errorCodeApi.autoGenerateErrorCodes(autoGenerateDTOs).checkError();
log.info("[execute][写入到 system 组件完成]");
}
......
package cn.iocoder.yudao.module.system.framework.errorcode.core.loader;
package cn.iocoder.yudao.framework.errorcode.core.loader;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
......
package cn.iocoder.yudao.module.system.framework.errorcode.core.loader;
package cn.iocoder.yudao.framework.errorcode.core.loader;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.system.framework.errorcode.core.dto.ErrorCodeRespDTO;
import cn.iocoder.yudao.module.system.framework.errorcode.core.service.ErrorCodeFrameworkService;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
......@@ -34,9 +34,9 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
*/
private final String applicationName;
/**
* 错误码 Service
* 错误码 Api
*/
private final ErrorCodeFrameworkService errorCodeService;
private final ErrorCodeApi errorCodeApi;
/**
* 缓存错误码的最大更新时间,用于后续的增量轮询,判断是否有更新
......@@ -55,7 +55,7 @@ public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
private void loadErrorCodes0() {
// 加载错误码
List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeService.getErrorCodeList(applicationName, maxUpdateTime);
List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeApi.getErrorCodeList(applicationName, maxUpdateTime).getCheckedData();
if (CollUtil.isEmpty(errorCodeRespDTOs)) {
return;
}
......
/**
* 错误码 ErrorCode 的自动配置功能,提供如下功能:
*
* 1. 远程读取:项目启动时,从 system-service 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提水可配置;
* 2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-service 服务加载最新的 ErrorCode 错误码;
* 3. 自动写入:项目启动时,将项目本地的错误码写到 system-server 服务中,方便管理员在管理后台编辑;
*
* @author 芋道源码
*/
package cn.iocoder.yudao.framework.errorcode;
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeRpcAutoConfiguration,\
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeAutoConfiguration
......@@ -46,6 +46,10 @@
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
......
......@@ -22,7 +22,7 @@ import java.util.List;
@Api(tags = "RPC 服务 - 错误码")
public interface ErrorCodeApi {
String PREFIX = ApiConstants.PREFIX + "/oauth2/token";
String PREFIX = ApiConstants.PREFIX + "/error-code";
@PostMapping(PREFIX + "/auto-generate")
@ApiOperation("自动创建错误码")
......
......@@ -78,6 +78,7 @@ yudao:
- /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
- /rpc-api/system/tenant/valid # 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验
- /rpc-api/system/error-code/* # 错误码的自动创建与下载的接口,无法带上租户编号
ignore-tables:
- system_tenant
- system_tenant_package
......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.system.errorcode.config.ErrorCodeAutoConfiguration
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>system-biz</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-biz-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot-starter-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 短信平台 阿里云、云片 -->
<dependency>
<groupId>com.yunpian.sdk</groupId>
<artifactId>yunpian-java-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<!-- 文件服务商 -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
package cn.iocoder.mall.system.biz.bo.smsSign;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* sms page
*
* @author Sin
* @time 2019/5/19 4:23 PM
*/
@Data
@Accessors(chain = true)
public class ListSmsSignBO {
/**
* 编号
*/
private Integer id;
/**
* 短信平台
*/
private Integer platform;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
* <p>
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
package cn.iocoder.mall.system.biz.bo.smsSign;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* @author Sin
* @time 2019/5/16 6:30 PM
*/
@Data
@Accessors(chain = true)
public class SmsSignBO {
/**
* 编号
*/
private Integer id;
/**
* 签名id 这个是第三方的
*/
private Integer signId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.biz.bo.smsTemplate;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* sms page
*
* @author Sin
* @time 2019/5/19 4:23 PM
*/
@Data
@Accessors(chain = true)
public class ListSmsTemplateBO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platform;
/**
* 短信模板 Code
*/
private String templateCode;
/**
* 短信模板
*/
private String template;
/**
* 短信类型
*/
private Integer smsType;
/**
* 审核状态
* <p>
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
///
/// 关联字段
/**
* 签名信息
*/
private Sign sign;
@Data
@Accessors(chain = true)
public static class Sign {
/**
* 编号
*/
private Integer id;
/**
* 签名id 这个是第三方的
*/
private String platformId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
* <p>
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
}
package cn.iocoder.mall.system.biz.bo.smsTemplate;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:41 PM
*/
@Data
@Accessors(chain = true)
public class SmsTemplateBO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platformId;
/**
* 短信模板
*/
private String template;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.biz.config;
import com.qiniu.util.Auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QiniuConfiguration {
@Value("${qiniu.access-key}")
private String accessKey;
@Value("${qiniu.secret-key}")
private String secretKey;
@Bean
public Auth auth() {
return Auth.create(accessKey, secretKey);
}
}
package cn.iocoder.mall.system.biz.convert;
import cn.iocoder.mall.system.biz.bo.smsSign.ListSmsSignBO;
import cn.iocoder.mall.system.biz.bo.smsSign.SmsSignBO;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsSignDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 短信 签名
*
* @author Sin
* @time 2019/5/16 6:31 PM
*/
@Mapper
public interface SmsSignConvert {
SmsSignConvert INSTANCE = Mappers.getMapper(SmsSignConvert.class);
@Mappings({})
SmsSignBO convert(SmsSignDO bean);
@Mappings({})
List<ListSmsSignBO> convert(List<SmsSignDO> beans);
}
package cn.iocoder.mall.system.biz.convert;
import cn.iocoder.mall.system.biz.bo.smsTemplate.ListSmsTemplateBO;
import cn.iocoder.mall.system.biz.bo.smsTemplate.SmsTemplateBO;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsSignDO;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsTemplateDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:43 PM
*/
@Mapper
public interface SmsTemplateConvert {
SmsTemplateConvert INSTANCE = Mappers.getMapper(SmsTemplateConvert.class);
@Mappings({})
SmsTemplateBO convert(SmsTemplateDO smsTemplateDO);
@Mappings({})
List<ListSmsTemplateBO> convert(List<SmsTemplateDO> smsTemplateDOList);
@Mappings({})
List<ListSmsTemplateBO.Sign> convertTemplateSign(List<SmsSignDO> smsSignDOList);
}
package cn.iocoder.mall.system.biz.dao.sms;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsSendLogDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsSendMapper extends BaseMapper<SmsSendLogDO> {
}
package cn.iocoder.mall.system.biz.dao.sms;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsSignDO;
import cn.iocoder.mall.system.biz.dto.smsSign.ListSmsSignDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsSignMapper extends BaseMapper<SmsSignDO> {
default IPage<SmsSignDO> listSmsSign(ListSmsSignDTO queryDTO) {
LambdaQueryWrapper<SmsSignDO> queryWrapper = new LambdaQueryWrapper<>();
if (queryDTO.getApplyStatus() != null) {
queryWrapper.eq(SmsSignDO::getApplyStatus, queryDTO.getApplyStatus());
}
if (!StringUtils.isEmpty(queryDTO.getSign())) {
queryWrapper.like(SmsSignDO::getSign, queryDTO.getSign());
}
if (!StringUtils.isEmpty(queryDTO.getId())) {
queryWrapper.eq(SmsSignDO::getId, queryDTO.getId());
}
Page<SmsSignDO> page = new Page<SmsSignDO>()
.setSize(queryDTO.getPageSize())
.setCurrent(queryDTO.getPageNo())
.setDesc("create_time");
return selectPage(page, queryWrapper);
}
}
package cn.iocoder.mall.system.biz.dao.sms;
import cn.iocoder.mall.system.biz.dataobject.sms.SmsTemplateDO;
import cn.iocoder.mall.system.biz.dto.smsTemplate.ListSmsTemplateDTO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsTemplateMapper extends BaseMapper<SmsTemplateDO> {
default IPage<SmsTemplateDO> listSmsTemplate(ListSmsTemplateDTO listSmsTemplateDTO) {
QueryWrapper<SmsTemplateDO> queryWrapper = new QueryWrapper<>();
if (listSmsTemplateDTO.getApplyStatus() != null) {
queryWrapper.eq("apply_status", listSmsTemplateDTO.getApplyStatus());
}
if (listSmsTemplateDTO.getSmsSignId() != null) {
queryWrapper.eq("sms_sign_id", listSmsTemplateDTO.getSmsSignId());
}
if (!StringUtils.isEmpty(listSmsTemplateDTO.getTemplate())) {
queryWrapper.like("template", listSmsTemplateDTO.getTemplate());
}
if (!StringUtils.isEmpty(listSmsTemplateDTO.getId())) {
queryWrapper.eq("id", listSmsTemplateDTO.getId());
}
Page<SmsTemplateDO> page = new Page<SmsTemplateDO>()
.setSize(listSmsTemplateDTO.getPageSize())
.setCurrent(listSmsTemplateDTO.getPageNo())
.setDesc("create_time");
return selectPage(page, queryWrapper);
}
}
package cn.iocoder.mall.system.biz.dataobject.sms;
import cn.iocoder.mall.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 client log
*
* @author Sin
* @time 2019/5/25 12:36 PM
*/
@Data
@Accessors(chain = true)
public class SmsSendLogDO extends BaseDO {
/**
* 编号
*/
private Integer id;
/**
* 短信模板
*/
private Integer templateId;
/**
* 短信
*/
private String template;
/**
* 参数
*/
private String params;
/**
* 发送信息
*/
private String message;
}
package cn.iocoder.mall.system.biz.dataobject.sms;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* 签名是短信发送前缀 如:【阿里云】、【小红书】
*
* @author Sin
* @time 2019/5/16 12:28 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_sign")
public class SmsSignDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 签名名称
*/
private String sign;
/**
* 平台
*
* 1、云片
* 2、阿里云
*/
private Integer platform;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 10、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.biz.dataobject.sms;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 模板
*
* @author Sin
* @time 2019/5/16 12:31 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_template")
public class SmsTemplateDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 模板 code(第三方平台 code)
*/
private String templateCode;
/**
* 短信签名 id
*/
private Integer platform;
/**
* 短信模板
*/
private String template;
/**
* 短信类型
*
* - 验证码类
* - 通知类
* - 营销类
*/
private Integer smsType;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 10、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.biz.dto.smsSign;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 添加 sign
*
* author: sin
* time: 2020/4/20 11:10 上午
*/
@Data
@Accessors(chain = true)
public class AddSignDTO implements Serializable {
private String sign;
private Integer platform;
}
package cn.iocoder.mall.system.biz.dto.smsSign;
import cn.iocoder.common.framework.vo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* page 短信模板 query
*
* @author Sin
* @time 2019/5/19 4:32 PM
*/
@Data
@Accessors(chain = true)
@ApiModel("短信服务查询")
public class ListSmsSignDTO extends PageParam {
@ApiModelProperty("编号")
private Integer id;
@ApiModelProperty("签名")
private String sign;
@ApiModelProperty("申请状态")
private Integer applyStatus;
}
package cn.iocoder.mall.system.biz.dto.smsSign;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 更新签名
* <p>
* author: sin
* time: 2020/4/20 11:05 上午
*/
@Data
@Accessors(chain = true)
public class UpdateSignDTO implements Serializable {
private Integer id;
private String sign;
private Integer platform;
}
package cn.iocoder.mall.system.biz.dto.smsTemplate;
import cn.iocoder.common.framework.vo.PageParam;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* page 短信模板 query
*
* @author Sin
* @time 2019/5/19 4:32 PM
*/
@Data
@Accessors(chain = true)
public class ListSmsTemplateDTO extends PageParam {
@NotNull
private String id;
@NotNull
private Integer smsSignId;
@NotNull
private String template;
@NotNull
private String applyStatus;
}
package cn.iocoder.mall.system.biz.enums.sms;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsApplyStatusEnum {
CHECKING(1, "审核中"),
SUCCESS(2, "审核成功"),
FAIL(10, "审核失败"),
;
private final Integer value;
private final String name;
SmsApplyStatusEnum(int code, String message) {
this.value = code;
this.name = message;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
package cn.iocoder.mall.system.biz.enums.sms;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsPlatformEnum implements IntArrayValuable {
YunPian(1, "云片"),
AliYun(2, "阿里云"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsPlatformEnum::getValue).toArray();
private final Integer value;
private final String name;
SmsPlatformEnum(Integer code, String message) {
this.value = code;
this.name = message;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.mall.system.biz.enums.sms;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsTypeEnum implements IntArrayValuable {
VERIFICATION_CODE(1, "验证码"),
NOTICE(2, "通知"),
MARKETING(3, "营销"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsTypeEnum::getValue).toArray();
private final Integer value;
private final String name;
SmsTypeEnum(Integer code, String message) {
this.value = code;
this.name = message;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.mall.system.biz.log.operation.annotation;
import java.lang.annotation.*;
/**
* @author Hccake
* @version 1.0
* @date 2019/10/15 18:09
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogging {
/**
* 日志信息
* @return
*/
String value();
}
package cn.iocoder.mall.system.biz.log.operation.aspect;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtils;
import cn.iocoder.mall.system.biz.log.operation.annotation.OperationLogging;
import cn.iocoder.mall.system.biz.log.operation.enums.LogStatus;
import cn.iocoder.mall.system.biz.log.operation.event.OperationLogEvent;
import cn.iocoder.mall.system.biz.log.operation.model.dto.OperationLogDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author Hccake
* @version 1.0
* @date 2019/10/15 18:16
*/
@Slf4j
@Aspect
@Order(0)
@RequiredArgsConstructor
public class OperationLogAspect {
private final ApplicationEventPublisher publisher;
@Around("@annotation(operationLogging)")
public Object around(ProceedingJoinPoint joinPoint, OperationLogging operationLogging) throws Throwable {
Signature signature = joinPoint.getSignature();
String strClassName = joinPoint.getTarget().getClass().getName();
String strMethodName = signature.getName();
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
// 获取日志
OperationLogDTO operationLogDTO = prodOperationLog();
operationLogDTO.setMsg(operationLogging.value());
// 记录参数
MethodSignature methodSignature = (MethodSignature) signature;
operationLogDTO.setParams(getParams(joinPoint, methodSignature));
// 开始时间
long startTime = System.currentTimeMillis();
Object result;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
operationLogDTO.setStatus(LogStatus.FAIL.getValue());
throw throwable;
}
// 结束时间
operationLogDTO.setResponseTime((int) (System.currentTimeMillis() - startTime));
// 发布事件
publisher.publishEvent(new OperationLogEvent(operationLogDTO));
return result;
}
/**
* 获取方法参数
* @param joinPoint joinPoint
* @param methodSignature 方法签名
* @return 方法参数的Json字符串形式
*/
private String getParams(ProceedingJoinPoint joinPoint, MethodSignature methodSignature) {
String[] parameterNames = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
if(ArrayUtil.isEmpty(parameterNames)){
return null;
}
Map<String, Object> paramsMap = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
paramsMap.put(parameterNames[i], args[i]);
}
return JSONUtil.toJsonStr(paramsMap);
}
/**
* 根据请求生成操作日志
* @return 操作日志DTO
*/
private OperationLogDTO prodOperationLog() {
HttpServletRequest request = ((ServletRequestAttributes) Objects
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
return new OperationLogDTO()
.setTraceId(MallUtils.getTraceId())
.setUri(URLUtil.getPath(request.getRequestURI()))
.setUserAgent(HttpUtil.getUserAgent(request))
.setIp(HttpUtil.getIp(request))
.setMethod(request.getMethod())
// TODO 获取管理员用户名 或者 用户ID
// .setOperator(Objects.requireNonNull(LogUtils.getUsername()))
.setStatus(LogStatus.SUCCESS.getValue());
}
}
package cn.iocoder.mall.system.biz.log.operation.enums;
/**
* @author Hccake
* @version 1.0
* @date 2020/5/15 14:47
*/
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作状态
*/
@Getter
@AllArgsConstructor
public enum LogStatus {
/**
* 成功
*/
SUCCESS(1),
/**
* 失败
*/
FAIL(0);
private final int value;
}
package cn.iocoder.mall.system.biz.log.operation.event;
import cn.iocoder.mall.system.biz.log.operation.model.dto.OperationLogDTO;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author
* 系统日志事件
*/
@Getter
@AllArgsConstructor
public class OperationLogEvent {
private final OperationLogDTO operationLogDTO;
}
package cn.iocoder.mall.system.biz.log.operation.event;
import cn.iocoder.mall.system.biz.log.operation.service.OperationLogSaveService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
/**
* @author
* 异步监听日志事件
*/
@Slf4j
public class OperationLogListener {
@Autowired
private OperationLogSaveService operationLogSaveService;
@Async
@Order
@EventListener(OperationLogEvent.class)
public void saveSysLog(OperationLogEvent event) {
operationLogSaveService.saveLog(event.getOperationLogDTO());
}
}
package cn.iocoder.mall.system.biz.log.operation.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 操作日志
*
* @author hccake
* @date 2020-05-15 15:12:53
*/
@Data
@Accessors(chain = true)
@ApiModel(value = "操作日志")
public class OperationLogDTO{
private static final long serialVersionUID = 1L;
/**
* 链路追踪编号
*/
@ApiModelProperty(value = "链路追踪编号")
private String traceId;
/**
* 账号编号
*/
@ApiModelProperty(value = "账号编号")
private Integer accountId;
/**
* 应用名
*/
@ApiModelProperty(value = "应用名")
private String applicationName;
/**
* 访问地址
*/
@ApiModelProperty(value = "访问地址")
private String uri;
/**
* 参数
*/
@ApiModelProperty(value = "参数")
private String params;
/**
* http 方法
*/
@ApiModelProperty(value = "http 方法")
private String method;
/**
* userAgent
*/
@ApiModelProperty(value = "userAgent")
private String userAgent;
/**
* ip
*/
@ApiModelProperty(value = "ip")
private String ip;
/**
* 请求时间
*/
@ApiModelProperty(value = "请求时间")
private LocalDateTime startTime;
/**
* 响应时长 -- 毫秒级
*/
@ApiModelProperty(value = "响应时长 -- 毫秒级")
private Integer responseTime;
/**
* 日志消息
*/
@ApiModelProperty(value = "日志消息")
private String msg;
/**
* 操作状态
*/
@ApiModelProperty(value = "操作状态")
private Integer status;
/**
* 创建者
*/
@ApiModelProperty(value = "创建者")
private String operator;
}
package cn.iocoder.mall.system.biz.log.operation.model.po;
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 com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 操作日志
*
* @author hccake
* @date 2020-05-15 15:12:53
*/
@Data
@TableName("operation_log")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "操作日志")
public class OperationLogPO extends Model<OperationLogPO> {
private static final long serialVersionUID = 1L;
/**
* 编号
*/
@TableId
private Integer id;
/**
* 链路追踪编号
*/
private String traceId;
/**
* 账号编号
*/
private Integer accountId;
/**
* 应用名
*/
private String applicationName;
/**
* 访问地址
*/
private String uri;
/**
* 参数
*/
private String params;
/**
* http 方法
*/
private String method;
/**
* userAgent
*/
private String userAgent;
/**
* ip
*/
private String ip;
/**
* 请求时间
*/
private LocalDateTime startTime;
/**
* 响应时长 -- 毫秒级
*/
private Integer responseTime;
/**
* 日志消息
*/
private String msg;
/**
* 操作状态
*/
private Integer status;
/**
* 创建者
*/
private String operator;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
package cn.iocoder.mall.system.biz.log.operation.service;
import cn.iocoder.mall.system.biz.log.operation.model.dto.OperationLogDTO;
/**
* 操作日志业务类
* @author Hccake
* @version 1.0
* @date 2019/10/15 19:57
*/
public interface OperationLogSaveService {
/**
* 保存操作日志
* @param operationLogDTO
* @return true/false
*/
boolean saveLog(OperationLogDTO operationLogDTO);
}
package cn.iocoder.mall.system.biz.service.sms;
import com.alibaba.fastjson.JSON;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 短信 AliYun client
*
* @author Sin
* @time 2019/5/25 12:28 PM
*/
@Component // TODO DOME FROM 芋艿 to 小范:建议类名改成 AliYunSmsClient
public class AliYunSmsClient implements SmsClient {
private static final Logger LOGGER = LoggerFactory.getLogger(AliYunSmsClient.class);
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
private static final String SUCCESS_CODE = "OK";
private static final String SUCCESS_MESSAGE = "OK";
/**
* 阿里云短信 - 批量推送最大数 500,支持 1000
*/
private static final int MAX_BATCH_SIZE = 500;
@Value("${sms.aliYun.accessKeyId?:'default_value'}")
private String accessKeyId;
@Value("${sms.aliYun.accessSecret?:'default_value'}")
private String accessSecret;
@Data
@Accessors(chain = true)
public static class Result {
/**
* 发送回执ID,可根据该ID在接口QuerySendDetails中查询具体的发送状态。
*/
private String BizId;
/**
* 请求状态码。
*
* - OK 蔡成功
*/
private String Code;
/**
* 状态码的描述。
*/
private String Message;
/**
* 请求ID。
*/
private String RequestId;
}
@Override
public SendResult singleSend(String mobile, String sign, String templateCode,
String template, Map<String, String> templateParams) {
// params
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain(DOMAIN);
request.setVersion("2017-05-25");
request.setAction("SendSms");
request.putQueryParameter("PhoneNumbers", mobile);
request.putQueryParameter("SignName", sign);
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", JSON.toJSONString(templateParams));
// 发送请求
return doSend(request);
}
@Override
public SendResult batchSend(List<String> mobileList, String sign, String templateCode,
String template, Map<String, String> templateParams) {
// 最大发送数为 1000,我们设置为 500 个, 分段发送
int maxSendSize = MAX_BATCH_SIZE;
int maxSendSizeCount = mobileList.size() % maxSendSize == 0
? mobileList.size() / maxSendSize
: mobileList.size() / maxSendSize + 1;
// 处理批量
SendResult sendResult = null;
for (int i = 0; i < maxSendSizeCount; i++) {
// 分批发送
List<String> batchSendMobile = mobileList
.subList(i * maxSendSize, (i + 1) * maxSendSize);
// params
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain(DOMAIN);
request.setVersion("2017-05-25");
request.setAction("SendBatchSms");
request.putQueryParameter("PhoneNumberJson", JSON.toJSONString(batchSendMobile));
request.putQueryParameter("SignNameJson", JSON.toJSONString(Collections.singletonList(sign)));
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParamJson", JSON.toJSONString(Collections.singletonList(templateParams)));
// 发送请求
sendResult = doSend(request);
}
return sendResult;
}
private SendResult doSend(CommonRequest request) {
// 获取 client
IAcsClient client = getClient();
try {
CommonResponse response = client.getCommonResponse(request);
Result result = JSON.parseObject(response.getData(), Result.class);
if (!SUCCESS_CODE.equals(result.getCode())) {
LOGGER.info("发送验证码失败 params {} res {}", JSON.toJSON(request), JSON.toJSON(result));
// 错误发送失败
return new SendResult()
.setIsSuccess(false)
.setCode(SendResult.ERROR_CODE)
.setMessage(result.getMessage());
} else {
LOGGER.info("发送验证码失败 params {} res", JSON.toJSON(request), JSON.toJSON(result));
// 发送成功
return new SendResult()
.setIsSuccess(true)
.setCode(SendResult.SUCCESS_CODE)
.setMessage(result.getMessage());
}
} catch (ClientException e) {
LOGGER.error("发送验证码异常 {}", ExceptionUtils.getMessage(e));
return new SendResult()
.setIsSuccess(false)
.setCode(SendResult.ERROR_CODE)
.setMessage(ExceptionUtils.getMessage(e));
}
}
/**
* 获取 client
*
* @return
*/
private IAcsClient getClient() {
return new DefaultAcsClient(DefaultProfile.getProfile("default", accessKeyId, accessSecret));
}
}
package cn.iocoder.mall.system.biz.service.sms;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
/**
* 短信平台
*
* @author Sin
* @time 2019/5/16 6:33 PM
*/
public interface SmsClient {
/**
* 短信发送 - 单个
*
* @param mobile 手机号
* @param sign 签名
* @param templateCode 短信模板code
* @param template 短信模板
* @param templateParams 短信模板 params
* @return 发送后信息
*/
SendResult singleSend(String mobile, String sign, String templateCode,
String template, Map<String, String> templateParams);
/**
* 短信发送 - 批量
*
* @param mobileList 手机号
* @param sign 签名
* @param templateCode 短信模板 code
* @param template 短信模板
* @param templateParams 短信模板params
* @return 发送后信息
*/
SendResult batchSend(List<String> mobileList, String sign, String templateCode,
String template, Map<String, String> templateParams);
@Data
@Accessors(chain = true)
class SendResult {
public static final int SUCCESS_CODE = 0;
public static final int ERROR_CODE = 1;
public static final String SUCCESS_MESSAGE = "SUCCESS";
/**
* 错误码
*/
private Integer code;
/**
* 错误信息
*/
private String message;
/**
* 是否成功
*/
private Boolean isSuccess;
}
}
package cn.iocoder.mall.system.biz.service.sms;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.system.biz.bo.smsSign.ListSmsSignBO;
import cn.iocoder.mall.system.biz.bo.smsTemplate.ListSmsTemplateBO;
import cn.iocoder.mall.system.biz.bo.smsSign.SmsSignBO;
import cn.iocoder.mall.system.biz.bo.smsTemplate.SmsTemplateBO;
import cn.iocoder.mall.system.biz.dto.smsSign.AddSignDTO;
import cn.iocoder.mall.system.biz.dto.smsSign.ListSmsSignDTO;
import cn.iocoder.mall.system.biz.dto.smsTemplate.ListSmsTemplateDTO;
import cn.iocoder.mall.system.biz.dto.smsSign.UpdateSignDTO;
import java.util.List;
import java.util.Map;
/**
* 短信服务
*
* @author Sin
* @time 2019/5/16 9:54 AM
*/
public interface SmsService {
/**
* 短信模板 - 分页
*
* @param listSmsSignDTO
* @return
*/
PageResult<ListSmsSignBO> listSmsSign(ListSmsSignDTO listSmsSignDTO);
/**
* 短信模板 - 分页
*
* @param listSmsTemplateDTO
* @return
*/
PageResult<ListSmsTemplateBO> listSmsTemplate(ListSmsTemplateDTO listSmsTemplateDTO);
/**
* 签名 - 创建
*
* @param addSignDTO
*/
void addSign(AddSignDTO addSignDTO);
/**
* 签名 - 获取
*
* @param id
*/
SmsSignBO getSign(Integer id);
/**
* 签名 - 更新
*
* @param updateSignDTO
*/
void updateSign(UpdateSignDTO updateSignDTO);
/**
* 签名 - 更新
*
* @param id
*/
void deleteSign(Integer id);
/**
* 模板 - 创建
*
* @param smsSignId 选用的哪个签名
* @param templateCode 模板code
* @param template 模板内容
* @param platform 平台
*/
void addTemplate(Integer smsSignId, String templateCode,
String template, Integer platform, Integer smsType);
/**
* 模板 - 获取
*
* @param id
*/
SmsTemplateBO getTemplate(Integer id, Integer platform);
/**
* 模板 - 更新
*
* @param id 模板id
* @param smsSignId 短期签名
* @param template 模板内容
* @param platform 短信平台
*/
void updateTemplate(Integer id, Integer smsSignId, String templateCode,
String template, Integer platform, Integer smsType);
/**
* 模板 - 删除
*
* @param id
*/
void deleteTemplate(Integer id);
/**
* 短信发送 - 单个
*
* @return
*/
void singleSend(String mobile, Integer smsTemplateId, Map<String, String> params);
/**
* 短信发送 - 批量
*
* @return
*/
void batchSend(List<String> mobileList, Integer smsTemplateId, Map<String, String> params);
}
package cn.iocoder.mall.system.biz.service.sms;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.system.biz.enums.SystemErrorCodeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 云片 短信平台
*
* @author Sin
* @time 2019/5/16 6:34 PM
*/
@Component
public class YunPianSmsClient implements SmsClient {
protected static final Logger LOGGER = LoggerFactory.getLogger(YunPianSmsClient.class);
private static final int SUCCESS_CODE = 0;
private static final String SUCCESS_MESSAGE = "SUCCESS";
/**
* 云片短信 - 批量推送最大数 500,支持 1000
*/
private static final int MAX_BATCH_SIZE = 500;
/**
* 模板 - 参数拼接
*/
private static final String PARAM_TEMPLATE = "#%s#";
/**
* 模板 - 签名拼接
*/
private static final String SIGN_TEMPLATE = "【%s】%s";
/**
* 签名 - 添加
*/
private static final String URL_SIGN_ADD = "https://sms.yunpian.com/v2/sign/add.json";
/**
* 签名 - 获取
*/
private static final String URL_SIGN_GET = "https://sms.yunpian.com/v2/sign/get.json";
/**
* 签名 - 更新
*/
private static final String URL_SIGN_UPDATE = "https://sms.yunpian.com/v2/sign/update.json";
/**
* 模板 - 添加
*/
private static final String URL_TEMPLATE_ADD = "https://sms.yunpian.com/v2/tpl/add.json";
/**
* 模板 - 获取
*/
private static final String URL_TEMPLATE_GET = "https://sms.yunpian.com/v2/tpl/get.json";
/**
* 模板 - 更新
*/
private static final String URL_TEMPLATE_UPDATE = "https://sms.yunpian.com/v2/tpl/update.json";
/**
* 模板 - 删除
*/
private static final String URL_TEMPLATE_DELETE = "https://sms.yunpian.com/v2/tpl/del.json";
/**
* 短信发送 - 单个
*/
private static final String URL_SEND_SINGLE = "https://sms.yunpian.com/v2/sms/single_send.json";
/**
* 短信发送 - 批量
*/
private static final String URL_SEND_BATCH = "https://sms.yunpian.com/v2/sms/batch_send.json";
//编码格式。发送编码格式统一用UTF-8
private static String ENCODING = "UTF-8";
@Value("${sms.yunPian.apiKey?:'default_value'}")
private String apiKey;
@Override
public SendResult singleSend(String mobile, String sign, String templateCode, String template, Map<String, String> templateParams) {
// build 模板
template = buildTemplate(sign, template, templateParams);
// 请求参数
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("mobile", mobile);
params.put("text", template);
// TODO: 2019/5/19 sin 运营商发送报告 回调
// params.put("callback_url", template);
String result = post(URL_SEND_SINGLE, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(SystemErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
// 转换 result
return new SendResult()
.setIsSuccess(SUCCESS_CODE == jsonObject.getInteger("code"))
.setCode(jsonObject.getInteger("code"))
.setMessage(jsonObject.getString("detail"));
}
@Override
public SendResult batchSend(List<String> mobileList, String sign,
String templateCode, String template,
Map<String, String> templateParams) {
// build 模板
template = buildTemplate(sign, template, templateParams);
// 最大发送数为 1000,我们设置为 500 个, 分段发送
int maxSendSize = MAX_BATCH_SIZE;
int maxSendSizeCount = mobileList.size() % maxSendSize == 0
? mobileList.size() / maxSendSize
: mobileList.size() / maxSendSize + 1;
int j = 0;
int j2 = mobileList.size();
for (int i = 0; i < maxSendSizeCount; i++) {
StringBuffer sendMobileStr = new StringBuffer();
for (int k = j; k < j2; k++) {
sendMobileStr.append(",");
sendMobileStr.append(mobileList.get(k));
}
String dividedMobile = sendMobileStr.toString().substring(1);
// 发送手机号
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("mobile", dividedMobile);
params.put("text", template);
// TODO: 2019/5/19 sin 运营商发送报告 回调
// params.put("callback_url", template);
String result = post(URL_SEND_BATCH, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(SystemErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
// 用于递增 maxSendSize
j = j2;
j2 = j + maxSendSize;
}
return new SendResult()
.setIsSuccess(true)
.setCode(SUCCESS_CODE)
.setMessage(SUCCESS_MESSAGE);
}
/**
* 构建模板
*
* @param sign
* @param template
* @param templateParams
* @return
*/
private static String buildTemplate(String sign, String template,
Map<String, String> templateParams) {
// 不处理 empty 数据
if (CollectionUtils.isEmpty(templateParams)) {
return template;
}
// 处理template参数
for (Map.Entry<String, String> entry : templateParams.entrySet()) {
String paramsKey = entry.getKey();
String value = entry.getValue();
String paramPlace = String.format(PARAM_TEMPLATE, paramsKey);
template = template.replaceAll(paramPlace, value);
}
template = String.format(SIGN_TEMPLATE, sign, template);
return template;
}
/**
* 基于HttpClient 4.3的通用POST方法
*
* @param url 提交的URL
* @param paramsMap 提交<参数,值>Map
* @return 提交响应
*/
public static String post(String url, Map<String, String> paramsMap) {
CloseableHttpClient client = HttpClients.createDefault();
String responseText = "";
CloseableHttpResponse response = null;
try {
HttpPost method = new HttpPost(url);
if (paramsMap != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (Map.Entry<String, String> param : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(param.getKey(),
param.getValue());
paramList.add(pair);
}
method.setEntity(new UrlEncodedFormEntity(paramList, ENCODING));
}
response = client.execute(method);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseText = EntityUtils.toString(entity, ENCODING);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (Exception e) {
e.printStackTrace();
}
}
LOGGER.debug("云片短信平台 res: {}", responseText);
return responseText;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>system-rest</artifactId>
<description>提供 system 服务的 Rest 接口的实现,提供对外调用</description>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-biz</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot-starter-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot-starter-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot-starter-swagger</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
package cn.iocoder.mall.system.rest.controller.file;
import cn.iocoder.common.framework.vo.CommonResult;
import com.qiniu.util.Auth;
import io.swagger.annotations.Api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 文件模块(Admins API)
*
* author: sin
* time: 2020/4/20 9:41 上午
*/
@RestController
@RequestMapping("admins/file")
@Api(tags = "文件模块")
public class AdminsFileController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private Auth auth;
@Value("${qiniu.bucket}")
private String bucket;
@GetMapping("/get-qiniu-token")
public CommonResult<String> getQiniuToken() {
String token = auth.uploadToken(bucket);
logger.info("[qiniu_token][token({}) get]", token);
return CommonResult.success(token);
}
}
package cn.iocoder.mall.system.rest.controller.sms;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.system.biz.bo.smsSign.ListSmsSignBO;
import cn.iocoder.mall.system.biz.dto.smsSign.ListSmsSignDTO;
import cn.iocoder.mall.system.biz.service.sms.SmsService;
import cn.iocoder.mall.system.rest.convert.sms.AdminsSmsConvert;
import cn.iocoder.mall.system.rest.request.sms.AddSignRequest;
import cn.iocoder.mall.system.rest.request.sms.UpdateSignRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 短信服务
*
* @author Sin
* @time 2019/5/26 12:26 PM
*/
@RestController
@RequestMapping("admins/sms/sign")
@Api("短信服务(签名)")
public class AdminsSmsSignController {
@Autowired
private SmsService smsService;
@GetMapping("page")
@ApiOperation("签名-page")
public CommonResult<PageResult<ListSmsSignBO>> pageSign(@Validated ListSmsSignDTO listSmsSignDTO) {
return CommonResult.success(smsService.listSmsSign(listSmsSignDTO));
}
@PostMapping("add")
@ApiOperation("签名-添加")
public CommonResult<?> addSign(@RequestBody AddSignRequest addSignRequest) {
smsService.addSign(AdminsSmsConvert.INSTANCE.convert(addSignRequest));
return CommonResult.success(null);
}
@PutMapping("update")
@ApiOperation("签名-更新")
public CommonResult<?> updateSign(@RequestBody UpdateSignRequest updateSignRequest) {
smsService.updateSign(AdminsSmsConvert.INSTANCE.convert(updateSignRequest));
return CommonResult.success(null);
}
@DeleteMapping("deleted")
@ApiOperation("签名-删除")
public CommonResult<?> deletedSign(@RequestParam("id") Integer id) {
smsService.deleteSign(id);
return CommonResult.success(null);
}
}
package cn.iocoder.mall.system.rest.controller.sms;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.system.biz.bo.smsTemplate.ListSmsTemplateBO;
import cn.iocoder.mall.system.biz.service.sms.SmsService;
import cn.iocoder.mall.system.rest.convert.sms.AdminsSmsConvert;
import cn.iocoder.mall.system.rest.request.sms.AddSmsTemplateRequest;
import cn.iocoder.mall.system.rest.request.sms.ListSmsTemplateRequest;
import cn.iocoder.mall.system.rest.request.sms.UpdateSmsTemplateRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 短信服务
*
* @author Sin
* @time 2019/5/26 12:26 PM
*/
@RestController
@RequestMapping("admins/sms/template")
@Api("短信服务(短信模板)")
public class AdminsSmsTemplateController {
@Autowired
private SmsService smsService;
@PostMapping("page")
@ApiOperation("短信模板-page")
public CommonResult<PageResult<ListSmsTemplateBO>> pageSign(@RequestBody ListSmsTemplateRequest request) {
return CommonResult.success(smsService.listSmsTemplate(AdminsSmsConvert.INSTANCE.convert(request)));
}
@PostMapping("add")
@ApiOperation("短信模板-添加")
public CommonResult addSign(@RequestBody AddSmsTemplateRequest smsTemplateAddPO) {
smsService.addTemplate(
smsTemplateAddPO.getSmsSignId(),
smsTemplateAddPO.getTemplateCode(),
smsTemplateAddPO.getTemplate(),
smsTemplateAddPO.getPlatform(),
smsTemplateAddPO.getSmsType());
return CommonResult.success(null);
}
@PutMapping("update")
@ApiOperation("短信模板-更新")
public CommonResult updateSign(@RequestBody UpdateSmsTemplateRequest smsTemplateUpdatePO) {
smsService.updateTemplate(
smsTemplateUpdatePO.getId(),
smsTemplateUpdatePO.getSmsSignId(),
smsTemplateUpdatePO.getTemplateCode(),
smsTemplateUpdatePO.getTemplate(),
smsTemplateUpdatePO.getPlatform(),
smsTemplateUpdatePO.getSmsType());
return CommonResult.success(null);
}
@DeleteMapping("deleted")
@ApiOperation("短信模板-删除")
public CommonResult deletedSign(@RequestParam("id") Integer id) {
smsService.deleteTemplate(id);
return CommonResult.success(null);
}
}
package cn.iocoder.mall.system.rest.convert.sms;
import cn.iocoder.mall.system.biz.dto.smsSign.AddSignDTO;
import cn.iocoder.mall.system.biz.dto.smsSign.UpdateSignDTO;
import cn.iocoder.mall.system.biz.dto.smsTemplate.ListSmsTemplateDTO;
import cn.iocoder.mall.system.rest.request.sms.AddSignRequest;
import cn.iocoder.mall.system.rest.request.sms.UpdateSignRequest;
import cn.iocoder.mall.system.rest.request.sms.ListSmsTemplateRequest;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* sms admins convert
*
* author: sin
* time: 2020/4/20 11:07 上午
*/
@Mapper
public interface AdminsSmsConvert {
AdminsSmsConvert INSTANCE = Mappers.getMapper(AdminsSmsConvert.class);
@Mappings({})
AddSignDTO convert(AddSignRequest bean);
@Mappings({})
UpdateSignDTO convert(UpdateSignRequest bean);
@Mappings({})
ListSmsTemplateDTO convert(ListSmsTemplateRequest bean);
}
package cn.iocoder.mall.system.rest.request.admin;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* @Author: jiangweifan
* @Date: 2020/5/12
* @Description: 管理员 - 用户信息 - 更新用户信息
*/
@ApiModel("更新用户信息Request")
@Data
@Accessors(chain = true)
public class AdminsUserUpdateRequest {
@ApiModelProperty(name = "id", value = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")
private Integer id;
@ApiModelProperty(name = "nickname", value = "昵称", required = true, example = "小王")
private String nickname;
@ApiModelProperty(name = "avatar", value = "头像", required = true, example = "http://www.iocoder.cn/xxx.jpg")
private String avatar;
}
package cn.iocoder.mall.system.rest.request.sms;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 添加 sign
*
* author: sin
* time: 2020/4/20 11:10 上午
*/
@Data
@Accessors(chain = true)
public class AddSignRequest implements Serializable {
private String sign;
private Integer platform;
}
package cn.iocoder.mall.system.rest.request.sms;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.system.biz.enums.sms.SmsPlatformEnum;
import cn.iocoder.mall.system.biz.enums.sms.SmsTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 短信模板 add
*
* @author Sin
* @time 2019/5/26 12:37 PM
*/
@ApiModel("短信模板-添加")
@Data
@Accessors(chain = true)
public class AddSmsTemplateRequest implements Serializable {
@ApiModelProperty("短信签名id")
@NotNull(message = "短信短信签名id不能为空!")
private Integer smsSignId;
@ApiModelProperty("短信模板code")
@NotNull
@Size(min = 3, max = 50, message = "短信code在 3-50 之间")
private String templateCode;
@ApiModelProperty("短信模板")
@NotNull
@Size(min = 3, max = 255, message = "短信在 3-255 之间")
private String template;
@ApiModelProperty("短信模板-平台")
@NotNull
@InEnum(value = SmsPlatformEnum.class)
private Integer platform;
@ApiModelProperty("短信模板-平台")
@NotNull
@InEnum(value = SmsTypeEnum.class)
private Integer smsType;
}
package cn.iocoder.mall.system.rest.request.sms;
import cn.iocoder.common.framework.vo.PageParam;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* page 短信模板 query
*
* @author Sin
* @time 2019/5/19 4:32 PM
*/
@Data
@Accessors(chain = true)
public class ListSmsTemplateRequest extends PageParam {
@NotNull
private String id;
@NotNull
private Integer smsSignId;
@NotNull
private String template;
@NotNull
private String applyStatus;
}
package cn.iocoder.mall.system.rest.request.sms;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 更新签名
* <p>
* author: sin
* time: 2020/4/20 11:02 上午
*/
@Data
@Accessors(chain = true)
public class UpdateSignRequest implements Serializable {
private Integer id;
private String sign;
private Integer platform;
}
package cn.iocoder.mall.system.rest.request.sms;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.system.biz.enums.sms.SmsPlatformEnum;
import cn.iocoder.mall.system.biz.enums.sms.SmsTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 短信模板 add
*
* @author Sin
* @time 2019/5/26 12:37 PM
*/
@ApiModel("短信模板-添加")
@Data
@Accessors(chain = true)
public class UpdateSmsTemplateRequest implements Serializable {
@ApiModelProperty("短信模板id")
@NotNull(message = "短信模板不能为空!")
private Integer id;
@ApiModelProperty("短信签名id")
@NotNull(message = "短信短信签名id不能为空!")
private Integer smsSignId;
@ApiModelProperty("短信模板code")
@NotNull
@Size(min = 3, max = 50, message = "短信code在 3-50 之间")
private String templateCode;
@ApiModelProperty("短信模板")
@NotNull
@Size(min = 3, max = 255, message = "短信在 3-255 之间")
private String template;
@ApiModelProperty("短信模板-平台")
@NotNull
@InEnum(value = SmsPlatformEnum.class)
private Integer platform;
@ApiModelProperty("短信模板-平台")
@NotNull
@InEnum(value = SmsTypeEnum.class)
private Integer smsType;
}
# 服务器的配置项
server:
port: 18083
servlet:
context-path: /system-api/
# Swagger 配置项
swagger:
title: 管理员子系统
description: 管理员子系统
version: 1.0.0
base-package: cn.iocoder.mall.system.rest.controller
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>system-service-api</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
package cn.iocoder.mall.system.api;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.api.bo.datadict.DataDictBO;
import cn.iocoder.mall.system.api.dto.datadict.DataDictAddDTO;
import cn.iocoder.mall.system.api.dto.datadict.DataDictUpdateDTO;
import java.util.Collection;
import java.util.List;
public interface DataDictService {
List<DataDictBO> selectDataDictList();
DataDictBO addDataDict(Integer adminId, DataDictAddDTO dataDictAddDTO);
Boolean updateDataDict(Integer adminId, DataDictUpdateDTO dataDictUpdateDTO);
Boolean deleteDataDict(Integer adminId, Integer dataDictId);
/**
* 获取字典值 - 单个
*
* 注意: dictValue:Object 为了方便调用,会自动转换为 dictValue:String
*
* @param dictKey
* @param dictValue
* @return
*/
CommonResult<DataDictBO> getDataDict(String dictKey, Object dictValue);
CommonResult<List<DataDictBO>> getDataDict(String dictKey);
/**
* 获取字典值 - 多个
*
* 注意:dictValueList:? 为了方便调用,会自动转换为 Set:String
*
* @param dictKey
* @param dictValueList
* @return
*/
CommonResult<List<DataDictBO>> getDataDictList(String dictKey, Collection<?> dictValueList);
}
package cn.iocoder.mall.system.api;
import cn.iocoder.mall.system.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.system.api.bo.sms.PageSmsSignBO;
import cn.iocoder.mall.system.api.bo.sms.SmsTemplateBO;
import cn.iocoder.mall.system.api.bo.sms.PageSmsTemplateBO;
import cn.iocoder.mall.system.api.dto.sms.PageQuerySmsSignDTO;
import cn.iocoder.mall.system.api.dto.sms.PageQuerySmsTemplateDTO;
import java.util.List;
import java.util.Map;
/**
* 短信服务
*
* @author Sin
* @time 2019/5/16 9:54 AM
*/
public interface SmsService {
/**
* 短信模板 - 分页
*
* @param queryDTO
* @return
*/
PageSmsSignBO pageSmsSign(PageQuerySmsSignDTO queryDTO);
/**
* 短信模板 - 分页
*
* @param queryDTO
* @return
*/
PageSmsTemplateBO pageSmsTemplate(PageQuerySmsTemplateDTO queryDTO);
/**
* 签名 - 创建
*
* @param sign
*/
void addSign(String sign, Integer platform);
/**
* 签名 - 获取
*
* @param id
*/
SmsSignBO getSign(Integer id);
/**
* 签名 - 更新
*
* @param id
* @param newSign
* @param platform
*/
void updateSign(Integer id, String newSign, Integer platform);
/**
* 签名 - 更新
*
* @param id
*/
void deleteSign(Integer id);
/**
* 模板 - 创建
*
* @param smsSignId 选用的哪个签名
* @param templateCode 模板code
* @param template 模板内容
* @param platform 平台
*/
void addTemplate(Integer smsSignId, String templateCode,
String template, Integer platform, Integer smsType);
/**
* 模板 - 获取
*
* @param id
*/
SmsTemplateBO getTemplate(Integer id, Integer platform);
/**
* 模板 - 更新
*
* @param id 模板id
* @param smsSignId 短期签名
* @param template 模板内容
* @param platform 短信平台
*/
void updateTemplate(Integer id, Integer smsSignId, String templateCode,
String template, Integer platform, Integer smsType);
/**
* 模板 - 删除
*
* @param id
*/
void deleteTemplate(Integer id);
/**
* 短信发送 - 单个
*
* @return
*/
void singleSend(String mobile, Integer smsTemplateId, Map<String, String> params);
/**
* 短信发送 - 批量
*
* @return
*/
void batchSend(List<String> mobileList, Integer smsTemplateId, Map<String, String> params);
}
package cn.iocoder.mall.system.api.bo.sms;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
import java.util.List;
/**
* sms page
*
* @author Sin
* @time 2019/5/19 4:23 PM
*/
@Data
@Accessors(chain = true)
public class PageSmsSignBO {
private Integer count;
private Long current;
private Long size;
private Long total;
private List<Sign> data;
@Data
@Accessors(chain = true)
public static class Sign {
/**
* 编号
*/
private Integer id;
/**
* 短信平台
*/
private Integer platform;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
* <p>
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
}
package cn.iocoder.mall.system.api.bo.sms;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.List;
/**
* sms page
*
* @author Sin
* @time 2019/5/19 4:23 PM
*/
@Data
@Accessors(chain = true)
public class PageSmsTemplateBO {
private Long total;
private Long current;
private Long size;
private List<Template> data;
@Data
@Accessors(chain = true)
public static class Template {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platform;
/**
* 短信模板 Code
*/
private String templateCode;
/**
* 短信模板
*/
private String template;
/**
* 短信类型
*/
private Integer smsType;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
///
/// 关联字段
/**
* 签名信息
*/
private Sign sign;
}
@Data
@Accessors(chain = true)
public static class Sign {
/**
* 编号
*/
private Integer id;
/**
* 签名id 这个是第三方的
*/
private String platformId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
}
package cn.iocoder.mall.system.api.bo.sms;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* @author Sin
* @time 2019/5/16 6:30 PM
*/
@Data
@Accessors(chain = true)
public class SmsSignBO {
/**
* 编号
*/
private Integer id;
/**
* 签名id 这个是第三方的
*/
private Integer signId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.api.bo.sms;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:41 PM
*/
@Data
@Accessors(chain = true)
public class SmsTemplateBO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platformId;
/**
* 短信模板
*/
private String template;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.system.api.constant;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsApplyStatusEnum {
CHECKING(1, "审核中"),
SUCCESS(2, "审核成功"),
FAIL(10, "审核失败"),
;
private final Integer value;
private final String name;
SmsApplyStatusEnum(int code, String message) {
this.value = code;
this.name = message;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
package cn.iocoder.mall.system.api.constant;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsPlatformEnum implements IntArrayValuable {
YunPian(1, "云片"),
AliYun(2, "阿里云"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsPlatformEnum::getValue).toArray();
private final Integer value;
private final String name;
SmsPlatformEnum(Integer code, String message) {
this.value = code;
this.name = message;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.mall.system.api.constant;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsTypeEnum implements IntArrayValuable {
VERIFICATION_CODE(1, "验证码"),
NOTICE(2, "通知"),
MARKETING(3, "营销"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsTypeEnum::getValue).toArray();
private final Integer value;
private final String name;
SmsTypeEnum(Integer code, String message) {
this.value = code;
this.name = message;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.mall.system.api.dto.sms;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* page 短信模板 query
*
* @author Sin
* @time 2019/5/19 4:32 PM
*/
@Data
@Accessors(chain = true)
@ApiModel("短信服务查询")
public class PageQuerySmsSignDTO implements Serializable {
@ApiModelProperty("每页大小")
@NotNull
private Integer size;
@ApiModelProperty("当前页")
@NotNull
private Integer current;
@ApiModelProperty("编号")
private Integer id;
@ApiModelProperty("签名")
private String sign;
@ApiModelProperty("申请状态")
private Integer applyStatus;
}
package cn.iocoder.mall.system.api.dto.sms;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* page 短信模板 query
*
* @author Sin
* @time 2019/5/19 4:32 PM
*/
@Data
@Accessors(chain = true)
public class PageQuerySmsTemplateDTO implements Serializable {
@NotNull
private Long current;
@NotNull
private Long size;
@NotNull
private String id;
@NotNull
private Integer smsSignId;
@NotNull
private String template;
@NotNull
private String applyStatus;
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>system-service-impl</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- RPC 相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- Registry 和 Config 相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.yunpian.sdk</groupId>
<artifactId>yunpian-java-sdk</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.1.0</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 提供给 mapstruct 使用 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package cn.iocoder.mall.admin.client;
import com.alibaba.fastjson.JSON;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 短信 AliYun client
*
* @author Sin
* @time 2019/5/25 12:28 PM
*/
@Component
public class SmsAliYunClient implements SmsClient {
private static final Logger LOGGER = LoggerFactory.getLogger(SmsAliYunClient.class);
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
private static final String SUCCESS_CODE = "OK";
private static final String SUCCESS_MESSAGE = "OK";
/**
* 阿里云短信 - 批量推送最大数 500,支持 1000
*/
private static final int MAX_BATCH_SIZE = 500;
@Value("${sms.aliYun.accessKeyId?:'default_value'}")
private String accessKeyId;
@Value("${sms.aliYun.accessSecret?:'default_value'}")
private String accessSecret;
@Data
@Accessors(chain = true)
public static class Result {
/**
* 发送回执ID,可根据该ID在接口QuerySendDetails中查询具体的发送状态。
*/
private String BizId;
/**
* 请求状态码。
*
* - OK 蔡成功
*/
private String Code;
/**
* 状态码的描述。
*/
private String Message;
/**
* 请求ID。
*/
private String RequestId;
}
@Override
public SendResult singleSend(String mobile, String sign, String templateCode,
String template, Map<String, String> templateParams) {
// params
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain(DOMAIN);
request.setVersion("2017-05-25");
request.setAction("SendSms");
request.putQueryParameter("PhoneNumbers", mobile);
request.putQueryParameter("SignName", sign);
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", JSON.toJSONString(templateParams));
// 发送请求
return doSend(request);
}
@Override
public SendResult batchSend(List<String> mobileList, String sign, String templateCode,
String template, Map<String, String> templateParams) {
// 最大发送数为 1000,我们设置为 500 个, 分段发送
int maxSendSize = MAX_BATCH_SIZE;
int maxSendSizeCount = mobileList.size() % maxSendSize == 0
? mobileList.size() / maxSendSize
: mobileList.size() / maxSendSize + 1;
SendResult sendResult = null;
for (int i = 0; i < maxSendSizeCount; i++) {
// 分批发送
List<String> batchSendMobile = mobileList
.subList(i * maxSendSize, (i + 1) * maxSendSize);
// params
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain(DOMAIN);
request.setVersion("2017-05-25");
request.setAction("SendBatchSms");
request.putQueryParameter("PhoneNumberJson", JSON.toJSONString(batchSendMobile));
request.putQueryParameter("SignNameJson", JSON.toJSONString(Collections.singletonList(sign)));
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParamJson", JSON.toJSONString(Collections.singletonList(templateParams)));
// 发送请求
sendResult = doSend(request);
}
return sendResult;
}
private SendResult doSend(CommonRequest request) {
// 获取 client
IAcsClient client = getClient();
try {
CommonResponse response = client.getCommonResponse(request);
Result result = JSON.parseObject(response.getData(), Result.class);
if (!SUCCESS_CODE.equals(result.getCode())) {
LOGGER.info("发送验证码失败 params {} res {}", JSON.toJSON(request), JSON.toJSON(result));
// 错误发送失败
return new SendResult()
.setIsSuccess(false)
.setCode(SendResult.ERROR_CODE)
.setMessage(result.getMessage());
} else {
LOGGER.info("发送验证码失败 params {} res", JSON.toJSON(request), JSON.toJSON(result));
// 发送成功
return new SendResult()
.setIsSuccess(true)
.setCode(SendResult.SUCCESS_CODE)
.setMessage(result.getMessage());
}
} catch (ClientException e) {
LOGGER.error("发送验证码异常 {}", ExceptionUtils.getMessage(e));
return new SendResult()
.setIsSuccess(false)
.setCode(SendResult.ERROR_CODE)
.setMessage(ExceptionUtils.getMessage(e));
}
}
/**
* 获取 client
*
* @return
*/
private IAcsClient getClient() {
return new DefaultAcsClient(DefaultProfile.getProfile("default", accessKeyId, accessSecret));
}
}
package cn.iocoder.mall.admin.client;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
/**
* 短信平台
*
* @author Sin
* @time 2019/5/16 6:33 PM
*/
public interface SmsClient {
@Data
@Accessors(chain = true)
class SendResult {
public static final int SUCCESS_CODE = 0;
public static final int ERROR_CODE = 1;
public static final String SUCCESS_MESSAGE = "SUCCESS";
/**
* 错误码
*/
private Integer code;
/**
* 错误信息
*/
private String message;
/**
* 是否成功
*/
private Boolean isSuccess;
}
/**
* 短信发送 - 单个
*
* @param mobile 手机号
* @param sign 签名
* @param templateCode 短信模板code
* @param template 短信模板
* @param templateParams 短信模板 params
* @return 发送后信息
*/
SendResult singleSend(String mobile, String sign, String templateCode,
String template, Map<String, String> templateParams);
/**
* 短信发送 - 批量
*
* @param mobileList 手机号
* @param sign 签名
* @param templateCode 短信模板 code
* @param template 短信模板
* @param templateParams 短信模板params
* @return 发送后信息
*/
SendResult batchSend(List<String> mobileList, String sign, String templateCode,
String template, Map<String, String> templateParams);
}
package cn.iocoder.mall.admin.client;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.system.api.constant.AdminErrorCodeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 云片 短信平台
*
* @author Sin
* @time 2019/5/16 6:34 PM
*/
@Component
public class SmsYunPianClient implements SmsClient {
protected static final Logger LOGGER = LoggerFactory.getLogger(SmsYunPianClient.class);
private static final int SUCCESS_CODE = 0;
private static final String SUCCESS_MESSAGE = "SUCCESS";
/**
* 云片短信 - 批量推送最大数 500,支持 1000
*/
private static final int MAX_BATCH_SIZE = 500;
/**
* 模板 - 参数拼接
*/
private static final String PARAM_TEMPLATE = "#%s#";
/**
* 模板 - 签名拼接
*/
private static final String SIGN_TEMPLATE = "【%s】%s";
/**
* 签名 - 添加
*/
private static final String URL_SIGN_ADD = "https://sms.yunpian.com/v2/sign/add.json";
/**
* 签名 - 获取
*/
private static final String URL_SIGN_GET = "https://sms.yunpian.com/v2/sign/get.json";
/**
* 签名 - 更新
*/
private static final String URL_SIGN_UPDATE = "https://sms.yunpian.com/v2/sign/update.json";
/**
* 模板 - 添加
*/
private static final String URL_TEMPLATE_ADD = "https://sms.yunpian.com/v2/tpl/add.json";
/**
* 模板 - 获取
*/
private static final String URL_TEMPLATE_GET = "https://sms.yunpian.com/v2/tpl/get.json";
/**
* 模板 - 更新
*/
private static final String URL_TEMPLATE_UPDATE = "https://sms.yunpian.com/v2/tpl/update.json";
/**
* 模板 - 删除
*/
private static final String URL_TEMPLATE_DELETE = "https://sms.yunpian.com/v2/tpl/del.json";
/**
* 短信发送 - 单个
*/
private static final String URL_SEND_SINGLE = "https://sms.yunpian.com/v2/sms/single_send.json";
/**
* 短信发送 - 批量
*/
private static final String URL_SEND_BATCH = "https://sms.yunpian.com/v2/sms/batch_send.json";
//编码格式。发送编码格式统一用UTF-8
private static String ENCODING = "UTF-8";
@Value("${sms.yunPian.apiKey?:'default_value'}")
private String apiKey;
@Override
public SendResult singleSend(String mobile, String sign, String templateCode, String template, Map<String, String> templateParams) {
// build 模板
template = buildTemplate(sign, template, templateParams);
// 请求参数
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("mobile", mobile);
params.put("text", template);
// TODO: 2019/5/19 sin 运营商发送报告 回调
// params.put("callback_url", template);
String result = post(URL_SEND_SINGLE, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
return new SendResult()
.setIsSuccess(SUCCESS_CODE == jsonObject.getInteger("code"))
.setCode(jsonObject.getInteger("code"))
.setMessage(jsonObject.getString("detail"));
}
@Override
public SendResult batchSend(List<String> mobileList, String sign,
String templateCode, String template,
Map<String, String> templateParams) {
// build 模板
template = buildTemplate(sign, template, templateParams);
// 最大发送数为 1000,我们设置为 500 个, 分段发送
int maxSendSize = MAX_BATCH_SIZE;
int maxSendSizeCount = mobileList.size() % maxSendSize == 0
? mobileList.size() / maxSendSize
: mobileList.size() / maxSendSize + 1;
int j = 0;
int j2 = mobileList.size();
for (int i = 0; i < maxSendSizeCount; i++) {
StringBuffer sendMobileStr = new StringBuffer();
for (int k = j; k < j2; k++) {
sendMobileStr.append(",");
sendMobileStr.append(mobileList.get(k));
}
String dividedMobile = sendMobileStr.toString().substring(1);
// 发送手机号
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("mobile", dividedMobile);
params.put("text", template);
// TODO: 2019/5/19 sin 运营商发送报告 回调
// params.put("callback_url", template);
String result = post(URL_SEND_BATCH, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
// 用于递增 maxSendSize
j = j2;
j2 = j + maxSendSize;
}
return new SendResult()
.setIsSuccess(true)
.setCode(SUCCESS_CODE)
.setMessage(SUCCESS_MESSAGE);
}
/**
* 构建模板
*
* @param sign
* @param template
* @param templateParams
* @return
*/
private static String buildTemplate(String sign, String template,
Map<String, String> templateParams) {
if (CollectionUtils.isEmpty(templateParams)) {
return template;
}
for (Map.Entry<String, String> entry : templateParams.entrySet()) {
String paramsKey = entry.getKey();
String value = entry.getValue();
String paramPlace = String.format(PARAM_TEMPLATE, paramsKey);
template = template.replaceAll(paramPlace, value);
}
template = String.format(SIGN_TEMPLATE, sign, template);
return template;
}
/**
* 基于HttpClient 4.3的通用POST方法
*
* @param url 提交的URL
* @param paramsMap 提交<参数,值>Map
* @return 提交响应
*/
public static String post(String url, Map<String, String> paramsMap) {
CloseableHttpClient client = HttpClients.createDefault();
String responseText = "";
CloseableHttpResponse response = null;
try {
HttpPost method = new HttpPost(url);
if (paramsMap != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (Map.Entry<String, String> param : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(param.getKey(),
param.getValue());
paramList.add(pair);
}
method.setEntity(new UrlEncodedFormEntity(paramList, ENCODING));
}
response = client.execute(method);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseText = EntityUtils.toString(entity, ENCODING);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (Exception e) {
e.printStackTrace();
}
}
LOGGER.debug("云片短信平台 res: {}", responseText);
return responseText;
}
}
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.system.api.bo.sms.PageSmsSignBO;
import cn.iocoder.mall.system.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 短信 签名
*
* @author Sin
* @time 2019/5/16 6:31 PM
*/
@Mapper
public interface SmsSignConvert {
SmsSignConvert INSTANCE = Mappers.getMapper(SmsSignConvert.class);
@Mappings({})
SmsSignBO convert(SmsSignDO smsSignDO);
@Mappings({})
List<PageSmsSignBO.Sign> convert(List<SmsSignDO> smsSignDOList);
}
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.system.api.bo.sms.PageSmsTemplateBO;
import cn.iocoder.mall.system.api.bo.sms.SmsTemplateBO;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import cn.iocoder.mall.admin.dataobject.SmsTemplateDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:43 PM
*/
@Mapper
public interface SmsTemplateConvert {
SmsTemplateConvert INSTANCE = Mappers.getMapper(SmsTemplateConvert.class);
@Mappings({})
SmsTemplateBO convert(SmsTemplateDO smsTemplateDO);
@Mappings({})
List<PageSmsTemplateBO.Template> convert(List<SmsTemplateDO> smsTemplateDOList);
@Mappings({})
List<PageSmsTemplateBO.Sign> convertTemplateSign(List<SmsSignDO> smsSignDOList);
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.SmsSendLogDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsSendMapper extends BaseMapper<SmsSendLogDO> {
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsSignMapper extends BaseMapper<SmsSignDO> {
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.mall.admin.dataobject.SmsTemplateDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsTemplateMapper extends BaseMapper<SmsTemplateDO> {
}
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 client log
*
* @author Sin
* @time 2019/5/25 12:36 PM
*/
@Data
@Accessors(chain = true)
public class SmsSendLogDO extends BaseDO {
/**
* 编号
*/
private Integer id;
/**
* 短信模板
*/
private Integer templateId;
/**
* 短信
*/
private String template;
/**
* 参数
*/
private String params;
/**
* 发送信息
*/
private String message;
}
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* 签名是短信发送前缀 如:【阿里云】、【小红书】
*
* @author Sin
* @time 2019/5/16 12:28 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_sign")
public class SmsSignDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 签名名称
*/
private String sign;
/**
* 平台
*
* 1、云片
* 2、阿里云
*/
private Integer platform;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 10、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 模板
*
* @author Sin
* @time 2019/5/16 12:31 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_template")
public class SmsTemplateDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 模板 code(第三方平台 code)
*/
private String templateCode;
/**
* 短信签名 id
*/
private Integer platform;
/**
* 短信模板
*/
private String template;
/**
* 短信类型
*
* - 验证码类
* - 通知类
* - 营销类
*/
private Integer smsType;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 10、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论