一、项目环境
- 后端技术栈:Mybatis, SpringBoot-2.2.5.RELEASE,SpringCloud(nacos+gateway)
- 数据库:mySql; Redis
- 软体:jdk1.8; IntelliJ IDEA2019; Centos7虚拟机; MobaXterm; RDM; Postman
二、文章主题
- 内容概述:为实现SpringCloud_Video项目,对其中P1~21部分内容进行复现。
- 项目源码:videoProject01_pub : dev02
- 功能展示:
三、文章内容
1. 后台管理系统的编写
- Step1:虚拟机配置双网卡,使虚拟机网络ip稳定。
- Step2:构建maven父项目 qs-admin: 用于维护依赖坐标的版本。
- Step3:构建maven子公共项目 qs-commons: 用于维护公共工具类、代码、依赖
- Step4:构建子微服务模块:qs-category, qs-users, qs-videos, qs-admins, qs-gateway
注意:对于子微服务模块,在commons中引入springbootweb依赖,子微服务引入commons依赖;而gateway引commons依赖要排除commons中的springbootweb依赖,即gateway是特殊的springboot应用。 - Step5:子微服务模块写配置yml文件,构建出springboot应用。
- Step6:写docker-compose.yml,实现nacos注册中心:
version: "3.3"
networks:
easywatch_network:
services:
nacos:
image: nacos/nacos-server:2.0.2
ports:
- "8848:8848"
environment:
- "JVM_XMS=256m"
- "JVM_XMX=256m"
- "MODE=standalone"
networks:
- easywatch_network
(1) 在虚拟机docker上启动nacos==>docker-compose up -d,此时外部可访问nacos注册中心(http://xxx.xxx.xxx.xxx:8848/nacos/#/login,用户名密码皆为nacos);
(2) 在commons中引入nacos依赖,由此全部子微服务就引入了nacos依赖,在全部子微服务的yml配置文件中配置nacos,重新run子微服务后即可全部注册到nacos。
- Step7:每个子微服务写测试用的DemoController,而后写gateway微服务
(1) gateway的作用是 rouer路由+filter过滤;
(2) 由于所有后端接口的入口都是网关,所以在此处可以进行token身份认证;
(3) 再次强调,网关是一种特殊的springboot微服务,不需要springweb依赖。
(4) 测试时,先引网关依赖,再写网关配置yml文件即可。详见视频P3
(5) predicates(断言),是前端请求进入网关之前的一个验证,满足条件即可进入,不满足便无法进入;但是在进入之后,在网关转发前端请求之前要加入filter过滤器,而后才能进入后端微服务。
(6) 网关yml配置文件中还要加入允许跨域的配置。 - Step8:配置docker-compose.yml,使得虚拟机通过docker接入数据库
volumes:
data:
services:
mysql:
image: mysql:5.7
ports:
- "3306:3306"
networks:
- easywatch_network
volumes:
- data:/var/lib/mysql
- ./easywatch.sql:/docker-entrypoint-initdb.d/easywatch.sql
environment:
- "MYSQL_ROOT_PASSWORD=root"
- "MYSQL_DATABASE=easywatch"
- Step9:写接口
其中,docker-compose中配置redis的代码为:
volumes:
redisdata:
services:
redis:
image: redis:5.0.10
ports:
- "6379:6379"
volumes:
- redisdata:/data
- Step10:前端测试
对于dist文件,执行 npm install -p anywhere(全局安装anywhere,anywhere是一个小型服务器,可在win环境下运行dist文件),而后执行anywhere -p xxxx。
2. 后台总结之redis的使用
- 推荐使用的redis方式:在书写业务先关数据时一定要加入对应的业务key信息(一般是一个固定写死的常量),设计如下:
// 接口中的属性都是公开的静态的常量
public interface RedisPrefix {
String TOKEN_KEY = "TOKEN:"; // 代表用户认证tokenkey
}
- redisTemplate的使用:
(1) 问题:1)redisTemplate操作对象时key和value都是对象,而我们往往需要key是String而value是对象;2)redisTemplate存放对象必须实现对象序列化。
(2) 改造:1)key使用String序列化方法; 2)value放入的对象修改为json序列化方式。
(3) 途径:构建ReidsTemplateConfig配置类:
package com.salieri.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
// 工厂在启动过程中会走这个配置类
// 工厂启动后这个配置类就做完了
@Configuration
public class RedisTemplateConfig {
@Autowired // 这说明构造方法中要给这个类注入这个对象,但实际上此时没用到这个对象
public RedisTemplateConfig(RedisTemplate redisTemplate) {
//1.创建jackson序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
//2..创建object mapper
ObjectMapper objectMapper = new ObjectMapper();
// 3,4得redis中的json可被转换回来
//3.允许访问对象中所有属性
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//4.转换json过程中保存类的信息
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//5.设置value的序列化规则和 key的序列化规则 //key String hashkey:String hashvalue: json序列化
StringRedisSerializer stringKeySerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringKeySerializer);
//6.jackson2JsonRedisSerializer就是JSON序列号规则,
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//6.5 设置hash类型key value 序列化方式 (hash--> key:string + hashkey:string + hashvalue:json序列化)
redisTemplate.setHashKeySerializer(stringKeySerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//7工厂创建redisTemplate对象之后在进行配置
redisTemplate.afterPropertiesSet();
}
}
3. 后台总结之自定义FilterFactory
现有admin服务、users服务、category服务、videos服务,可见除了admin服务都需要进行权限保护,需要局部过滤来处理。即需要在网关中统一处理接口的权限认证。
- Step1:自定义网关FilterFactory局部过滤
已知springcloud gateway中默认提供filterFactory,其中GatewayFilterFactory是父接口。那么,我们可以自定义FilterFacotry:
代码如下:
// 自定义token工厂
@Component // 代表在工厂中创建对象 // @configuration是配置的意思,这里推荐用轻量级Component
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {
private static final Logger log = LoggerFactory.getLogger(TokenGatewayFilterFactory.class);
@Autowired
private RedisTemplate redisTemplate;
public TokenGatewayFilterFactory() {
super(Config.class);
}
// 重心
@Override
public GatewayFilter apply(Config config) {
// 传统web模型: servlet的service方法 或 springmvc中 需要两个参数: httpServletRequest httpServletResponse
// 新web模型: springwebflux 需要一个参数: ServerWebExchange exchange
// 传统javaweb中的Filter需要参数: request response filterchain.dofilter(request,response)
// 新web模型: chian.filter(exchange)进行放行
// 新web模型的使用: 在gateway的application.yml中在相应的微服务上编写 filters: -Token
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("进入filter");
// 1. 获取token信息
if(exchange.getRequest().getQueryParams().get("token") == null) throw new RuntimeException("非法令牌");
String token = exchange.getRequest().getQueryParams().get("token").get(0);
log.info("token:{}",token);
// 2. 根据token信息获取redis
if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY+token)) throw new RuntimeException("不合法的令牌");
return chain.filter(exchange);
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return null;
}
public static class Config {
}
}
4. 后台总结之网关的异常处理
- 已知后端传递给前端的应是json数据,现在报错信息是一张张html(web机制要的是页面,前后端分离机制要的是json)
- 网关的异常处理机制-----webflux编程
Step1:自定义类 gateway-> config.GlobalExceptionConfiguration:
// @Order //order执行顺序: int代表多个异常处理时哪个先执行
@Override
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {}
Step2:自定义异常 gateway -> exceptions.IllegalTokenException:
package com.salieri.exceptions;
// 自定义异常
public class IllegalTokenException extends RuntimeException {
public IllegalTokenException(String message) {
super(message);
}
}
======================
而后在网关报异常时使用
Step3:在config.GlobalExceptionConfiguration中注意状态码的设置。
5. 微服务架构的部署分析
- Step1:对现有微服务系统进行打包
(1) maven聚合开发:打包时不能在单独某个module打包,必须在root根项目上打包,即所有module一次性全部打包。
(2) springboot项目打包注意事项: 执行jar包出现这个错误: xxx-1.0-SNAPSHOT.jar中没有主清单属性。 解决方案:引入部署插件(只能放在springboot应用中)
<!-- 引入部署插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.salieri.AdminGatewayApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
- Step2:打包完成后,在整个微服务架构中创建images文件夹
Dockerfile一例:
FROM openjdk:8-jre
ENV APP_PATH=/apps
WORKDIR $APP_PATH
COPY easywatch_category-1.0-SNAPSHOT.jar $APP_PATH/app.jar
EXPOSE 8981
ENTRYPOINT ["java","-jar"]
CMD ["app.jar"]
- Step3:更改admin-dist->static->js->app.0ebddf58.js中的后端连接地址,编写最终的docker-compose.yml
version: "3.3"
networks:
easywatch_network:
volumes:
data:
redisdata:
services:
nacos:
image: nacos/nacos-server:2.0.2
ports:
- "8848:8848"
environment:
- "JVM_XMS=256m"
- "JVM_XMX=256m"
- "MODE=standalone"
networks:
- easywatch_network
mysql:
image: mysql:5.7
ports:
- "3306:3306"
networks:
- easywatch_network
volumes:
- data:/var/lib/mysql
- ./easywatch.sql:/docker-entrypoint-initdb.d/easywatch.sql
environment:
- "MYSQL_ROOT_PASSWORD=root"
- "MYSQL_DATABASE=easywatch"
redis:
image: redis:5.0.10
ports:
- "6379:6379"
volumes:
- redisdata:/data
admins:
build:
context: ./images/admins
dockerfile: Dockerfile
ports:
- "8980:8980"
depends_on:
- mysql
- nacos
- redis
admins01:
build:
context: ./images/admins
dockerfile: Dockerfile
ports:
- "8988:8980"
depends_on:
- mysql
- nacos
- redis
category:
build:
context: ./images/category
dockerfile: Dockerfile
ports:
- "8981:8981"
depends_on:
- mysql
- nacos
- redis
gateway:
build:
context: ./images/gateway
dockerfile: Dockerfile
ports:
- "9999:9999"
depends_on:
- mysql
- nacos
- redis
users:
build:
context: ./images/users
dockerfile: Dockerfile
ports:
- "8982:8982"
depends_on:
- mysql
- nacos
- redis
videos:
build:
context: ./images/videos
dockerfile: Dockerfile
ports:
- "8983:8983"
depends_on:
- mysql
- nacos
- redis
nginx:
image: nginx:1.21.1
ports:
- "80:80"
volumes:
- ./admin-dist:/usr/share/nginx/html:ro
- centos7虚拟机中的容器状态:
- nacos注册中心中的微服务状态: