1. 搭建用户微服务
用户搜索到自己心仪的商品,接下来就要去购买,但是购买必须先登录。所以接下来我们编写用户微服务,实现用户的注册和登录功能。
1.1 用户微服务的结构
由于其它微服务也会调用用户微服务,因此这里我们需要使用聚合工程。
我们会在 leyou-user 中创建两个子工程:
- leyou-user-interface:主要是对外暴露的接口及相关实体类
- leyou-user-service:所有业务逻辑及内部使用接口
1.2 创建 leyou-user
-
右键 leyou 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
删除 src 目录
1.3 创建 leyou-user-interface
-
右键 leyou-user 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
添加依赖
<?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>leyou-user</artifactId> <groupId>com.leyou.user</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.user</groupId> <artifactId>leyou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.0</version> </dependency> </dependencies> </project>
1.4 创建 leyou-user-service
-
右键 leyou-user 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
项目结构如下
-
添加依赖
<?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>leyou-user</artifactId> <groupId>com.leyou.user</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.user</groupId> <artifactId>leyou-user-service</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--eureka-client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mysql 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</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> <!--leyou-user-interface--> <dependency> <groupId>com.leyou.user</groupId> <artifactId>leyou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
编写启动类
@SpringBootApplication @EnableDiscoveryClient @MapperScan("com.leyou.user.mapper") public class LeyouUserApplication { public static void main(String[] args) { SpringApplication.run(LeyouUserApplication.class,args); } }
-
编写配置文件
server: port: 8085 spring: application: name: user-service datasource: url: jdbc:mysql://localhost:3306/leyou username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver eureka: client: service-url: defaultZone: http://localhost:10086/eureka instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15 mybatis: type-aliases-package: com.leyou.user.pojo
-
在 leyou-user-interface 中创建 com.leyou.user.pojo 包
1.5 添加路由规则
我们修改 leyou-gateway,添加路由规则,对 leyou-user-service 进行路由。
server:
port: 10010
spring:
application:
name: leyou-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
registry-fetch-interval-seconds: 5
zuul:
prefix: /api
routes:
item-service: /item/**
search-service: /search/**
user-service: /user/**
2. 后台功能准备
2.1 接口文档
整个用户微服务的开发,我们将模拟公司内面向接口的开发。现在项目经理已经设计好了接口文档《用户中心接口说明》,如下:
我们将根据文档直接编写后台功能,不关心页面实现。
2.2 用户表
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='用户表';
注意:由于根据用户名查询的频率较高,所以我们给用户名创建了索引
2.3 基本代码
2.3.1 实体类
在 com.leyou.user.pojo 包中添加 User 类
@Table(name = "tb_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;// 用户名
@JsonIgnore
private String password;// 密码
private String phone;// 电话
private Date created;// 创建时间
@JsonIgnore
private String salt;// 密码的盐值
}
注意:为了安全考虑。这里对 password 和 salt 添加了注解 @JsonIgnore,这样在 json 序列化时,就不会把 password 和 salt 返回。
2.3.2 Mapper
在 com.leyou.user.mapper 包中添加 UserMapper 接口
public interface UserMapper extends Mapper<User> {
}
2.3.3 Service
在 com.leyou.user.service 包中添加 UserService 类
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
}
2.3.4 Controller
在 com.leyou.user.controller 包中添加 UserController 类
@Controller
public class UserController {
@Autowired
private UserService userService;
}
3. 实现数据校验功能
3.1 接口说明
实现用户数据的校验,包括对手机号、用户名的唯一性校验。
接口路径
GET /check/{data}/{type}
参数说明
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
data | 要校验的数据 | 是 | String | 无 |
type | 要校验的数据类型:1,用户名;2,手机; | 否 | Integer | 1 |
返回结果
返回布尔类型结果:
- true:可用
- false:不可用
状态码:
- 200:校验成功
- 400:参数有误
- 500:服务器内部异常
3.2 Controller
根据接口文档可知:
- 请求方式:GET
- 请求路径:/check/{param}/{type}
- 请求参数:param、type
- 返回结果:true 或 false
在 UserController 添加 checkUserData 方法
/**
* 校验数据是否可用
*
* @param data
* @param type
* @return
*/
@GetMapping("check/{data}/{type}")
public ResponseEntity<Boolean> checkUserData(@PathVariable("data") String data, @PathVariable(value = "type") Integer type) {
Boolean bool = this.userService.checkData(data, type);
if (bool == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
return ResponseEntity.ok(bool);
}
3.3 Service
在 UserService 添加 checkData 方法
/**
* 校验数据是否可用
*
* @param data
* @param type
* @return
*/
public Boolean checkData(String data, Integer type) {
User user = new User();
if (type == 1) {
user.setUsername(data);
} else if (type == 2) {
user.setPhone(data);
} else {
return null;
}
// 返回数据库是否存在要校验的数据,存在则为 true,不存在则为 false
return this.userMapper.selectCount(user) == 0;
}
3.4 测试
-
打开 user 表,有两条数据
-
使用 Postman 发送 Get 请求,校验用户名 “zhangsan” 是否唯一,成功返回 false
4. 实现发送短信功能
注册页面上有短信发送的按钮,当用户点击发送短信,我们需要生成验证码,发送给用户。
我们将使用阿里云提供的短信服务来实现发送短信功能。
4.1 开通阿里云短信服务
-
进入阿里云官网,在搜索栏搜索 “短信服务”
https://www.aliyun.com/
-
点击立即开通
-
点击管理控制台
-
点击国内消息,添加签名。签名就是短信内容头部的标签,标注短信发送者的身份。
-
点击模板管理,添加模板。模板就是短信的内容,包含验证码变量。
-
点击右上角头像,AccessKey 管理
-
点击创建 AccessKey。 AccessKey ID 和 AccessKey Secret 是访问阿里云 API 的密钥,具有该账户完全的权限。
-
点击费用,充值。短信服务发送的每条短信收费 0.045 元,大概充值 2-3 元即可。
4.2 测试发送短信
-
进入管理控制台,点击快速入门,选择签名和模板等,右边就会生成短信。点击查看 API Demo。
-
可以看到右边已经生成 Java 发送短信的代码了,将它复制下来。
-
创建一个 Maven 工程,在 pom 文件中添加依赖。
<?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"> <modelVersion>4.0.0</modelVersion> <groupId>com.sms</groupId> <artifactId>sms-test</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.0.3</version> </dependency> </dependencies> </project>
-
创建 SendSms 类,填写你自己的 accessKeyId,accessSecret
import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; public class SendSms { public static void main(String[] args) { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "<accessKeyId>", "<accessSecret>"); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); request.setAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", "12345678901"); request.putQueryParameter("SignName", "乐优商城"); request.putQueryParameter("TemplateCode", "SMS_189236195"); request.putQueryParameter("TemplateParam", "{\"code\":\"123456\"}"); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } } }
-
启动运行,短信发送成功
4.3 搭建短信微服务
因为系统中不止注册一个地方需要短信发送,因此我们将短信发送抽取为微服务:leyou-sms,凡是需要的地方都可以使用。
另外,因为短信发送 API 调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:
- 其它服务要发送短信时,发送消息给 RabbitMQ。
- 短信服务监听 RabbitMQ 消息,收到消息后发送短信。
4.3.1 创建 leyou-sms
-
右键 leyou 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
添加依赖
<?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>leyou</artifactId> <groupId>com.leyou.parent</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.sms</groupId> <artifactId>leyou-sms</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--amqp--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--aliyun-java-sdk-core--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.0.3</version> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
-
编写启动类
@SpringBootApplication public class LeyouSmsApplication { public static void main(String[] args) { SpringApplication.run(LeyouSmsApplication.class, args); } }
-
编写配置文件 application.yaml
server: port: 8086 spring: application: name: sms-service rabbitmq: host: 127.0.0.1 username: leyou password: leyou virtual-host: /leyou
4.3.2 编写短信工具类
-
把短信服务的一些常量抽取到 application.yaml 中
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; } }
-
编写发送短信工具类
@Component @EnableConfigurationProperties(SmsProperties.class) public class SmsUtils { @Autowired private SmsProperties smsProperties; public void sendSms(String phone, String code, String signName, String template) { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); request.setAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", signName); request.putQueryParameter("TemplateCode", template); request.putQueryParameter("TemplateParam", "{\"code\":\""+code+"\"}"); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } } }
-
编写消息监听器
消息体是一个 Map,里面有两个 key:
- phone:电话号码
- code:短信验证码
@Component @EnableConfigurationProperties(SmsProperties.class) public class SmsListener { @Autowired private SmsUtils smsUtils; @Autowired private SmsProperties prop; /** * 处理发送短信消息 * * @param msg */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.sms.queue", durable = "true"), exchange = @Exchange( value = "leyou.sms.exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = {"sms.verify.code"})) public void listenSms(Map<String, String> msg) { // 判断消息是否为空 if (CollectionUtils.isEmpty(msg)) { return; } String phone = msg.get("phone"); String code = msg.get("code"); // 判断电话或验证码是否为空 if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) { return; } // 发送消息 smsUtils.sendSms(phone, code, prop.getSignName(), prop.getVerifyCodeTemplate()); } }
-
最终目录结构
4.3.3 测试
-
启动 leyou-sms
-
打开 RabbitMQ 管理界面,成功生成队列和通道。
5. 实现发送验证码功能
前面我们已经实现了发送短信的功能,接下来我们在用户微服务中实现发送验证码的功能。
5.1 接口说明
发送验证码流程
- 我们接收页面发送来的手机号码
- 生成一个随机验证码
- 将验证码保存在服务端(用来校验用户输入的验证码)
- 发送消息(包括手机号、验证码)给 RabbitMQ
- 短信微服务监听 RabbitMQ 消息,收到消息后发送短信。
接口路径
POST /code
参数说明
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
phone | 用户的手机号码 | 是 | String | 无 |
返回结果
-
返回值:无
-
状态码:
- 201:请求已接收
- 400:参数有误
- 500:服务器内部异常
问题分析
那么问题来了:生成验证码后,验证码保存在哪里呢?
验证码有一定有效期,一般是 5 分钟,我们可以利用 Redis 的过期机制来保存。
5.2 添加依赖
在 leyou-user-service 中添加依赖:
- Spring Data Redis
- Spring AMQP
- leyou-common
<!--spring-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--amqp-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--leyou-common-->
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>leyou-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
5.3 配置文件
添加配置文件 application.yaml
spring:
redis:
host: 192.168.222.132
rabbitmq:
host: 127.0.0.1
username: leyou
password: leyou
virtual-host: /leyou
5.4 生成验证码工具类
在 leyou-common 中添加工具类 NumberUtils
public class 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);
}
}
5.5 Controller
在 UserController 中添加 sendVerifyCode 方法
/**
* 发送手机验证码
* @param phone
* @return
*/
@PostMapping("code")
public ResponseEntity<Void> sendVerifyCode(@RequestParam("phone") String phone) {
// 请求参数错误
if(StringUtils.isBlank(phone)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
this.userService.sendVerifyCode(phone);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
5.6 Service
在 UserService 中添加 sendVerifyCode 方法
@Autowired
private AmqpTemplate amqpTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX = "user:code:phone:"; // Redis 中 key 的前缀
/**
* 发送手机验证码
*
* @param phone
* @return
*/
public void sendVerifyCode(String phone) {
// 生成验证码
String code = NumberUtils.generateCode(6);
// 发送消息到 RabbitMQ
Map<String, String> msg = new HashMap<>();
msg.put("phone", phone);
msg.put("code", code);
amqpTemplate.convertAndSend("leyou.sms.exchange", "sms.verify.code", msg);
// 保存验证码到 Redis,5 分钟后过期
stringRedisTemplate.opsForValue().set(KEY_PREFIX + phone, code, 5, TimeUnit.MINUTES);
}
5.7 测试
-
启动 leyou-gateway、leyou-register、leyou-user、leyou-sms 四个微服务
-
打开 Postman 发送一条 POST 请求,发送验证码,响应 201
-
打开 Redis,成功保存验证码
-
手机收到验证码
6. 实现用户注册功能
6.1 接口说明
用户注册流程
- 我们接收页面发送来的用户注册数据
- 校验短信验证码
- 生成盐,对密码进行加密
- 将用户注册数据写入 MySQL
- 删除 Redis 中的验证码
接口路径
POST /register
参数说明
以 form 表单格式提交:
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
username | 用户名,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
password | 用户密码,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
phone | 手机号码 | 是 | String | 无 |
code | 短信验证码 | 是 | String | 无 |
返回结果:
-
返回值:无
-
状态码:
- 201:注册成功
- 400:参数有误,注册失败
- 500:服务器内部异常,注册失败
6.2 加密工具类
-
在 leyou-user-service 中添加工具类 CodecUtils
public class CodecUtils { /** * MD5 加密 * * @param data * @param salt * @return */ public static String md5Hex(String data,String salt) { if (StringUtils.isBlank(salt)) { salt = data.hashCode() + ""; } return DigestUtils.md5Hex(salt + DigestUtils.md5Hex(data)); } /** * 生成 salt * * @return */ public static String generateSalt(){ return StringUtils.replace(UUID.randomUUID().toString(), "-", ""); } }
-
该工具类需要 apache 的加密工具包
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency>
6.3 Controller
在 UserController 中添加 register 方法
/**
* 用户注册
*
* @param user
* @param code
* @return
*/
@PostMapping("register")
public ResponseEntity<Void> register(User user, @RequestParam("code") String code) {
this.userService.register(user, code);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
6.4 Service
在 UserService 中添加 register 方法
/**
* 用户注册
*
* @param user
* @param code
*/
public void register(User user, String code) {
// 校验验证码
String cacheCode = stringRedisTemplate.opsForValue().get(KEY_PREFIX + user.getPhone());
if (!StringUtils.equals(code, cacheCode)) {
return;
}
// 生成 salt
String salt = CodecUtils.generateSalt();
user.setSalt(salt);
// 对密码数据进行加密
user.setPassword(CodecUtils.md5Hex(user.getPassword(),salt));
// 将用户注册数据写入 MySQL
user.setId(null);
user.setCreated(new Date());
userMapper.insertSelective(user);
// 删除 Redis 中的验证码
stringRedisTemplate.delete(KEY_PREFIX + user.getPhone());
}
6.5 测试
-
启动 leyou-gateway、leyou-register、leyou-user、leyou-sms 四个微服务
-
打开 Postman 发送一条 POST 请求,发送验证码,响应 201
-
打开 Redis,查看验证码
-
打开 Postman 发送一条 POST 请求,注册用户,响应 201
-
打开 MySQL,注册成功
-
打开 Redis,验证码被删除
6.6 数据校验
上面已经实现了用户注册,但服务端并没对用户的注册数据进行数据校验。如果有人绕过前端的数据校验,直接向服务端发送请求,也是可以注册成功的,这样显然是不安全的,所以我们必须在服务端也进行数据校验。
我们这里会使用 Hibernate-Validator 框架完成数据校验,Web 启动器中已经集成了相关依赖。
6.6.1 Hibernate-Validator 简介
Hibernate Validator 是 Hibernate 提供的一个开源框架,经常用来验证 bean 的字段,使用注解方式非常方便的实现服务端的数据校验。
hibernate Validator 是 Bean Validation 的参考实现。Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。
6.6.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 计算合法性 |
6.6.3 实现数据校验
-
我们在 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 private String password;// 密码 @Pattern(regexp = "^1[35678]\\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; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", phone='" + phone + '\'' + ", created=" + created + ", salt='" + salt + '\'' + '}'; } }
-
修改 UserController 中的 register 方法,给 User 添加 @Valid 注解
/** * 用户注册 * * @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(); }
6.7 再次测试
-
启动 leyou-gateway、leyou-register、leyou-user、leyou-sms 四个微服务
-
我们填写一些不合法的注册数据,发送请求,响应 400 状态码
-
并且返回错误信息
7. 在注册页完成注册
-
在注册页填写信息,完成注册
-
查看 MySQL,注册成功
8. 实现查询用户功能
8.1 接口说明
功能说明
根据用户名和密码查询指定用户。
接口路径
GET /query
参数说明
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
username | 用户名,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
password | 用户密码,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
返回结果
-
JSON 格式数据
{ "id": 6572312, "username":"test", "phone":"13688886666", "created": 1342432424 }
-
状态码
- 200:查询成功
- 400:用户名或密码错误
- 500:服务器内部异常,查询失败
8.2 Controller
在 UserController 中添加 queryUser 方法
/**
* 根据用户名和密码查询用户
*
* @param username
* @param password
* @return
*/
@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.status(HttpStatus.BAD_REQUEST).build();
}
return ResponseEntity.ok(user);
}
8.3 Service
在 UserService 中添加 queryUser 方法
/**
* 根据用户名和密码查询用户
*
* @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);
// 校验用户名
if (user == null) {
return null;
}
// 校验密码
if (!user.getPassword().equals(CodecUtils.md5Hex(password, user.getSalt()))) {
return null;
}
// 用户名密码都正确
return user;
}
8.4 测试
我们填写用户名和密码,发送请求,响应 200 状态码,查询成功。