基于Guns+dubbo+SpringBoot的项目开发

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、业务实体分类

  1. guns-api中的UserModel和UserInfoModel是跨模块的数据实体,用于具体的业务模块调用(功能性Model)
  2. 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

------扫码进群,怕你禁不住福利的诱惑------

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值