此博客用于个人学习,来源于网上,对知识点进行一个整理。
1. 发送短信服务:
短信微服务已经准备好,我们就可以继续编写用户中心接口了。
1.1 接口说明:
业务逻辑是这样的:
- 我们接收页面发送来的手机号码
- 生成一个随机验证码
- 将验证码保存在服务端
- 发送短信,将验证码发送到用户手机
验证码有一定有效期,一般是5分钟,我们可以利用 Redis 的过期机制来保存。
1.2 Redis:
1)Spring Data Redis:是Spring Data 家族的一部分。 对 Jedis 客户端进行了封装,与 spring 进行了整合。可以非常方便的来实现 redis 的配置和操作。
2)RedisTemplate 基本操作:
Spring Data Redis 提供了一个工具类:RedisTemplate。里面封装了对于 Redis 的五种数据结构的各种操作,包括:
- redisTemplate.opsForValue() :操作字符串
- 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>>
3) StringRedisTemplate:
RedisTemplate 在创建时,可以指定其泛型类型:
- K:代表 key 的数据类型
- V: 代表 value 的数据类型
这里的类型不是 Redis 中存储的数据类型,而是 Java 中的数据类型,RedisTemplate 会自动将 Java 类型转为 Redis 支持的数据类型:字符串、字节、二进制等等。不过 RedisTemplate 默认会采用 JDK 自带的序列化(Serialize)来对对象进行转换。生成的数据十分庞大,因此一般我们都会指定 key 和 value 为 String 类型,这样就由我们自己把对象序列化为 json 字符串来存储即可。
因为大部分情况下,我们都会使用 key 和 value 都为 String 的 RedisTemplate,因此 Spring 就默认提供了这样一个实现:
需要在项目中引入 Redis 启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后在配置文件中指定 Redis 地址:
spring:
redis:
host: 192.168.56.101
1.3 在项目中实现:
要三个步骤:
- 生成随机验证码
- 将验证码保存到 Redis 中,用来在注册的时候验证
- 发送验证码到 leyou-sms-service 服务,发送短信
需要引入 Redis 和 AMQP:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
添加 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 中,因此需要引入依赖:
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>leyou-common</artifactId>
<version>${leyou.latest.version}</version>
</dependency>
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);
}
1)UserController:
在 leyou-user-service 工程中的 UserController 添加方法:
/**
* 发送手机验证码
* @param phone
* @return
*/
@PostMapping("code")
public ResponseEntity<Void> sendVerifyCode(@RequestParam("phone")String phone){
this.userService.sendVerifyCode(phone);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
2)UserService:
在 Service 中添加代码:
@Autowired
private UserMapper userMapper;
@Autowired
private AmqpTemplate amqpTemplate;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = "user:verify:";
public void sendVerifyCode(String phone) {
if (StringUtils.isBlank(phone)){
return;
}
//生成验证码
String code = NumberUtils.generateCode(6);
//发送消息到rabbitMQ
Map<String,String> msg = new HashMap<>();
msg.put("phone",phone);
msg.put("code",code);
this.amqpTemplate.convertAndSend("leyou.sms.exchange","verifycode.sms");
//把验证码保存到redis中
this.redisTemplate.opsForValue().set(KEY_PREFIX + phone,code,5, TimeUnit.MINUTES);
}
要设置短信验证码在 Redis 的缓存时间为5分钟
2. 注册功能:
2.1 接口说明:
基本逻辑:
- 校验短信验证码
- 生成盐
- 对密码加密
- 写入数据库
- 删除 Redis 中的验证码
2.2 UserController:
/**
* 注册
* @param user
* @param code
* @return
*/
@PostMapping("register")
public ResponseEntity<Void> register(@Valid User user,@RequestParam("code")String code){
this.userService.register(user,code);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
2.3 UserService:
public void register(User user,String code) {
//从redis中获取缓存验证码
String redisCode = this.redisTemplate.opsForValue().get(KEY_PREFIX + user.getPhone());
//1.校验验证码
if (!StringUtils.equals(code,redisCode)){
return;
}
//2.生成盐巴
String salt = CodecUtils.generateSalt();
user.setSalt(salt);
//3.加盐加密
user.setPassword(CodecUtils.md5Hex(user.getPassword(),salt));
//4.新增用户
user.setId(null);
user.setCreated(new Date());
this.userMapper.insertSelective(user);
}
此处用到了生成密码的工具类——CodeUtils:
public class CodecUtils {
public static String md5Hex(String data,String salt) {
if (StringUtils.isBlank(salt)) {
salt = data.hashCode() + "";
}
return DigestUtils.md5Hex(salt + DigestUtils.md5Hex(data));
}
public static String shaHex(String data, String salt) {
if (StringUtils.isBlank(salt)) {
salt = data.hashCode() + "";
}
return DigestUtils.sha512Hex(salt + DigestUtils.sha512Hex(data));
}
public static String generateSalt(){
return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
}
}
该工具类需要 apache 加密工具包:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
2.4 hibernate-validate:
刚才虽然实现了注册,但是服务端并没有进行数据校验,而前端的校验是很容易被有心人绕过的。所以我们必须在后台添加数据校验功能。我们这里会使用 Hibernate-Validator 框架完成数据校验,而 SpringBoot 的 web 启动器中已经集成了相关依赖。
1)Hibernate-Validator 概述:
Hibernate Validator 是 Hibernate 提供的一个开源框架,使用注解方式非常方便的实现服务端的数据校验。
2)Bean 校验的注解:
Constraint | 详细信息 |
---|---|
@Valid | 被注释的元素是一个对象,需要检查此对象的所有字段值 |
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@NotBlank | 被注释的字符串的必须非空 |
@URL(protocol=,host=, port=,regexp=, flags=) | 被注释的字符串必须是一个有效的url |
@CreditCardNumber | 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性 |
3)给 User 添加校验:
在 leyou-user-interface 中添加 Hibernate-Validator 依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
在 User 对象的部分属性上添加注解:
@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;// 用户名
@Length(min = 4,max = 30,message = "密码必须在4-30位之间")
@JsonIgnore //对象序列化为json字符串时,忽略该属性
private String password;// 密码
@Pattern(regexp = "^1[356789]\\d{9}$",message = "手机号不合法")
private String phone;// 电话
private Date created;// 创建时间
@JsonIgnore
private String salt;// 密码的盐值
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
4)在 controller 上进行控制:
在 controller 中改造 register 方法,只需要给 User 添加 @Valid 注解即可。
3. 根据用户名和密码查询用户:
3.1 接口说明:
功能:查询功能,根据参数中的用户名和密码查询指定用户。
参数说明
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
username | 用户名,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
password | 用户密码,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
返回结果
用户的json格式数据
{
"id": 6572312,
"username":"test",
"phone":"13688886666",
"created": 1342432424
}
状态码
- 200:注册成功
- 400:用户名或密码错误
- 500:服务器内部异常,注册失败
3.2 controller:
@GetMapping("query")
public ResponseEntity<User> queryUser(@RequestParam("username")String username,@RequestParam("password")String password){
User user = this.userService.queryUser(username,password);
if (user == null){
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(user);
}
3.3 service:
/**
* 查询用户
* @param username
* @param password
* @return
*/
public User queryUser(String username, String password) {
User record = new User();
record.setUsername(username);
User user = this.userMapper.selectOne(record);
//判断user是否为空
if (user == null){
return null;
}
//获取盐,对用户输入的密码加密
password = CodecUtils.md5Hex(password,user.getSalt());
//和数据库中的密码进行比较
if (StringUtils.equals(password,user.getPassword())){
return user;
}
return null;
}
查询时也要对密码进行加密后判断是否一致。