文章目录

  • 设计
  • 文档工具
  • swagger
  • RAP2
  • 测试和监控
  • 外部保护->POSTMAN tests
  • 1.添加环境变量
  • 2.设置测试方法
  • 2.1 全局测试
  • 2.2 接口schema校验
  • 3.批量运行
  • 4.不同环境的处理办法
  • 内部保护->prometheus http埋点监控
  • 1.`pom.xml` 添加依赖
  • 2.配置restTemplate
  • 3.micrometer配置
  • 4.添加actuator暴露端点
  • 5.`prometheus.yml`添加配置
  • 6.`grafana`添加dashboard
  • 最终效果
  • 链接汇总


经常在技术群里或者各种博客中看到大家在争执API测试工具应该用哪个,什么http client,postman,apifox,jmeter,那该如何选择适合自己的工具呢,如何才能提高生产力,更快的做完手上工作呢,其实,归根到底,没有最好,只有适合自己的才是最好的,在接口开发的不同阶段,选择适合自己的工具,来更快的实现目标就行。

CURD多年,前后台测试接口,跨团队测试接口,后台调用外部接口,经常性的会出现各种各样的扯皮现象,主要来来回回就是这几个:

  • 接口格式不对
  • 接口字段不对
  • 接口缺失字段
  • 接口不够规范
  • 接口不够稳定

那,该如何避免或者节省这之间的来来回回的开销呢?

设计

  1. 定规范
  1. 确定请求方式,常用的 GET POST,REST规范的GET/POST/DELETE/PUT,其他的OPTIONS/PATCH/COPY/HEAD/LINK/UNLINK/PURGE/LOCK/UNLOCK/PROPFIND/VIEW
  2. 定请求参数,是request param还是request body,request body类型是什么
  3. 接口是否需要安全校验,采用哪种校验方式
  4. 返回体结构,一般来说后端返回的数据,会基于标准的HTTP STATUS在额外封装一层,用于关联业务或者后台内部的处理逻辑。
{
    "code":200,
    "msg":"请求成功",
    "data":{}
}
  1. 约定返回体结构的CODE标准,一般采用标准的http status(200/400/401/403/500常用,在实际开发过程中,部分开发人员会对部分http状态码做自定义定义,需在接口最开始,定好全局规范)
  2. 约定返回体结构的实际业务数据结构,一般来说,包括以下内容:

类型

字段名称

是否必选

类型

中文含义

文档工具

本人经常用的接口文档工具是swagger和RAP2。但在实际工作中,碰到的部分同事,基于开发人员自己的习惯,总是想着现有代码,才有接口,这就陷入了一个误区。接口不是一个人的事情,往大了说,属于整个项目运转过程中的一个个生命线,往小了说,是接口开发方和接口使用方之间的"冲突博弈"过程。

所以,不管是用什么工具,提前定义好接口字段,然后双方进行各自调试,完成后,进行合并验证,是最高效的开发方式。

swagger

用于团队内部之间的对接开发,所见即所得,可提供实际数据的便捷查询。

RAP2

一般用于跨团队、跨部门的协同开发工作,提供mock,项目组前端同事可直接直接调用测试。

测试和监控

接口对接阶段是最容易发生扯皮的地方,因个人做的是政府相关项目,一般来说,开发环境的数据没法做到百分百模拟正式环境,和其他同事或者团队对接时候,经常性出现这种问题,开发调试阶段,接口不符合规范,好不容易接口调完了,拿到正式环境,数据一上来,发现对方提供的数据标准又有问题。

网络隔离,VPN速度慢,各种来回扯皮,让人烦不胜烦。

于是逐渐开始思考,能否利用自己手上已有的工具,尽量做到尽快定位问题,尽快排查问题呢?经过一段时间的探究,针对接口对接过程中的相关问题,研究出了以下方法论

外部保护->POSTMAN tests

1.添加环境变量

一般来说,环境变量包括IP、参数、token等,即分为两种,静态变量和动态变量,变量一般统一放在collection中的Variables中

软件接口对接java 软件接口对接技术_java

使用方法:{{variable_name}}

  • 静态变量
    静态变量一般就是接口的请求参数,可以直接通过双括号进行引入
  • 动态变量
    最常见的动态变量就是header中的各种校验参数,token最为常见,一般接口提供方会额外提供一个token请求接口,配合postman的Pre-request Scripts进行使用
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});
 
// 把responseBody转为json字符串
var data = JSON.parse(responseBody);
// 设置环境变量token,供后面的接口引用,位置就是上一步获取的位置
pm.environment.set("access_token", data.result.access_token);

软件接口对接java 软件接口对接技术_数据_02

2.设置测试方法
2.1 全局测试

对所有接口来说,共通就是返回体结构测试,即保证code正确、数据不为空

在collection中进行Tests添加

pm.test("HTTP请求 200", function () {
    pm.response.to.have.status(200);
});

var jsonData = pm.response.json();

pm.test("接口返回200", function () {
    pm.expect(jsonData.resultCode === 200).to.equal(true);
});

pm.test("接口返回数据不为空", function () {
    if(Array.isArray(jsonData.result)){
        pm.expect(jsonData.result.length > 0).to.equal(true);
    }else{
        pm.expect(jsonData.result !== null).to.equal(true);
    }
});
2.2 接口schema校验
  • json转json schema
    postman内置了Ajv JSON schema validator的校验方式,为了方便起见,我们需要把接口设计阶段定义的json转为json schema,然后通过 pm.response.to.have.jsonSchema进行校验
  • 如接口定义阶段,json模拟数据如下
{
  "resultCode": 200,
  "resultMsg": "success",
  "result": {
    "basicData": {
      "shipCode": "与四山立百",
      "shipNo": "但命及风他",
      "shipRegNo": "最离矿则它设历",
      "shipFirstregNo": "光具样任般高时",
      "origShipRegNo": "构天当斯消然",
      "shipId": "她提圆性识",
      "origShipName": "根得取特水应干",
      "shipIdFlagCode": "了细十产",
      "shipInspectNo": "布人关油速热酸",
      "shipImo": "行家适青",
      "shipMmsi": "马铁至土治那",
      "shipCallsign": "所目入口安华日",
      "shipName": "向等代手研",
      "shipNameEn": "接上决压除",
      "origShipNameEn": "头接治",
      "shipRegionFlagCode": "机石家毛党布",
      "shipRouteCode": "更与林几器",
      "sailAreaCode": "根设眼实值还",
      "regportCode": "格重安程速",
      "origRegportName": "结中上时",
      "shipHullMaterialCode": "片流便包还众",
      "shipTypeCode": "备满发直安外能",
      "shipValue": "线革商再",
      "shipLength": 39657,
      "shipBreadth": 56071,
      "shipDepth": 98131,
      "shipGrosston": 93962,
      "shipNetton": 55891,
      "shipDwt": "斯取声对律公人",
      "shipEngineTypeCode": "然此已认",
      "shipEngineNum": 5815,
      "shipEnginePower": 20505,
      "shipPropellerTypeCode": "适较八自情放",
      "shipPropellerNum": 81206,
      "shipSlotNum": 63116,
      "shipParkNum": 23051,
      "shipPassengerNum": 91588,
      "shipSummerDraft": 1200,
      "shipWindLevel": "号话用",
      "shipMinFreeboard": 30004,
      "shipyard": "造口放将信下",
      "shipyardEn": "米选斯计改",
      "shipBuiltAddr": "转七入见发",
      "shipBuiltAddrEn": "空千律认商",
      "shipBuiltDate": "1983-05-14 10:06:54",
      "rebuiltShipyard": "点常主反行南类",
      "rebuiltShipyardEn": "也么以府有",
      "shipRebuiltAddr": "而是六水",
      "shipRebuiltAddrEn": "第自们断况",
      "icCardNo": "养听维点算办",
      "origDeletionDate": "1991-06-28 12:11:21",
      "shipRebuiltDate": "1985-04-07 13:17:08",
      "statusFlagCode": "矿行市正亲电",
      "shipIdSealFlagCode": "说亲变内",
      "mortgageFlagCode": "快需大半圆证",
      "bareboatFlagCode": "中位阶也论",
      "alterFlagCode": "江业查",
      "handoutCardFlagCode": "地王圆收究求",
      "financialLeaseFlagCode": "以主总上器东",
      "hibernateFlagCode": "意眼应处",
      "trialShipFlagCode": "选热上",
      "detainFlagCode": "型开所积",
      "permanentSealRemark": "光定部王手命动",
      "orgCode": "还行保在即",
      "shipRouteCodeCn": "列照她你",
      "shipReginFlagCodeCn": "河",
      "sailAreaCodeCn": "连速老江她",
      "shipTypeCodeCn": "内九量劳关",
      "shipRegionFlagCodeCn": "段示线所",
      "regportCodeCn": "化石值究",
      "shipEngineTypeCodeCn": "高构克他群",
      "shipPropellerTypeCodeCn": "个动所快理",
      "orgCodeCn": "商张说器形"
    }
  }
}
  • 通过在线工具进行转换,json schema转换
{
  "type": "object",
  "required": [],
  "properties": {
    "resultCode": {
      "type": "number"
    },
    "resultMsg": {
      "type": "string"
    },
    "result": {
      "type": "object",
      "required": [],
      "properties": {
        "basicData": {
          "type": "object",
          "required": [],
          "properties": {
            "shipCode": {
              "type": "string"
            },
            "shipNo": {
              "type": "string"
            },
            "shipRegNo": {
              "type": "string"
            },
            "shipFirstregNo": {
              "type": "string"
            },
            "origShipRegNo": {
              "type": "string"
            },
            "shipId": {
              "type": "string"
            },
            "origShipName": {
              "type": "string"
            },
            "shipIdFlagCode": {
              "type": "string"
            },
            "shipInspectNo": {
              "type": "string"
            },
            "shipImo": {
              "type": "string"
            },
            "shipMmsi": {
              "type": "string"
            },
            "shipCallsign": {
              "type": "string"
            },
            "shipName": {
              "type": "string"
            },
            "shipNameEn": {
              "type": "string"
            },
            "origShipNameEn": {
              "type": "string"
            },
            "shipRegionFlagCode": {
              "type": "string"
            },
            "shipRouteCode": {
              "type": "string"
            },
            "sailAreaCode": {
              "type": "string"
            },
            "regportCode": {
              "type": "string"
            },
            "origRegportName": {
              "type": "string"
            },
            "shipHullMaterialCode": {
              "type": "string"
            },
            "shipTypeCode": {
              "type": "string"
            },
            "shipValue": {
              "type": "string"
            },
            "shipLength": {
              "type": "number"
            },
            "shipBreadth": {
              "type": "number"
            },
            "shipDepth": {
              "type": "number"
            },
            "shipGrosston": {
              "type": "number"
            },
            "shipNetton": {
              "type": "number"
            },
            "shipDwt": {
              "type": "string"
            },
            "shipEngineTypeCode": {
              "type": "string"
            },
            "shipEngineNum": {
              "type": "number"
            },
            "shipEnginePower": {
              "type": "number"
            },
            "shipPropellerTypeCode": {
              "type": "string"
            },
            "shipPropellerNum": {
              "type": "number"
            },
            "shipSlotNum": {
              "type": "number"
            },
            "shipParkNum": {
              "type": "number"
            },
            "shipPassengerNum": {
              "type": "number"
            },
            "shipSummerDraft": {
              "type": "number"
            },
            "shipWindLevel": {
              "type": "string"
            },
            "shipMinFreeboard": {
              "type": "number"
            },
            "shipyard": {
              "type": "string"
            },
            "shipyardEn": {
              "type": "string"
            },
            "shipBuiltAddr": {
              "type": "string"
            },
            "shipBuiltAddrEn": {
              "type": "string"
            },
            "shipBuiltDate": {
              "type": "string"
            },
            "rebuiltShipyard": {
              "type": "string"
            },
            "rebuiltShipyardEn": {
              "type": "string"
            },
            "shipRebuiltAddr": {
              "type": "string"
            },
            "shipRebuiltAddrEn": {
              "type": "string"
            },
            "icCardNo": {
              "type": "string"
            },
            "origDeletionDate": {
              "type": "string"
            },
            "shipRebuiltDate": {
              "type": "string"
            },
            "statusFlagCode": {
              "type": "string"
            },
            "shipIdSealFlagCode": {
              "type": "string"
            },
            "mortgageFlagCode": {
              "type": "string"
            },
            "bareboatFlagCode": {
              "type": "string"
            },
            "alterFlagCode": {
              "type": "string"
            },
            "handoutCardFlagCode": {
              "type": "string"
            },
            "financialLeaseFlagCode": {
              "type": "string"
            },
            "hibernateFlagCode": {
              "type": "string"
            },
            "trialShipFlagCode": {
              "type": "string"
            },
            "detainFlagCode": {
              "type": "string"
            },
            "permanentSealRemark": {
              "type": "string"
            },
            "orgCode": {
              "type": "string"
            },
            "shipRouteCodeCn": {
              "type": "string"
            },
            "shipReginFlagCodeCn": {
              "type": "string"
            },
            "sailAreaCodeCn": {
              "type": "string"
            },
            "shipTypeCodeCn": {
              "type": "string"
            },
            "shipRegionFlagCodeCn": {
              "type": "string"
            },
            "regportCodeCn": {
              "type": "string"
            },
            "shipEngineTypeCodeCn": {
              "type": "string"
            },
            "shipPropellerTypeCodeCn": {
              "type": "string"
            },
            "orgCodeCn": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
  • 根据接口实际情况,修改上述schema的具体定义
    如string字段可为空
    原始定义:
"orgCodeCn": {
     "type": "string"
}

修改后:

"orgCodeCn": {
     "type": ["string","null"]
}

如时间格式是YYYY-MM-DD

原始定义:

"origDeletionDate": {
    "type": "string"
}

修改后:

"origDeletionDate": {
    "type": "string",
    "pattern":"(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)"
}

具体schema的其他属性和相关说明,可参考 JSON Schema规范(中文版)

注意:pattern用的是正则表达式,一些常用的正则在网上搜索一下,二次校验即可。没必要自己花大量精力造轮子。

3.批量运行
  • 先运行登录接口,更新token至全局变量
  • 泡杯热茶,运行整个collection
  • 查看运行结果,一眼便知道哪些接口有问题了
4.不同环境的处理办法

导出collection文件至内网实际环境,需要更换全局变量即可

内部保护->prometheus http埋点监控

在微服务中,网关服务可以完成对系统内接口的监控和流量检测,但很多时候,对复杂的业务系统来说,还需要对接外部的服务,硬编码rest请求处理。

如何保证系统可以持续观测外部接口的状态和处理时间呢,这里推荐使用springboot+prometheus的方式,对系统内的接口进行埋点监测,对异常的访问,配合alertmanager+webhook进行告警实时通知。

1.pom.xml 添加依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
	<groupId>io.micrometer</groupId>
	<artifactId>micrometer-registry-prometheus</artifactId>
	<version>1.8.5</version>
</dependency>
2.配置restTemplate

这里将系统内的restTemplate的默认HTTP请求先修改为OKHTTP

@Bean
public RestTemplate restTemplate(MeterRegistry registry) {

    // 先配置OKhttpClient,添加eventListener收集okhttpClient指标
    OkHttpClient client = new OkHttpClient
        .Builder()
            .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
            .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
            .eventListener(OkHttpMetricsEventListener
                           	.builder(registry, "okhttp.requests")
                				.uriMapper(req -> req.url().encodedPath())
                				.tags(Tags.of("okhttp", "performance"))
                   		   	.build())
        .build();
    
    OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(client);
    // 通过配置文件配置即可,注入代码省略
    factory.setConnectTimeout(connectTimeout);
    factory.setReadTimeout(readTimeout);
    factory.setWriteTimeout(writeTimeout);
    return new RestTemplate(factory);
}

@Bean
public OkHttpClient okHttpClient(MeterRegistry registry) {
    return new OkHttpClient.Builder()
            .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
                    .tags(Tags.of("okhttp", "performance"))
                    .build())
            .build();
}
3.micrometer配置
@Configuration
public class MicroMeterConfig {
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer(@Value("${spring.application.name}") String applicationName) {

        return meterRegistry -> meterRegistry
                .config()
                 .commonTags(Collections.singletonList(Tag.of("application", applicationName)));
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}
4.添加actuator暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

配置完成后,采用传统的方式进行使用即可

@Autowired
private RestTemplate restTemplate;
5.prometheus.yml添加配置
- job_name: 'spring_grafana'
    scrape_interval: 5s
    scrape_timeout: 4s
    metrics_path: 'actuator/prometheus'
    static_configs:
      - targets: ['ip:port']
6.grafana添加dashboard

Spring Boot 2.1 System Monitor

最终效果

软件接口对接java 软件接口对接技术_postman_03

链接汇总

  1. postman scripts
  2. JSON转SCHEMA在线工具
  3. RAP2接口文档管理
  4. grafana
  5. springboot使用Micrometer集成Prometheus监控