微服务思想(SOA/RPC)

1 HttpClient介绍

1.1 远程调用分析
在这里插入图片描述
1.2 HttpClient介绍
HTTP 协议是 Internet 上使用得最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。Commons HttpClient项目现已终止,不再开发。 它已被Apache HttpComponents项目里的HttpClient和HttpCore模块取代,它们提供了更好的性能和更大的灵活性。
1.3 HttpClient入门案例
1.3.1 编辑POM.xml文件

 	  <!--添加httpClient jar包 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

1.3.2 入门案例

package com.jt;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class TestHttpClient {

    /**
     * 1.实例化HttpClient对象
     * 2.定义url地址
     * 3.封装请求方式GET/POST/PUT....
     * 4.发送请求获取响应的结果.response
     * 5.判断响应是否正确. 200表示请求正确,获取响应的结果.
     * 6.解析服务器返回值.获取有效数据
     */
    @Test//在java代码中发起http请
    public void testGet() throws IOException {
        HttpClient httpClient = HttpClients.createDefault();
        String url = "https://www.cctv.com/";
        HttpGet httpGet = new HttpGet(url);
        HttpResponse httpResponse = httpClient.execute(httpGet);
        int status  = httpResponse.getStatusLine().getStatusCode();   //获取状态码
        if(status == 200 ){
            HttpEntity entity = httpResponse.getEntity();   //获取响应的实体对象
            String result = EntityUtils.toString(entity, "UTF-8");
            System.out.println(result);
        }
    }
}


1.3.3 关于HttpClient 案例2
需求: 用户通过浏览器网址http://www.jt.com/user/findAll请求,要求获取jt-sso中的所有的用户信息.
提示: jt-web服务器没有办法直接访问数据库…
jt-sso服务器可以访问数据…
jt-web向jt-sso发起请求为http://sso.jt.com/user/findAll
关键代码建立远程调用
web 的 controller层

package com.jt.controller;

import com.jt.pojo.User;
import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * httpClient代码测试
     * http://www.jt.com/user/findAll
     */
     @Autowired
     private UserService userService;

     @RequestMapping("/findAll")
     @ResponseBody
     public List<User> findAll(){

        return userService.findAll();
     }


}

web的service层

package com.jt.service;

import com.jt.pojo.User;
import com.jt.util.ObjectMapperUtil;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl implements UserService{

    //httpClient在java代码中 是万能的请求方式......
    //封装好的高级API SpringCloud 底层实现...
    //jt-web服务器  由jt-sso动态获取
    @Override
    public List<User> findAll() {
        List<User> userList = new ArrayList<>();
        String url = "http://sso.jt.com/user/findAll";
        HttpGet httpGet = new HttpGet(url);
        HttpClient httpClient = HttpClients.createDefault();
        try {
            HttpResponse httpResponse = httpClient.execute(httpGet);
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                //说明请求正确
                String json = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
                userList = ObjectMapperUtil.toObj(json,userList.getClass());
                //拿到对象之后,按照指定的业务要求,对数据进行二次加工 解密/添加某些数据
                //xxxxxxxxxx
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return userList;
    }
}

sso的controllerceng

package com.jt.controller;

import com.fasterxml.jackson.databind.util.JSONPObject;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

//要求返回的数据都是JSON
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 测试: 要求查询user表中的所有的数据
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){

        return userService.findAll();
    }
}

sso的serviceceng

package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;


    @Override
    public List<User> findAll() {

        return userMapper.selectList(null);
    }

}

1.3.4 关于httpClient和JSONP说明
面试题:
问: HTTPClient是跨域请求吗? 不是 就是远程过程调用…
在这里插入图片描述

2 微服务思想(服务小型化)

2.1 SOA思想
面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
核心理念: 服务松耦合的思想…
在这里插入图片描述
2.2 RPC
RPC是远程过程调用(Remote Procedure Call)的缩写形式。
漫话RPC:
一个阳光明媚的早晨,老婆又在翻看我订阅的技术杂志。

“老公,什么是RPC呀,为什么你们程序员那么多黑话!”,老婆还是一如既往的好奇。
“RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用嘛”,我一边看着书,一边漫不经心的回答着。
“啥?你在说啥?谁不知道翻译成中文是什么意思?你个废柴,快给我滚去洗碗!”
“我去。。。”,我如梦初醒,我对面坐着的可不是一个程序员,为了不去洗碗,我瞬间调动起全部脑细胞,星辰大海在我脑中汇聚,灵感涌现…

“是这样,远程过程调用,自然是相对于本地过程调用来说的嘛。”
“嗯哼,那先给老娘讲讲,本地过程调用是啥子?”
“本地过程调用,就好比你现在在家里,你要想洗碗,那你直接把碗放进洗碗机,打开洗碗机开关就可以洗了。这就叫本地过程调用。”

“哎呦,我可不干,那啥是远程过程调用?”
“远程嘛,那就是你现在不在家,跟姐妹们浪去了,突然发现碗还没洗,打了个电话过来,叫我去洗碗,这就是远程过程调用啦”,多么通俗易懂的解释,我真是天才!

“哦!我明白了”,说着,老婆开始收拾包包。
“你这是干啥去哦”
“我?我要出门浪去呀,待会记得接收我的远程调用哦,哦不,咱们要专业点,应该说,待会记得接收我的RPC哦!”

总结:
1.本地过程调用
完成业务时,如果可以调用自己的方法完成.称之为本地过程调用(自己调用自己的方法 同一个服务器中)
2.远程过程调用
完成业务时,自己的方法不能操作或者没有该功能时,需要调用别人的服务器(方法)来完成业务.(不同的服务器之间的调用)
RPC:不同的服务器之间的调用就是RPC

2.3 微服务的调用思想
微服务思想: 将程序(项目)按照分布式的思想进行拆分(SOA),并且可以自动的实现故障的迁移(服务自动发现机制),无需人为的干预

2.3.1 传统方式调用
说明:
1.按照常规的调用方式,每次增加/减少服务器时,都需要编辑conf配置文件. 没有发办法实现服务自动的发现(不够智能)
2.只要服务器进行RPC调用都必须经过nginx服务器,.则nginx压力很高
在这里插入图片描述
在这里插入图片描述
2.3.2 微服务调用思想
在这里插入图片描述
步骤:
1.服务启动时,会链接注册中心,将服务数据(服务名称|IP|端口)写入注册中心.
2.注册中心接收用户服务数据之后,动态维护服务列表.
3/4.消费者启动时,链接注册中心,之后将服务列表缓存到本地(缓存到消费者内存中(快)) 方便下次调用.
5.当用户调用服务消费者时.消费者根据当前服务列表的信息,进行负载均衡,挑选其中一个服务进行访问.
6.注册中心为了保证服务列表的正确性,通过心跳检测机制.实时监控所有服务生产者,如果服务器宕机,则注册中心将第一时间更新服务列表.并且全网广播 通知所有的消费者.更新服务列表.
优点:用户每次访问 几乎可以保证访问的服务器都是正确的.

3 Zookeeper

3.1Zookeeper介绍
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.3\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
总结:Zookeeper负责服务的协调调度.当客户端发起请求时,返回正确的服务器地址.

3.2 ZK集群搭建
3.2.1 Zookeeper安装在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.2.2 Zookeeper安装
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.3 搭建集群规则
规则: 剩余存活节点的数量 > N/2 N代表节点总数
计算:
1个节点 1-1 > 1/2 假的 一个节点不能搭建集群
2个节点 2-1 > 2/2 假的 二个节点不能搭建集群
3个节点 3-1 > 3/2 真的 集群的最小的单位3台.
4个节点 4-1 > 4/2 真的

3.4 为什么集群一般都是奇数台
从容灾性/经济性的角度考虑问题
奇数 3台 3-1 > 3/2 宕机1台可以正常工作
3-2 > 3/2 宕机2台集群崩溃
偶数 4台 4-1 > 4/2 宕机1台可以正常工作
4-2 > 4/2 宕机2台集群崩溃
答: 发现奇数台和偶数台的容灾性相同的,所以搭建奇数台更好.

3.5 zk集群选举规则
规则: myid最大值优先 只讨论1轮
考题:
编号 1,2,3,4,5,6,7 依次启动. 至少4台…
问题1: 谁当主机? 4当主…
问题2: 谁永远当不了主机? 1,2,3

