使用duboo仿猫眼微服务架构实战 第一天

文章目录

  • 业务架构构建
    • API网关介绍
      • API网关的具体作用
    • 构建业务系统
      • guns环境测试
      • 整体工程搭建
        • 网关子模块的分离搭建
        • 在工程中整合dubbo
        • 抽离公共API
  • 用户模块的开发
    • 用户数据表设计
    • 用户接口实现
      • 修改网关配置
      • 添加接口
      • 在gateway配置文件中添加忽略列表:
      • 修改jwt返回内容以匹配业务
      • ThreadLocal保存用户信息

业务架构构建

API网关介绍

使用duboo仿猫眼微服务架构实战 第一天_第1张图片

API网关类似于Facade模式。

API网关的具体作用

1.身份验证与安全
类似于防火墙作用,理论上可以认为API网关之后的所有操作和文件都是安全的。
2.审查和检测
对用户的行为做一个记录。本值上就是处理一些边缘数据。
3.动态路由
在cloud中用到了。主要是用于服务和接口的映射
4.压力测试
针对于高并发的场景,实现阶梯型的压力测试,即慢慢加大并发
5.负载均衡
dobbo中不需要使用到这个、功能
6.动静分离
主要是为了对后端的行为进行保护。动静分离之后可以实现静态资源的快速获取。

构建业务系统

服务聚合:对于属于同种功能的服务进行聚合,仅提供一个接口

guns环境测试

首先到码云上下载guns的源代码:https://gitee.com/naan1993/guns/

使用IDEA在本地打开下载好的源代码工程。进入到rest模块目录下

找到项目中需要的sql语句,然后在自己的mysql数据库中建立相应的数据库和表。

DROP DATABASE IF EXISTS guns_rest;
CREATE DATABASE IF NOT EXISTS guns_rest DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin');

SET FOREIGN_KEY_CHECKS = 1;

在项目中找到数据库的配置,根据自己的实际情况修改相应的数据库配置

spring:
  datasource:
      url: jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
      username: root
      password: 961212
      filters: log4j,wall,mergeStat

然后就可以启动guns了。如果有问题就解决问题。
首先保错是log4j的错误,到maven仓库中找到依赖添加进去就可以了。

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
	<dependency>
	    <groupId>log4j</groupId>
	    <artifactId>log4j</artifactId>
	    <version>1.2.17</version>
	</dependency>

再次启动:
再次报错,发现是URL的问题。重新替换配置文件中的mysql的url

jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8

后面实际是加了一个关于时区的配置。
guns启动成功之后,在配置文件当中可以看到jwt下有一个auth-path的节点值为auth。接下来就到浏览器中访问这个路径:http://localhost:auth?userName=admin&password=admin
可以看到对应的输出。到这里说gun已经配置成功了。

整体工程搭建

以后所有的工程都会基于上面的那个rest子模块为模板进行构建:

网关子模块的分离搭建

1.复制rest模块,并改名为guns-gateway模块
2.修改总的pom文件,添加子模块的依赖

    <modules>
        <module>guns-admin</module>
        <module>guns-core</module>
        <module>guns-rest</module>
        <module>guns-generator</module>
        <module>guns-gateway</module>
    </modules>

3.修改网关模块的pom文件
主要是修改名字

    <groupId>com.stylefeng.guns</groupId>
    <artifactId>guns-gateway</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <name>guns-gateway</name>
    <description>API网关服务</description>

其他的都不需要修改。
然后发现工程中gateway模块的名字比较奇怪。所以此时在project_structure中对名字进行相应的修改。

在工程中整合dubbo

首先在gateway的pom文件中添加dubbo和zookeeper的依赖

   <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
       <dependency>
            <groupId>com.alibaba.spring.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

然后修改配置文件:主要是dubbo的配置(在spring节点下修改)

  application:
    name: meeting-gateway
  dubbo:
    server: true
    registry: zookeeper://localhost:2181

一定要记住在启动类上添加注解:
@EnableDubboConfiguration

然后到这里dubbo的集成就结束了。
先打开zookeeper然后在启动这个模块。看到zookeeper的日志中有打印出注册信息说明配值成功。

抽离公共API

将guns-cores复制一份改为guns-api
然后按照上面讲过的步骤对其进行修改。可以把不用的东西都给删除了。几乎都是不用的。
在api中写好所有的接口之后,要install
在gateway以及其他包中可以直接引入api模块就可以了。
api模块中装的是所有模块都会依赖到的公共接口。
到这里架构搭建就基本完成了。

用户模块的开发

完成的目标:
1.学会API网关权限验证和其他服务交互
2.dubbo的负载均衡策略选择和使用

用户数据表设计

DROP TABLE IF EXISTS mooc_user_t;
CREATE TABLE mooc_user_t(
   UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号',
   user_name VARCHAR(50) COMMENT '用户账号',
   user_pwd VARCHAR(50) COMMENT '用户密码',
   nick_name VARCHAR(50) COMMENT '用户昵称',
   user_sex INT COMMENT '用户性别 0-男,1-女',
   birthday VARCHAR(50) COMMENT '出生日期',
   email VARCHAR(50) COMMENT '用户邮箱',
   user_phone VARCHAR(50) COMMENT '用户手机号',
   address VARCHAR(50) COMMENT '用户住址',
   head_url VARCHAR(50) COMMENT '头像URL',
   biography VARCHAR(200) COMMENT '个人介绍',
   life_state INT COMMENT '生活状态 0-单身,1-热恋中,2-已婚,3-为人父母',
   begin_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'
) COMMENT '用户表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

insert into mooc_user_t(user_name,user_pwd,nick_name,user_sex,birthday,email,user_phone,address,head_url,life_state,biography) values('admin','0192023a7bbd73250516f069df18b500','隔壁泰山',0,'2018-07-31','[email protected]','13888888888','北京市海淀区朝阳北路中南海国宾馆','films/img/head-img.jpg',0,'没有合适的伞,我宁可淋着雨');
insert into mooc_user_t(user_name,user_pwd,nick_name,user_sex,birthday,email,user_phone,address,head_url,life_state,biography) values('jiangzh','5e2de6bd1c9b50f6e27d4e55da43b917','阿里郎',0,'2018-08-20','[email protected]','13866666666','北京市朝阳区大望路万达广场','films/img/head-img.jpg',1,'我喜欢隔壁泰山');

在该数据表中,主要是用户的一些基本信息。其中用户的id是自增的。

用户接口实现

修改网关配置

首先复制一个gateway模块取名为guns-user
修改user模块的端口,并且可以关闭rest节点下的验证了。因为我们默认通过了api网关的数据都是安全的。
修改的配置如下:(非连续)

rest:
  auth-open: false #jwt鉴权机制是否开启(true或者false)
  sign-open: false #签名机制是否开启(truefalse)
server:
  port: 8083 #项目端口
spring:
application:
 name: meeting-user
dubbo:
 server: true
 registry: zookeeper://localhost:2181
 protocol:
   name: dubbo
   port: 20881

这里使用8083端口,不与网关模块冲突

配置改完之后,添加一个user实现类测试一下:

@Service(interfaceClass = UserApi.class)
@Component
public class UserImpl implements UserApi {
    @Override
    public String login(String username, String password) {
        System.out.println("this is form user "+username+":"+password);
        return "this is form user "+username+":"+password;
    }
}

@Service(interfaceClass = UserApi.class)这个注解将UserApi这个服务暴露于dubbo当中,有了这个注解就不用去配置文件中配置了。
然后打开zookeeper,启动这个类可以看到zookeeper中已经发现了这个服务。

在网关中写测试类:

@Component
public class ClientTest {
    @Reference(interfaceClass = UserApi.class)
    private UserApi userApi;

    public void run(){
        userApi.login("admin","kejia");

    }

}

注意@Reference(interfaceClass = UserApi.class)这个注解,与Service注解对应。这个注解是用来使用服务的。
最后测试时成功的。user端打印出了信息。

添加接口

修改UserApi文件如下:

public interface UserApi {
    public String login(String username,String password);

    boolean register(UserModel userModel);

    boolean checkUsername(String username);

    UserInfoModel getUserInfo(int uuid);

    UserInfoModel updateUserInfo(UserInfoModel userInfoModel);
}

其中userModel仅用于登录验证,UserInfoModel 保存用户全部信息。

public class UserModel {
    private String username;
    private String password;
    private String phone;
    private String address;

 public class UserInfoModel {
    private Integer uuid;
    private String username;
    private String nickname;
    private String email;
    private String phone;
    private int sex;
    private String birthday;
    private String lifeState;
    private String biography;
    private String address;
    private String headAddress;
    private long beginTime;
    private long updateTime;

在gateway配置文件中添加忽略列表:

jwt:
  header: Authorization   #http请求头所需要的字段
  secret: mySecret        #jwt秘钥
  expiration: 604800      #7天 单位:秒
  auth-path: auth         #认证请求的路径
  md5-key: randomKey      #md5加密混淆key
  ignore-url: /user/,/film #忽略列表配置

在 package com.stylefeng.guns.rest.config.properties;下找到JwtProperties配置类
添加忽略列表的配置:

