Ajax cross-domain and remote calls, Dubbo applications

1. Description of SpringMVC request path

1. By default, SpringMVC can only intercept prefix-type requests. www.jt.com/item/562379
2. If a suffix is ​​added to the request path, the suffix will be mistakenly regarded as a request parameter and participate in the operation.
562379.html will be used together as a parameter. , leading to parameter exceptions.

1.SpringMVC enables suffix type matching to achieve pseudo-static

Note: If the suffix type matching identifier is turned on, if the request ends with an operation such as .html/.do/.action, it will still be correctly intercepted by SpringMVC, and the suffix will not participate in the passing of parameters.

Edit configuration class

@Configuration
public class MvcConfigurer implements WebMvcConfigurer{
    
    
    
    //开启匹配后缀型配置
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    
    
       // 匹配结尾 / :会识别 url 的最后一个字符是否为 /
        // localhost:8080/test 与 localhost:8080/test/ 等价
        configurer.setUseSuffixPatternMatch(true);
        // 匹配后缀名:会识别 xx.* 后缀的内容
        // localhost:8080/test 与 localhost:8080/test.html 等价
        configurer.setUseTrailingSlashMatch(true);
    }
}

Why should we add the .html suffix at the end of the request
? The purpose of adding .html is to make it easier for search engines to record the page and improve the exposure of the website.
Search engine rules: Search engines generally record pages ending in .html in the company. Then record it in the database and create an index for it to improve user retrieval efficiency.
Insert image description here

2. Cross-domain

1. Same-origin policy:
Browser regulations: When initiating ajax, if the ajax request protocol/domain name/port number is the same as the address of the current browser, the same-origin policy is met. The browser can parse the return value normally. If There is one difference between the three, which violates the same-origin policy. The browser will not parse the return value.
Insert image description here

Insert image description here
2. What is cross-domain?
Due to business needs, usually the data in server A may come from server B. When the browser parses the page through the URL, if an ajax request is initiated inside the page. If the browser's access address and the Ajax access address do not meet When using the same origin policy, it is called a cross-domain request.
Cross-domain issues: Violating the provisions of the same-origin policy is a cross-domain request.
Cross-domain elements:
1. Browser
2. Parsing ajax
3. Violating the same-origin policy

3. How to solve cross-domain problems?

3.1.JSONP cross-domain access can be used to solve the problem of cross-domain data access in mainstream browsers

JSONP cross-domain principle:
1) Use the src attribute in javascrpit to implement cross-domain requests.
2) Customize the callback function function callback(xxxx);
3) Encapsulate the return value result in a special format callback(json);
4) Due to the use of The src attribute is called so only the get request type is supported.

1. Business requirements:
When a user enters a user name when registering, a request should be made to the jt-sso single sign-on system to verify whether the user data exists. If it exists, the user will be prompted.

Example: Business interface document description:
Insert image description here
2. Edit UserController

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;

@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 业务需求: 查询所有用户信息
     * url:  sso.jt.com   localhost:8093   /findAll
     * 返回值: List<User>
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){
    
    

        return userService.findAll();
    }


    /**
     * 需求:实现用户信息校验
     * 校验步骤:  需要接收用户的请求,之后利用RestFul获取数据,
     *            实现数据库校验,按照JSONP的方式返回数据.
     * url地址:   http://sso.jt.com/user/check/admin123/1?r=0.8&callback=jsonp16
     * 参数:      restFul方式获取
     * 返回值:    JSONPObject
     */
    @RequestMapping("/check/{param}/{type}")
    public JSONPObject checkUser(@PathVariable String param,
                                 @PathVariable Integer type,
                                 String callback){
    
    
        //只需要校验数据库中是否有结果
        boolean flag = userService.checkUser(param,type);
        SysResult sysResult = SysResult.success(flag);
        return new JSONPObject(callback, sysResult);
    }
}


