畅购-第二天 FastDF文件上传、网关鉴权
当天GIT地址:第二天的GIT地址
一、跨域
出现跨域的原因:
浏览器的同源策略。在统一个协议、ip、 端口下就叫同源。不同的话就会出现跨域问题。
解决方案:
1、在controller加上一个@CrossOrigin就可以就可以允许跨域问题
2、在网关里配置
二、 通用mapper的自定义的方法
多表查询通用mapper无法帮你生成,还是需要自己实现接口的方法
-
1、根据分类的名称查询品牌信息
//根据分类的名称查询 品牌的信息 @Select("select name,image from tb_brand where id in (select brand_id from tb_category_brand where category_id in (select id from tb_category where name=#{categoryName}))") List<Map> findBrandListByCategoryName(@Param("categoryName") String categoryName);
-
2、根据分类的名称查询规格
//根据分类名称查询规格 @Select("select name,options from tb_spec where template_id in (select template_id from tb_category where name=#{categoryName})") List<Map> findSpecListByCategoryName(String categoryName);
-
3、根据分类的名称查询参数
//根据分类名称查询出场参数 @Select("select name,options from tb_para where template_id in (select template_id from tb_category where name=#{name})") List<Map> findParaListByCategoryName(String name);
三、FastDFS文件上传
上传流程:
-
1、客户端上传一张图片通过Tracker,Tracker再告诉你上传到Storage 位置。
-
2、并返回你上传的图片在Storage 位置:组名,虚拟磁盘路径,数据两级目录,文件名。
-
3、你拿着这台服务的协议、端口、IP拼接上这个地址就会获取到图片具体位置
再changgou-service下创建一个文件上传的单独的模块changgou-service-file
导入依赖:
<dependencies>
<!--springboot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--fastdfs-->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
导入文件上传的工具类:
- 1、封装上传属性的实体类工具类
- 2、上传的实体类
大致流程:
-
1、获取文件进行非空判断
-
2、获取文件的全名称
-
3、 获取文件的后缀名 例如 jpg
-
4、 获取文件的字节数组
-
5、创建文件上传的封装类,把全名称、后缀名、字节数组传入去
-
6、进行文件上传。获取到返回值一个数组。
-
7、获取数值的一个元素是组名、第二个元素是虚拟盘符等等组成的
-
8、将Storage的地址(就是虚拟机的ip地址和端口)拼接上组名和虚拟盘符等等
try { //上传文件不能为空 if (file == null) { throw new RuntimeException("文件不能为空"); } //1、获取文件的完整名称 String originalFilename = file.getOriginalFilename(); //上传文件名称不能为空 if (StringUtils.isEmpty(originalFilename)) { throw new RuntimeException("文件不能为空"); } //2、截取文件的后缀名称 例如.jpg 但是不要点 只要jpg String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); //3、获取文件内容 byte[] filenameBytes = file.getBytes(); //4、创建文件上传的实体类 全名称 内容 后缀名称 FastDFSFile fastDFSFile = new FastDFSFile(originalFilename,filenameBytes,extName); //5、文件上传 String[] upload = FastDFSClient.upload(fastDFSFile); //6、返回分装结果 fastDFS访问路径 加上组名 String url = FastDFSClient.getTrackerUrl() + upload[0] + "/" + upload[1]; return new Result(true, StatusCode.ERROR, "文件上传成功", url); } catch (Exception e) { e.printStackTrace(); return new Result(false, StatusCode.ERROR, "文件上传失败"); }
四、 微服务网关Gateway
优点如下:
- 安全 ,只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
- 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
- 易于统一认证授权。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数
总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。
网关的搭建
1)在changgou_gateway工程中,创建changgou_gateway_system工程
-
1、坐标的添加
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
yml的配置:
spring:
application:
name: sysgateway
#redis 配置 配合限流使用的
redis:
host: 192.168.200.128
cloud:
#网关的配置
gateway:
#配置了跨域请求
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix= 1 #过滤掉斜线后面第一个
- name: RequestRateLimiter #请求数限流 名字不能随便写
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 1 #令牌桶总容量
- id: system
uri: lb://system
predicates:
- Path=/system/**
filters:
- StripPrefix= 1 #过滤掉斜线后面第一个
# 将自己的服务注册到eureka上
server:
port: 9101
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true #将自己的ip注册到eureka上
在网关里配置跨域设置
过滤器的使用:
注意:
- 1、类上要加component注解
- 2、实现GlobalFilter、Ordered 两个接口
@Component
public class IpFilter implements GlobalFilter, Ordered {
/**
* 过滤器业务
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取request对象
ServerHttpRequest request = exchange.getRequest();
//获取ip
System.out.println("IP的过滤器执行了");
InetSocketAddress remoteAddress = request.getRemoteAddress();
System.out.println("IP的地址是"+remoteAddress.getHostName());
//放行
return chain.filter(exchange);
}
/**
* 过滤器执行的顺序
* @return
*/
@Override
public int getOrder() {
return 1;
}
}
网关限流
令牌桶的漏桶算法:
令牌桶算法是比较常见的限流算法之一,大概描述如下: 1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理; 2)根据限流大小,设置按照一定的速率往桶里添加令牌; 3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝; 4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除; 5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流
大致概况:
例如这个令牌桶里有200个,突然间来了300请求。有200的请求拿到了令牌去访问了服务。剩下的请求没有拿到,就被丢弃。
代码的实现漏桶
坐标的引入
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
在启动类里配置KeyResolver bean对象
/**
* 定义一个KeyResolver
* @return
*/
@Bean
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//获取请求的ip地址并对齐执行限流服务
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
};
}
在yml里的配置
- burstCapacity:令牌桶总容量。
- replenishRate:令牌桶每秒填充平均速率。
- key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
五、BCrypt密码加密
//原始方法。通过每次生成的盐不用了来达到每次的密文不同
for(int i=0;i<10;i++) {
//获取盐
String gensalt = BCrypt.gensalt();
System.out.println("盐:" + gensalt);
//基于当前的盐对密码进行加密
String saltPassword = BCrypt.hashpw("123456", gensalt);
System.out.println("加密后的密文:" + saltPassword);
//解密
boolean checkpw = BCrypt.checkpw("123456", saltPassword);
System.out.println("密码校验结果:" + checkpw);
}
System.out.println("===================================================");
//通过security框架的加密方式 封装了BCrypt
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println(encode);
//解密看看
System.out.println(passwordEncoder.matches( "123456",encode));
新增会员时密码加密
修改changgou_service_system项目的AdminServiceImpl
/**
* 增加
* @param admin
*/
@Override
public void add(Admin admin){
//通过security对密码进行添加
admin.setPassword(new BCryptPasswordEncoder().encode(admin.getPassword()));
adminMapper.insert(admin);
}
登录时判断
-
1、创建一个新的admin对象。
-
2、将传入的用户的名称和状态封装到这个新的admin对象。
-
3、拿着新的admin对象去数据库中,查询是否有这个对象信息
-
4、有的话拿着从数据库中查询上来的密文密码。和这次传入的密码进行校验。校验成功登录成功
service层的代码处理
//将传入的用户的名称和状态封装到这个新的admin对象
Admin admin1 = new Admin();
admin1.setLoginName(admin.getLoginName());
admin1.setStatus("1");
//根据用户查询密码
Admin resultAdmin = adminMapper.selectOne(admin1);
//判断是否有这个用户
if (resultAdmin == null) {
return false;
}
//判断密码不能为空
if (resultAdmin.getPassword().equals("") || resultAdmin.getPassword() == null) {
return false;
}
//判断密码是否正确 通过BCrypt来进行解密。
if (new BCryptPasswordEncoder().matches(admin.getPassword(), resultAdmin.getPassword())) {
return true;
}
return false;
六、JWT微服务的鉴权
测试token
坐标:
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
具体代码
public static void main(String[] args) {
Date systemDate = new Date();
JwtBuilder jwtBuilder = Jwts.builder()
.setId("66") //设置jwt编码
.setSubject("卢本伟牛批")//设置主题
.setIssuedAt(new Date())//设置签发日期
//.setExpiration(systemDate) //设置过期时间
.claim("name","小江")
.signWith(SignatureAlgorithm.HS256, "lbwq");//设置加密方式为HS256和 密钥 设置位数必须为3位或3位以上
//生成令牌
String jwtToken = jwtBuilder.compact();
System.out.println(jwtToken);
//解析jwt令牌
Claims lbwq = Jwts.parser().setSigningKey("lbwq").parseClaimsJws(jwtToken).getBody();
System.out.println(lbwq);
//解析得到{jti=66, sub=卢本伟牛批, iat=1601004574, name=小江}
}
鉴权的大致流程
1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
项目中的具体使用
1.1 令牌的生成
- 如果service的登录判断成功,就生成一个令牌,令牌携带这用户的名称返回给前端
/**
* 登录判断
* @param admin
* @return
*/
@PostMapping("login")
public Result login(Admin admin) {
boolean login = adminService.login(admin);
if (login) {
//给与令牌
Map<String,String> info = new HashMap<>();
info.put("username", admin.getLoginName());
//生成一个令牌
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null);
info.put("token", token);
//将令牌返回给前端
return new Result(true, StatusCode.OK, "登录成功",info);
} else {
return new Result(false, StatusCode.ERROR, "登录失败");
}
}
}
1.2 网关对令牌的解析
- 1、获取请求、响应对象
- 2、根据亲求的url进行判断是不是登录,登录就放行
- 3、 获取请求头
- 4、从亲求头获取令牌,如果没有令牌,就返回告诉用户无权访问
- 5、 如果有令牌。就对其解析。解析成功放行,失败就返回告诉用户无权访问
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求
ServerHttpRequest request = exchange.getRequest();
//获取响应
ServerHttpResponse response = exchange.getResponse();
//如果是登录就放行
if (request.getURI().getPath().contains("/admin/login")) {
return chain.filter(exchange);
}
//获取请求头
HttpHeaders headers = request.getHeaders();
//从请求头里获取令牌信息
String token = headers.getFirst("token");
//如果令牌为空,告诉浏览器无权访问
if (StringUtils.isEmpty(token)) {
//如果不存在,则向客户端返回错误提示信息
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//6.1 如果令牌存在,解析jwt令牌,判断该令牌是否合法,如果令牌不合法,则向客户端返回错误提示信息
try {
//成功放行
JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//如果解析失败,则向客户端返回错误提示信息
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//令牌认证通过放行
return chain.filter(exchange);
}
//拦截的顺序
@Override
public int getOrder() {
return 0;
}
}
e(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//6.1 如果令牌存在,解析jwt令牌,判断该令牌是否合法,如果令牌不合法,则向客户端返回错误提示信息
try {
//成功放行
JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//如果解析失败,则向客户端返回错误提示信息
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//令牌认证通过放行
return chain.filter(exchange);
}
//拦截的顺序
@Override
public int getOrder() {
return 0;
}
}