欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

搭建个人OpenAPI

程序员文章站 2022-05-28 23:09:41
简介 OpenAPI Open API 即开放 API,也称开放平台。 所谓的开放 API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列 API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这 ......

简介

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://localhost:8080/open-api/v1/info
    • 如果请求参数不带access_key,网关服务会直接返回 401 未授权的错误;
    • 如果请求参数带access_key,但是与我们数据库的安全验证不匹配,网关服务也会直接返回 401 错误;

搭建个人OpenAPI

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

搭建个人OpenAPI

权限缓存

以上已经实现了基本权限的验证,但是每次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错误,这样限流的功能就完成了

搭建个人OpenAPI

部署环境

微服务注册

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

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

  • 配置open-api工程
eureka:
  client:
    serviceurl:
      defaultzone: http://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

搭建个人OpenAPI

启动服务

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

搭建个人OpenAPI