3. Edit UserServiceImpl

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;
    private static Map<Integer,String> paramMap = new HashMap<>();

    static {
    
    
        paramMap.put(1, "username");
        paramMap.put(2, "phone");
        paramMap.put(3, "email");
    }




    @Override
    public List<User> findAll() {
    
    

        return userMapper.selectList(null);
    }

    /**
     *  校验数据库中是否有数据....
     *  Sql: select count(*) from tb_user where username="admin123";
     *  要求:返回数据true用户已存在,false用户不存在
     */
    @Override
    public boolean checkUser(String param, Integer type) {
    
    
        String column = paramMap.get(type);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(column,param);
        int count = userMapper.selectCount(queryWrapper);
        return count>0?true:false;
        //return count>0;
    }
}


4. Modify global exception handling

package com.jt.aop;

import com.fasterxml.jackson.databind.util.JSONPObject;
import com.jt.vo.SysResult;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice  //定义全局异常处理
public class SystemException {
    
    

    //遇到运行时异常时方法执行.
    //JSONP报错 返回值 callback(JSON) 如果请求参数中包含callback参数,则标识为跨域请求
    @ExceptionHandler({
    
    RuntimeException.class})
    public Object fail(Exception e, HttpServletRequest request){
    
    
        e.printStackTrace();    //输出异常信息.
        String callback = request.getParameter("callback");
        if(StringUtils.isEmpty(callback)){
    
    
            //如果参数为空表示 不是跨域请求.
            return SysResult.fail();
        }else{
    
    
            //有callback参数,表示是跨域请求.
            SysResult sysResult = SysResult.fail();
            return new JSONPObject(callback,sysResult);
        }
    }
}


3.2. CORS cross-domain implementation
1) CORS introduction
Because for security reasons, browsers do not allow Ajax to call resources outside the current source. That is the browser's same-origin policy.
CORS needs to be supported by both the browser and the server. Currently, all mainstream browsers support this function, and IE browser cannot be lower than IE10. On the browser side, the entire CORS communication process is automatically completed by the browser. Response header information is added to the request. If the server allows cross-domain access, the browser's same-origin policy will allow it. 2) Configure the back-end
Insert image description here
server

package com.jt.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration  //标识我是一个配置类
public class CorsConfig implements WebMvcConfigurer {
    
    

    //在后端 配置cors允许访问的策略
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    
    
        registry.addMapping("/**")              //表示所有访问后端的服务器的请求都允许跨域
                .allowedMethods("GET","POST")  //定义允许跨域的请求类型
                .allowedOrigins("*")           //任意网址都可以访问
                .allowCredentials(true)        //请求跨域时是否允许携带Cookie/Session相关
                .maxAge(1800);                 //设定请求长链接超时时间.默认30分钟 是否允许跨域请求 30分钟之内不会再次验证
    }
}


4. About cross-domain explanation
1. What is cross-domain? When a browser parses Ajax and initiates a URL request that violates the same-origin policy, it is called cross-domain.
2. When to use cross-domain? Generally, server A needs to obtain it from server B. When collecting data, you can use a cross-domain method.
3. What is JSONP? JSONP is a usage pattern of JSON that uses the src attribute in javaScript to make cross-domain requests. (2. Customize the callback function, 3. Specially format the return value package)

4. What is CORS? CORS is the current mainstream way to achieve cross-domain. It is now supported by all mainstream browsers. You need to configure whether cross-domain configuration is allowed on the server side. As long as it is configured (add the cross-domain allowed flag in the response header) ), the same-origin policy does not take effect, and cross-domain can be implemented.

3.RPC remote call

1.RPC remote call definition

RPC (Remote Procedure Call) remote procedure call protocol, simply put, is a node requesting services provided by another node. RPC is accompanied by the emergence of distribution. Because the distributed client and server are deployed on different machines, remote calls are required.

2. Introduction to several major RPC frameworks
1. Support multi-language RPC frameworks, google's gRPC, Apache (facebook)'s Thrift
2. Only support RPC frameworks in specific languages, such as Sina's Motan
3. Support service-oriented features such as service governance Distributed frameworks, such as Alibaba’s dubbo
4. Spring cloud with a complete ecosystem

3. Description of microservice calling principle
3.1 Standard:
1. Distributed design based on the idea of ​​business splitting
2. When an exception occurs in the service, fault migration can be automatically realized without human intervention.

