在estore阶段,我们已经做过注册功能,而且也是阿里云短信,所以,这里相对来说较为轻松
接下来,我们将进行注册中心的搭建
第一步,新建3个模块
聚合工程,一个父工程,两个子工程,一个是注册服务,一个是实体类
我们的service工程还是那些依赖,不同的是,我们这里多了一个mq的依赖,我们希望,消息的发送是异步的,通过短信发送的微服务发送短信
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- mybatis启动器 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!-- 通用Mapper启动器 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--spring对mq的支持--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>com.leyou.service</groupId> <artifactId>leyou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
第二步,导入配置文件,这是我们第五个微服务,我们这边多了一个redis的配置和一个mq的配置
server: port: 8085 spring: application: name: user-service datasource: url: jdbc:mysql://127.0.0.1:3306/leyou username: root password: 123 driver-class-name: com.mysql.jdbc.Driver redis: host: 192.168.56.101 rabbitmq: host: 192.168.56.101 username: admin password: admin virtual-host: /leyou template: retry: enabled: true initial-interval: 10000ms max-interval: 210000ms multiplier: 2 publisher-confirms: true eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${eureka.instance.ip-address}.${server.port} lease-renewal-interval-in-seconds: 3 lease-expiration-duration-in-seconds: 10 mybatis: type-aliases-package: com.leyou.user.service.pojo
第三步,controller层的书写
1.用户注册,肯定要有数据的校验,手机号是否存在等信息,这里,我们首先写一个数据的校验
/** * 校验输入的信息 * * @param data * @param type * @return */ @GetMapping("check/{data}/{type}") public ResponseEntity<Boolean> checkUser( @PathVariable(value = "data", required = true) String data, @PathVariable(value = "type") Integer type ) { Boolean flag = this.userService.checkUser(data, type); return ResponseEntity.status(HttpStatus.OK).body(flag); }
public Boolean checkUser(String data, Integer type) { User user = new User(); if (type==1){ user.setUsername(data); }else if (type==2){ user.setPhone(data); }else { return null; } return this.userMapper.selectCount(user)==0; }
2.发送短信
/** * 发送短信 * * @param phone * @return */ @PostMapping("send") public ResponseEntity<Void> sendMessage(@RequestParam("phone") String phone) { //校验手机号是否正确 String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$"; if (!phone.matches(regex)) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } this.userService.sendMessage(phone); return ResponseEntity.status(HttpStatus.OK).build(); }
发送短信的service
/** * 发送短信的方法 * @param phone */ public void sendMessage(String phone) { /*封装信息,发送消息*/ //生成一个验证码 String code = NumberUtils.generateCode(6); System.out.println(code); try { Map<String, String> map = new HashMap<>(); map.put("code",code); map.put("phone",phone); /*rabbitmq发送消息*/
this.amqpTemplate.convertAndSend("ly.sms.exchange","sms.verify.code",map);// 把 code 放到 redis this . redisTemplate .opsForValue () .set ( KEY_PREFIX +phone ,code , 5 , TimeUnit . MINUTES ) ; } catch ( AmqpException e ) { logger .error ( " 发送短息失败, phone:{},code:{}" ,phone ,code ) ; } }
这里,我们将短信的验证码放入redis中,用消息队列异步发送短信
这里有个注意点,我们的redisTemplate用的是StringRedisTemplate
@Autowired private StringRedisTemplate redisTemplate;
我们这里使用的是StringRedisTemplate而不是redisTemplate,因为我们要指定我们的key和value的值都是string类型的,
redisTemplate采用jdk自带的序列化手段对我们的对象进行序列化,他生成的数据时十分庞大的,所以,我们选择自己转为json。
3.用户的注册
/** * 用户注册的方法 * @param code * @param user * @return */ @PostMapping("register") public ResponseEntity<Void> userRegister(@RequestParam("code")String code, @Valid User user) { Boolean boo = this.userService.register(user,code); if (boo == null || !boo) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } return ResponseEntity.status(HttpStatus.CREATED).build(); }
service
/** * 用户注册的方法 * @param user * @param code * @return */ public Boolean register(User user, String code) { //判断用户的数据类型是否正确 String username = user.getUsername(); String password = user.getPassword(); String phone = user.getPhone(); String regex = "^[0-9a-zA-Z_]{1,26}$"; if (!username.matches(regex)||!password.matches(regex)) { return false; } //判断验证码是否正确 String redisCode = this.redisTemplate.opsForValue().get(KEY_PREFIX + phone); if (redisCode==null&&code==null&&!redisCode.equals(code)){ return false; } //对密码进行加密 String salt = Md5Utils.generate(); String md5Password = Md5Utils.encryptPassword(password, salt); user.setSalt(salt); user.setPassword(md5Password); Date createTime = new Date(System.currentTimeMillis()); user.setCreated(createTime); //向数据库中添加数据 Boolean result = this.userMapper.insertSelective(user)==1; //注册成功,将redis中的验证码删除 if (result) { try { this.redisTemplate.delete(KEY_PREFIX + phone); } catch (Exception e) { logger.error("删除redis中的数据失败,key:{}"+KEY_PREFIX + phone); } } return result; }
这里,我们实现注册,并删除redis中的验证码。这里我们的密码采用加盐的方式进行处理的。
4.验证用户名和密码是否正确
/** * 校验用户名和密码是否正确 * @param username * @param password * @return */ @GetMapping("query") public ResponseEntity<User> queryUser( @RequestParam(value = "username",required = true)String username, @RequestParam(value = "password",required = true)String password ){ User user = this.userService.queryUserByIdAndPassword(username,password); if (user==null){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } return ResponseEntity.status(HttpStatus.OK).body(user); }
这是我们主要的几个功能
我们为了实现服务端的数据校验,还用了一个框架
引入依赖
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>
对实体类加注解进行限制
@Table(name = "tb_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Length(min = 4,max = 30,message = "用户的值只能在4-30位之间") private String username;// 用户名 @JsonIgnore @Length(min = 4,max = 30,message = "密码的值只能在4-30位之间") private String password;// 密码 @Pattern(regexp = "^1[35678]\\d{9}$", message = "手机号格式不正确") private String phone;// 电话
这里的@JsonIgnore并不属于这个框架,而是jackson为我们提供的对象转为json时的忽略注解
第四步,用阿里云发送短信
我们首先通过mq接受消息
@RabbitListener(bindings = @QueueBinding( value = @Queue(value = "ly.sms.queue", durable = "true"), exchange = @Exchange(value = "ly.sms.exchange", ignoreDeclarationExceptions = "true"), key = {"sms.verify.code"})) public void ListenSms(Map<String,String> map){ if (map==null||map.size()<1){ return; } String phone = map.get("phone"); String code = map.get("code"); if (StringUtils.isBlank(phone)||StringUtils.isBlank("code")){ return; } try { SendSmsResponse sendSmsResponse = smsUtils.sendSms(phone, code, smsProperties.getSignName(), smsProperties.getVerifyCodeTemplate()); if ("ok".equals(sendSmsResponse.getCode())){ //发送消息成功,结束方法 return; } } catch (ClientException e) { logger.error("短信发送出现错误"); throw new RuntimeException("短信发送错误,重试中"); } }
我们把阿里云需要的配置放到配置类中
@ConfigurationProperties(prefix = "ly.sms") public class SmsProperties { String accessKeyId; String accessKeySecret; String signName; String verifyCodeTemplate;
还有阿里云发送短信的工具类
@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; } }这样,我们的注册功能应该就算是完成了了
另外,RedisTemplate的基本操作
redisTemplate.opsForHash() :操作hash
redisTemplate.opsForList():操作list
redisTemplate.opsForSet():操作set
redisTemplate.opsForZSet():操作zset
其它一些通用命令,如expire,可以通过redisTemplate.xx()来直接调用
5种结构:
String:等同于java中的,
Map<String,String>
list:等同于java中的
Map<String,List<String>>
set:等同于java中的
Map<String,Set<String>>
sort_set:可排序的set
hash:等同于java中的:`Map<String,Map<String,String>>