Day04_谷粒商城(谷粒商城高级篇三)摘要

文章目录

  • 笔记链接:
  • P211 认证服务环境的搭建
  • P212 注册页面—验证码倒计时
  • P213 注册页面—整合短信服务1
  • P214 注册页面—整合短信服务2
  • P215 注册页面—注册功能1
  • P216 注册页面—注册功能2
  • P217 注册页面—注册功能3
  • P218 注册页面—注册功能4
  • P219 登陆页面—用户名密码登录
  • P220 登陆页面—完成微博登录1
  • P221 登陆页面—完成微博登录2
  • P222 登陆页面—完成微博登录3
  • P223 登陆页面—完成微博登录4
  • P224 登陆页面—完成微博登录5
  • P225 SpringSession—session不共享、不跨域问题
  • P226 SpringSession—session问题的解决
  • P227 SpringSession—SpringSession整合redis1
  • P228 SpringSession—SpringSession整合redis2
  • P229 SpringSession—SpringSession整合redis3
  • P230 页面调整
  • P231 单点登录1
  • P232 单点登录2
  • P233 单点登录3
  • P234 单点登录4
  • P235 单点登录5
          • 1.为什么要单点登录?
          • 2.单点登录的原理?
          • 3.单点登录代码的实现
  • P236 购物车—环境的搭建
  • P237 购物车—数据模型的分析1
  • P238 购物车—数据模型的分析2
  • P239 购物车—拦截器鉴别用户
  • P240 页面调整
  • P241 添加商品到购物车1
  • P242 添加商品到购物车2
  • P243 添加商品到购物车3
  • P244 获取购物车
  • P245 选中购物车项
  • P246 修改购物项数量
  • P247 删除购物项

笔记链接:

笔记链接:谷粒商城-个人笔记(高级篇三)

P211 认证服务环境的搭建

Day04_谷粒商城(谷粒商城高级篇三)摘要_第1张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第2张图片

P212 注册页面—验证码倒计时

Day04_谷粒商城(谷粒商城高级篇三)摘要_第3张图片

Day04_谷粒商城(谷粒商城高级篇三)摘要_第4张图片

P213 注册页面—整合短信服务1

总说:

p213和p214的这些操作是完成用户在注册页面的发送验证码的操做:前台发送/sms/sendcode的请求给后台的gulimall-auth-server,然后gulimall-auth-server会先验证一下验证码是否在60秒前发送过,如果没有就使用OpenFeign远程调用gulimall-thrid-party的sendCode方法完成第三方服务的发送验证码功能

Day04_谷粒商城(谷粒商城高级篇三)摘要_第5张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第6张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第7张图片
在这里插入图片描述
在这里插入图片描述

P214 注册页面—整合短信服务2

Day04_谷粒商城(谷粒商城高级篇三)摘要_第8张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第9张图片
在这里插入图片描述

P215 注册页面—注册功能1

总说:

p215和p216和p217和p218的这些操作是完成用户在注册页面的注册功能:我们之前把验证码发给用户了,用户会填好验证码和注册信息后发送给gulimall-auth-server,然后gulimall-auth-server会进行初步校验(校验密码格式、手机号格式 + 验证码校验),校验通过会利用OpenFeign远程调用gulimall-member的regist()方法来进行会员注册,会员注册肯定有失败有成功,对于那些注册失败我们使用异常机制

Day04_谷粒商城(谷粒商城高级篇三)摘要_第10张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第11张图片
在这里插入图片描述
请认真看这一节的注释,看不懂就回去看视频。这一节还是讲了不少有用的东西

P216 注册页面—注册功能2

Day04_谷粒商城(谷粒商城高级篇三)摘要_第12张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第13张图片
在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第14张图片
在这里插入图片描述

P217 注册页面—注册功能3

给用户密码加密的三种方式对比:MD5加密、盐值加密、BCrypt加密

P218 注册页面—注册功能4

本届内容就是完成测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第15张图片

P219 登陆页面—用户名密码登录

总说:

前台发送 /login到后台的gulimall-auth-server模块中,然后gulimall-auth-server会使用OpenFeign远程调用gulimall-member的login()方法,在该方法中进行用户名密码比对完成登录

Day04_谷粒商城(谷粒商城高级篇三)摘要_第16张图片
在这里插入图片描述

P220 登陆页面—完成微博登录1

Day04_谷粒商城(谷粒商城高级篇三)摘要_第17张图片

Day04_谷粒商城(谷粒商城高级篇三)摘要_第18张图片

P221 登陆页面—完成微博登录2

P221讲了去微博开放平台进行社交登录申请与测试

有两个地址很重要,
(1)是“ 在登录页引导用户至授权页”的地址:
这一步是前台完成的,前台html中的url要写成

Get
https://api.weibo.com/oauth2/authorize?client_id=1917008757&response_type=code&redirect_uri=http://gulimall.com/oauth2.0/weibo/success

client_id:是你创建网站应用时的app key,
redirect_uri是用户使用微博登录后重定向到哪里去。

我们指定redirect_uri=http://gulimall.com/oauth2.0/weibo/success也就是说用户用户使用微博登录后,相当于发送 /oauth2.0/weibo/success到后台的gulimall-auth-server模块中,那么gulimall-auth-server会使用code换取token,这就涉及到换取token的url:

(2)是换取token的url
这一步是后台完成的,后台发送这样的url才能获取到token

POST
https://api.weibo.com/oauth2/access_token?client_id=1917008757&client_secret=94d9cc62c60d5f9f3d0c62389593024f&grant_type=authorization_code&redirect_uri=http://auth.gulimall.com/oauth2.0/weibo/success&code=CODE

client_id: 创建网站应用时的app key;
client_secret: 创建网站应用时的app secret
redirect_uri: 认证完成后的跳转链接(需要和平台高级设置一致);
code:换取令牌的认证码

后台发送这么个请求就可以根据用户授权返回的code换取token(换回来的不仅仅是token,还有uid用户id、expires_in令牌的过期时间等等),拿到token就可以向微博官方发送别的请求换取用户信息

P222 登陆页面—完成微博登录3

这里是引用

P223 登陆页面—完成微博登录4

P224 登陆页面—完成微博登录5

p222—p224就是编码与测试

总说:

①前台发送 /oauth2.0/weibo/success到后台的gulimall-auth-server模块中,然后gulimall-auth-server会先使用code换取token,然后拿着token到OpenFeign远程调用gulimall-memberoauth2Login()方法,在该方法中会先判断用户是否是第一次用微博登录,如果是第一次的话我们就得给该用户注册(到微博里面查询该用户的基本信息存储到咱们的数据库里面);如果该用户之前已经用微博登陆过,那就到数据库中更新一下token

②前台用户用微博登录后我们会拿到用户的code,后台用code到微博里面换取token这样才能用token访问到用户基本信息;用户每登陆一次访问微博的token就会变一次,所以当用户下次用微博登陆时我们需要到数据库更新一下token

P225 SpringSession—session不共享、不跨域问题

Day04_谷粒商城(谷粒商城高级篇三)摘要_第19张图片

(1)session不能跨域问题

Day04_谷粒商城(谷粒商城高级篇三)摘要_第20张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第21张图片

(2) 分布式下session共享问题
多台服务器都有会员服务,你在A服务器上把用户信息保存到内存上了,下次如果落在B服务器上,即使浏览器带着cookie来了,由于B服务器内存肯定没有存储用户信息,这也是问题。

Day04_谷粒商城(谷粒商城高级篇三)摘要_第22张图片

P226 SpringSession—session问题的解决

Day04_谷粒商城(谷粒商城高级篇三)摘要_第23张图片

  1. session复制
    用户登录后,A服务器得到session后,把session也复制到别的机器上,显然这种处理很不好

  2. 客户端存储
    把session存储到浏览器上,肯定相当不安全

  3. hash一致性
    根据用户,到指定的机器上登录。但是远程调用还是不好解决

  4. redis统一存储
    最终的选择方案,把session放到redis中,这样每个微服务都可以获取到session

P227 SpringSession—SpringSession整合redis1

P228 SpringSession—SpringSession整合redis2

P229 SpringSession—SpringSession整合redis3

总说:

浏览器会在auth.gulimall.com里面登录成功,auth.gulimall.com会将登陆成功的用户的从数据库查到的用户相关信息存到session里面,而且存session时不是存到自己的内存里面而是存到redis里面,然后auth.gulimall.com给浏览器发cookie,而且发的cookie的作用域不能仅仅是auth.gulimall.com而是要放大服务到.gulimall.com,此时浏览器访问其它任何服务都会带上这个cookie。

如果你把redis里面的session情况,那就是把登陆过的用户信息清空,虽然前台的浏览器访问后台时携带了cookie信息,但是到redis里面查不到用户信息,所以你就得重新登陆。而且我们设置了redis里面的session默认30分钟过期,也就是30分钟后redis里面的用户信息就没有了