4 Dubbo框架

4.1 Dubbo介绍
Dubbo(读音[ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架(SOA),使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 [1] Spring框架无缝集成。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

4.2 Dubbo入门案例
4.2.1 修改配置文件
1).检查POM文件是否正确
父级中的子模块信息 时动态生成的 其中的信息必须正确
在这里插入图片描述
2).修改SpringBoot版本
在这里插入图片描述
4.3 接口定义
说明: interface与被消费者/生产者(提供者)依赖.
在这里插入图片描述
4.4 编辑服务提供者
4.4.1 提供者代码结构
在这里插入图片描述
4.4.2 编辑Service实现类

package com.jt.dubbo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
@Service(timeout=3000)	//3秒超时 内部实现了rpc
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService {
	
	@Autowired
	private UserMapper userMapper;
	
	@Override
	public List<User> findAll() {
		
		System.out.println("我是第一个服务的提供者");
		return userMapper.selectList(null);
	}
	
	@Override
	public void saveUser(User user) {
		
		userMapper.insert(user);
	}
}


4.4.3 编辑YML配置文件
关于服务提供者工作说明
在这里插入图片描述

server:
  port: 9000   #定义端口

spring:
  datasource:
    #引入druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#关于Dubbo配置   
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

      
mybatis-plus:
  type-aliases-package: com.jt.dubbo.pojo       #配置别名包路径
  mapper-locations: classpath:/mybatis/mappers/*.xml  #添加mapper映射文件
  configuration:
    map-underscore-to-camel-case: true                #开启驼峰映射规则

4.5 编辑服务消费者
4.5.1 消费者代码结构
在这里插入图片描述
4.5.2 编辑Controller代码
在这里插入图片描述
4.5.3 编辑YML配置文件
在这里插入图片描述
4.5.4 页面效果测试
在这里插入图片描述
4.6 微服务框架测试
4.6.1 服务高可用测试
关闭一个服务提供者,检查用户的访问是否收到影响. 经过测试.用户访问不受影响.

4.6.2 zk高可用测试
将zk主机关闭,检查用户访问是否受限. 经过测试访问不受限制.

4.6.3 zk集群宕机测试
将zk集群全部关闭,检查用户访问是否受限?? 不受影响 因为消费者本地也有服务列表信息(之前缓存的)

4.7 关于负载均衡说明
4.7.1 集中式负载均衡
说明:统一由服务器进行负载均衡的调度. 反向代理机制.
在这里插入图片描述
4.7.2 客户端式负载均衡
在这里插入图片描述
4.7.3 Dubbo框架中负载均衡方式
1.ConsistentHashLoadBalance 一致性hash算法 消费者绑定唯一的服务提供者
在这里插入图片描述
2.LeastActiveLoadBalance 最小访问 挑选当前服务器压力最小的提供者进行访问.
在这里插入图片描述
3.RandomLoadBalance 随机算法 是默认的负载均衡机制
在这里插入图片描述
4. RoundRobinLoadBalance 轮询机制
在这里插入图片描述

5 项目Dubbo改造

5.1 导入jar包

 	   <!--引入dubbo配置 -->
       <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

5.2 定义中立接口
在这里插入图片描述
5.3 改造JT-MANAGE
5.3.1 定义实现类
在这里插入图片描述5.3.2 编辑YML配置文件

server:
  port: 8091
  servlet:
    context-path: /
spring:
  datasource:
    #url: jdbc:mysql://192.168.126.129:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com.jt.mapper: debug

#关于Dubbo配置
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-item     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

5.4 构建服务消费者-JT-WEB
5.4.1 编辑ItemController
在这里插入图片描述
5.4.2 编辑YML配置文件

server:
  port: 8092    
spring:     #定义springmvc视图解析器
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
    

6 商品前台展现

6.1 业务分析
说明:用户点击某个商品时,应该跳转到商品的展现页面item.jsp中.
数据获取规则:
需要查询后台服务器获取当前的商品信息,之后将数据保存到域对象中,通过el表达式动态获取${item.title }
数据1: Item对象信息
数据2: ItemDesc对象信息
在这里插入图片描述
6.2 编辑ItemController

package com.jt.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller     //页面跳转.......
public class ItemController {

    //先启动服务消费者 暂时不校验是否有提供者
    @Reference(check = false)
    private DubboItemService itemService;

    /**
     *
     * 业务需求: 根据商品id号查询商品/商品详情信息
     * url地址:  http://www.jt.com/items/562379.html
     * 参数:     restFul风格
     * 返回值:   跳转页面item.jsp
     * 页面取值:  Item/ItemDesc对象
     */
    @RequestMapping("/items/{itemId}")
    public String findItemById(@PathVariable Long itemId, Model model){

        Item item = itemService.findItemById(itemId);
        ItemDesc itemDesc = itemService.findItemDescById(itemId);
        //需要将数据保存到域对象中
        model.addAttribute("item", item);
        model.addAttribute("itemDesc", itemDesc);
        return "item";
    }
}


6.3 编辑ItemService

package com.jt.web.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.beans.factory.annotation.Autowired;

@Service(timeout = 3000)
public class DubboItemServiceImpl implements DubboItemService {

    @Autowired
    private ItemMapper itemMapper;
    @Autowired
    private ItemDescMapper itemDescMapper;

    //查询商品信息
    @Override
    public Item findItemById(Long itemId) {

        return itemMapper.selectById(itemId);
    }

    //查询商品详情信息
    @Override
    public ItemDesc findItemDescById(Long itemId) {

        return itemDescMapper.selectById(itemId);
    }
}


6.4 效果展现
在这里插入图片描述
6.5 关于POJO报错说明
说明:如果遇到这样的报错信息,则需要重启服务器即可.
原因: 由于热部署速度很快 导致zk服务器内部出现了2条一模一样的服务信息.不知道应该访问哪个报错.
在这里插入图片描述

7 用户登录模块改造

7.1 编辑DubboUserService
在这里插入图片描述
7.2 编辑YML配置文件

server:
  port: 8093
  servlet:
    context-path: /
spring:
  datasource:
    #url: jdbc:mysql://192.168.126.129:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com.jt.mapper: debug

#关于Dubbo配置
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-user    #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20881  #每一个服务都有自己特定的端口 不能重复.


7.3用户注册实现
7.3.1 业务分析
7.3.1.1 页面URL分析
在这里插入图片描述
7.3.1.2 参数分析
在这里插入图片描述
7.3.1.3 检查页面JS
在这里插入图片描述
7.3.2 编辑JT-WEB UserController
在这里插入图片描述
7.3.3 编辑JT-SSO UserService

package com.jt.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;

@Service(timeout = 3000)
public class DubboUserServiceImpl implements DubboUserService{

    @Autowired
    private UserMapper userMapper;


    @Override
    public void saveUser(User user) {
        //将密码md5加密
        byte[] bytes = user.getPassword().getBytes();
        String md5Pass = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Pass);
        //要求email不为null 暂时使用电话代替
        user.setEmail(user.getPhone());
        userMapper.insert(user);
    }
}

8 实现用户单点登录

8.1 用户登录实现
8.1.1 传统登录存在的问题
问题说明: 按照如下的方式进行设计,用户需要在不同的服务器中进行多次登录操作.用户体验较差.
在这里插入图片描述
8.1.2 登录操作优化
知识铺垫:
Session: 在一个会话内,可以实现数据的共享 范围大 公共的共享数据一般会保留到Session中.
Request: 在一个请求内,实现数据的共享. 范围小
上述的对象都是服务器端对象. 保存在服务器中. 如果服务器变化了,或者关闭/宕机了 则对象全部失效.
Cookie: Cookie是在客户端实现数据共享的一种机制, 同时可以保存服务器端传回来的数据(业务需要)
token策略: 动态生成一个密钥.
在这里插入图片描述
8.1.3 SSO单点登录设计
8.1.3.1 SSO介绍
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的
8.1.3.2 单点登录业务流程
在这里插入图片描述
8.1.3.3 页面分析
1.url分析
在这里插入图片描述
2.参数说明
在这里插入图片描述
3.页面JS
在这里插入图片描述
8.1.3.4 编辑UserController

@Reference(check = false)
    private DubboUserService dubboUserService;
    @Autowired
    private JedisCluster jedisCluster;
 /**
     *
     * 业务需求:  完成用户登录操作
     * URL地址:   http://www.jt.com/user/doLogin?r=0.6659570464851978
     * 请求参数:  用户名和密码
     * 返回值:    SysResult对象
     *
     * 知识点讲解:
     *     Cookie: 在客户端保存服务器数据,在客户端实现数据共享.
     *          cookie.setMaxAge(); cookie生命周期
     *          cookie.setMaxAge(0);     立即删除cookie
     *          cookie.setMaxAge(100);   设定100秒有效期 100秒之后自动删除
     *          cookie.setMaxAge(-1);    关闭会话后删除
     *    2.设定path cookie的权限设定
     *          cookie.setPath("/")      一般条件下设定为/ 通用
     *              权限:根目录及其子目录有效
     *          cookie.setPath("/user")
     *              权限:/user目录下有效
     *    3.设定Cookie资源共享
     *      cookie特点: 自己的域名下,只能看到自己的Cookie. 默认条件下不能共享的
     *      cookie.setDomain("jt.com"); 只有在xxx.jt.com的域名中实现数据共享
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public SysResult doLogin(User user, HttpServletResponse response){

        String ticket = dubboUserService.doLogin(user);
        if(!StringUtils.hasLength(ticket)){

            return SysResult.fail();
        }
        Cookie cookie = new Cookie("JT_TICKET", ticket);
        cookie.setMaxAge(7*24*60*60);   //设定7天有效
        cookie.setPath("/");  //请求在根目录中都可以获取cookie
        cookie.setDomain("jt.com");
        response.addCookie(cookie);
        return SysResult.success();
    }

8.1.3.5 编辑UserService

/**
     * 业务说明: 实现用户单点登录操作
     *  1.根据用户名和密码查询数据库
     * @param user
     * @return
     */
    @Override
    public String doLogin(User user) {//username/password不为null
        //密文加密
        String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass);
        //条件构造器  根据对象中不为null的属性充当where条件 查询的是user的全部信息
        User userDB = userMapper.selectOne(new QueryWrapper(user));

        String ticket = null;
        if(userDB !=null){
            //用户名和密码正确
            ticket = UUID.randomUUID().toString().replace("-", "");
            //数据安全性 没有办法得到保证  要对敏感数据进行脱敏处理
            userDB.setPassword("123456你猜猜?");
            String json = ObjectMapperUtil.toJSON(userDB);
            jedisCluster.setex(ticket, 7*24*60*60, json);

        }
        return ticket;
    }

8.1.3.6 页面效果展现
在这里插入图片描述
8.1.4 用户信息回显
8.1.4.1 业务说明
如果用户登录之后,应该在系统首页中展现用户名称.
实现思路:
1.跨域实现 Ajax请求 根据密钥信息动态获取用户信息.
2.httpClient方式实现. 调用层级较多.
3.利用Dubbo框架实现.
在这里插入图片描述
8.1.4.2 页面分析
1.页面URL分析
在这里插入图片描述
2.页面JS分析
在这里插入图片描述
8.1.4.3编辑JT-SSO Controller

/**
     * 业务需求:
     *     根据用户ticket信息,查询用户信息
     * 1.url地址:http://sso.jt.com/user/query/8d5fc189ccde43f7a6b6bf4aecd9eb0e?callback=jsonp1613793443098&_=1613793443147
     * 2.请求参数: ticket信息
     * 3.返回值结果:SysResult对象
     * 注意: JSONP方式进行跨域请求. callback(JSON)
     */
    @RequestMapping("/query/{ticket}")
    public JSONPObject findUserByTicket(String callback,@PathVariable String ticket){

        //利用ticket从redis中动态获取数据
        String json = jedisCluster.get(ticket);
        //User user = ObjectMapperUtil.toObj(json, User.class);
        if(StringUtils.hasLength(json)){
            return new JSONPObject(callback, SysResult.success(json));
        }
        return new JSONPObject(callback,SysResult.fail());
    }


8.1.5 封装CookieUtil API

package com.jt.util;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//工具API 主要负责 新增cookie 删除cookie  根据key获取cookie  获取cookie的值
public class CookieUtil {

    public static void addCookie(HttpServletResponse response,String name, String value, int maxAge, String path, String domain){
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath(path);
        cookie.setDomain(domain);
        response.addCookie(cookie);
    }

    public static void delCookie(HttpServletResponse response,String name,String path, String domain){

        addCookie(response, name, "", 0, path, domain);
    }

    public static Cookie getCookie(HttpServletRequest request,String name){
        Cookie[] cookies = request.getCookies();
        if(cookies !=null && cookies.length >0){
            for (Cookie cookie : cookies){
                if(name.equals(cookie.getName())){

                    return cookie;
                }
            }
        }
        return null;
    }

    public static String getCookieValue(HttpServletRequest request,String name){
        Cookie cookie = getCookie(request, name);
        return cookie==null?null:cookie.getValue();
    }
}


8.1.5 用户退出操作
8.1.5.1 业务说明
当用户点击退出按钮时,应该重定向到系统首页. 应该删除Cookie 删除Redis中的数据…
在这里插入图片描述
8.1.5.2 编辑UserController

 /**
     * 实现用户退出操作
     * url地址: http://www.jt.com/user/logout.html
     * 返回值:  重定向到系统首页
     */
    @RequestMapping("/logout")
    public String logout(HttpServletRequest request,HttpServletResponse response){

        String ticket = CookieUtil.getCookieValue(request, "JT_TICKET");
        if(StringUtils.hasLength(ticket)){
            //删除redis
            jedisCluster.del(ticket);
            //删除cookie
            CookieUtil.delCookie(response, "JT_TICKET", "/", "jt.com");
        }

        return "redirect:/"; //代表缺省值
    }

9 关于前台权限控制

9.1 业务说明
当用户在没有登录的条件下,不允许访问敏感业务数据例如购物车/订单业务等…
难点:
1.如何判断用户是否登录? 1.检查cookie 2.检查redis
2.如何控制权限? 拦截器!
9.2 拦截器业务实现原理
9.2.1 SpringMVC流程图复习
在这里插入图片描述
9.2.2 拦截器工作流程
在这里插入图片描述
9.2.3 配置拦截器
在这里插入图片描述
9.2.4 编辑拦截器接口

package com.jt.interceptor;

import com.jt.pojo.User;
import com.jt.util.CookieUtil;
import com.jt.util.ObjectMapperUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.JedisCluster;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.function.Predicate;
@Component  //将对象交给Spring容器管理
public class UserInterceptor implements HandlerInterceptor {

    @Autowired
    private JedisCluster jedisCluster;

    /**
     * 通过拦截器实现拦截,重定向系统登录页面
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     *
     * 返回值说明:
     *      boolean   false 表示拦截 一般配合重定向方式使用
     *
     * 业务调用:
     *      1.获取cookie中的数据
     *      2.检查redis中是否有数据
     *      3.校验用户是否登录.
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取cookie中的数据,检查是否登录
        String ticket = CookieUtil.getCookieValue(request, "JT_TICKET");
        if(StringUtils.hasLength(ticket) && jedisCluster.exists(ticket)){//判断数据是否有值
                //2.动态获取user信息
                String json = jedisCluster.get(ticket);
                //3.将json转化为用户对象
                User user = ObjectMapperUtil.toObj(json, User.class);
                request.setAttribute("JT_USER", user);
                return true;    //表示程序放行
        }

        //实现用户重定向
        response.sendRedirect("/user/login.html");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        request.removeAttribute("JT_USER");
    }
}


9.2.5 编辑CartController
在这里插入图片描述
9.3 ThreadLocal
9.3.1 ThreadLocal作用
名称: 本地线程变量
作用: 在同一个线程内,实现数据共享.
在这里插入图片描述
9.3.2 封装工具API

package com.jt.util;

import com.jt.pojo.User;

public class UserThreadLocal {
    //线程的一个属性  ThreadLocal是线程安全的
    private static ThreadLocal<User> threadLocal = new ThreadLocal<>();

    public static void set(User user){
        threadLocal.set(user);
    }

    public static User get(){

        return threadLocal.get();
    }

    public static void remove(){

        threadLocal.remove();   //删除数据
    }
}


9.3.3 基于ThreadLocal实现数据取赋值操作
1.拦截器赋值
在这里插入图片描述
2.用户取值
在这里插入图片描述
3.数据销毁
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值