1.默认条件下 SpringMVC只能拦截前缀型请求. www.jt.com/item/562379
2.如果请求路径添加了后缀,则后缀会误当做请求参数,参与运算.
562379.html一起当做参数来使用,导致参数异常.
说明:开启后缀类型匹配标识 如果请求 以.html/.do/.action等操作结尾时依然会被SpringMVC正确的拦截,并且后缀不参与参数的传递
编辑配置类
@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);
}
}
为什么要在请求的结尾添加.html的后缀
添加.html的目的是为了让搜索引擎更加容易的记录页面.提高网站的曝光率.
搜索引擎的规则: 搜索引擎一般会记录公司中.html结尾的页面之后记录在数据库中并且为其创建索引提高用户的检索效率
1.同源策略:
浏览器规定: 发起ajax时,如果ajax请求协议/域名/端口号如果与当前的浏览器的地址相同时,则满足同源策略.浏览器可以正常的解析返回值. 如果三者之间有一个不同,则违反了同源策略.浏览器不会解析返回值.
2. 什么是跨域
由于业务需要,通常A服务器中的数据可能来源于B服务器. 当浏览器通过网址解析页面时,如果页面内部发起ajax请求.如果浏览器的访问地址与Ajax访问地址不满足同源策略时,则称之为跨域请求.
跨域问题: 违反同源策略的规定就是跨域请求.
跨域要素:
1.浏览器
2.解析ajax
3.违反了同源策略
3.如何解决跨域问题?
3.1.JSONP跨域访问,可用于解决主流浏览器的跨域数据访问的问题
JSONP跨域原理:
1)利用javascrpit中的src属性实现跨域请求.
2)自定义回调函数 function callback(xxxx);
3)将返回值结果进行特殊的格式封装 callback(json);
4)由于利用src属性进行调用 所以只能支持get请求类型.
1.业务需求
当用户在注册时,如果输入用户名,则应该向jt-sso单点登录系统发起请求,校验用户数据是否存在.如果存在则提示用户.
例子:业务接口文档说明:
2.编辑 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
*/
@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.编辑 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.修改全局异常处理
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跨域实现
1)CORS介绍
因为出于安全的考虑, 浏览器不允许Ajax调用当前源之外的资源. 即浏览器的同源策略.
CORS需要浏览器和服务器同时支持。目前,所有主流浏览器都支持该功能,IE浏览器不能低于IE10。在浏览器端, 整个CORS通信过程都是浏览器自动完成,在请求之中添加响应头信息,如果服务器允许执行跨域访问.,则浏览器的同源策略放行.
2)配置后端服务器
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.关于跨域说明
1.什么叫跨域 浏览器解析Ajax时,发起url请求违反了同源策略时,称之为跨域.
2.什么时候用跨域 一般A服务器需要从B服务器中获取数据时,可以采用跨域的方式.
3.什么是JSONP JSONP是JSON的一种使用模式 利用javaScript中的src属性进行跨域请求.(2.自定义回调函数,3.将返回值进行特殊格式封装)
4.什么是CORS CORS是当前实现跨域的主流方式,现在所有的主流浏览器都支持,需要在服务器端配置是否允许跨域的配置. 只要配置了(在响应头中添加允许跨域的标识),则同源策略不生效,则可以实现跨域.
1.RPC远程调用定义
RPC(Remote Procedure Call)远程过程调用协议,简单来说是一个节点请求另一个节点提供的服务。RPC是伴随着分布式的出现的,因为分布式客户端和服务端部署在不同的机器上,所以需要远程调用。
2.几大RPC框架介绍
1.支持多语言的RPC框架,google的gRPC,Apache(facebook)的Thrift
2.只支持特定语言的RPC框架,例如新浪的Motan
3.支持服务治理等服务化特性的分布式框架,例如阿里的dubbo
4.拥有完整生态的spring cloud
3.微服务调用原理说明
3.1标准:
1.根据业务拆分的思想 进行了分布式的设计
2.当服务发生异常时可以自动的实现故障的迁移 无需人为的干预.
3.2 传统服务调用方式
说明: 由于nginx做负载均衡时需要依赖配置文件.但是当服务器新增/减少时.都需要手动的修改.不能自动化的实现.所以暂时达不到微服务的标准.
3.3微服务的调用原理介绍
步骤:
1.Dubbo的作用
Dubbo是阿里巴巴开源的 RPC 分布式服务框架.内部使用了Netty、Zookeeper,保证了高性能高可用性。
简单的说,Dubbo 就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有 Dubbo 这样的分布式服务框架的需求,并且本质上是个服务调用的东西,说白了就是个远程服务调用的分布式框架。
节点角色说明
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明
0.服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
这个框架要完成调度必须要有一个分布式的注册中心,存储所有服务的元数据,用到zookeeper
2.Zookeeper的作用
zookeeper是一个提供分布式协调服务的框架,解决了分布式一致性问题,主要是用于dubbo框架的注册中心
哪一个服务由哪一个机器来提供必须让调用者知道,简单来说就是ip地址和服务器名称的对应关系。 当然也可以将这种对应关系通过硬编码在调用方业务代码中实现,但是如果提供服务的机器挂掉调用方无法知晓,如果不更改代码会继续请求挂掉的机器提供服务。zookeeper可以通过心跳机制检测挂掉的服务器并将挂掉的服务器的ip和服务器对应的关系从列表中删除。
3.Zookeeper和Dubbo的关系
3.1 Dubbo将注册中心进行抽象,使得它可以外接不同的存储媒介给注册中心提供服务。有ZooKeeper,Memcached,Redis等。
3.2 引入zookeeper作为存储媒介,也就把zookeeper的特性引了进来。
0.spring整合dubbo需要添加依赖
添加jar包文件
<!--引入dubbo配置 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
父级工程的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>
<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.创建接口模块
注意:该子级工程需要继承父级工程
2.1 接口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-interface</artifactId>
</project>
2.2 定义接口
3.创建服务生产者
注意:该子级工程需要继承父级工程,并且要依赖接口模块
2.1 生产者的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 提供者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的包路径,扫描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 定义生产者的实现类
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.1 消费者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 编辑YML配置文件
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 编辑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负载均衡策略
5.1 客户端负载均衡
Dubbo/SpringCloud等微服务框架
5.2服务端负载均衡
说明:客户端发起请求之后,必须由统一的服务器进行负载均衡,所有的压力都在服务器中.
NGINX
5.3 Dubbo负载均衡方式
@RestController
public class UserController {
//利用dubbo的方式为接口创建代理对象 利用rpc调用
//@Reference(loadbalance = "random") //默认策略 负载均衡随机策略
//@Reference(loadbalance = "roundrobin") //轮询方式
//@Reference(loadbalance = "consistenthash") //一致性hash 消费者绑定服务器提供者
@Reference(loadbalance = "leastactive") //挑选当前负载小的服务器进行访问
private UserService userService;
}
1.单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,**用户只需一次登录就可以访问所有相互信任的应用系统。**这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的 [1]
2.京淘项目单点登录设计
实现步骤:
1.当用户输入用户名和密码点击登录时,将请求发送给JT-WEB消费者服务器.
2.JT-WEB服务器将用户信息传递给JT-SSO单点登录系统完成数据校验.
3.如果登录成功,则动态生成密钥信息,将user数据转化为json.保存到redis中. 注意超时时间的设定.
4.JT-SSO将登录的凭证 传给JT-WEB服务器.
5.JT-WEB服务器将用户密钥TICKET信息保存到用户的cookie中 注意超时时间设定.
6.如果登录不成功,则直接返回错误信息即可.
1.编辑UserController
关于cookie共享的设置:
有没有明确指定domain域名相当于一个开关.开关打开(明确指定)则子域可共享,如果开关关闭(不知道域名,使用默认值)则表示只有当前域可用! 另外删除时,如果增加的时候明确指定了domain,删除的时候也需要明确指定,否则会导致,同一个cookie名出现两个值
/**
* 完成用户登录操作
* 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.编辑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;
}
}
1.登录后数据回显
js页面分析
2.编辑jt-sso的UserController
/**
* 业务说明:
* 通过跨域请求方式,获取用户的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));
}
}
3.退出业务
当用户点击退出操作时,应该重定向到系统首页,同时删除redis信息/Cookie信息
3.1编辑Cookie工具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.1编辑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:/";
}
业务说明: 当购物车点击新增时,需要重定向到购物车列表页面. 完成购物车"新增""
注意事项: 如果用户重复添加购物车.则只做购物车数量的更新,如果购物车没有记录,则新增数据.
/**
* 购物车新增操作
* 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";
}
/**
* 如果购物车已存在,则更新数量,否则新增.
* 如何判断购物车数据是否存在 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);
}
}
需求: 如果用户不登录,则不允许访问购物车列表页面,如果没有登录则应该重定向到用户登录页面.
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);
}
}
名称: 本地线程变量
作用: 可以在同一个线程内,实现数据的共享.
public class UserThreadLocal {
//static不会影响影响线程 threadLocal创建时跟随线程.
//private static 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();
}
}
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/**");
}
}
说明:通过拦截器动态获取userId
@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");
}
}
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";
}
}
@Override
public List<Cart> findCartListByUserId(Long userId) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
return cartMapper.selectList(queryWrapper);
}
/**
* 订单提交
* 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);
}
@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;
}
}
/**
* 实现商品查询
* 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";
}
@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);
}