目录
-
- 1.7 认证服务
- 1.8 购物车服务
- cartList.html
- 1.9 订单服务
-
- 1.9.1 环境搭建
- 1.9.2 整合SpringSession
- 1.9.3 订单基本概念
- 1.9.4 订单登录拦截
- 1.9.5 订单确认页模型抽取编写
- 1.9.6 订单确认页数据获取
- 1.9.7 订单确认页请求完成
- 1.9.8 Feign远程调用丢失请求头问题
- 1.9.9 Feign异步调用丢失上下文的问题
- 1.9.10 订单确认页渲染
- 1.9.11 订单确认页库存查询
- 1.9.12 订单确认页模拟运费效果
- 1.9.13 订单确认页细节显示
- 1.9.14 接口幂等性
- 1.9.15 订单确认页完成
- 1.9.16 原子验证令牌
- 1.9.17 构造订单数据
- 1.9.18 构造订单项数据
- 1.9.19 订单验价
- 1.9.20 保存订单数据
- 1.9.21 锁定库存
1.7 认证服务
1.7.1 环境搭建
1)、创建认证服务微服务
我们添加的依赖如上图所示。主要是springbootDevTools + Lombok + spring Web + Thymeleaf + OpenFeign(远程调用情况)
2)、引入依赖
<dependencies>
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
3)、添加相应的域名
4)、动静分离
- 我们需要将登陆页面(login.html)和认证页面(reg.html)都放到新创建的认证服务下的templates下。
- 同理在nginx中的静态文件夹下的html中创建两个login文件夹和reg的文件夹。
- 修改login.html和reg.html页面中的静态资源路径。将href=" 改为href=“/static/login/ 和 src=” 改为src="/static/login/ 。同理reg.html页面中我们也要进行相同的修改。
5)、nacos中注册
- application.properties
spring.application.name=gulimall-auth-server
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
server.port=50000
- GulimallAuthServerApplication
@EnableFeignClients //加入远程调用
@EnableDiscoveryClient //加入服务注册发现功能
@SpringBootApplication
public class GulimallAuthServerApplication {
http://localhost:8848/nacos/#/login
输入这个网址之后,本机才能打开naocs注册中心地址。否则不行。
6)、配置网关
- id: gulimall_auth_route
uri: lb://gulimall-auth-server
predicates:
- Host=auth.gulimall.com
7)、测试访问登录页面
-
我们暂时将login.html改为index.html页面,这样因为是默认的会被模板引擎解析。但是这个是暂时的。
8)、实现各个页面之间跳转
1、实现登录页面点击”谷粒商城“图标能跳转到首页:
login.html
2、实现首页点击登录和注册能跳转到登录和注册页面:
修改商品服务下的首页index.html
认证服务编写 controller 实现跳转
@Controller
public class LoginController {
@GetMapping("/login.html")
public String loginPage(){
return "login";
}
@GetMapping("/reg.html")
public String regPage(){
return "reg";
}
}
登录页面点击“立即注册”能够跳转到注册页面。
注册页面点击“请登录”能够跳转到登录页面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QykLGWJ1-1673532968095)(null)]
ps:这里可以稍微修改一下 登录页面的宽度,让页面更好看一点。
1.7.2 验证码功能
1)、验证码功能
①把 reg.html页面中这一处修改为 “发送验证码”
发送验证码,有60秒倒计时:
$(function (){
$("#sendCode").click(function () {
//2、倒计时
if ($(this).hasClass("disabled")){
//正在倒计时。
}else{
//1、给指定手机号码发送验证码
timeoutChangeStyle();
}
});
})
var num = 60;
function timeoutChangeStyle(){
$("#sendCode").attr("class","disabled");
if (num == 0){
$("#sendCode").text("发送验证码");
num = 60;
$("#sendCode").attr("class","");
}else{
var str = num +"s 后再次发送";
$("#sendCode").text(str);
//每隔1s调用timeoutChangeStyle()
setTimeout("timeoutChangeStyle()",1000);
}
num --;
}
效果:
②修改后台代码
如果编写一个接口仅仅是为了跳转页面,没有数据的处理,如果这样的跳转接口多了则可以使用SpringMVC的view Controller(视图控制器)将请求与页面进行绑定.
新建 GulimallWebConfig
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
/**
* 视图映射
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/**
* * @GetMapping("/login.html")
* * public String loginPage(){
* *
* * return "login";
* * }
* * @param registry
*/
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
ps:idea快捷键:实现接口方法
alt + shift + p
以前的 LoginController 里面的 方法就可以注释掉了。(这个地方编写的代码仅仅是帮助我们实现跳转页面的)
@Controller
public class LoginController {
/**
* 发送一个请求直接跳转到一个页面。
* springMVC viewcontroller:将请求和页面映射过来。
*/
// @GetMapping("/login.html")
// public String loginPage(){
//
// return "login";
// }
//
// @GetMapping("/reg.html")
// public String regPage(){
//
// return "reg";
// }
}
- 为了我们内存问题,我们可以将认证微服务的内存修改为只占用100m
2)、整合验证码
- 我们可以去阿里云云市场中购买三网短信接口,使用这个来进行完成我们的短信验证码功能。
- 试试调试功能
- 请求示例
public static void main(String[] args) {
String host = "https://dfsns.market.alicloudapi.com";
String path = "/data/send_sms";
String method = "POST";
String appcode = "你自己的AppCode";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
//根据API的要求,定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("content", "code:1234");
bodys.put("phone_number", "156*****140");
bodys.put("template_id", "TPL_0000");
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
ps:当我们在页面上点击“发送验证码”,我们不能通过js代码带上我们的APPCODE ,这样就直接将APPCODE 暴露给别人了,然后别人使用它发送大量短信(让短信服务崩溃),这样就有危机了。我们通过后台来发送验证码,这样比较保险。
- 在第三方微服务下进行简单的测试
- 直接将上面的请求示例的代码放到test中,手机号写自己的,测试看看。
- 我们发现需要引入依赖,所以我们
package com.atguigu.gulimall.thirdparty.utils;
这个包下面将请求示例中给我们提供的地址中的HttpUtils这个java代码复制到utils包中。 - 测试,发现发送成功
①短信远程服务准备
- 我们可以进行自定义,不要短信验证码写死。将请求实例中的代码抽取为一个组件 — SmsComponent
package com.atguigu.gulimall.thirdparty.component;
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {
private String host;
private String path;
private String appcode;
public void sendSmsCode(String phone, String content) {
String method = "POST";
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "APPCODE " + appcode);
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("content", "code: " +content);
bodys.put("phone_number", phone);
bodys.put("template_id", "TPL_0000");
try {
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 在pom.xml文件中将下面的这个依赖引入,这样我们在application.yml文件中编写的时候就有提示了
- application.yml文件中编写我们自定义的。
- 在test中进行测试
@Test
public void testSms(){
smsComponent.sendSmsCode("15642848274","8639");
}
随便写一个,发现测试通过。
- 编写controller,提供给别的服务进行调用。
@RestController
@RequestMapping("/sms")
public class SmsSendController {
@Autowired
SmsComponent smsComponent;
/**
* 提供给别的服务进行调用
*
* @return
*/
@GetMapping("/sendcode")
public R sendCode(@RequestParam("phone") String phone,@RequestParam("content") String content){
smsComponent.sendSmsCode(phone,content);
return R.ok();
}
}
②认证服务远程调用短信
- 在认证微服务中编写一个feign接口(相应的主启动类上要加入
@EnableFeignClients
这个注解)
package com.atguigu.gulimall.auth.feign;
@FeignClient("gulimall-third-party")
public interface ThirdPartFeignService {
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("content") String content);
}
③ 验证码防刷
-
我们将发送的验证码要按照一定的规则存储到redis中,到时候认证的时候需要根据用户输入的和实际存储的验证码进行对比。而且还要防止其他人利用网页刷新,将原来60s重新设置60s,导致短信服务崩溃。
-
redis保存验证码
注意引入:redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
- 设置redis中保存的前缀
package com.atguigu.common.constant; public class AuthServerConstant { public static final String SMS_CODE_CACHE_PREFIX = "sms:code:"; }
- 如果没有超过60s,设置错误代码
-
修改LoginController
-
@Controller public class LoginController { @Autowired ThirdPartFeignService thirdPartFeignService; @Autowired StringRedisTemplate redisTemplate; /** * 发送一个请求直接跳转到一个页面 * SpringMVC viewcontroller;将请求和页面映射过来 * @return */ @ResponseBody //返回json数据 @GetMapping("/sms/sendcode") public R sendCode(@RequestParam("phone")String phone){ //TODO //1.接口防刷 //相同手机号,即使再次刷新页面,但是手机号是相同的,你再次发送也不会生效 String redisContent = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone); if(!StringUtils.isEmpty(redisContent)){ long l = Long.parseLong(redisContent.split("_")[1]); if(System.currentTimeMillis() - l < 60000){ //系统当前时间减去保存redis的时间间隔小于60s return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(), BizCodeEnume.SMS_CODE_EXCEPTION.getMsg()); } } //2.验证码的再次校验。redis.存key-phone,value-code sms:code:17512080612 -> 45678 String content = UUID.randomUUID().toString().substring(0, 5); String substring = content + "_"+System.currentTimeMillis(); //redis缓存验证码,防止同一个phone在60秒内再次发送验证码 redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,substring,10, TimeUnit.MILLISECONDS); thirdPartFeignService.sendCode(phone,content); return R.ok(); } @PostMapping("/regist") public String regist(@Valid UserRegistVo vo, BindingResult result, Model model){ if(result.hasErrors()){ Map<String, String> errors = result.getFieldErrors().stream().collect((Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage))); model.addAttribute("errors",errors); //校验出错,转发到注册页 return "forward:/reg.html"; } //真正注册,调用远程服务进行注册 //注册成功回到首页,回到登录页 return "redirect:/login.html"; } }
-
为页面设置的输入验证码功能
-
- 回调函数
1.7.3 一步一坑的注册页环境
1)、编写 vo封装注册页内容
package com.atguigu.gulimall.auth.vo;
@Data
public class UserRegistVo {
@NotEmpty(message = "用户名必须提交")
@Length(min = 6,max = 18,message = "用户名必须是6-18位字符")
private String userName;
@NotEmpty(message = "密码必须填写")
@Length(min = 6,max = 18,message = "密码必须是6-18位字符")
private String password;
@NotEmpty(message = "手机号必须填写")
@Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机号格式不正确")
private String phone;
@NotEmpty(message = "验证码必须填写")
private String content;
}
后端使用jsr303校验。
JSR303校验的结果,被封装到 BindingResult ,再结合 BindingResult.getFieldErrors() 方法获取错误信息, 有错误就重定向至注册页面。
2)、编写 controller接口
使用@Valid注解开启数据校验功能,将校验后的结果封装到BindingResult中。 LoginController
package com.atguigu.gulimall.auth.controller;
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result) {
if (result.hasErrors()) {
//校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
//注册成功回到首页,回到登录页
return "redirect:/login.html";
}
3)、编写注册页面
为每个input框设置name属性,值需要与Vo的属性名一一对应
点击注册按钮没有发送请求,说明:为注册按钮绑定了单击事件,禁止了默认行为。将绑定的单击事件注释掉
4)、为Model绑定校验错误信息
使用方法引用的方式。
5)、编写前端页面获取错误信息
<form action="/regist" method="post" class="one">0
<div class="register-box">
<label class="username_label">用 户 名
<input name="userName" maxlength="20" type="text" placeholder="您的用户名和登录名">
</label>
<div class="tips" style="color:red" th:text="${errors!=null?(#maps.containsKey(errors, 'userName')?errors.userName:''):''}">
</div>
</div>
<div class="register-box">
<label class="other_label">设 置 密 码
<input name="password" maxlength="20" type="password" placeholder="建议至少使用两种字符组合">
</label>
<div class="tips" style="color:red" th:text="${errors!=null?(#maps.containsKey(errors, 'password')?errors.password:''):''}">
</div>
</div>
<div class="register-box">
<label class="other_label">确 认 密 码
<input maxlength="20" type="password" placeholder="请再次输入密码">
</label>
<div class="tips">
</div>
</div>
<div class="register-box">
<label class="other_label">
<span>中国 0086∨</span>
<input name="phone" class="phone" id="phoneNum" maxlength="20" type="text" placeholder="建议使用常用手机">
</label>
<div class="tips" style="color:red" th:text="${errors!=null?(#maps.containsKey(errors, 'phone')?errors.phone:''):''}">
</div>
</div>
<div class="register-box">
<label class="other_label">验 证 码
<input name="code" maxlength="20" type="text" placeholder="请输入验证码" class="caa">
</label>
<a id="sendCode">发送验证码</a>
<div class="tips" style="color:red" th:text="${errors!=null?(#maps.containsKey(errors, 'code')?errors.code:''):''}">
</div>
6)、测试–踩坑
- 第一个踩坑
- 我的错误却是````Request method ‘GET’ not supported ```````Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method ‘GET’ not supported]```
这个是我百度找到的结果,使用@RequestMapping
- 第二个踩坑
- 刷新页面,会重复提交表单
-
出现问题:分布式下重定向使用session存储数据会出现一些问题(这个后续来解决)
-
完整代码
/**
* //TODO 重定向携带数据,利用session原理。将数据放在session中,只要跳到下一个页面取出这个数据以后,session里面的数据就会删掉
*
*
*
* // TODO 1、分布式下的session问题。
* RedirectAttributes redirectAttributes : 模拟重定向携带数据
* @param vo
* @param result
* @param redirectAttributes
* @return
*/
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes, HttpSession session) {
if (result.hasErrors()) {
/**
* .map(fieldError ->{
* String field = fieldError.getField();
* String defaultMessage = fieldError.getDefaultMessage();
* errors.put(field,defaultMessage);
* return
* })
*
*
*/
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
// model.addAttribute("errors", errors);
redirectAttributes.addFlashAttribute("errors",errors);
// Request method 'POST' not supported
//用户注册 -》/regist[post] ---->转发/reg.html(路径映射默认都是get方法访问的。)
//真正注册。调用远程服务进行注册
//校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
//注册成功回到首页,回到登录页
return "redirect:/login.html";
}
ps: 以上内容是注册用户
在 gulimall-auth-server服务中编写注册的主体逻辑
- 从redis中确认手机验证码是否正确,一致则删除验证码,(令牌机制)
- 会员服务调用成功后,重定向至登录页(防止表单重复提交),否则封装远程服务返回的错误信息返回至注册页面
- 重定向的请求数据,可以利用RedirectAttributes参数转发
- 但是他是利用的session原理,所以后期我们需要解决分布式的session问题
- 重定向取一次后,session数据就消失了,因为使用的是.addFlashAttribute(
- 重定向时,如果不指定host,就直接显示了注册服务的ip,所以我们重定义写http://…
注: RedirectAttributes可以通过session保存信息并在重定向的时候携带过去
1.7.4 异常机制
1)、校验验证码
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes, HttpSession session) {
if (result.hasErrors()) {
/**
* .map(fieldError ->{
* String field = fieldError.getField();
* String defaultMessage = fieldError.getDefaultMessage();
* errors.put(field,defaultMessage);
* return
* })
*/
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
// model.addAttribute("errors", errors);
redirectAttributes.addFlashAttribute("errors",errors);
// Request method 'POST' not supported
//用户注册 -》/regist[post] ---->转发/reg.html(路径映射默认都是get方法访问的。)
//校验出错,重定向到注册页
return "redirect:http://auth.gulimall.com/reg.html";
}
//1、校验验证码
String code = vo.getCode();
String s = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
if (!StringUtils.isEmpty(s)){
if (code.equals(s.split("_")[0])){
//删除验证码;令牌机制
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
//验证通过。//真正注册。调用远程服务进行注册。
}else{
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else{
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
//注册成功回到首页,回到登录页
return "redirect:/login.html";
}
验证短信验证码通过,下面开始去数据库保存。
-
member远程服务 ------- 通过
gulimall-member
会员服务注册逻辑-
通过异常机制判断当前注册会员名和电话号码是否已经注册,如果已经注册,则抛出对应的自定义异常,并在返回时封装对应的错误信息
-
如果没有注册,则封装传递过来的会员信息,并设置默认的会员等级、创建时间
-
2)、会员服务中编写Vo接受数据
package com.atguigu.gulimall.member.vo;
@Data
public class MemberRegistVo {
/**
* 这个地方就不需要校验了,因为只有正确了,才会进行保存
*/
private String userName;
private String password;
private String phone;
}
3)、编写会员服务的用户注册接口
- MemberController
//因为我们注册会提交很多的东西,所以是 post方式提交
@RequestMapping("/regist")
public R regist(@RequestBody MemberRegistVo vo){
memberService.regist(vo);
return R.ok();
}
- MemberServiceImpl
@Override
public void regist(MemberRegistVo vo) {
MemberDao memberDao = this.baseMapper;
MemberEntity entity = new MemberEntity();
//设置默认等级
MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
entity.setLevelId(levelEntity.getId());
//检查用户名和手机号是否唯一。为了让controller能够感知异常,使用异常机制:一直往上抛
checkPhoneUnique(vo.getPhone());
checkUsernameUnique(vo.getUserName());
entity.setMobile(vo.getPhone());
entity.setUsername(vo.getUserName());
//密码要进行加密存储。
memberDao.insert(entity);
}
-
MemberLevelDao.xml -> :查询会员的默认等级
<select id="getDefaultLevel" resultType="com.atguigu.gulimall.member.entity.MemberLevelEntity"> SELECT * FROM `ums_member_level` WHERE default_status = 1 </select>
4)、异常类的编写
- PhoneExistException
package com.atguigu.gulimall.member.exception;
public class PhoneExistException extends RuntimeException{
public PhoneExistException(){
super("手机号存在");
}
}
- UsernameExistException
public class UsernameExistException extends RuntimeException {
public UsernameExistException(){
super("用户名存在");
}
}
- 检查方法编写->MemberServiceI
void checkPhoneUnique(String phone) throws PhoneExistException;
void checkUsernameUnique(String username) throws UsernameExistException;
- MemberServiceImpl
@Override
public void checkPhoneUnique(String phone) throws PhoneExistException{
MemberDao memberDao = this.baseMapper;
Integer mobile = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (mobile > 0){
throw new PhoneExistException();
}
}
@Override
public void checkUsernameUnique(String username) throws UsernameExistException {
MemberDao memberDao = this.baseMapper;
Integer count = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
if (count > 0){
throw new UsernameExistException();
}
}
如果抛出异常,则进行捕获
@Test
public void contextLoads() {
//e10adc3949ba59abbe56e057f20f883e
//抗修改性:彩虹表。 123456 -> xxxx
String s = DigestUtils.md5Hex("123456");
//MD5不能直接进行密码的加密存储:可以被直接暴力破解
// System.out.println(s);
}
1.7.5 MD5&盐值&BCrypt
- 密码的设置,前端传来的密码是明文,存储到数据库中需要进行加密。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4ovczRY-1673533055339)(null)]
@Test
public void contextLoads() {
//e10adc3949ba59abbe56e057f20f883e
//抗修改性:彩虹表。 123456 -> xxxx
String s = DigestUtils.md5Hex("123456");
//MD5不能直接进行密码的加密存储:可以被直接暴力破解
// System.out.println(s);
}
Apache.common下DigestUtils工具类的md5Hex()方法,将MD5加密后的数据转化为16进制
MD5并安全,很多在线网站都可以破解MD5,通过使用彩虹表,暴力破解。
因此,可以通过使用MD5+盐值进行加密
盐值:随机生成的数
方法1是加默认盐值: 1 1 1xxxxxxxx
方法2是加自定义盐值
//盐值加密:随机值 加盐:$1$+8位字符
//$1$qqqqqqqq$AZofg3QwurbxV3KEOzwuI1
//验证: 123456进行盐值(去数据库查)加密
// String s1 = Md5Crypt.md5Crypt("123456".getBytes(), "$1$qqqqqqqq");
// System.out.println(s1);
这种方法需要在数据库添加一个专门来记录注册时系统时间的字段,此外还需额外在数据库中存储盐值。
可以使用Spring家的BCryptPasswordEncoder,它的encode()方法使用的就是MD5+盐值进行加密,盐值是随机产生的,通过matches()方法进行密码是否一致。
//使用 spring家的
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//$2a$10$R/VBymW1UA.VzeBedBcspe.iypJIyQiWkka/Ds5SDG7h6r0wQsF6G
String encode = passwordEncoder.encode("123456");
boolean matches = passwordEncoder.matches("123456", "$2a$10$R/VBymW1UA.VzeBedBcspe.iypJIyQiWkka/Ds5SDG7h6r0wQsF6G");
// $2a$10$jLJp4edbLb9pnCg9quGk0u2uvsm4E/6TD5zi1wqHY4jz/f1ydS.LS=>true
System.out.println(encode+"=>"+matches);
用户注册业务中的密码加密
//密码要进行加密存储。
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode(vo.getPassword());
entity.setPassword(encode);
1.7.6 注册完成
1)、在common的exception包下,编写异常枚举
USER_EXIST_EXCEPTION(15001,"用户存在"),
PHONE_EXIST_EXCEPTION(15002,"手机号存在"),
2)、进行异常的捕获
- MemberController
package com.atguigu.gulimall.member.controller;
// @PostMapping("/regist")
@RequestMapping("/regist")
public R regist(@RequestBody MemberRegistVo vo ){
try {
memberService.regist(vo);
} catch (PhoneExistException e) {
return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(), BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());
}catch (UsernameExistException e){
R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(), BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());
}
return R.ok();
}
3)、远程服务接口编写
- 在 auth 服务下新建 MemberFeignService
@FeignClient("gulimall-member")
public interface MemberFeignService {
@PostMapping("/member/member/regist")
public R regist(@RequestBody UserRegistVo vo);
}
4)、 远程服务调用
package com.atguigu.gulimall.auth.controller;
/**
* //todo 重定向携带数据,利用的是session原理,将数据放在session中,只要跳到下一个页面,取出这个数据以后,session里面的数据就会删掉
* RedirectAttributes redirectAttributes :模拟重定向携带数据
*
* @param vo
* @param result
* @param redirectAttributes
* @return
*/
// @PostMapping("/regist")
@RequestMapping("/regist") //这个地方和老师不一样,写成postmapping的话,
// 会报 Request method 'GET' not supported,百度下改为requestmapping才行
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes,
HttpSession session) {
if (result.hasErrors()) {
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
// model.addAttribute("errors",errors);
redirectAttributes.addFlashAttribute("errors", errors);
// Request method 'GET' not supported
// 用户注册 -》 /regist[约定是post表单提交] - 》转发/reg.html(路径映射默认都是get方式访问的)
//校验出错,转发到注册页
return "redirect:http://auth.gulimall.com/reg.html";
// return "forward:/reg.html";
}
//1.校验验证码
String content = vo.getContent();
String s = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
if (!StringUtils.isEmpty(s)) {
if (content.equals(s.split("_")[0])) {
//删除验证码 ;令牌机制
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vo.getPhone());
//验证通过 //真正注册,调用远程服务进行注册
R r = memberFeignService.regist(vo);
if(r.getCode() == 0){
//成功
return "redirect:http://auth.gulimall.com/login.html";
}else{
HashMap<String,String> errors = new HashMap<>();
errors.put("msg",r.getData("msg",new TypeReference<String>(){
}));
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
Map<String, String> errors = new HashMap<>();
errors.put("content", "验证码错误");
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
Map<String, String> errors = new HashMap<>();
errors