导航:
Java笔记汇总:
目录
1.7 登录注册页面的路径加"/static/login"或"/static/reg"
1.9 新建配置类,映射请求和页面,实现WebMvcConfigurer接口
3.1 feign远程调用,“认证服务”调用“第三方服务”发送短信
3.2 登录controller发送验证码(简单实现,不考虑接口防刷)
3.5.3 LoginController发送验证码(考虑接口防刷)
1. 环境搭建
1.1 新建模块gulimall-auth-server
1.2 pom引入依赖
引入common模块,排除gulimall-common包的mybatis-plus
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-auth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-auth-server</name>
<description>认证中心(社交登录、OAuth2.0、单点登录)</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.zhourui.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<!--不需要数据库操作移除mybatis-plus,防止报错-->
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.3 引导类开启远程调用
//可以远程调用,使服务能够被nacos发现
@EnableFeignClients
@EnableDiscoveryClient
1.4 yml配置和关闭thymeleaf缓存
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-auth-server
server:
port: 20000
feign:
thymeleaf:
cache: false
1.5 hosts文件新增域名映射
192.168.157.128 auth.gulimall.com
1.6 登录、注册页面动静分离
静态文件移到Nginx:
动态文件reg.html和login.html放到项目templates文件夹下:
1.7 登录注册页面的路径加"/static/login"或"/static/reg"
对应nginx上的静态地址就行
1.8 网关配置认证路由
gulimall-gateway/src/main/resources/application.yml
#认证服务
- id: gulimall_auth_host
uri: lb://gulimall-auth-server
predicates:
- Host=auth.gulimall.com
1.9 新建配置类,映射请求和页面,实现WebMvcConfigurer接口
gulimall-auth-server/src/main/java/site/xx/gulimall/auth/config/GulimallWebConfig.java
package site.xxx.gulimall.auth.config;
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
/**
* 视图映射:发送一个请求,直接跳转到一个页面
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//将请求"/login.html"映射到"login"页面
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
这样就不需要写controller处理请求页面映射了:
@Controller public class LoginController { @GetMapping(value = "/login.html") public String loginPage(HttpSession session) { return "login"; } @GetMapping(value = "/reg.html") public String regPage(HttpSession session) { return "reg"; } }
1.10 修改前端
修改,使登录、注册、其他页面之间可以正常跳转:
编写验证码的单击事件:
1.11 测试
http://auth.gulimall.com/login.html
http://auth.gulimall.com/reg.html
2. 整合短信验证码
2.1 购买阿里云短信服务
购买地址:
【三网合一短信接口-支持协号转网】短信接口 短信验证码发送接口(免费试用)【最新版】_数据API_数据应用_电商-云市场-阿里云
2.2 api接口
2.2.1 请求响应
调用地址:http(s)://fesms.market.alicloudapi.com/sms/
请求方式:GET
返回类型:JSON
请求参数:
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
code | STRING | 必选 | 要发送的验证码 |
phone | STRING | 必选 | 接收人的手机号 |
sign | STRING | 可选 | 签名编号【联系客服人员申请,测试请用1】 |
skin | STRING | 必选 | 模板编号【联系旺旺客服申请,测试请用1~21】 |
响应:
{ "Message": "发送成功", "RequestId": "B26BB173-E569-46BD-B0A7-7EAF581C06D7", "BizId": "267610413312539060^0", "Code": "OK" }
2.2.2 postMan测试
发送错误手机号:
正常发送:
2.2.3 代码测试,填入自己的appcode
gulimall-third-party/src/test/java/site/zhourui/gulimall/thirdparty/SMSTest.java
package site.xx.gulimall.thirdparty;
public class SMSTest {
public static void main(String[] args) {
String host = "https://fesms.market.alicloudapi.com";// 【1】请求地址 支持http 和 https 及 WEBSOCKET
String path = "/sms/";// 【2】后缀
String appcode = "自己的appcode "; // 【3】开通服务后 买家中心-查看AppCode
String code = "123456"; // 【4】请求参数,详见文档描述
String phone = "17748781xxx"; // 【4】请求参数,详见文档描述
String sign = "1"; // 【4】请求参数,详见文档描述
String skin = "1"; // 【4】请求参数,详见文档描述
String urlSend = host + path + "?code=" + code + "&phone=" + phone + "&sign=" + sign + "&skin=" + skin ; // 【5】拼接请求链接
try {
URL url = new URL(urlSend);
HttpURLConnection httpURLCon = (HttpURLConnection) url.openConnection();
httpURLCon.setRequestProperty("Authorization", "APPCODE " + appcode);// 格式Authorization:APPCODE
// (中间是英文空格)
int httpCode = httpURLCon.getResponseCode();
if (httpCode == 200) {
String json = read(httpURLCon.getInputStream());
System.out.println("正常请求计费(其他均不计费)");
System.out.println("获取返回的json:");
System.out.print(json);
} else {
Map<String, List<String>> map = httpURLCon.getHeaderFields();
String error = map.get("X-Ca-Error-Message").get(0);
if (httpCode == 400 && error.equals("Invalid AppCode `not exists`")) {
System.out.println("AppCode错误 ");
} else if (httpCode == 400 && error.equals("Invalid Url")) {
System.out.println("请求的 Method、Path 或者环境错误");
} else if (httpCode == 400 && error.equals("Invalid Param Location")) {
System.out.println("参数错误");
} else if (httpCode == 403 && error.equals("Unauthorized")) {
System.out.println("服务未被授权(或URL和Path不正确)");
} else if (httpCode == 403 && error.equals("Quota Exhausted")) {
System.out.println("套餐包次数用完 ");
} else {
System.out.println("参数名错误 或 其他错误");
System.out.println(error);
}
}
} catch (MalformedURLException e) {
System.out.println("URL格式错误");
} catch (UnknownHostException e) {
System.out.println("URL地址错误");
} catch (Exception e) {
// 打开注释查看详细报错异常信息
// e.printStackTrace();
}
}
/*
* 读取返回结果
*/
private static String read(InputStream is) throws IOException {
StringBuffer sb = new StringBuffer();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = null;
while ((line = br.readLine()) != null) {
line = new String(line.getBytes(), "utf-8");
sb.append(line);
}
br.close();
return sb.toString();
}
}
2.3 第三方模块整合短信服务
2.3.0 导入HttpUtils
从下面地址下载并导入到公共模块:
package com.xunqi.common.utils;
https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
2.3.1 新增短信服务组件SmsComponent
package site.xx.gulimall.thirdparty.component;
//这个注解设置yml配置文件前缀,这样配置后yml数据就会自动注入到 Bean 中,不用再@Value
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {
private String host;
private String path;
private String skin;
private String sign;
private String appcode;
public void sendCode(String phone,String code) {
String method = "GET";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("code", code);
querys.put("phone", phone);
querys.put("skin", skin);
querys.put("sign", sign);
//JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.zip
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 或者直接下载:
* http://code.fegine.com/HttpUtils.zip
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
* 相关jar包(非pom)直接下载:
* http://code.fegine.com/aliyun-jar.zip
*/
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
//System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。
//状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误
//获取response的body
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3.2 yml添加自定义的短信属性配置
配置提示依赖,不加不影响运行。
gulimall-third-party/pom.xml
<!--自定义配置的提示 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
gulimall-third-party/src/main/resources/application.yml
建议配置到nacos
sms:
host: http://fesms.market.alicloudapi.com
path: /sms/
skin: 1
sign: 1
appcode: 004c4072d4ed40b489d77b987ad3404d
2.3.3 新增发验证码Controller
SmsSendController,提供给别的服务进行调用
package site.xx.gulimall.thirdparty.controller;
@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {
@Autowired
private SmsComponent smsComponent;
/**
* 提供给别的服务进行调用
* @param phone
* @param code
* @return
*/
@GetMapping(value = "/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
//发送验证码
smsComponent.sendCode(phone,code);
return R.ok();
}
}
2.3.4 测试
测试:localhost:30000/sms/sendCode?phone=17748781568&code=8888
成功同时收到短信
3. 认证服务调用短信服务
3.1 feign远程调用,“认证服务”调用“第三方服务”发送短信
认证模块:
package site.xx.gulimall.auth.feign;
@FeignClient("gulimall-third-party")
public interface ThirdPartFeignService {
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);
}
确保启动类注解了@EnableFeignClients和@EnableDiscoveryClient
3.2 登录controller发送验证码(简单实现,不考虑接口防刷)
验证码用随机UUID子串。
package site.xxx.gulimall.auth.controller;
@Controller
public class LoginController {
@Autowired
ThirdPartFeignService thirdPartFeignService;
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone)
{
String code = UUID.randomUUID().toString().substring(0, 5);
thirdPartFeignService.sendCode(phone,code);
return R.ok();
}
}
3.3 前端调用接口发送验证码
发送验证码短信成功
3.5 使用redis实现接口防刷
3.5.1 业务流程
接口写在前端js代码里,仍然可以被其他人拿来盗刷
由于发送验证码的接口暴露,为了防止恶意攻击,我们不能随意让接口被调用。
- 在redis中以
phone-code
将电话号码和验证码进行存储并将当前时间与code一起存储
- 如果调用时以当前
phone
取出的v不为空且当前时间在存储时间的60s以内,说明60s内该号码已经调用过,返回错误信息- 60s以后再次调用,需要删除之前存储的
phone-code
- code存在一个过期时间,我们设置为10min,10min内验证该验证码有效
接口防刷过程:
1)先查询redis,是否超过60s,否则不允许发送短信
2)存入redis,key为“sms:code:手机号”,value为“六位数字+当前系统时间”,通过比较redis存的时间和现在时间是否超过一分钟,防止一分钟内不断刷验证码,构造参数存入过期时间10min,并且存入当前系统时间
3.5.1 引入redis依赖
认证模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.5.2 yml配置redis地址信息
认证模块
redis:
host: 192.168.157.128
port: 6379
3.5.3 LoginController发送验证码(考虑接口防刷)
该常量用于redis短信验证码前缀
认证模块相关的常量类:
package site.xx.common.constant;
public class AuthServerConstant {
//短信验证码的缓存前缀
public static final String SMS_CODE_CACHE_PREFIX = "sms:code:";
}
common模块的错误码枚举类:
controller
package site.xx.gulimall.auth.controller;
@Controller
public class LoginController {
@Autowired
private ThirdPartFeignService thirdPartFeignService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
//1、接口防刷
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
if (!StringUtils.isEmpty(redisCode)) {
//活动存入redis的时间,用当前时间减去存入redis的时间,通过比较redis存的时间和现在时间是否超过一分钟,防止一分钟内不断刷验证码
long currentTime = Long.parseLong(redisCode.split("_")[1]);
if (System.currentTimeMillis() - currentTime < 60000) {
//60s内不能再发
return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(),BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());
}
}
//2、验证码的再次效验 redis.存key-phone,value-code
// String code = UUID.randomUUID().toString().substring(0, 5);
// String redisValue = code+"_"+System.currentTimeMillis();
int code = (int) ((Math.random() * 9 + 1) * 100000);// 验证码只可以是数字
String codeNum = String.valueOf(code);
//redis存的值为六位数字+当前系统时间,防止一分钟内不断刷验证码
String redisStorage = codeNum + "_" + System.currentTimeMillis();
//存入redis并指定过期时间10min,十分钟内验证码有效
stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone,
redisStorage,10, TimeUnit.MINUTES);
thirdPartFeignService.sendCode(phone, codeNum);
return R.ok();
}
}
在60秒之内再次发送该号码的验证码
4. 注册页面相关功能实现
4.1 准备
4.1.1 抽取注册模型类
package site.xx.gulimall.auth.vo;
@Data
public class UserRegisterVo {
@NotEmpty(message = "用户名必须提交")
@Length(min = 6, max = 19, 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 code;
}
4.1.2 业务流程
注册的主体逻辑:
- 若JSR303校验未通过,则通过
BindingResult
封装错误信息,并重定向至注册页面 - 若通过JSR303校验,则需要从
redis
中取值判断验证码是否正确,正确的话通过会员模块的feign注册(检查验证码、用户名、手机号 唯一),校验通过后,调用会员服务添加会员信息 - 会员服务调用成功则重定向至登录页,否则封装远程服务返回的错误信息返回至注册页面
4.1.3 controller参数注解@Valid
注:
RedirectAttributes
可以通过session保存信息并在重定向的时候携带过去。这里用于存错误消息
LoginController注册方法校验:
//BindingResult参数获取校验结果
//RedirectAttributes可以通过session保存信息并在重定向的时候携带过去。这里用于存错误消息
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result,
RedirectAttributes attributes) {
//如果有错误回到注册页面
if (result.hasErrors()) {
Map<String, String> errors = result.getFieldErrors().stream().collect(
Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
// flash是一闪而过,此数据只取一次
attributes.addFlashAttribute("errors",errors);
//效验出错,重定向到注册页面。不用转发是为了防止刷新时重复提交表单
// 不用return reg是因为本来就在注册页面点击发送了这个注册请求,要重定向清空表单
return "redirect:http://auth.gulimall.com/reg.html";
}
//1、效验验证码
return null
}
4.2 【会员模块】存储会员信息
4.2.1 抽取模型类,接收用户信息
package site.xx.gulimall.member.vo;
/**
* 会员注册Vo
*/
@Data
public class MemberUserRegisterVo {
private String userName;
private String password;
private String phone;
}
4.2.2 自定义“用户名与手机号重复”的异常类
package site.xx.gulimall.member.exception;
public class PhoneException extends RuntimeException {
public PhoneException() {
super("存在相同的手机号");
}
}
package site.xx.gulimall.member.exception;
public class UsernameException extends RuntimeException {
public UsernameException() {
super("存在相同的用户名");
}
}
4.2.3 注册controller实现
业务流程:
- 通过异常机制判断当前注册会员名和电话号码是否已经注册
- 如果已经注册,则抛出对应的自定义异常,并在返回时封装对应的错误信息
- 如果没有注册,则封装传递过来的会员信息,并设置默认的会员等级、创建时间
member/controller/MemberController.java
@PostMapping(value = "/register")
public R register(@RequestBody MemberUserRegisterVo vo) {
try {
memberService.register(vo);
//异常机制:通过捕获对应的自定义异常判断出现何种错误并封装错误信息
} catch (PhoneException e) {
return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());
} catch (UsernameException e) {
return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(),BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());
}
return R.ok();
}
4.2.4 使用BCrypt实现密码加密解密
- Message Digest algorithm 5,信息摘要算法
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。可以用md5值对比两个文件是否相同。
- 不可逆,但因为抗修改性和抗碰撞,所以可以暴力破解,不能直接用md5加密密码。
- 通过生成随机数与MD5生成字符串进行组合
- 数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5即可
BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
和其他加密方式相比,BCryptPasswordEncoder有着它自己的优势所在,首先加密的hash值每次都不同,就像md5的盐值加密一样,只不过盐值加密用到了随机数,前者用到的是其内置的算法规则,毕竟随机数没有设合适的话还是有一定几率被攻破的。其次BCryptPasswordEncoder的生成加密存储串也有60位之多。最重要的一点是,md5的加密不是spring security所推崇的加密方式了,所以我们还是要多了解点新的加密方式。
BCryptPasswordEncoder每次加密相同的值,都会得到不同的密文
BCryptPasswordEncoder加密(encode)解密(matches)
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//加密
String encode = bCryptPasswordEncoder.encode("123456");
//测试匹配,第一个参数是原始密码,第二个参数是编码后的密码。尽管每次加密后的值都不同,但matches能够匹配
boolean matches = bCryptPasswordEncoder.matches("123456",encode); //true
4.2.5 注册业务实现
member/service/MemberService.java
/**
* 用户注册
* @param vo
*/
void register(MemberUserRegisterVo vo);
/**
* 判断邮箱是否重复
* @param phone
* @return
*/
void checkPhoneUnique(String phone) throws PhoneException;
/**
* 判断用户名是否重复
* @param userName
* @return
*/
void checkUserNameUnique(String userName) throws UsernameException;
member/service/impl/MemberServiceImpl.java
@Override
public void register(MemberUserRegisterVo vo) {
MemberEntity memberEntity = new MemberEntity();
//设置默认等级【普通会员】
MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
memberEntity.setLevelId(levelEntity.getId());
//设置其它的默认信息
//检查用户名和手机号是否唯一。感知异常,异常机制
checkPhoneUnique(vo.getPhone());
checkUserNameUnique(vo.getUserName());
memberEntity.setNickname(vo.getUserName());
memberEntity.setUsername(vo.getUserName());
//密码进行MD5加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode(vo.getPassword());
memberEntity.setPassword(encode);
memberEntity.setMobile(vo.getPhone());
memberEntity.setGender(0);
memberEntity.setCreateTime(new Date());
//保存数据
baseMapper.insert(memberEntity);
}
//检查手机号是否已存在
@Override
public void checkPhoneUnique(String phone) throws PhoneException {
Integer phoneCount = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (phoneCount > 0) {
throw new PhoneException();
}
}
//检查用户名是否已存在
@Override
public void checkUserNameUnique(String userName) throws UsernameException {
Integer usernameCount = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
if (usernameCount > 0) {
throw new UsernameException();
}
}
4.3 【认证模块】注册业务实现
业务流程
- 若JSR303校验未通过,则通过
BindingResult
封装错误信息,并重定向至注册页面 - 若通过JSR303校验,则需要从
redis
中取值判断验证码是否正确,正确的话通过会员模块的feign注册(检查验证码、用户名、手机号 唯一),校验通过后,调用会员服务添加会员信息 - 会员服务调用成功则重定向至登录页,否则封装远程服务返回的错误信息返回至注册页面
代码实现
feign接口
package site.zhourui.gulimall.auth.feign; @FeignClient("gulimall-member") public interface MemberFeignService { @PostMapping(value = "/member/member/register") R register(@RequestBody UserRegisterVo vo); }
确保启动类注解了“发现客户端”和“开启feign ”
auth/controller/LoginController.java
/**
*
* TODO: 重定向携带数据:利用session原理,将数据放在session中。
* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉
* TODO:分布下session问题
* RedirectAttributes:重定向也可以保留数据,不会丢失。这里用于存错误消息
* 用户注册
* @return
*/
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result, RedirectAttributes attributes) {
//如果有错误回到注册页面
if (result.hasErrors()) {
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
attributes.addFlashAttribute("errors", errors);
//效验出错回到注册页面
return "redirect:http://auth.gulimall.com/reg.html";
// 1、return "reg"; 请求转发【使用Model共享数据】【异常:,405 POST not support】
// 2、"redirect:http:/reg.html"重定向【使用RedirectAttributes共享数据】【bug:会以ip+port来重定向】
// 3、redirect:http://auth.gulimall.com/reg.html重定向【使用RedirectAttributes共享数据】
}
//1、效验验证码
String code = vos.getCode();
//获取存入Redis里的验证码
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
if (!StringUtils.isEmpty(redisCode)) {
// 判断验证码是否正确【有BUG,如果字符串存储有问题,没有解析出code,数据为空,导致验证码永远错误】
if (code.equals(redisCode.split("_")[0])) {
//删除验证码(不可重复使用);令牌机制
stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
//验证码通过,真正注册,调用远程服务进行注册【会员服务】
R register = memberFeignService.register(vos);
if (register.getCode() == 0) {
//成功,重定向到登录页面
return "redirect:http://auth.gulimall.com/login.html";
} else {
//失败
Map<String, String> errors = new HashMap<>();
errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
//验证码错误
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
// redis中验证码过期
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码过期");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}