    private String ignoreUrl = "";

    public String getIgnoreUrl() {
        return ignoreUrl;
    }

    public void setIgnoreUrl(String ignoreUrl) {
        this.ignoreUrl = ignoreUrl;
    }

刚刚忽略的url只是为了让jwt不做验证,接下来找到com.stylefeng.guns.rest.modular.auth.filter.AuthFilter的doFilterInternal方法,造中间添加以下的代码:

        //在这里配置忽略列表
        String ignoreUrl = jwtProperties.getIgnoreUrl();
        String[] url = ignoreUrl.split(",");
        for(int i=0;i<url.length;i++){
            if(request.getServletPath().equals(url[i])){
                chain.doFilter(request, response);
                return;
            }
        }

在这段代码中对用户的请求url进行判断,如果与忽略列表中的字段能都匹配上,那么接下来的jwt验证就不用做了。所以这段代码应该放在这个方法的开始的位置。

修改jwt返回内容以匹配业务

在做这个功能之前,首先要知道,在guns中,所有的jwt验证都是在gateway的AuthController中进行了,在这里进行了验证之后,就可以保证我们通过网关获取到的服务都是一些安全有效的服务。我们要做的事情就是对于端 传过来的用户登录信息,使用自己的方法来验证,在createAuthenticationToken方法中做如下的修改。当然userApi,在上面已经注入进来了。

        //这里就是在验证用户名和密码是否有效
        boolean validate = true;
        //返回一个唯一的userid,用来标志用户。
        int userId= userApi.login(authRequest.getUserName(),authRequest.getPassword());
        //如果validate为0,说明验证是不通过的。
        if(userId == 0) {
            validate = false;
        }

通过这个验证之后,我们还可以得到一个唯一的用户id。
将这个用户id放入到token中,之后我们就可以从中获取到使用的用户的唯一id

            final String token = jwtTokenUtil.generateToken(""+userId, randomKey);     

在gateway中添加一个新的vo类:封装了后台信息处理的结果类

public class ResponseVO<M> {
    // 返回状态【0-成功,1-业务失败,999-表示系统异常】
    private int status;
    // 返回信息
    private String msg;
    // 返回数据实体;
    private M data;
    private ResponseVO(){}
    public static<M> ResponseVO success(M m){
        ResponseVO responseVO = new ResponseVO();
        responseVO.setStatus(0);
        responseVO.setData(m);

        return responseVO;
    }
    public static<M> ResponseVO success(String msg){
        ResponseVO responseVO = new ResponseVO();
        responseVO.setStatus(0);
        responseVO.setMsg(msg);

        return responseVO;
    }
    public static<M> ResponseVO serviceFail(String msg){
        ResponseVO responseVO = new ResponseVO();
        responseVO.setStatus(1);
        responseVO.setMsg(msg);
        return responseVO;
    }
    public static<M> ResponseVO appFail(String msg){
        ResponseVO responseVO = new ResponseVO();
        responseVO.setStatus(999);
        responseVO.setMsg(msg);
        return responseVO;
    }

将原来方法的返回值和异常换成这里定义的这个:

 if (validate) {
            final String randomKey = jwtTokenUtil.getRandomKey();
            final String token = jwtTokenUtil.generateToken(""+userId, randomKey);
            //后台处理结果返回
            return ResponseVO.success(new AuthResponse(token,randomKey));
        } else {
            //throw new GunsException(BizExceptionEnum.AUTH_REQUEST_ERROR);
            //抛出异常,使用我们自定义的那个异常。
            return ResponseVO.appFail("用户名或者密码错误");
        }

ThreadLocal保存用户信息

先新建一个ThreadLocal的静态工具类,里面提供保存和取出的工具方法。这里我们选择的放入threadLocal的就是userId,为了防止整个用户的信息过大,导致内存溢出。

public class CurrentUser {
    //线程绑定的存储空间
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

        public static void saveUserId(String id){
        threadLocal.set(id);
        }
        public static String getCurrentUser(){
        return threadLocal.get();
    }

}

然后修改filter文件,把userId添加进ThreadLocal就可以了。


        final String requestHeader = request.getHeader(jwtProperties.getHeader());
        String authToken = null;
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);
            //到了这里说明肯定是要进行jwt验证的了。
            //获取userId
            String userId = jwtTokenUtil.getUsernameFromToken(authToken);
            if(userId==null){
                return ;
            }
            //放入ThreadLocal当中。
            CurrentUser.saveUserId(userId);

到这里ThreadLocal的添加就完成了。

你可能感兴趣的:(java)