3.2 Traditional service calling method
Description: Because nginx needs to rely on configuration files when doing load balancing. However, when servers are added/reduced, manual modification is required. It cannot be automated. Therefore, it cannot meet the standards of microservices for the time being. 3.3
Insert image description here
Microservices Introduction to the principle of calling services
Insert image description here
Steps:

  1. When the service provider starts, it registers its service information (service name/IP/port number) to the registration center.
  2. The service registration center records the provider's information and updates the service list information.
  3. When the consumer starts, it will first connect to the registration center and obtain service list data.
  4. After the consumer receives the service list data, it saves the information locally to facilitate the next call.
  5. When the consumer receives the user's request, it makes an RPC remote call based on the information in its own service list.
  6. When the service provider goes down, the registration center will have a heartbeat detection mechanism. If the check is down, the local service list data will be updated.
  7. After the service list is maintained, the entire network broadcasts to notify all consumers to update the service list.

4.The relationship between Dubbo and Zookeeper

1.The role of Dubbo

Dubbo is Alibaba's open source RPC distributed service framework. Netty and Zookeeper are used internally to ensure high performance and high availability.

To put it simply, Dubbo is a service framework. If there is no need for distribution, there is actually no need to use it. Only when it is distributed, there is a need for a distributed service framework like Dubbo, and it is essentially a service call To put it bluntly, it is a distributed framework for remote service invocation.
Insert image description here
Node role description

Provider: The service provider that exposes the service.
Consumer: The service consumer that calls the remote service.
Registry: A registration center for service registration and discovery.
Monitor: A monitoring center that counts service call times and call times.
Container: The service runs the container.
Insert image description here

Calling relationship description
0. The service container is responsible for starting, loading, and running the service provider.

When a service provider starts, it registers the services it provides with the registration center.

When a service consumer starts, he subscribes to the registration center for the services he needs.

The registration center returns the service provider address list to the consumer. If there is a change, the registration center will push the change data to the consumer based on the long connection.

The service consumer selects a provider to call from the provider address list based on the soft load balancing algorithm. If the call fails, it selects another provider to call.

Service consumers and providers accumulate the number of calls and call times in memory, and regularly send statistical data to the monitoring center every minute.

To complete the scheduling, this framework must have a distributed registration center to store the metadata of all services, using zookeeper.

2.The role of Zookeeper

Zookeeper is a framework that provides distributed coordination services and solves the problem of distributed consistency. It is mainly used for the registration center of the dubbo framework.

The caller must know which service is provided by which machine. Simply put, it is the correspondence between the IP address and the server name. Of course, this correspondence can also be hard-coded in the caller's business code. However, if the machine providing the service hangs up, the caller has no way of knowing. If the code is not changed, it will continue to request the dead machine to provide services. ZooKeeper can detect the failed server through the heartbeat mechanism and delete the corresponding relationship between the IP address of the failed server and the server from the list.

3.The relationship between Zookeeper and Dubbo

3.1 Dubbo abstracts the registration center so that it can connect to different storage media to provide services to the registration center. There are ZooKeeper, Memcached, Redis, etc.
3.2 The introduction of zookeeper as a storage medium also introduces the features of zookeeper.

5.Application of dubbo framework

0.spring needs to add dependencies when integrating dubbo

Add jar package file

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

1. Create a parent project
Insert image description here

The pom.xml file of the parent project

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.jt.dubbo</groupId>
	<artifactId>dubbo-jt</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<java.version>1.8</java.version>
		<!--添加maven插件 -->
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<!--springBoot动态的引入springMVC全部的配置 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!--引入测试类 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!--添加属性注入依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>

		<!--支持热部署 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>springloaded</artifactId>
			<version>1.2.8.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>

		<!--引入插件lombok 自动的set/get/构造方法插件 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<!--引入数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!--引入druid数据源 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.12</version>
		</dependency>

		<!--spring整合mybatis-plus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.6</version>
		</dependency>

		<!--spring整合redis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
		</dependency>

		<!--springBoot整合JSP添加依赖 -->
		<!--servlet依赖 注意与eureka整合时的问题 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
		</dependency>

		<!--jstl依赖 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>

		<!--使jsp页面生效 -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

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

		<dependency>
			<groupId>com.alibaba.boot</groupId>
			<artifactId>dubbo-spring-boot-starter</artifactId>
			<version>0.2.0</version>
		</dependency>
	</dependencies>
	<modules>
		<module>dubbo-jt-demo-interface</module>
		<module>dubbo-jt-demo-provider</module>
		<module>dubbo-jt-demo-consumer</module>
		<module>dubbo-jt-demo-provider2</module>
	</modules>