Day04_谷粒商城(谷粒商城高级篇三)摘要_第24张图片
在这里插入图片描述
在这里插入图片描述

P230 页面调整

Day04_谷粒商城(谷粒商城高级篇三)摘要_第25张图片
在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第26张图片

P231 单点登录1

P232 单点登录2

P233 单点登录3

P234 单点登录4

P235 单点登录5

1.为什么要单点登录?

springsession只能把auth.gulimall.com作用域放大到gulimall.com,解决了同域名的共享session问题,但要是访问同样是尚硅谷的atguigu.com怎么办呢?这种不同的域名也想共享session该怎么做呢?
你在新浪微博里面注册登录了,同时就要保证在新浪体育、新浪新闻里面全都可以拿到session数据

2.单点登录的原理?

两个域名不一样的服务端client1和client2,还有一个负责登录的ssoserver,还有一个浏览器,它们四个之间的故事

Day04_谷粒商城(谷粒商城高级篇三)摘要_第27张图片

先说明一下这个路径的含义:http://ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees的含义就是让你访问http://ssoserver.com:8080/login.html登陆页面,而 redirect_url=http:I/client1.com:8081/employees的含义是当你完成登陆后会重定向到http:I/client1.com:8081/employees的位置

第1-11步的解析:只有登陆了才能查看员工信息。一开始浏览器访问client1.com的员工信息http:I/client1.com:8081/employees,client1会根据这个url有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees,ssoserver.com会判断是否登陆过,没有登陆过就展示这个登陆页面,用户会输入账号密码进行登录,提交登陆请求http:/ssoserver.com:8080/doLogin?usermame,password,redirect_url给ssoserver.com,那么ssoserver.com会保存用户状态到redis,同时ssoserver.com会命令重定向到http: /lclient1.com:8081/employees?token=dadadadsdeuieu(浏览器访问路径),同时ssoserver.com会命令浏览器保存sso_token=dadadadsdeuieu这样式的cookie。浏览器这次就可以访问员工信息了,他的访问路径是刚刚提到的http://lclient1.com:8081/employees?token=dadadadsdeuieu比一开始访问员工信息的http:I/client1.com:8081/employees多了token=dadadadsdeuieu,这就回到第2步了,client1会根据有没有token参数判断是否登录,这次client1会觉得它登陆过了就可以访问员工信息了。

第12-19步解析:这次浏览器要访问客户端2的boss信息http:I/client2.com:8081/boss,client2会根据有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client2.com:8081/boss,ssoserver.com会判断是否登陆过,由于浏览器有sso_token=dadadadsdeuieu这样式的cookie,而且从redis能查到,说明它之前在client1或者client2登陆过,ssoserver.com会命令重定向到http:/lclient2.com:8082/boss?token=dadadadsdeuieu,所以浏览器就会访问http://lclient2.com:8082/boss?token=dadadadsdeuieu,这就回到了第2步,client2会根据有没有token参数判断是否登录,登陆过就响应页面。

所以说,以后浏览器无论访问client1还是client2,由于浏览器中保存了cookie,所以ssoserver.com就会判定它登陆过,所以以后都不用登陆。

3.单点登录代码的实现

client1的代码:

@GetMapping(value = "/employees") 
public String employees(Model model,
                        HttpSession session,
                        @RequestParam(value = "redisKey", required = false) String redisKey) {
     

    if (!StringUtils.isEmpty(redisKey)) {
      
    	//redisKey非空(也就是token非空),说明去过server端登录过了
    	
    	// 拿着token去服务器,在服务端从redis中查出来用户的username
        RestTemplate restTemplate=new RestTemplate();
        
        ResponseEntity<Object> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userInfo?redisKey="+ redisKey, Object.class);
        
        Object loginUser = forEntity.getBody();
        
        session.setAttribute("loginUser", loginUser);//设置到自己的session中
    }
    //尝试从自己的session中获取"loginUser"
    Object loginUser = session.getAttribute("loginUser");

    if (loginUser == null) {
      
    	//又没有token,session里又没有"loginUser",让它去登录页登录
        return "redirect:" + "http://ssoserver.com:8080/login.html" + "?url=http://clientA.com/employees";
    } else {
     
    	//自己的session里有"loginUser",即使没有token也说明登录过
        List<String> emps = new ArrayList<>();
        emps.add("张三");
        emps.add("李四");
        model.addAttribute("emps", emps);
        return "employees"; //转到前端页面,前端会把数据拿出来展示
    }
}

