谷粒商城项目笔记之高级篇(二)

目录

1.7 认证服务

1.7.1 环境搭建

1)、创建认证服务微服务

image-20221207101443720

image-20221208084930446

我们添加的依赖如上图所示。主要是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)、添加相应的域名

1669992683902

4)、动静分离
  • 我们需要将登陆页面(login.html)和认证页面(reg.html)都放到新创建的认证服务下的templates下。

image-20221208085506098

  • 同理在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页面,这样因为是默认的会被模板引擎解析。但是这个是暂时的。

1669994363346

8)、实现各个页面之间跳转

1、实现登录页面点击”谷粒商城“图标能跳转到首页:

1669995151526

login.html

1669994603488

2、实现首页点击登录和注册能跳转到登录和注册页面:

1669995167254

修改商品服务下的首页index.html

1669995275064

认证服务编写 controller 实现跳转

@Controller
public class LoginController {
   


    @GetMapping("/login.html")
    public String loginPage(){
   

        return "login";
    }

    @GetMapping("/reg.html")
    public String regPage(){
   

        return "reg";
    }

}

1669995498525

1669995518913

登录页面点击“立即注册”能够跳转到注册页面。

1669997262187

1669997287655

注册页面点击“请登录”能够跳转到登录页面。

1669997360037

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QykLGWJ1-1673532968095)(null)]

ps:这里可以稍微修改一下 登录页面的宽度,让页面更好看一点。

1670050548930

1.7.2 验证码功能

1)、验证码功能

①把 reg.html页面中这一处修改为 “发送验证码”

1670051075825

1670051102425

发送验证码,有60秒倒计时:

1670053604720

 $(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 --;
			}

效果:

image-20221208101355024

②修改后台代码

如果编写一个接口仅仅是为了跳转页面,没有数据的处理,如果这样的跳转接口多了则可以使用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

image-20221207151958063

2)、整合验证码
  • 我们可以去阿里云云市场中购买三网短信接口,使用这个来进行完成我们的短信验证码功能。

image-20221208102804165

  • 试试调试功能

image-20221208102959531

  • 请求示例
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文件中编写的时候就有提示了

image-20221207161433805

  • application.yml文件中编写我们自定义的。

image-20221208104956838

  • 在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:"; 
}

image-20221209154851934

  • 如果没有超过60s,设置错误代码

image-20221209134426471

  • 修改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";
          }
      
      
      }
      
    • 为页面设置的输入验证码功能

image-20221207201940453

image-20221207202133227

image-20221207202451596

  • 回调函数

image-20221209134559335

image-20221208201050661

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的属性名一一对应

image-20221209160650647

点击注册按钮没有发送请求,说明:为注册按钮绑定了单击事件,禁止了默认行为。将绑定的单击事件注释掉

image-20221209160747606

4)、为Model绑定校验错误信息

image-20221209160951368

使用方法引用的方式。

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)、测试–踩坑
  1. 第一个踩坑

image-20221209161542510

  • 我的错误却是````Request method ‘GET’ not supported ```````Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method ‘GET’ not supported]```

这个是我百度找到的结果,使用@RequestMapping

image-20221208203633375

image-20221208203735433

  1. 第二个踩坑
    • 刷新页面,会重复提交表单

image-20221209161902450

image-20221209161919082

image-20221209161936543

  1. 出现问题:分布式下重定向使用session存储数据会出现一些问题(这个后续来解决)

  2. 完整代码

/**
     *   //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();
        }
    }

image-20221209162924382

如果抛出异常,则进行捕获

 @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,通过使用彩虹表,暴力破解。

image-20221209163154663

因此,可以通过使用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);

用户注册业务中的密码加密

image-20221209163323417

//密码要进行加密存储。
        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)、 远程服务调用

image-20221209163557340

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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
gulimall_pms 商品 drop table if exists pms_attr; drop table if exists pms_attr_attrgroup_relation; drop table if exists pms_attr_group; drop table if exists pms_brand; drop table if exists pms_category; drop table if exists pms_category_brand_relation; drop table if exists pms_comment_replay; drop table if exists pms_product_attr_value; drop table if exists pms_sku_images; drop table if exists pms_sku_info; drop table if exists pms_sku_sale_attr_value; drop table if exists pms_spu_comment; drop table if exists pms_spu_images; drop table if exists pms_spu_info; drop table if exists pms_spu_info_desc; /*==============================================================*/ /* Table: pms_attr */ /*==============================================================*/ create table pms_attr ( attr_id bigint not null auto_increment comment '属性id', attr_name char(30) comment '属性名', search_type tinyint comment '是否需要检索[0-不需要,1-需要]', icon varchar(255) comment '属性图标', value_select char(255) comment '可选值列表[用逗号分隔]', attr_type tinyint comment '属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]', enable bigint comment '启用状态[0 - 禁用,1 - 启用]', catelog_id bigint comment '所属分类', show_desc tinyint comment '快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整', primary key (attr_id) ); alter table pms_attr comment '商品属性'; /*==============================================================*/ /* Table: pms_attr_attrgroup_relation */ /*==============================================================*/ create table pms_attr_attrgroup_relation ( id bigint not null auto_increment comment 'id', attr_id bigint comment '属性id', attr_group_id bigint comment '属性分组id', attr_sort int comment '属性组内排序', primary key (id) ); alter table pms_attr_attrgroup_relation comment '属性&

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值