</project>

2. Create an interface module
. Note: The child project needs to inherit the parent project.

2.1 Interface pom.xml file

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.jt.dubbo</groupId>
    <artifactId>dubbo-jt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>dubbo-jt-demo-interface</artifactId>
</project>

2.2 Define the interface
Insert image description here
Insert image description here
3. Create a service producer
Note: The child project needs to inherit the parent project and depend on the interface module

2.1 Producer pom.xml

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.jt.dubbo</groupId>
    <artifactId>dubbo-jt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>dubbo-jt-demo-provider</artifactId>
  <dependencies>
  	<dependency>
  		<groupId>com.jt.dubbo</groupId>
  		<artifactId>dubbo-jt-demo-interface</artifactId>
  		<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  </dependencies>
</project>

2.2 Provider yml configuration file

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的包路径,扫描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  #这个是dubbo的端口,每一个服务都有自己特定的端口 不能重复.消费者通过dubbo协议+ip(dubbo动态生成的)+端口号来调用提供者

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

2.3 Define the producer’s implementation class

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. Service consumer
Note: This child project needs to inherit the parent project and depend on the interface module

4.1 Consumer pom.xml

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.jt.dubbo</groupId>
    <artifactId>dubbo-jt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>dubbo-jt-demo-consumer</artifactId>
  <dependencies>
  	<dependency>
  		<groupId>com.jt.dubbo</groupId>
  		<artifactId>dubbo-jt-demo-interface</artifactId>
  		<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  </dependencies>
</project>

4.2 Edit YML configuration file

server:
  port: 9001
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

4.2 Edit Controller

package com.jt.dubbo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;

@RestController
public class UserController {
    
    
	
	//利用dubbo的方式为接口创建代理对象 利用rpc调用
	//调用远程服务就像调用自己的服务一样的简单!!!
	@Reference
	private UserService userService; 
	
	/**
	 * Dubbo框架调用特点:远程RPC调用就像调用自己本地服务一样简单
	 * @return
	 */
	@RequestMapping("/findAll")
	public List<User> findAll(){
    
    
		
		//远程调用时传递的对象数据必须序列化.
		return userService.findAll();
	}
	
	@RequestMapping("/saveUser/{name}/{age}/{sex}")
	public String saveUser(User user) {
    
    
		
		userService.saveUser(user);
		return "用户入库成功!!!";
	}
}


5. Dubbo load balancing strategy

5.1 Client load balancing
Dubbo/SpringCloud and other microservice frameworks
Insert image description here
5.2 Server load balancing
Note: After the client initiates a request, a unified server must perform load balancing, and all pressure is on the server.
NGINX
Insert image description here
5.3 Dubbo load balancing method

@RestController
public class UserController {
    
    
	
	//利用dubbo的方式为接口创建代理对象 利用rpc调用
	//@Reference(loadbalance = "random")			//默认策略  负载均衡随机策略
	//@Reference(loadbalance = "roundrobin")		//轮询方式
	//@Reference(loadbalance = "consistenthash")	//一致性hash  消费者绑定服务器提供者
	@Reference(loadbalance = "leastactive")			//挑选当前负载小的服务器进行访问
	private UserService userService; 

}

2. User single sign-in

Project structure chart

Insert image description here

1.SSO single sign-in instructions