这里是引用

client2的代码:

代码一模一样,就是改一下访问路径@GetMapping(value = "/boss")

ssoserver的代码:

<body>
<form action="/doLogin" method="post">
    
    <input type="hidden" name="url" th:value="${url}">
    

        用户名:<input name="username" value="test"><br/>
        密码:<input name="password" type="password" value="test">
    <input type="submit" value="登录">
form>
body>
@Controller
public class LoginController {
     

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@ResponseBody
	@GetMapping("/userInfo") //client1或client2会调用这个方法得到redis中的存储过的user信息
	public Object userInfo(@RequestParam("redisKey") String redisKey){
     
		// 拿着其他域名转发过来的token去redis里查
		Object loginUser = stringRedisTemplate.opsForValue().get(redisKey);
		return loginUser;
	}


	@GetMapping("/login.html") // 子系统都来这
	public String loginPage(@RequestParam("url") String url,
							Model model,
							@CookieValue(value = "redisKey", required = false) String redisKey) {
     
							
		//这是从浏览器中拿到的cookie,非空代表就登录过了
		if (!StringUtils.isEmpty(redisKey)) {
     
			//非空代表就登录过了
			return "redirect:" + url + "?redisKey=" + redisKey;
		}
		
		model.addAttribute("url", url);

		//没登录过才去登录页
		return "login";
	}


	@PostMapping("/doLogin") //在前端输入用户名和密码后就会来到这里,进行server端统一认证
	public String doLogin(@RequestParam("username") String username,
						  @RequestParam("password") String password,
						  HttpServletResponse response,
						  @RequestParam(value="url",required = false) String url){
     
						  
		//确认用户后,生成cookie,浏览器中存储,redis中也存储
		if(!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)){
     //非空就简单认为登录正确

			String redisKey = UUID.randomUUID().toString().replace("-", "");//用uuid代替token
			
			Cookie cookie = new Cookie("redisKey", redisKey);
			
			response.addCookie(cookie);//浏览器中存储cookie
			
			stringRedisTemplate.opsForValue().set(redisKey, username+password+"...", 30, TimeUnit.MINUTES);//redis中存储
			
			return "redirect:" + url + "?redisKey=" + redisKey;//重定向时候带着token
		}
		
		// 登录失败,再次登录
		return "login";
	}

}

演示

代码用的网友的,截屏用到老师的,网友喜欢自己起名字,把token改为redisKey什么的,不要计较细节上的不同

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Day04_谷粒商城(谷粒商城高级篇三)摘要_第28张图片
在这里插入图片描述
在这里插入图片描述

P236 购物车—环境的搭建

Day04_谷粒商城(谷粒商城高级篇三)摘要_第29张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第30张图片

P237 购物车—数据模型的分析1

P238 购物车—数据模型的分析2

总说:

本节内容就是说明了用户购物车里的信息应该使用哪个数据库存储(MySQL还是Redis?),以及使用了Redis后是用List存储这些信息呢还是使用Hash存储这些信息?以及购物车VO、购物项VO的编写

Day04_谷粒商城(谷粒商城高级篇三)摘要_第31张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第32张图片

在(3)VO编写中为什么不用@Data而是自己写getter和setter方法?因为在购物项VO中计算“总价=单价x数量”这是需要手动计算的,使用@Data只会覆盖了自定义的setter方法;而且在购物车VO中应该是获取总价、获取商品数量,所以有的属性不应该有setter方法。

在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第33张图片

P239 购物车—拦截器鉴别用户

总说:

在购物车的所有Controller执行之前,我们先执行一个拦截器。在拦截器里判断用户是否登录,从session中获取不到用户信息就说明他没有登录,没有登录的话就从浏览器中获取一下user-key,如果浏览器中没有user-key那就说明用户是第一次没有登录的状态下进入京东,我们就得创建一个cookie名字叫做user-key,而且设置cookie的作用域、过期时间,假如明天他来了,我们能从浏览器中获取到该用户的user-key。

一个用户进来我们执行的 “ 拦截器—Controller—Service—Dao ” 这一套流程让同一个线程执行,这就使用了ThreadLocal技术,ThreadLocal是同一个线程共享数据,这个线程里面的数据会共享,使用过程就是:

ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();//创建一个threadLocal
threadLocal.set(userInfoTo);//把要共享的数据设置进去
....
UserInfoTo userInfoTo = threadLocal.get();//后期就可以获取到这个共享的数据

Day04_谷粒商城(谷粒商城高级篇三)摘要_第34张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第35张图片
在这里插入图片描述

P240 页面调整

拦截器写了,Controller还有service什么的都没有写,在此之前,我们先打通整个页面(首页可以进入商品页,从商品页添加商品到购物车,然后点击购物车就可以进入购物车页),本节内容是前台代码。

Day04_谷粒商城(谷粒商城高级篇三)摘要_第36张图片

Day04_谷粒商城(谷粒商城高级篇三)摘要_第37张图片
在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第38张图片
Day04_谷粒商城(谷粒商城高级篇三)摘要_第39张图片
在这里插入图片描述
Day04_谷粒商城(谷粒商城高级篇三)摘要_第40张图片

P241 添加商品到购物车1

P242 添加商品到购物车2

P243 添加商品到购物车3

总说:

因为是拦截器先执行的,所以先得到拦截器ThreadLocal的返回结果UserInfoTo userInfoTo = threadLocal.get(),如果userInfoTo.getUserId()不为空表示账号用户,反之为临时用户 ,然后决定用临时购物车还是用户购物车。将用户购物车信息存到redis中,redis中肯定需要键值对,账号用户的购物车的redis中的key是gulimall:cart:1(1是用户id,表示1号用户的购物车);临时用户的redis中的key是gulimall:cart:uuid其中uuid就是我们拦截器里存下的user-key。redisTemplate.boundHashOps(cartKey)是说以后所有对redis的增删改查都是针对redia中key为cartKey的增删改查。上面的一套逻辑被封装到getCartOps()方法里面了。

添加新商品到购物车,第一步先看redis里面能不能查到skuid,查不到说明购物车里面之前没有添加过此商品,那就需要远程查询此商品的一系列信息;能查到说明购物车有此商品,将数据取出修改数量即可。

Day04_谷粒商城(谷粒商城高级篇三)摘要_第41张图片
在这里插入图片描述
在这里插入图片描述

Day04_谷粒商城(谷粒商城高级篇三)摘要_第42张图片

//原文中的博客少了对getCartItem(Long skuId)方法的解释,你也许会问哪里少了getCartItem()?这还用问吗,你一个Ctrl+F搜一下不久知道了
    @Override
    public CartItemVo getCartItem(Long skuId) {
     
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();

        String redisValue = (String) cartOps.get(skuId.toString());

        CartItemVo cartItemVo = JSON.parseObject(redisValue, CartItemVo.class);

        return cartItemVo;
    }

测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

P244 获取购物车

总说:

若用户未登录,则直接使用user-key获取购物车数据;否则使用userId获取购物车数据,并将user-key对应临时购物车数据与用户购物车数据合并,并删除临时购物车

csdn上的笔记只是为了清楚地记录了老师这节课操作了什么,而具体代码我的建议就是尽量看idea上的代码,别看csdn上的代码,

Day04_谷粒商城(谷粒商城高级篇三)摘要_第43张图片

//原文中的博客少了对getCartItems(String cartKey) 方法的解释

     * 获取购物车里面的数据
    private List<CartItemVo> getCartItems(String cartKey) {
     
        //获取购物车里面的所有商品
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        List<Object> values = operations.values();
        if (values != null && values.size() > 0) {
     
            List<CartItemVo> cartItemVoStream = values.stream().map((obj) -> {
     
                String str = (String) obj;
                CartItemVo cartItem = JSON.parseObject(str, CartItemVo.class);
                return cartItem;
            }).collect(Collectors.toList());
            return cartItemVoStream;
        }
        return null;

    }
//原文中的博客少了对clearCart()方法的解释
    @Override
    public void clearCart(String cartKey) {
     
        redisTemplate.delete(cartKey);
    }

测试

Day04_谷粒商城(谷粒商城高级篇三)摘要_第44张图片
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

P245 选中购物车项

Day04_谷粒商城(谷粒商城高级篇三)摘要_第45张图片

Day04_谷粒商城(谷粒商城高级篇三)摘要_第46张图片

P246 修改购物项数量

在这里插入图片描述

Day04_谷粒商城(谷粒商城高级篇三)摘要_第47张图片

P247 删除购物项

Day04_谷粒商城(谷粒商城高级篇三)摘要_第48张图片

Day04_谷粒商城(谷粒商城高级篇三)摘要_第49张图片

你可能感兴趣的:(谷粒商城)