本文共 8693 字,大约阅读时间需要 28 分钟。
CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(32) NOT NULL COMMENT '密码,加密存储', `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号', `created` datetime NOT NULL COMMENT '创建时间', `salt` varchar(32) NOT NULL COMMENT '密码加密的salt值', PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 COMMENT='用户表';
数据结构比较简单,因为根据用户名查询的频率较高,所以我们给用户名创建了索引
判断type的值1:校验用户名2:校验手机号使用selectCount(record)==0
阿里大于 参照demo工程redis 安装 SDR使用:SpringDataRedis/StringRedisTemplate搭建了一个微服务:sms-service,监听rabbitmq的队列,获取到消息之后发送短信1.生成验证码2.发送消息给rabbitMQ的队列3.保存验证码到redis中
因为系统中不止注册一个地方需要短信发送,因此我们将短信发送抽取为微服务:leyou-sms-service,凡是需要的地方都可以使用。
另外,因为短信发送API调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:
我们首先把一些常量抽取到application.yml中:
leyou: sms: accessKeyId: JWffwFJIwada # 你自己的accessKeyId accessKeySecret: aySRliswq8fe7rF9gQyy1Izz4MQ # 你自己的AccessKeySecret signName: 乐优商城 # 签名名称 verifyCodeTemplate: SMS_133976814 # 模板名称
然后注入到属性类中:
@ConfigurationProperties(prefix = "leyou.sms")public class SmsProperties { String accessKeyId; String accessKeySecret; String signName; String verifyCodeTemplate; public String getAccessKeyId() { return accessKeyId; } public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; } public String getAccessKeySecret() { return accessKeySecret; } public void setAccessKeySecret(String accessKeySecret) { this.accessKeySecret = accessKeySecret; } public String getSignName() { return signName; } public void setSignName(String signName) { this.signName = signName; } public String getVerifyCodeTemplate() { return verifyCodeTemplate; } public void setVerifyCodeTemplate(String verifyCodeTemplate) { this.verifyCodeTemplate = verifyCodeTemplate; }}
我们把阿里提供的demo进行简化和抽取,封装一个工具类:
@Component@EnableConfigurationProperties(SmsProperties.class)public class SmsUtils { @Autowired private SmsProperties prop; //产品名称:云通信短信API产品,开发者无需替换 static final String product = "Dysmsapi"; //产品域名,开发者无需替换 static final String domain = "dysmsapi.aliyuncs.com"; static final Logger logger = LoggerFactory.getLogger(SmsUtils.class); public SendSmsResponse sendSms(String phone, String code, String signName, String template) throws ClientException { //可自助调整超时时间 System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); //初始化acsClient,暂不支持region化 IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", prop.getAccessKeyId(), prop.getAccessKeySecret()); DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); IAcsClient acsClient = new DefaultAcsClient(profile); //组装请求对象-具体描述见控制台-文档部分内容 SendSmsRequest request = new SendSmsRequest(); request.setMethod(MethodType.POST); //必填:待发送手机号 request.setPhoneNumbers(phone); //必填:短信签名-可在短信控制台中找到 request.setSignName(signName); //必填:短信模板-可在短信控制台中找到 request.setTemplateCode(template); //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 request.setTemplateParam("{\"code\":\"" + code + "\"}"); //选填-上行短信扩展码(无特殊需求用户请忽略此字段) //request.setSmsUpExtendCode("90997"); //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 request.setOutId("123456"); //hint 此处可能会抛出异常,注意catch SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); logger.info("发送短信状态:{}", sendSmsResponse.getCode()); logger.info("发送短信消息:{}", sendSmsResponse.getMessage()); return sendSmsResponse; }}
接下来,编写消息监听器,当接收到消息后,我们发送短信。
@Component@EnableConfigurationProperties(SmsProperties.class)public class SmsListener { @Autowired private SmsUtils smsUtils; @Autowired private SmsProperties prop; @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.sms.queue", durable = "true"), exchange = @Exchange(value = "leyou.sms.exchange", ignoreDeclarationExceptions = "true"), key = { "sms.verify.code"})) public void listenSms(Mapmsg) throws Exception { if (msg == null || msg.size() <= 0) { // 放弃处理 return; } String phone = msg.get("phone"); String code = msg.get("code"); if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) { // 放弃处理 return; } // 发送消息 SendSmsResponse resp = this.smsUtils.sendSms(phone, code, prop.getSignName(), prop.getVerifyCodeTemplate()); }}
我们注意到,消息体是一个Map,里面有两个属性:
Spring Data Redis 提供了一个工具类:RedisTemplate。里面封装了对于Redis的五种数据结构的各种操作,包括:
其它一些通用命令,如expire,可以通过redisTemplate.xx()来直接调用
5种结构:
Map<String,String>
Map<String,List<String>>
Map<String,Set<String>>
RedisTemplate在创建时,可以指定其泛型类型:
注意:这里的类型不是Redis中存储的数据类型,而是Java中的数据类型,RedisTemplate会自动将Java类型转为Redis支持的数据类型:字符串、字节、二进制等等。
不过RedisTemplate默认会采用JDK自带的序列化(Serialize)来对对象进行转换。生成的数据十分庞大,因此一般我们都会指定key和value为String类型,这样就由我们自己把对象序列化为json字符串来存储即可。
因为大部分情况下,我们都会使用key和value都为String的RedisTemplate,因此Spring就默认提供了这样一个实现:
需要三个步骤:
leyou-sms-service
服务,发送短信因此,我们需要引入Redis和AMQP:
org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-amqp
添加RabbitMQ和Redis配置:
spring: redis: host: 192.168.56.101 rabbitmq: host: 192.168.56.101 username: leyou password: leyou virtual-host: /leyou
另外还要用到工具类,生成6位随机码,这个我们封装到了leyou-common
中,因此需要引入依赖:
com.leyou.common leyou-common ${leyou.latest.version}
NumberUtils中有生成随机码的工具方法:
/** * 生成指定位数的随机数字 * @param len 随机数的位数 * @return 生成的随机数 */public static String generateCode(int len){ len = Math.min(len, 8); int min = Double.valueOf(Math.pow(10, len - 1)).intValue(); int num = new Random().nextInt( Double.valueOf(Math.pow(10, len + 1)).intValue() - 1) + min; return String.valueOf(num).substring(0,len);}
UserController
在leyou-user-service工程中的UserController添加方法:
/** * 发送手机验证码 * @param phone * @return */@PostMapping("code")public ResponseEntitysendVerifyCode(String phone) { Boolean boo = this.userService.sendVerifyCode(phone); if (boo == null || !boo) { return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>(HttpStatus.CREATED);}
UserService
在Service中添加代码:
@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate AmqpTemplate amqpTemplate;static final String KEY_PREFIX = "user:code:phone:";static final Logger logger = LoggerFactory.getLogger(UserService.class);public Boolean sendVerifyCode(String phone) { // 生成验证码 String code = NumberUtils.generateCode(6); try { // 发送短信 Mapmsg = new HashMap<>(); msg.put("phone", phone); msg.put("code", code); this.amqpTemplate.convertAndSend("leyou.sms.exchange", "sms.verify.code", msg); // 将code存入redis this.redisTemplate.opsForValue().set(KEY_PREFIX + phone, code, 5, TimeUnit.MINUTES); return true; } catch (Exception e) { logger.error("发送短信失败。phone:{}, code:{}", phone, code); return false; }}
注意:要设置短信验证码在Redis的缓存时间为5分钟
1.校验验证码2.生成盐3.加盐加密4.新增用户5.删除Redis中的验证码hibernate-validate:对bean Validate(JSR303 规范)的实现提供了一系列的注解,通过注解就可以校验数据的合法性@Valid
1.根据用户查询用户2.判断用户是否存在3.对用户输入的密码加盐加密4.对密码进行比较
转载地址:http://pxxab.baihongyu.com/