1. Single sign-on (SingleSignOn, SSO) is a one-time authentication login by the user. When a user logs in once on the identity authentication server, he or she can gain access to other related systems and application software in the single sign-on system. At the same time, this implementation does not require the administrator to modify the user's login status or other information. This means that in multiple application systems, ** users only need to log in once to access all mutually trusted application systems. **This method reduces the time consumption caused by logging in and assists user management. It is currently more popular[1]

2. Single sign-in design for Jingtao project

Insert image description here

Implementation steps:
1. When the user enters the user name and password and clicks to log in, the request is sent to the JT-WEB consumer server.
2. The JT-WEB server passes the user information to the JT-SSO single sign-on system to complete data verification.
3 .If the login is successful, the key information is dynamically generated, and the user data is converted into json and saved in redis. Pay attention to the timeout setting. 4.JT
-SSO passes the login credentials to the JT-WEB server.
5.JT -The WEB server saves the user key TICKET information into the user's cookie. Pay attention to the timeout setting.
6. If the login is unsuccessful, just return the error message directly.

2. Implementation of user single sign-on

1.Edit UserController

Regarding cookie sharing settings:
Whether the domain name is clearly specified is equivalent to a switch. If the switch is turned on (clearly specified), the subdomain can be shared. If the switch is turned off (the domain name is not known, use the default value), it means that only the current domain is available! Also delete When adding, if the domain is explicitly specified when adding, it also needs to be explicitly specified when deleting. Otherwise, two values ​​will appear for the same cookie name.

 /**
     * 完成用户登录操作
     * 1.url地址: http://www.jt.com/user/doLogin?r=0.9309436837648131
     * 2.参数:    {username:_username,password:_password},
     * 3.返回值结果:  SysResult对象
     *
     * 4.Cookie:
     *   4.1 setPath("/")  path表示如果需要获取cookie中的数据,则url地址所在路径设定.
     *       url:http://www.jt.com/person/findAll
     *       cookie.setPath("/");   一般都是/
     *       cookie.setPath("/person");
     *   4.2 setDomain("xxxxx")  设定cookie共享的域名地址.
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public SysResult doLogin(User user, HttpServletResponse response){
    
    
        String ticket = userService.doLogin(user);
        if(StringUtils.isEmpty(ticket)){
    
    
            //说明用户名或者密码错误
            return SysResult.fail();
        }else{
    
    
            //1.创建Cookie
            Cookie cookie = new Cookie("JT_TICKET",ticket);
            cookie.setMaxAge(7*24*60*60);   //设定cookie存活有效期
            cookie.setPath("/");            //设定cookie有效范围
            cookie.setDomain("jt.com");     //设定该cookie子域名可共享, 是实现单点登录必备要素
            response.addCookie(cookie);
            return SysResult.success();     //表示用户登录成功!!
        }
    }

2. Edit UserService

/**
     * 1.获取用户信息校验数据库中是否有记录
     * 2.有  开始执行单点登录流程
     * 3.没有 直接返回null即可
     * @param user
     * @return
     */
    @Override
    public String doLogin(User user) {
    
      //username/password
        //1.将明文加密
        String md5Pass =
                DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        //根据对象中不为null的属性当做where条件.
        User userDB = userMapper.selectOne(queryWrapper);
        if(userDB == null){
    
    
            //用户名或密码错误
            return null;
        }else{
    
     //用户名和密码正确  实现单点登录操作
            String ticket = UUID.randomUUID().toString();
            //如果将数据保存到第三方 一般需要脱敏处理
            userDB.setPassword("123456你信不??");
            String userJSON = ObjectMapperUtil.toJSON(userDB);
            jedisCluster.setex(ticket, 7*24*60*60, userJSON);
            return ticket;
        }
    }

3. User exits: You need to delete the corresponding cache and cookies

1. Data echo after logging in

