Commit b34f5d7d authored by huangcb's avatar huangcb

Init

parent 1ef70a46
package com.esv.freight.notice.common.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @description: RedisTemplate封装组件
* @project: freight-file-service
* @name: com.esv.freight.notice.common.component.RedisComponent
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/09 10:02
* @version:1.0
*/
@Component
@Slf4j
public class RedisComponent {
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public RedisComponent(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
if (0L >= time) {
return false;
}
try {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//================================String=================================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
return true;
} else {
return false;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0
* @param timeUnit 时间单位
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
return true;
} else {
return false;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
if (0L >= time) {
return false;
}
try {
redisTemplate.opsForHash().putAll(key, map);
expire(key, time);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
if (0L >= time) {
return false;
}
try {
redisTemplate.opsForHash().put(key, item, value);
expire(key, time);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
if (0L >= time) {
return 0L;
}
try {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
if (0L >= time) {
return false;
}
try {
redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
if (0L >= time) {
return false;
}
try {
redisTemplate.opsForList().rightPushAll(key, value);
expire(key, time);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
log.error(e.getMessage(), e);
return 0;
}
}
}
package com.esv.freight.notice.common.constants; package com.esv.freight.notice.common.em;
/** /**
* @description: DB记录是否删除标识 * @description: DB记录是否删除标识
......
package com.esv.freight.notice.common.util;
import java.util.Random;
/**
* @description: 短信验证码工具类
* @project: notice-service
* @name: com.esv.freight.notice.common.util.SmsCaptchaUtils
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/15 16:51
* @version:1.0
*/
public class SmsCaptchaUtils {
/**
* 短信验证码字符序列
**/
private static final char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
/**
* 短信验证码默认长度
**/
public static final int DEFAULT_SIZE = 6;
/**
* description 获取默认长度(6位)的短信验证码
* param []
* return java.lang.String
* author Administrator
* createTime 2020/04/15 16:56
**/
public static String getCaptcha() {
return getCaptcha(DEFAULT_SIZE);
}
/**
* description 获取指定长度的短信验证码
* param [size]
* return java.lang.String
* author Administrator
* createTime 2020/04/15 16:54
**/
public static String getCaptcha(int size) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < size; i++) {
int n = new Random().nextInt(chars.length);
stringBuffer.append(chars[n]);
}
return stringBuffer.toString();
}
}
package com.esv.freight.notice.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @description: Cache配置类
* @project: freight-file-service
* @name: com.esv.freight.notice.config.CacheConfig
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/09 9:57
* @version:1.0
*/
@Configuration
@EnableCaching
public class CacheConfig {
/**
* description 为SpringCache注册缓存管理器
* param [redisConnectionFactory]
* return org.springframework.cache.CacheManager
* author Administrator
* createTime 2020/03/19 14:26
**/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
long timeToLive = 60L;
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(timeToLive))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
return cacheManager;
}
/**
* RedisTemplate相关配置
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 值采用json序列化
template.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
/**
* 对hash类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 对redis字符串类型数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 对链表类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 对无序集合类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 对有序集合类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
package com.esv.freight.notice.module.sms.controller;
import com.esv.freight.notice.common.exception.EException;
import com.esv.freight.notice.common.response.ECode;
import com.esv.freight.notice.common.response.EResponse;
import com.esv.freight.notice.module.sms.em.CaptchaEnum;
import com.esv.freight.notice.module.sms.form.CaptchaForm;
import com.esv.freight.notice.module.sms.service.SmsCaptchaService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* @description:
* @project: notice-service
* @name: com.esv.freight.notice.module.sms.controller.SmsCaptchaController
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/15 18:07
* @version:1.0
*/
@Slf4j
@RestController
@RequestMapping("/sms/captcha")
public class SmsCaptchaController {
private SmsCaptchaService smsCaptchaService;
@Autowired
public SmsCaptchaController(SmsCaptchaService smsCaptchaService) {
this.smsCaptchaService = smsCaptchaService;
}
/**
* description 发送短信验证码
* param [form]
* return com.esv.freight.notice.common.response.EResponse
* author Administrator
* createTime 2020/04/15 18:21
**/
@PostMapping("/send")
public EResponse sendSmsCaptcha(@RequestBody @Validated CaptchaForm form) throws EException {
// 校验参数
CaptchaEnum captchaEnum = CaptchaEnum.getEnumByType(form.getType());
if (CaptchaEnum.UNDEFINED == captchaEnum) {
return EResponse.error(ECode.PARAM_ERROR.code(), "type类型不合法");
}
if (!this.isValidMobile(form.getPhone())) {
return EResponse.error(1001, "无效的手机号");
}
smsCaptchaService.sendSmsCaptcha(form);
return EResponse.ok();
}
/**
* description 校验短信验证码
* param [form]
* return com.esv.freight.notice.common.response.EResponse
* author Administrator
* createTime 2020/04/16 18:10
**/
@PostMapping("/check")
public EResponse checkSmsCaptcha(@RequestBody @Validated CaptchaForm form) throws EException {
// 校验参数
CaptchaEnum captchaEnum = CaptchaEnum.getEnumByType(form.getType());
if (CaptchaEnum.UNDEFINED == captchaEnum) {
return EResponse.error(ECode.PARAM_ERROR.code(), "type类型不合法");
}
if (!this.isValidMobile(form.getPhone())) {
return EResponse.error(1001, "无效的手机号");
}
if (StringUtils.isBlank(form.getCaptcha())) {
return EResponse.error(ECode.PARAM_ERROR.code(), "验证码不能为空");
}
smsCaptchaService.checkSmsCaptcha(form);
return EResponse.ok();
}
/**
* 正则验证手机号码
**/
private boolean isValidMobile(String phone) {
Pattern p = compile("^[1][0-9]{10}$");
Matcher m = p.matcher(phone);
return m.matches();
}
}
package com.esv.freight.notice.module.sms.dao;
import com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 短信验证码表
*
* @author 黄朝斌
* @email huangchaobin@esvtek.com
* @date 2020-04-15 16:46:31
*/
@Mapper
public interface SmsCaptchaDao extends BaseMapper<SmsCaptchaEntity> {
/**
* description 查询指定类型、手机号的最后一个验证码
* param [queryEntity]
* return com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity
* author Administrator
* createTime 2020/04/16 15:56
**/
SmsCaptchaEntity selectLatestCaptcha(SmsCaptchaEntity queryEntity);
/**
* description 失效指定类型、手机号的最后一个验证码
* param [updateEntity]
* return int
* author Administrator
* createTime 2020/04/16 18:05
**/
int updateLatestCaptcha(SmsCaptchaEntity updateEntity);
}
package com.esv.freight.notice.module.sms.em;
import org.apache.commons.lang3.StringUtils;
/**
* @description: 短信验证码类型枚举
* @project: freight-file-service
* @name: com.esv.freight.notice.module.sms.em.CaptchaEnum
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/15 11:20
* @version:1.0
*/
public enum CaptchaEnum {
UNDEFINED("undefined", "未定义"),
LOGIN("login", "登录"),
RESET_PWD("reset_pwd", "重置密码"),
UPDATE_PWD("update_pwd", "修改密码");
private String type;
private String name;
CaptchaEnum(String type, String name) {
this.type = type;
this.name = name;
}
public static CaptchaEnum getEnumByType(String type) {
if (StringUtils.isBlank(type)){
return CaptchaEnum.UNDEFINED;
}
for (CaptchaEnum em : CaptchaEnum.values()) {
if (em.type.equalsIgnoreCase(type)) {
return em;
}
}
return CaptchaEnum.UNDEFINED;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.esv.freight.notice.module.sms.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 短信验证码表
*
* @author 黄朝斌
* @email huangchaobin@esvtek.com
* @date 2020-04-15 16:46:31
*/
@Data
@TableName("base_sms_captcha")
public class SmsCaptchaEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
*
*/
@TableId
private Long id;
/**
* 手机号码
*/
private String phone;
/**
* 验证码
*/
private String captcha;
/**
* 验证码类型:login-登录、reset_pwd-重置密码、update_pwd-修改密码、undefined-未定义
*/
private String captchaType;
/**
* 失效时间
*/
private Date invalidTime;
/**
* 0-未删除,1-已删除
*/
private Boolean deleted;
/**
* 创建时间
*/
private Date createTime;
/**
* 验证时间
*/
private Date verifyTime;
}
package com.esv.freight.notice.module.sms.form;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/**
* @description:
* @project: notice-service
* @name: com.esv.freight.notice.module.sms.form.CaptchaForm
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/15 17:58
* @version:1.0
*/
@Data
public class CaptchaForm {
@Length(min = 11, max = 11, message = "参数phone长度不合法")
@NotBlank(message = "参数phone不能为空")
private String phone;
@Length(max = 16, message = "参数type长度不合法")
@NotBlank(message = "参数type不能为空")
private String type;
@Length(min = 6, max = 6, message = "参数captcha长度不合法")
private String captcha;
}
package com.esv.freight.notice.module.sms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.esv.freight.notice.common.exception.EException;
import com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity;
import com.esv.freight.notice.module.sms.form.CaptchaForm;
/**
* 短信验证码表
*
* @author 黄朝斌
* @email huangchaobin@esvtek.com
* @date 2020-04-15 16:46:31
*/
public interface SmsCaptchaService extends IService<SmsCaptchaEntity> {
/**
* description 发送短信验证码
* param [form]
* return void
* author Administrator
* createTime 2020/04/16 9:05
**/
void sendSmsCaptcha(CaptchaForm form) throws EException;
/**
* description 校验短信验证码
* param [form]
* return void
* author Administrator
* createTime 2020/04/16 17:23
**/
void checkSmsCaptcha(CaptchaForm form) throws EException;
}
package com.esv.freight.notice.module.sms.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.esv.freight.notice.common.component.RedisComponent;
import com.esv.freight.notice.common.em.DbDeletedEnum;
import com.esv.freight.notice.common.exception.EException;
import com.esv.freight.notice.common.util.DateUtils;
import com.esv.freight.notice.common.util.SmsCaptchaUtils;
import com.esv.freight.notice.module.sms.dao.SmsCaptchaDao;
import com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity;
import com.esv.freight.notice.module.sms.form.CaptchaForm;
import com.esv.freight.notice.module.sms.service.SmsCaptchaService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service("smsCaptchaService")
@RefreshScope
@Slf4j
public class SmsCaptchaServiceImpl extends ServiceImpl<SmsCaptchaDao, SmsCaptchaEntity> implements SmsCaptchaService {
@Value("${spring.application.name}")
private String applicationName;
/**
* 短信验证码频次,默认60秒
**/
@Value("${sms.captcha.frequency:60}")
private long SMS_CAPTCHA_FREQUENCY;
/**
* 短信验证码总数,默认20
**/
@Value("${sms.captcha.total:20}")
private int SMS_CAPTCHA_TOTAL;
/**
* 短信验证码有效期,默认10分钟
**/
@Value("${sms.captcha.period:10}")
private long SMS_CAPTCHA_PERIOD;
private RedisComponent redisComponent;
@Autowired
public SmsCaptchaServiceImpl(RedisComponent redisComponent) {
this.redisComponent = redisComponent;
}
@Override
public void sendSmsCaptcha(CaptchaForm form) throws EException {
String phone = form.getPhone();
// 1.校验短信验证码
this.checkSendSmsCaptcha(form);
// 2.获取短信验证码随机数
String smsCaptcha = SmsCaptchaUtils.getCaptcha();
// 3.TODO,调用第三方服务发送短信验证码
// 4.将短信验证码放入缓存
// 频次
redisComponent.set(this.getFrequencyCacheKey(phone), "yes", SMS_CAPTCHA_FREQUENCY);
// 值
redisComponent.set(this.getValueCacheKey(form), smsCaptcha, SMS_CAPTCHA_PERIOD, TimeUnit.MINUTES);
// 总数量
int total;
if (redisComponent.hasKey(this.getTotalCacheKey(phone))) {
total = (int) redisComponent.get(this.getTotalCacheKey(phone)) + 1;
} else {
total = 1;
}
redisComponent.set(this.getTotalCacheKey(phone), total, 1, TimeUnit.DAYS);
// 5.DB存储短信验证码
SmsCaptchaEntity entity = new SmsCaptchaEntity();
entity.setPhone(phone);
entity.setCaptcha(smsCaptcha);
entity.setCaptchaType(form.getType());
entity.setInvalidTime(DateUtils.plusMinutes(new Date(), Integer.parseInt(String.valueOf(SMS_CAPTCHA_PERIOD))));
this.baseMapper.insert(entity);
}
@Override
public void checkSmsCaptcha(CaptchaForm form) throws EException {
// 1.获取短信验证码
String rightCaptcha = null;
String captchaCacheKey = this.getValueCacheKey(form);
if (redisComponent.hasKey(captchaCacheKey)) {
// 从缓存获取验证码
rightCaptcha = redisComponent.get(captchaCacheKey).toString();
} else {
// 从DB查询短信验证码
SmsCaptchaEntity queryEntity = new SmsCaptchaEntity();
queryEntity.setPhone(form.getPhone());
queryEntity.setCaptchaType(form.getType());
SmsCaptchaEntity smsCaptchaEntity = this.baseMapper.selectLatestCaptcha(queryEntity);
if (null == smsCaptchaEntity) {
throw new EException(1003, "验证码不存在或已失效");
}
Date InvalidTime = smsCaptchaEntity.getInvalidTime();
if (0 <= InvalidTime.compareTo(new Date())) {
rightCaptcha = smsCaptchaEntity.getCaptcha();
}
}
// 2.校验短信验证码
if (!rightCaptcha.equals(form.getCaptcha())) {
throw new EException(1002, "验证码错误");
}
// 3.短信验证码正确,失效操作
if (redisComponent.hasKey(captchaCacheKey)) {
redisComponent.del(captchaCacheKey);
}
SmsCaptchaEntity updateEntity = new SmsCaptchaEntity();
updateEntity.setDeleted(DbDeletedEnum.YES.getCode());
updateEntity.setVerifyTime(new Date());
updateEntity.setPhone(form.getPhone());
updateEntity.setCaptchaType(form.getType());
this.baseMapper.updateLatestCaptcha(updateEntity);
}
/**
* description 校验短信验证码
* param [form]
* return void
* author Administrator
* createTime 2020/04/16 9:29
**/
private void checkSendSmsCaptcha(CaptchaForm form) throws EException {
String phone = form.getPhone();
// 校验频次
if (redisComponent.hasKey(this.getFrequencyCacheKey(phone))) {
throw new EException(1002, "获取短信验证码过于频繁,请稍后再试");
}
// 校验总数
Object total = redisComponent.get(this.getTotalCacheKey(phone));
if (null != total && SMS_CAPTCHA_TOTAL <= Integer.parseInt(total.toString())) {
throw new EException(1003, "当日短信验证码总数已达上限,请明天再试");
}
}
/**
* 获取短信验证码频次缓存key
**/
private String getFrequencyCacheKey(String phone) {
StringBuffer sb = new StringBuffer();
sb.append(applicationName);
sb.append("::sms::captcha::frequency::");
sb.append(phone);
return sb.toString();
}
/**
* 获取短信验证码总数缓存key
**/
private String getTotalCacheKey(String phone) {
StringBuffer sb = new StringBuffer();
sb.append(applicationName);
sb.append("::sms::captcha::total::");
sb.append(DateUtils.getSysdateStr(DateUtils.DATE_FORMAT2));
sb.append("::");
sb.append(phone);
return sb.toString();
}
/**
* 获取短信验证码内容缓存key
**/
private String getValueCacheKey(CaptchaForm form) {
StringBuffer sb = new StringBuffer();
sb.append(applicationName);
sb.append("::sms::captcha::value::");
sb.append(form.getPhone());
sb.append("::");
sb.append(form.getType());
return sb.toString();
}
}
\ No newline at end of file
...@@ -21,6 +21,18 @@ spring: ...@@ -21,6 +21,18 @@ spring:
log-slow-sql: true log-slow-sql: true
slow-sql-millis: 1000 slow-sql-millis: 1000
merge-sql: false merge-sql: false
redis:
database: 0
host: 192.168.31.248
port: 6379
password:
timeout: 1000
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
#mybatis #mybatis
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:/mapper/**/*Dao.xml mapper-locations: classpath*:/mapper/**/*Dao.xml
...@@ -40,4 +52,10 @@ mybatis-plus: ...@@ -40,4 +52,10 @@ mybatis-plus:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO id-type: AUTO
logic-delete-value: 1 logic-delete-value: 1
logic-not-delete-value: 0 logic-not-delete-value: 0
\ No newline at end of file #短信
sms:
captcha:
frequency: 60
total: 5
period: 10
\ No newline at end of file
...@@ -21,6 +21,18 @@ spring: ...@@ -21,6 +21,18 @@ spring:
log-slow-sql: true log-slow-sql: true
slow-sql-millis: 1000 slow-sql-millis: 1000
merge-sql: false merge-sql: false
redis:
database: 0
host: 192.168.31.248
port: 6379
password:
timeout: 1000
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
#mybatis #mybatis
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:/mapper/**/*Dao.xml mapper-locations: classpath*:/mapper/**/*Dao.xml
...@@ -40,4 +52,10 @@ mybatis-plus: ...@@ -40,4 +52,10 @@ mybatis-plus:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO id-type: AUTO
logic-delete-value: 1 logic-delete-value: 1
logic-not-delete-value: 0 logic-not-delete-value: 0
\ No newline at end of file #短信
sms:
captcha:
frequency: 60
total: 20
period: 10
\ No newline at end of file
<?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="com.esv.freight.notice.module.sms.dao.SmsCaptchaDao">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity" id="smsCaptchaMap">
<result property="id" column="id"/>
<result property="phone" column="phone"/>
<result property="captcha" column="captcha"/>
<result property="captchaType" column="captcha_type"/>
<result property="invalidTime" column="invalid_time"/>
<result property="deleted" column="deleted"/>
<result property="createTime" column="create_time"/>
<result property="verifyTime" column="verify_time"/>
</resultMap>
<!-- 查询指定类型、手机号的最后一个验证码 -->
<select id="selectLatestCaptcha" parameterType="com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity"
resultType="com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity">
select phone, captcha, captcha_type, invalid_time
from base_sms_captcha
where deleted = false
and phone = #{phone}
and captcha_type = #{captchaType}
and invalid_time = (select max(invalid_time) from base_sms_captcha where deleted = false and phone = #{phone} and captcha_type = #{captchaType})
</select>
<!-- 失效指定类型、手机号的最后一个验证码 -->
<update id="updateLatestCaptcha" parameterType="com.esv.freight.notice.module.sms.entity.SmsCaptchaEntity">
update base_sms_captcha
set deleted = true,
verify_time = #{verifyTime}
where
deleted = false
and phone = #{phone}
and captcha_type = #{captchaType}
and verify_time is null
</update>
</mapper>
\ No newline at end of file
package com.esv.freight.notice.common.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @description:
* @project: notice-service
* @name: com.esv.freight.notice.common.util.SmsCaptchaUtilsTest
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/15 16:59
* @version:1.0
*/
@SpringBootTest
@Slf4j
public class SmsCaptchaUtilsTest {
@Before
public void before() {
log.info("=================================== Test Start ===================================");
}
@After
public void after() {
log.info("=================================== Test End ===================================");
}
/**
* 获取默认长度的短信验证码
**/
@Test
public void getCaptcha_default_length_test() {
String smsCaptcha = SmsCaptchaUtils.getCaptcha();
log.info("短信验证码:{}", smsCaptcha);
Assert.assertTrue(SmsCaptchaUtils.DEFAULT_SIZE == smsCaptcha.length());
}
/**
* 获取指定长度的短信验证码
**/
@Test
public void getCaptcha_define_length_test() {
int defineLength = 4;
String smsCaptcha = SmsCaptchaUtils.getCaptcha(defineLength);
log.info("短信验证码:{}", smsCaptcha);
Assert.assertTrue(defineLength == smsCaptcha.length());
}
}
package com.esv.freight.notice.module.sms.controller;
import com.alibaba.fastjson.JSONObject;
import com.esv.freight.notice.BaseTestController;
import com.esv.freight.notice.common.response.ECode;
import com.esv.freight.notice.module.sms.em.CaptchaEnum;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
/**
* @description:
* @project: notice-service
* @name: com.esv.freight.notice.module.sms.controller.SmsCaptchaControllerTest
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/04/16 15:14
* @version:1.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SmsCaptchaControllerTest extends BaseTestController {
private static String PHONE = "13912345678";
@Value("${sms.captcha.frequency:60}")
private long SMS_CAPTCHA_FREQUENCY;
@Value("${sms.captcha.total:20}")
private int SMS_CAPTCHA_TOTAL;
/**
* 获取短信验证码成功
**/
@Test
public void a1_sendSmsCaptcha_success_test() throws Exception {
String url = "/sms/captcha/send";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.SUCCESS.code(), result.getIntValue("code"));
}
/**
* 获取短信验证码失败:type未定义
**/
@Test
public void b1_sendSmsCaptcha_param_type_error_test() throws Exception {
String url = "/sms/captcha/send";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("type", "test");
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.PARAM_ERROR.code(), result.getIntValue("code"));
}
/**
* 获取短信验证码失败:无效的手机号
**/
@Test
public void b2_sendSmsCaptcha_param_phone_error_test() throws Exception {
String url = "/sms/captcha/send";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", "1391234567x");
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(1001, result.getIntValue("code"));
}
/**
* 获取短信验证码失败:超频
**/
@Test
public void b3_sendSmsCaptcha_frequency_error_test() throws Exception {
String url = "/sms/captcha/send";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(1002, result.getIntValue("code"));
}
/**
* 获取短信验证码失败:超量
**/
@Test
public void b4_sendSmsCaptcha_total_error_test() throws Exception {
String url = "/sms/captcha/send";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", "13012345678");
reqJson.put("type", CaptchaEnum.LOGIN.getType());
for (int i = 0; i < SMS_CAPTCHA_TOTAL; i++) {
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info("第{}次发送短信验证码", (i+1));
log.info(responseStr);
Thread.sleep(SMS_CAPTCHA_FREQUENCY * 1000);
}
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info("第{}次发送短信验证码", (SMS_CAPTCHA_TOTAL+1));
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(1003, result.getIntValue("code"));
}
/**
* 校验短信验证码成功
**/
@Test
public void c1_checkSmsCaptcha_success_test() throws Exception {
String url = "/sms/captcha/check";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("captcha", "325922");
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.SUCCESS.code(), result.getIntValue("code"));
}
/**
* 校验短信验证码失败:验证码不合法
**/
@Test
public void c1_checkSmsCaptcha_param_captcha_error_test() throws Exception {
String url = "/sms/captcha/check";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("captcha", "");
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.PARAM_ERROR.code(), result.getIntValue("code"));
}
/**
* 校验短信验证码失败:验证码不合法
**/
@Test
public void c1_checkSmsCaptcha_captcha_not_exits_error_test() throws Exception {
String url = "/sms/captcha/check";
// 构造数据
JSONObject reqJson = new JSONObject();
reqJson.put("phone", PHONE);
reqJson.put("captcha", "325922");
reqJson.put("type", CaptchaEnum.LOGIN.getType());
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(reqJson.toJSONString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(1003, result.getIntValue("code"));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment