1、基础环境构建
1.1、微服务基本概念
- Provider:服务提供者,提供服务实现
- Consumer:服务调用者,调用Provider提供的服务实现
- 同一个服务可以既是Provider,又是Consumer
1.2、SpringBoot集成dubbo
详见:https://github.com/alibaba/dubbo-spring-boot-starter/blob/master/README_zh.md
1.3、注册中心概述
1.3.1、直连提供者
- 消费端知道服务提供者的地址,直接进行连接
- 该种方式一般只在测试环境中使用
- 直连提供者限制了分布式的易扩展性
1.3.2、Dubbo官方结构图
1.3.3、Zookeeper安装
详见:https://blog.csdn.net/weixin_43192102/article/details/89957432
1.3.4、SpringBoot集成zookeeper
- 引入zookeeper依赖
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
- 配置配置文件
spring.dubbo.registry=zookeeper://localhost:2181
2、业务基础环境构建
2.1、API网关介绍
2.1.1、API网关示意图
2.1.2、API网关简介
-
API网关有点类似设计模式中的Facade模式
-
API网关一般都是微服务系统中的门面
-
API网关是微服务的重要组成部分
2.1.3、API 网关的常见作用
- 身份验证和安全:是否登录、环境是否安全,上传的文件是否安全等,通常认为API网关后的所有微服务都是安全的。此处API网关相当于防火墙,将不安全因素挡掉
- 审查和监测:类似拦截器,可以对边缘数据(当前业务执行了多长时间、当前业务是否需要用户登录、当前业务调用了什么服务)进行统计
- 动态路由:将请求和服务做映射,请求经过API网关转到其他服务(Dubbo中自带不需要,Cloud中需要)
- 压力测试:往API网关中阶梯型的灌入数据(例:双十一双十二)
- 负载均衡:Dubbo中自带不需要
- 静态相应处理:动静分离,对后台业务进行保护
2.2、具体业务
2.2.1、身份安全
用户是否登录,用户信息是否安全等
2.2.2、服务聚合
例如提交订单需要做三件事(用户是否登录、库存是否够、提交订单),前端调用三个接口比较麻烦,那么API网关就会暴露一个接口,然后内部调用这三个服务
- 服务聚合就是将多个服务调用封装
- 服务聚合可以简化前端调用方式
- 服务聚合提供更好的安全性、可扩展性
2.3、Guns下载
链接:https://gitee.com/stylefeng/guns
3、API网关模块构建
- 复制一份guns-rest,命名为guns-gateway
- 修改guns总的pom文件,添加guns-gateway模块
<modules>
<module>guns-gateway</module>
</modules>
- 修改guns-gateway的pom文件对应的<artifactId>,<name>,<description>
- 修改复制出来的guns-gateway名称
- 集成dubbo和zookeeper,引入相关依赖
- 修改配置文件
spring:
application:
name: metting-gatewar
dubbo:
server: true
registry: zookeeper://localhost:2181
- 修改Application启动文件名为GatewayApplication.java,并添加
@EnableDubboConfiguration 注解
4、抽离业务接口
主要放各个业务调用的接口和实体类。
- 复制一份guns-core,命名为guns-api
- 过程同上
- 删除pom文件中的所有依赖,删除target文件,删除guns目录下的所有文件,新建api目录和相应的业务模块
- 创建接口和实体类
- 在需要引用接口的地方依赖guns-api模块,例在guns-gateway模块的pom文件中:
<!-- 注册guns-api -->
<dependency>
<groupId>com.stylefeng</groupId>
<artifactId>guns-api</artifactId>
</dependency>
- 在guns总的配置文件添加api依赖
<!-- 依赖guns-api -->
<dependency>
<groupId>com.stylefeng</groupId>
<artifactId>guns-api</artifactId>
<version>${guns.version}</version>
</dependency>
5、具体业务模块构建(例用户模块)
5.1、用户服务与网关的交互
- 复制一份guns-gateway,命名为guns-user
- 修改同guns-gateway
- 修改配置文件,关闭JWT验证,因为jwt验证已经在API网关做了
rest:
auth-open: false #jwt鉴权机制是否开启(true或者false)
sign-open: false #签名机制是否开启(true或false)
- 修改name,配置协议
spring:
application:
name: metting-user
dubbo:
server: true
registry: zookeeper://localhost:2181
#配置协议(如果修改协议只需修改name)
protocol:
name: dubbo
port: 20881
5.2、配置忽略列表
即配置什么样的URL访问路径不进行JWT验证,例如:忽略 /user/register形式的URL
- 在API网关的配置文件添加如下配置:
jwt:
ignore-url: /user/register,/user/check #配置忽略列表
- 在/config/properties/JwtProperties.java中添加private String ignoreUrl = "" 字段,并添加get和set方法
- 在/modual/auth/filter/AuthFilter.java中配置忽略列表:
//配置忽略列表:如果访问路径包括:/user, /film 则直接返回,不执行后面的验证
String ignoreUrl = jwtProperties.getIgnoreUrl();
if (!StringUtils.isEmpty(ignoreUrl)){
String[] ignoreUrls = ignoreUrl.split(",");
if (ignoreUrls.length > 0) {
for (int i = 0; i < ignoreUrls.length; i ++) {
if (request.getServletPath().equals(ignoreUrls[i])) {
chain.doFilter(request, response);
return;
}
}
}
}
5.3、修改JWT申请的返回报文
修改guns-gateway下的modular/auth/controller/AuthController.java
@Reference(interfaceClass = UserAPI.class)
private UserAPI userAPI;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@RequestMapping(value = "${jwt.auth-path}")
public ResultVo createAuthenticationToken(AuthRequest authRequest) {
//boolean validate = reqValidator.validate(authRequest); 去掉自带的用户名、密码验证机制
//编写自己的验证机制
boolean validate = true;
Integer userId = userAPI.login(authRequest.getUserName(), authRequest.getPassword());
if (userId == 0) {
validate = false;
}
if (validate) {
final String randomKey = jwtTokenUtil.getRandomKey();
//生成验证的token
final String token = jwtTokenUtil.generateToken(userId + "", randomKey);
//return ResponseEntity.ok(new AuthResponse(token, randomKey));
return ResultVoUtil.success(new AuthResponse(token, randomKey));
} else {
return ResultVoUtil.serviceFail("用户名或密码错误");
}
}
5.4、ThreadLocal保存用户信息
有两种办法:一个是保存全部的用户信息;一个是保存用户id,到时候通过用户id查询用户信息。推荐使用保存用户id
public class CurrentUserUtil {
//线程绑定的存储空间
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* 保存用户id
* @param userId 用户id
*/
public static void saveUserId(String userId){
threadLocal.set(userId);
}
/**
* 获取当前用户id
* @return 用户id
*/
public static String getCurrentUserId(){
return threadLocal.get();
}
}
在AuthFilter.java中验证JWT是否有效时,存入用户信息
/* 验证JWT是否有效 */
final String requestHeader = request.getHeader(jwtProperties.getHeader());
String authToken;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
//得到token
authToken = requestHeader.substring(7);
//通过token获取userId,并将其放入ThreadLocal,以便后续业务调用
//此处获取的为AuthController中生成token时传入的userName
String userId = jwtTokenUtil.getUsernameFromToken(authToken);
if (userId == null) {
return;
}else {
CurrentUserUtil.saveUserId(userId);
}
//验证token是否过期,包含了验证jwt是否正确
try {
boolean flag = jwtTokenUtil.isTokenExpired(authToken);
if (flag) {
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return;
}
} catch (JwtException e) {
//有异常就是token解析失败
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
return;
}
} else {
//header没有带Bearer字段
RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage()));
return;
}
5.5、使用自带的代码生成器生成数据项
修改test目录下的/generator/EntityGenerator.java,修改后运行
// 全局配置
gc.setOutputDir("E:\\java7\\IDEA-WorkSpace\\guns\\guns-user\\src\\main\\java");//这里写你自己的java目录
// 数据源配置
dsc.setUsername("root");
dsc.setPassword("996235954k");
dsc.setUrl("jdbc:mysql://localhost:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=UTC");
// 策略配置
strategy.setInclude(new String[]{"mooc_user_t"}); //表名
// 包配置
pc.setEntity("com.stylefeng.guns.rest.common.persistence.model");
pc.setMapper("com.stylefeng.guns.rest.common.persistence.dao");
pc.setXml("com.stylefeng.guns.rest.common.persistence.dao.mapping");
5.6、业务实体分类
- guns-api中的UserModel和UserInfoModel是跨模块的数据实体,用于具体的业务模块调用(功能性Model)
- guns-user中的MOOCUserT是只在user模块使用的实体,与mooc_user_t表字段一一对应,主要用户操作数据库信息
6、具体业务模块构建(影片模块)
6.1、影片详情查询(异步调用)
1、将需要异步调用的接口单独抽取出来
public interface FilmServiceAsyncAPI {
/* 查询影片描述信息 */
FilmDescVO getFilmDesc(String filmId);
/* 获取图片信息 */
ImgVO getImgs(String filmId);
/* 获取导演信息 */
ActorVO getDirectorInfo(String filmId);
/* 获取演员信息 */
List<ActorVO> getActors(String filmId);
}
2、在service层实现接口方法
3、在API网关处异步调用
//此处开启异步
@Reference(interfaceClass = FilmServiceAsyncAPI.class, async = true)
private FilmServiceAsyncAPI filmServiceAsyncAPI;
@GetMapping("/films/{filmParam}")
public ResultVo getFilms(@PathVariable("filmParam") String filmParam, int searchType) throws ExecutionException, InterruptedException {
//去数据库查询影片详情
FilmDetailVO filmDetail = filmServiceAPI.getFilmDetail(filmParam, searchType);
String filmId = filmDetail.getFilmId();
//根据前台需要的数据格式对数据进行拼装
//对聚合的服务进行异步调用
RequestInfo04VO info04VO = new RequestInfo04VO();
RequestActorVO requestActorVO = new RequestActorVO();
//FilmDescVO filmDescVO = filmServiceAPI.getFilmDesc(filmId); //非异步调用写法
/*
异步调用写法
*/
filmServiceAsyncAPI.getFilmDesc(filmId);
Future<FilmDescVO> filmDescVOFuture = RpcContext.getContext().getFuture();
filmServiceAsyncAPI.getDirectorInfo(filmId);
Future<ActorVO> actorVOFuture = RpcContext.getContext().getFuture();
filmServiceAsyncAPI.getActors(filmId);
Future<List<ActorVO>> actorsFuture = RpcContext.getContext().getFuture();
filmServiceAsyncAPI.getImgs(filmId);
Future<ImgVO> imgVOFuture = RpcContext.getContext().getFuture();
//拼接actors
requestActorVO.setDirector(actorVOFuture.get());
requestActorVO.setActors(actorsFuture.get());
//拼接info04
//info04VO.setBiography(filmDescVO.getBiography());
info04VO.setBiography(filmDescVOFuture.get().getBiography());
info04VO.setActors(requestActorVO);
//拼接data
filmDetail.setInfo04(info04VO);
filmDetail.setImgs(imgVOFuture.get());
return ResultVoUtil.success(IMG_PRE, filmDetail);
}
4、在API网关启动的Application处添加注解:@EnableAsync
------扫码进群,怕你禁不住福利的诱惑------