实用篇:接口开发的那些“软技能”

经常在技术群里或者各种博客中看到大家在争执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":{}
      }
      
  2. 约定返回体结构的CODE标准,一般采用标准的http status(200/400/401/403/500常用,在实际开发过程中,部分开发人员会对部分http状态码做自定义定义,需在接口最开始,定好全局规范)

  3. 约定返回体结构的实际业务数据结构,一般来说,包括以下内容:

    类型
    字段名称
    是否必选
    类型
    中文含义

文档工具

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

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

swagger

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

RAP2

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

测试和监控

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

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

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

外部保护->POSTMAN tests

1.添加环境变量

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

image-20221013155643797

使用方法:{{variable_name}}

  • 静态变量

    静态变量一般就是接口的请求参数,可以直接通过双括号进行引入

    image-20221013155825529

  • 动态变量

    最常见的动态变量就是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);
    

    image-20221013160033227

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

    image-20221013194222658

  • 查看运行结果,一眼便知道哪些接口有问题了

    image-20221013194400859

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

最终效果

image-20221013201647147

链接汇总

  1. postman scripts
  2. JSON转SCHEMA在线工具
  3. RAP2接口文档管理
  4. grafana
  5. springboot使用Micrometer集成Prometheus监控
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值