js page analysis
Insert image description here
2. Edit the UserController of jt-sso

 /**
     * 业务说明:
     *   通过跨域请求方式,获取用户的JSON数据.
     *   1.url地址:  http://sso.jt.com/user/query/efd321aec0ca4cd6a319b49bd0bed2db?callback=jsonp1605775149414&_=1605775149460
     *   2.请求参数:  ticket信息
     *   3.返回值:   SysResult对象 (userJSON)
     *   需求: 通过ticket信息获取user JSON串
     */
    @RequestMapping("/query/{ticket}")
    public JSONPObject findUserByTicket(@PathVariable String ticket,String callback){
    
    

        String userJSON = jedisCluster.get(ticket);
        if(StringUtils.isEmpty(userJSON)){
    
    

            return new JSONPObject(callback, SysResult.fail());
        }else{
    
    
            return new JSONPObject(callback, SysResult.success(userJSON));
        }
    }

Insert image description here

3. Exit the business.
When the user clicks to exit the operation, he should be redirected to the system homepage and delete the redis information/Cookie information at the same time.

3.1 Edit Cookie Tool API

package com.jt.util;

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

public class CookieUtil {
    
    

    //1.新增cookie
    public static void addCookie(HttpServletResponse response,String cookieName, String cookieValue, int seconds, String domain){
    
    
        Cookie cookie = new Cookie(cookieName,cookieValue);
        cookie.setMaxAge(seconds);
        cookie.setDomain(domain);
        cookie.setPath("/");
        response.addCookie(cookie);
    }


    //2.根据name查询value的值
    public static String getCookieValue(HttpServletRequest request,String cookieName){
    
    

        Cookie[] cookies = request.getCookies();
        if(cookies !=null && cookies.length >0){
    
    
            for (Cookie cookie : cookies){
    
    
                if(cookieName.equals(cookie.getName())){
    
    
                    return cookie.getValue();
                }
            }
        }
        return null;
    }



    //3.删除cookie
    public static void deleteCookie(HttpServletResponse response,String cookieName,String domain){
    
    

        addCookie(response,cookieName,"",0, domain);
    }
}


3.1Edit UserController

	/**
     * 实现用户的退出操作.重定向到系统首页
     * url: http://www.jt.com/user/logout.html
     * 业务:
     *      1.删除Redis中的数据  key
     *      2.删除Cookie记录
     */
    @RequestMapping("logout")
    public String logout(HttpServletRequest request,HttpServletResponse response){
    
    
        //1.根据JT_TICKET获取指定的ticket
        String ticket = CookieUtil.getCookieValue(request,"JT_TICKET");

        //2.判断ticket是否为null
        if(!StringUtils.isEmpty(ticket)){
    
    
            jedisCluster.del(ticket);
            CookieUtil.deleteCookie(response,"JT_TICKET","jt.com");
        }

        return "redirect:/";
    }

3. Implement shopping cart

1. New addition to shopping cart

1.1 Business description

Business description: When the shopping cart clicks Add, it needs to be redirected to the shopping cart list page.
Notes on completing the "Add" shopping cart: If the user repeatedly adds the shopping cart, only the shopping cart quantity will be updated. If the shopping cart If there is no record, add new data.

1.2 Edit CartController

  /**
     * 购物车新增操作
     * url地址:http://www.jt.com/cart/add/1474391990.html
     * url参数: 购物车属性数据
     * 返回值:  重定向到购物车列表页面
     */
    @RequestMapping("/add/{itemId}")
    public String addCart(Cart cart){
    
    
        Long userId = 7L;
        cart.setUserId(userId);
        cartService.addCart(cart);
        return "redirect:/cart/show.html";
    }

1.3 Edit CartService

/**
     * 如果购物车已存在,则更新数量,否则新增.
     * 如何判断购物车数据是否存在   userId itemId
     *
     * @param cart
     */
    @Override
    public void addCart(Cart cart) {
    
    
        //1.查询购物车信息 userId,itemId
        QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", cart.getUserId());
        queryWrapper.eq("item_id",cart.getItemId());
        Cart cartDB = cartMapper.selectOne(queryWrapper);
        if(cartDB == null){
    
    
            //第一次新增购物车
            cartMapper.insert(cart);
        }else{
    
    
            //用户已经加购,更新数量
            int num = cartDB.getNum() + cart.getNum();
            Cart cartTemp = new Cart();
            cartTemp.setNum(num).setId(cartDB.getId());
            cartMapper.updateById(cartTemp);
        }
    }

2. Shopping cart permission control

Requirements: If the user is not logged in, access to the shopping cart list page is not allowed. If the user is not logged in, the user should be redirected to the user login page.

2.1 SpringMVC calling schematic diagram

Insert image description here

2.2 How the interceptor works

Insert image description here

2.3 Edit Cookie Tool API

package com.jt.util;

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

public class CookieUtil {
    
    

    //1.新增cookie
    public static void addCookie(HttpServletResponse response,String cookieName, String cookieValue, int seconds, String domain){
    
    
        Cookie cookie = new Cookie(cookieName,cookieValue);
        cookie.setMaxAge(seconds);
        cookie.setDomain(domain);
        cookie.setPath("/");
        response.addCookie(cookie);
    }


    //2.根据name查询value的值
    public static String getCookieValue(HttpServletRequest request,String cookieName){
    
    

        Cookie[] cookies = request.getCookies();
        if(cookies !=null && cookies.length >0){
    
    
            for (Cookie cookie : cookies){
    
    
                if(cookieName.equals(cookie.getName())){
    
    
                    return cookie.getValue();
                }
            }
        }
        return null;
    }



    //3.删除cookie
    public static void deleteCookie(HttpServletResponse response,String cookieName,String domain){
    
    

        addCookie(response,cookieName,"",0, domain);
    }
}


2.4 Edit ThreadLocal tool API

Name: Local thread variable
Function: Can realize data sharing within the same thread.

public class UserThreadLocal {
    
    

    //static不会影响影响线程  threadLocal创建时跟随线程.
    //private static ThreadLocal<Map<k,v>> threadLocal = new 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();
    }

}

2.5 Edit interceptor configuration

package com.jt.config;

import com.jt.interceptor.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfigurer implements WebMvcConfigurer{
    
    
	
	//开启匹配后缀型配置
	@Override
	public void configurePathMatch(PathMatchConfigurer configurer) {
    
    
		
		configurer.setUseSuffixPatternMatch(true);
	}

	//配置拦截器策略
	@Autowired
	private UserInterceptor userInterceptor;
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
		registry.addInterceptor(userInterceptor)
				.addPathPatterns("/cart/**","/order/**");
	}
}


2.6 Edit interceptors

Description: Dynamically obtain userId through interceptor

@Component  //spring容器管理对象
public class UserInterceptor implements HandlerInterceptor {
    
    

    @Autowired
    private JedisCluster jedisCluster;

  /**
     * 参数介绍:
     * @param1  request  用户请求对象
     * @param2  response 服务器响应对象
     * @param3  handler  当前处理器本身
     * @return  Boolean  true 请求放行    false 请求拦截  一般配合重定向使用,页面跳转到登录页面 使得程序流转起来
     * @throws Exception
     * 如果用户不登录则重定向到登录页面
     * 需求:   拦截/cart开头的所有的请求进行拦截.,并且校验用户是否登录.....
     * 拦截器选择: preHandler
     * 需求:   如何判断用户是否登录?
     * 依据:   1.根据cookie    2.判断redis
     * 
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //1.判断用户是否登录  检查cookie是否有值
        String ticket = CookieUtil.getCookieValue(request,"JT_TICKET");
        //2.校验ticket
        if(!StringUtils.isEmpty(ticket)){
    
    
            //3.判断redis中是否有值.
            if(jedisCluster.exists(ticket)){
    
    
                //4.动态获取json信息
                String userJSON = jedisCluster.get(ticket);
        		User user = ObjectMapperUtil.toObj(userJSON,User.class);
        		//方法一: 利用request来传值
                request.setAttribute("JT_USER",user);
                //方法二: 利用ThreadLocal来传值
                UserThreadLocal.set(user);
                return true;  //表示用户已经登录.
            }
        }
         //重定向到用户登录页面
        response.sendRedirect("/user/login.html");
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        //销毁数据
        request.removeAttribute("JT_USER");
    }
}

4. Order module

1. Order form design

Insert image description here

2. Build jt-order project

Insert image description here

3. Order confirmation page jumps

3.1 Edit OrderController

package com.jt.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Cart;
import com.jt.service.DubboCartService;
import com.jt.service.DubboOrderService;
import com.jt.thread.UserThreadLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/order")
public class OrderController {
    
    

    @Reference(timeout = 3000,check = false)
    private DubboOrderService orderService;
    @Reference(timeout = 3000,check = false)
    private DubboCartService cartService;

    /**
     * 订单页面跳转
     * url: http://www.jt.com/order/create.html
     * 页面取值: ${carts}
     */
    @RequestMapping("/create")
    public String create(Model model){
    
    

        //1.根据userId查询购物车信息
        Long userId = UserThreadLocal.get().getId();
        List<Cart> cartList = cartService.findCartListByUserId(userId);
        model.addAttribute("carts",cartList);
        return "order-cart";
    }
}


