当前位置: 移动技术网 > IT编程>开发语言>Java > 搭建个人OpenAPI

搭建个人OpenAPI

2020年01月17日  | 移动技术网IT编程  | 我要评论

简介

openapi

open api 即开放 api,也称开放平台。 所谓的开放 api(openapi)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列
api(application programming interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的 api,所开放的 api 就被称作 openapi(开放 api )。

restful api

representational state transfer,翻译是”表现层状态转化”。可以总结为一句话:rest 是所有 web 应用都应该遵守的架构设计指导原则。
面向资源是 rest 最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。rest 要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。

什么是 restful api?
符合 rest 设计标准的 api,即 restful api。rest 架构设计,遵循的各项标准和准则,就是 http 协议的表现,换句话说,http 协议就是属于 rest 架构的设计模式。比如,无状态,请求-响应。。。

简单实践

那如何构建咱们自己的open api,这里做了简单的代码示例,包括基础的权限验证、限流控制,方便笔者自己构建其他应用服务时的调用。

  • 部署环境(阿里云ecs服务器)
    • 操作系统:centos7.1
    • 容器管理:docker version 1.13.1
    • 微服务注册中心镜像:webapp/eureka-server
    • 微服务配置中心镜像:webapp/config-server
    • 微服务api应用镜像:webapp/open-api
    • 微服务api网关镜像:webapp/api-gateway
  • 源码:

api工程

创建工程

  • 创建gateway工程(open-api),引入依赖
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>

<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-config-client</artifactid>
</dependency>

暴露api

open-api工程很简单,实现业务逻辑,对外暴露接口即可,这里我们简单示例,新建一个测试controller,返回一行文本。

@restcontroller
@requestmapping("/v1")
public class testcontroller {

    @getmapping("/info")
    public string info(){
        return "hello world!";
    }
}

启动服务,可以通过 http://localhost:8081/v1/info 正常访问。

gateway工程

创建工程

创建gateway工程(api-gateway),导入相关依赖

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-actuator</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
<dependency>
    <groupid>mysql</groupid>
    <artifactid>mysql-connector-java</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-config-client</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-starter-netflix-zuul</artifactid>
</dependency>
<dependency>
    <groupid>org.projectlombok</groupid>
    <artifactid>lombok</artifactid>
</dependency>

访问权限控制

  • 数据库建权限表
create table `access_info` (
  `access_key` varchar(32) not null comment '访问码',
  `access_desc` varchar(32) not null comment '访问说明',
  `visit_module` varchar(32) not null comment '访问模块',
  `access_status` tinyint(3) not null default '0' comment '访问状态, 0:不允许访问 1:允许访问',
  `create_time` timestamp not null default current_timestamp comment '创建时间',
  `update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
  primary key (`access_key`)
) engine=innodb default charset=utf8;
  • 从数据库获取权限
@data
@entity
public class accessinfo {

    /**
     * 访问码.
     */
    @id
    private string accesskey;

    /**
     * 访问说明.
     */
    private string accessdesc;

    /**
     * 访问模块.
     */
    private string visitmodule;

    /**
     * 访问状态, 0:不允许访问 1:允许访问
     */
    private accessstatus accessstatus;


    /**
     * 创建时间.
     */
    private date createtime;

    /**
     * 更新时间.
     */
    private date updatetime;
}

@repository
public interface accessinforepository extends jparepository<accessinfo, string> {

}

@service
public class accessinfoservice {

    private final accessinforepository accessinforepository;

    public accessinfoservice(accessinforepository accessinforepository) {
        this.accessinforepository = accessinforepository;
    }

    /**
     * 获取所有访问权限信息
     *
     * @return
     */
    public list<accessinfo> findall() {
        return accessinforepository.findall();
    }
}
  • 新建accessfilter ,继承zuulfilter,来实现权限验证
@component
public class accessfilter extends zuulfilter {

    private final accessinfoservice accessinfoservice;

    public accessfilter(accessinfoservice accessinfoservice) {
        this.accessinfoservice = accessinfoservice;
    }

    @override
    public string filtertype() {
        return pre_type;
    }

    @override
    public int filterorder() {
        return 0;
    }

    @override
    public boolean shouldfilter() {
        return true;
    }

    @override
    public object run() {
        requestcontext currentcontext = requestcontext.getcurrentcontext();
        httpservletrequest request = currentcontext.getrequest();

        if (!isauthorized(request)) {
            httpstatus httpstatus = httpstatus.unauthorized;
            currentcontext.setsendzuulresponse(false);
            currentcontext.setresponsestatuscode(httpstatus.value());
        }
        return null;
    }

    /**
     * 判断请求是否有权限
     *
     * @param request
     * @return
     */
    private boolean isauthorized(httpservletrequest request) {
        // 检查请求参数是否包含 access_key
        string access_key = request.getparameter("access_key");
        if (!stringutils.isempty(access_key)) {
            // 检查 access_key 是否匹配
            list<accessinfo> accessinfos = accessinfoservice.findall();
            optional<accessinfo> accessinfo = accessinfos.stream()
                    .filter(s -> access_key.equals(s.getaccesskey())).findany();
            if (accessinfo.ispresent()) {
                return true;
            }
            return false;
        }
        return false;
    }
}
  • 启动网关服务,访问 http://www.lhsxpumps.com/_localhost:8080/open-api/v1/info
    • 如果请求参数不带access_key,网关服务会直接返回 401 未授权的错误;
    • 如果请求参数带access_key,但是与我们数据库的安全验证不匹配,网关服务也会直接返回 401 错误;

image-20200113164209186

  • 请求access_key,也通过后台数据库验证,则调用成功

image-20200113164052292

权限缓存

以上已经实现了基本权限的验证,但是每次api的请求,都会进行数据库的校验。

2020-01-13 16:44:43.591  info 25028 --- [trap-executor-0] c.n.d.s.r.aws.configclusterresolver      : resolving eureka endpoints via configuration
hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_
hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_
hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_

实际生产中肯定不能这么操作,对数据库的压力太大,所以,我们要对权限验证的数据进行缓存。

  • 首先,在启动类上增加@enablecaching注解
@enablediscoveryclient
@enablezuulproxy
@springbootapplication
@enablecaching
public class apigatewayapplication {

    public static void main(string[] args) {
        springapplication.run(apigatewayapplication.class, args);
    }

}
  • accessinfo需要实现serializable接口,方便序列化后保存在redis中。
@data
@entity
public class accessinfo implements serializable {
    //...
}
  • 获取所有访问权限信息的方法上增加缓存处理
@cacheable(value = "api-gateway:accessinfo")
public list<accessinfo> findall() {
    return accessinforepository.findall();
}
  • 重新启动服务后,多调用几次api接口,发现第一次加载时会调用一次数据库,后面都是取缓存中的权限信息,不再查询数据库。

限流控制

ratelimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率。

  • 新建ratelimitfilter继承zuulfilter,定义一个ratelimiter,这里为了测试方便,每秒设置最多2个请求
/**
 * 限流
 */
@component
public class ratelimitfilter extends zuulfilter {

    //每秒产生n个令牌
    private static final ratelimiter ratelimiter = ratelimiter.create(2);

    @override
    public string filtertype() {
        return pre_type;
    }


    @override
    public int filterorder() {
        return servlet_detection_filter_order - 1;
    }

    @override
    public boolean shouldfilter() {
        return true;
    }


    @override
    public object run() {
        if (!ratelimiter.tryacquire()) {
            requestcontext currentcontext = requestcontext.getcurrentcontext();
            httpstatus httpstatus = httpstatus.too_many_requests;
            currentcontext.setsendzuulresponse(false);
            currentcontext.setresponsestatuscode(httpstatus.value());
        }

        return null;
    }
}
  • 重启服务后,1s内如果多次访问接口,会提示 429 too many requests错误,这样限流的功能就完成了

image-20200113184107059

部署环境

微服务注册

将打包的jar文件生成docker镜像,然后部署在个人服务器上,之前笔者已经部署过服务注册中心(eureka-server)和统一配置中心(config-server),所以把两个新应用注册并部署即可。

这里是微服务部署,将服务注册到服务中心,并从统一配置中心获取配置属性,后面可以通过实例名称来进行访问。

  • 配置open-api工程
eureka:
  client:
    serviceurl:
      defaultzone: http://www.lhsxpumps.com/_eureka1:8761/eureka/,http://eureka2:8762/eureka/ # 指定服务注册地址

spring:
  application:
    name: open-api  # 应用名称

server:
  port: 8081
  • 配置api-gateway工程
eureka:
  client:
    serviceurl:
      defaultzone: http://eureka-server:8761/eureka/ #指定服务注册地址

spring:
  application:
    name: api-gateway  #应用名称
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server

image-20200116154951810

启动服务

依次启动eureka-server、config-server、open-api、api-gateway服务,这样我们就可以通过访问域名地址来访问自己的api了。这里尤其注意open-api启动后再启动api-gateway服务,不然api-gateway服务在eureka-server上无法找到open-api服务,所以不会配置默认的路由规则,会导致服务不可用。

image-20200116165920076

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网