乐优商城(十)用户注册
文章目录
- 1. 搭建用户微服务
- 1.1 用户微服务的结构
- 1.2 创建 leyou-user
- 1.3 创建 leyou-user-interface
- 1.4 创建 leyou-user-service
- 1.5 添加路由规则
- 2. 后台功能准备
- 2.1 接口文档
- 2.2 用户表
- 2.3 基本代码
- 2.3.1 实体类
- 2.3.2 Mapper
- 2.3.3 Service
- 2.3.4 Controller
- 3. 实现数据校验功能
- 3.1 接口说明
- 3.2 Controller
- 3.3 Service
- 3.4 测试
- 4. 实现发送短信功能
- 4.1 开通阿里云短信服务
- 4.2 测试发送短信
- 4.3 搭建短信微服务
- 4.3.1 创建 leyou-sms
- 4.3.2 编写短信工具类
- 4.3.3 测试
- 5. 实现发送验证码功能
- 5.1 接口说明
- 5.2 添加依赖
- 5.3 配置文件
- 5.4 生成验证码工具类
- 5.5 Controller
- 5.6 Service
- 5.7 测试
- 6. 实现用户注册功能
- 6.1 接口说明
- 6.2 加密工具类
- 6.3 Controller
- 6.4 Service
- 6.5 测试
- 6.6 数据校验
- 6.6.1 Hibernate-Validator 简介
- 6.6.2 Bean 校验的注解
- 6.6.3 实现数据校验
- 6.7 再次测试
- 7. 在注册页完成注册
- 8. 实现查询用户功能
- 8.1 接口说明
- 8.2 Controller
- 8.3 Service
- 8.4 测试
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-servicedatasource:url: jdbc:mysql://localhost:3306/leyouusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driver eureka:client:service-url:defaultZone: http://localhost:10086/eurekainstance:lease-renewal-interval-in-seconds: 5lease-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/eurekaregistry-fetch-interval-seconds: 5
zuul:prefix: /apiroutes: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;// 用户名@JsonIgnoreprivate String password;// 密码private String phone;// 电话private Date created;// 创建时间@JsonIgnoreprivate 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 {@Autowiredprivate UserMapper userMapper;
}
2.3.4 Controller
在 com.leyou.user.controller 包中添加 UserController 类
@Controller
public class UserController {@Autowiredprivate 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,不存在则为 falsereturn 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-servicerabbitmq:host: 127.0.0.1username: leyoupassword: leyouvirtual-host: /leyou
4.3.2 编写短信工具类
把短信服务的一些常量抽取到 application.yaml 中
leyou:sms:accessKeyId: JWffwFJIwada # 你自己的accessKeyIdaccessKeySecret: aySRliswq8fe7rF9gQyy1Izz4MQ # 你自己的AccessKeySecretsignName: 乐优商城 # 签名名称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 {@Autowiredprivate 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 {@Autowiredprivate SmsUtils smsUtils;@Autowiredprivate 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.132rabbitmq:host: 127.0.0.1username: leyoupassword: leyouvirtual-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);// 发送消息到 RabbitMQMap<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;}// 生成 saltString salt = CodecUtils.generateSalt();user.setSalt(salt);// 对密码数据进行加密user.setPassword(CodecUtils.md5Hex(user.getPassword(),salt));// 将用户注册数据写入 MySQLuser.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 位之间")@JsonIgnoreprivate String password;// 密码@Pattern(regexp = "^1[35678]\\d{9}$", message = "手机号格式不正确")private String phone;// 电话private Date created;// 创建时间@JsonIgnoreprivate 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;}@Overridepublic 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 状态码,查询成功。
乐优商城(十)用户注册相关推荐
- 乐优商城源码/数据库及笔记总结
文章目录 1 源码 2 笔记 2.1 项目概述 2.2 微服务 3 项目优化 4 项目或学习过程中涉及到的设计模式 5 安全问题 6 高内聚低耦合的体现 7 项目中待优化的地方 1 源码 Github ...
- 乐优商城之项目搭建(四)
文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...
- 【javaWeb微服务架构项目——乐优商城day15】——会调用订单系统接口,实现订单结算功能,实现微信支付功能
0.学习目标 会调用订单系统接口 实现订单结算功能 实现微信支付功能 源码笔记及资料: 链接:https://pan.baidu.com/s/1_opfL63P1pzH3rzLnbFiNw 提取码:v ...
- 乐优商城(四)商品规格管理
文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...
- 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询
乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...
- 乐优商城之分类查询品牌查询(八)
文章目录 (一)编写分类查询 (二)跨域问题 (三)cors跨域原理 (四)解决跨域问题 (五)品牌查询页面分析 (六)品牌查询后台代码 (七)分页查询排序的原理 (八)axios (一)编写分类查询 ...
- 乐优商城(05)--商品管理
乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...
- 乐优商城(填坑)——后台登录
后台管理模块增加登录验证,与门户网站一样都是采用无状态登录. 一.新增全局函数 在main.js中新增用户验证: 二.修改路由 先显示登录页面 效果: 三.解决cookie写入问题 在http.js中 ...
- 乐优商城(10)--数据同步
乐优商城(10)–数据同步 一.RabbitMQ 1.1.问题分析 目前已经完成了商品详情和搜索系统的开发.思考一下,是否存在问题? 商品的原始数据保存在数据库中,增删改查都在数据库中完成. 搜索服务 ...
最新文章
- PC和手机怎么实现绝对居中?
- opencv 裁剪 java_如何在opencv java中裁剪检测到的面部图像
- Hadoop streaming: Exception in thread main java.io.IOException: No space left on device
- 华为诺亚方舟实验室主任李航:神经符号处理开启自然语言处理新篇章
- python判断语句_python条件判断语句if elif else使用
- CPU指令集是什么东西
- 第七篇:Spring Boot整合Thymeleaf_入门试炼03
- mysql修改启动command_MySQL Command Line[mysql命令行常用命令]
- Ubuntu安装cacti步骤
- 我为什么鼓励大家运营个人公众号
- emerald sword(打倒大魔王)
- 挖金矿问题java课程设计_某15万吨金矿采矿方法课程设计
- iOS 9 真机调试
- CSS如何设置div半透明效果
- img标签,hr标签
- Visual Paradigm 如何清除系统代理设置
- 游戏开发20课 tilemap 绘制
- 云上OneNET智慧大棚
- ThinkingInJava_吸血鬼数
- 什么叫组网_什么叫MESH组网
热门文章
- Http Chunked理解
- Linux命令行中使用计算器的5个命令
- Unix和UnixNano
- 运营商三要素验证原理,这篇文章就够了!
- RPC failed; curl 56 OpenSSL SSL_read: No error解决方法
- Wish | IT桔子
- vue2与vue3的diff算法区别
- 米家机器人连接不上了怎么重置_米家扫地机器人怎么重置wifi_米家扫地机器人wifi怎么重置...
- .NET、C#与Silverlight开发圣典——分享15位MVP的最佳实践经验
- BSN北京市区块链主干网正式发布