3.2 Edit OrderService

@Override
    public List<Cart> findCartListByUserId(Long userId) {
    
    
        QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        return cartMapper.selectList(queryWrapper);
    }

4. Order submission

4.1 Edit OrderController

 /**
     * 订单提交
     * url: http://www.jt.com/order/submit
     * 参数: 整个form表单
     * 返回值: SysResult对象   携带返回值orderId
     * 业务说明:
     *   当订单入库之后,需要返回orderId.让用户查询.
     */
    @RequestMapping("/submit")
    @ResponseBody
    public SysResult saveOrder(Order order){
    
    
        Long userId = UserThreadLocal.get().getId();
        order.setUserId(userId);
        String orderId = orderService.saveOrder(order);
        if(StringUtils.isEmpty(orderId))
            return SysResult.fail();
        else
            return SysResult.success(orderId);

    }

4.2 Edit OrderService

@Service(timeout = 3000)
public class DubboOrderServiceImpl implements DubboOrderService {
    
    

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;
    @Autowired
    private OrderShippingMapper orderShippingMapper;

    /**
     * Order{order订单本身/order物流信息/order商品信息}
     * 难点:  操作3张表完成入库操作
     * 主键信息: orderId
     * @param order
     * @return
     */
    @Override
    public String saveOrder(Order order) {
    
    
        //1.拼接OrderId
        String orderId =
                "" + order.getUserId() + System.currentTimeMillis();
        //2.完成订单入库
        order.setOrderId(orderId).setStatus(1);
        orderMapper.insert(order);

        //3.完成订单物流入库
        OrderShipping orderShipping = order.getOrderShipping();
        orderShipping.setOrderId(orderId);
        orderShippingMapper.insert(orderShipping);

        //4.完成订单商品入库
        List<OrderItem> orderItems = order.getOrderItems();
        //批量入库  sql: insert into xxx(xxx,xx,xx)values (xx,xx,xx),(xx,xx,xx)....
        for (OrderItem orderItem : orderItems){
    
    
            orderItem.setOrderId(orderId);
            orderItemMapper.insert(orderItem);
        }
        System.out.println("订单入库成功!!!!");
        return orderId;
    }
}

5. The order is successfully transferred

5.1 Edit OrderController

 /**
     * 实现商品查询
     * 1.url地址: http://www.jt.com/order/success.html?id=71603356409924
     * 2.参数说明: id 订单编号
     * 3.返回值类型: success.html
     * 4.页面取值方式: ${order.orderId}
     */
    @RequestMapping("/success")
    public String findOrderById(String id,Model model){
    
    
        Order order = orderService.findOrderById(id);
        model.addAttribute("order",order);
        return "success";
    }

5.2 Edit OrderService

 @Override
    public Order findOrderById(String id) {
    
    
        //1.查询订单信息
        Order order  = orderMapper.selectById(id);
        //2.查询订单物流信息
        OrderShipping orderShipping = orderShippingMapper.selectById(id);
        //3.查询订单商品
        QueryWrapper<OrderItem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id",id);
        List<OrderItem> lists =orderItemMapper.selectList(queryWrapper);
        return order.setOrderItems(lists).setOrderShipping(orderShipping);
    }

5.3 Page effect display

Insert image description here

Guess you like

Origin blog.csdn.net/m0_49353216/article/details/109727237