API网关类似于Facade模式。
1.身份验证与安全
类似于防火墙作用,理论上可以认为API网关之后的所有操作和文件都是安全的。
2.审查和检测
对用户的行为做一个记录。本值上就是处理一些边缘数据。
3.动态路由
在cloud中用到了。主要是用于服务和接口的映射
4.压力测试
针对于高并发的场景,实现阶梯型的压力测试,即慢慢加大并发
5.负载均衡
dobbo中不需要使用到这个、功能
6.动静分离
主要是为了对后端的行为进行保护。动静分离之后可以实现静态资源的快速获取。
服务聚合:对于属于同种功能的服务进行聚合,仅提供一个接口
首先到码云上下载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中对名字进行相应的修改。
首先在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的日志中有打印出注册信息说明配值成功。
将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 #签名机制是否开启(true或false)
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;
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验证就不用做了。所以这段代码应该放在这个方法的开始的位置。
在做这个功能之前,首先要知道,在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的就是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的添加就完成了。