前言在实际工作中,我们经常需要和第三方平台打交道,可能会与第三方平台API接口对接,或者为第三方平台提供API接口第三方平台调用。那么问题来了,如何设计一个优雅的API接口,能够满足安全性、可重复调用、稳定性、良好定位等各种需求的问题呢?今天就和大家聊一聊设计API接口需要注意的几点。希望对您有所帮助。1.签名为了防止API接口中的数据被篡改,我们经常需要对API接口进行签名。接口请求方将请求参数+时间戳+key拼接成一个字符串,然后通过md5等哈希算法生成一个前签。然后在请求参数或者请求头中添加sign参数,传递给API接口。API接口的网关服务获取到sign值,然后使用相同的请求参数+时间戳+key拼接成一个字符串,使用相同的m5算法生成另一个sign,比较两个sign值是否相等。如果两个符号相等,则认为是有效请求,API接口的网关服务会将请求转发给相应的业务系统。如果两个符号不相等,API接口的网关服务会直接返回签名错误。问题来了:为什么签名要加上时间戳?答:为了安全起见,为了防止同一个请求被重复使用,增加密钥不被破解的可能性,我们必须为每个请求设置一个合理的过期时间,例如:15分钟。这样的请求在15分钟内有效,如果超过15分钟,API接口的网关服务会返回有效期已过的异常提示。目前用于生成签名的密钥有两种:一种是双方约定一个固定值的privateKey。另一种是API接口提供者提供AK/SK两个值,双方同意在签名中使用SK作为密钥。AK接口的调用者将其作为header中的accessKey传递给API接口提供者,以便API接口提供者根据AK获取SK并生成新的sgin。2、加密有时候,我们的API接口会直接传输非常重要的数据,比如:用户的银行卡号、转账金额、用户身份证等,将这些参数以明文形式直接暴露在公网是非常危险的。由此,我们需要对数据进行加密。目前使用最多的方法是使用BASE64加密解密。我们可以把所有的数据按照一定的规则拼接成一个大字符串,然后加一个key拼接在一起。然后使用JDK1.8之后的Base64工具进行处理,效果如下:[加密前的数据]www.baidu.com[加密后的数据]d3d3LmJhaWR1LmNvbQ==为了安全,可以使用Base64进行多次加密。API接口的调用者在传递参数时,body中只有一个参数数据,即经过base64加密后的数据。API接口的网关服务,收到数据后,根据双方预先确定的密钥、加密算法、加密次数等进行解密,反序列化参数数据。3、ip白名单是为了进一步加强API接口的安全性,防止接口的签名或加密被破解。攻击者可以在自己的服务器上请求接口。请求限制请求ip,添加ip白名单。只有白名单中的ip地址才能成功请求API接口,否则直接返回无访问权限。API网关服务也可以加入ip白名单。但也要防止公司内部的应用服务器被攻破。这种情况下,也可以从内部服务器发起API接口请求。这时候就需要添加web防火墙,比如:ModSecurity等。4.限流如果你的API接口被第三方平台调用,就意味着调用频率无法控制。第三方平台调用你的API接口时,如果一下子并发过高,可能你的API服务不可用,接口会直接挂掉。因此,需要对API接口进行限流。限流的方式有3种:对请求ip的限流:比如同一个ip,一分钟内,对API接口的总请求次数不能超过10000次。限制请求接口:比如在一分钟内,同一IP请求指定API接口的次数不能超过2000次。限制请求用户的流量:比如同一个AK/SK用户,在一分钟内,对API接口的总请求次数不能超过10000次。在我们实际工作中,可以通过nginx、redis或者gateway来实现限流功能。5、参数校验我们需要对API接口进行参数校验,比如:校验必填字段是否为空,校验字段类型,校验字段长度,校验枚举值等等。这样做可以拦截一些无效的请求。比如在添加数据的时候,如果字段长度超过了数据字段的最大长度,数据库就会直接报错。但是这种异常请求在API接口的前期是可以识别出来的。无需再到数据库保存数据的步骤,浪费系统资源。有些金额字段本来就是正数,但是如果用户传入了一个负数,如果接口不进行校验的话,可能会造成一些不必要的损失。还有一些状态字段。如果不进行校验,如果用户传入一个系统中不存在的枚举值,保存的数据就会异常。可见做参数校验是非常有必要的。hiberate的Validator框架是Java中用来校验数据最多的,它包含@Null、@NotEmpty、@Size、@Max、@Min等注解。使用它们来验证数据非常方便。当然,有些日期字段和枚举字段可能需要通过自定义注解来实现参数校验。6.统一返回值我之前调用过别人的API接口,正常的返回数据是json格式的,例如:{"code":0,"message":null,"data":[{"id":123,"name":"abc"}]},签名错误返回的JSON格式:{"code":1001,"message":"签名错误","data":null}没有数据权限返回的JSON格式:{"rt":10,"errorMgt":"Nopermission","result":null}这是一个棘手的方法。返回值中有很多不同格式的返回数据,会造成对接方的理解困难。这种情况,可能是API网关定义了一个返回值结构,业务系统又定义了一个返回值结构。如果是网关异常,则返回网关定义的返回值结构;如果是业务系统异常,则返回业务系统的返回值结构。但是这样会导致API接口出现不同的异常时返回不同的返回值结构,非常不利于接口的维护。其实我们在设计API网关的时候就可以解决这个问题。当业务系统发生异常时,抛出业务异常RuntimeException,有一个message字段定义异常信息。所有API接口都必须经过API网关。API网关捕获业务异常,转化为统一的异常结构返回,这样可以统一返回值结构。7、异常的统一封装我们的API接口需要对异常进行统一的处理。不知道大家有没有遇到过这样的场景:有时候在API接口中,需要访问数据库,但是表不存在,或者sql语句异常,会直接返回sql信息API接口。返回值包含异常堆栈信息、数据库信息、错误代码和行号等信息。将这些内容直接暴露给第三方平台是非常危险的。一些不法分子可能会利用接口返回值中的信息进行SQL注入或者直接移除数据库,给我们的系统造成一定的损失。因此,非常有必要统一处理API接口中的异常,将异常转换成这样:{"code":500,"message":"Internalservererror","data":null}返回码为500,返回信息消息是服务器内部异常。这样第三方平台就知道API接口内部有问题,但是如果不知道具体原因,可以找我们排查问题。我们可以打印出堆栈信息、数据库信息、内部日志文件中的错误代码行数等信息。我们可以在网关中拦截异常,做统一封装,处理后给第三方平台一个没有敏感信息的错误信息。8、请求日志当第三方平台请求你的API接口时,接口请求日志非常重要,通过它可以快速分析定位问题。我们需要将API接口的请求url、请求参数、请求头、请求方法、响应数据和响应时间记录到日志文件中。最好有traceId,通过traceId可以串联整个请求的日志,过滤掉多余的日志。当然,有的时候,不仅仅是你公司的开发人员需要查看请求日志,第三方平台的用户也需要能够查看接口的请求日志。这时候需要登录数据库,比如:mongodb或者elasticsearch,然后创建一个UI页面,为第三方平台的用户开启查看权限。这样他们就可以在外网查看请求日志,也可以自己定位一些问题。9、幂等设计第三方平台极有可能在很短的时间内多次请求我们的接口,例如:1秒内请求两次。可能是他们的业务系统有bug,或者是在尝试重试失败的接口调用,所以我们的API接口需要做到幂等。也就是说,需要支持第三方平台在极短的时间内多次请求同一个参数的API接口。第一次请求会向数据库中添加数据,但第二次请求后不会添加新数据。将返回成功。这样做的目的不是为了生成错误的数据。在日常工作中,我们可以通过在数据库中添加唯一索引或者将requestId和请求参数保存在redis中来保证接口的幂等性。对接口幂等感兴趣的朋友可以看看我的另一篇文章《高并发下如何保证接口的幂等性?》,里面有很详细的介绍。10.限制记录条数对于我提供的批量接口,必须限制请求的记录条数。如果请求的数据过多,容易造成API接口超时等问题,使API接口不稳定。一般情况下,建议请求中的参数最多支持500条记录。如果用户传入的记录超过500条,界面会直接给出提示。建议这个参数是可配置的,而且一定要提前和第三方平台协商好,避免上线后出现不必要的问题。11、压测上线前,我们必须对API接口进行压测,了解各个接口的qps状态。这样我们就可以更好的预估需要部署多少个服务器节点,这对于API接口的稳定性是非常重要的。虽然之前说的是API接口受限,但实际上API接口能否达到受限的门槛,还是要打个问号。如果不做压力测试,风险很大。比如:你的API接口限制每秒只能处理50个请求,但是实际的API接口只能处理30个请求,那么你的API接口就处理不了了。我们可以使用jmeter或者apachebenc对工作中的API接口进行压力测试。12、异步处理一般API接口的逻辑都是同步处理的,请求完成后立即返回结果。但是有的时候,我们的API接口里面的业务逻辑是非常复杂的,尤其是一些批量接口,如果业务是同步处理的话,会花费很长时间。在这种情况下,为了提高API接口的性能,我们可以将其改为异步处理。在API接口中可以发送一个mq消息,然后直接返回成功。之后有专门的mq消费者异步消费消息,做业务逻辑处理。第三方平台可以通过两种方式获取直接异步处理的接口。第一种方法是:我们回调第三方平台的接口,告知他们API接口的处理结果,也就是有多少个支付接口工作。第二种方式是:第三方平台通过轮询的方式调用我们另外一个查询状态的API接口,每隔一段时间查询一次状态。传入的参数是前面API接口中的id集合。13、数据脱敏有时第三方平台调用我们的API接口时,获取到的部分数据是敏感数据,比如:用户手机号、银行卡号等,如果这些信息是通过直接保存到外网的API接口,非常不安全,容易造成用户隐私数据泄露。这就需要对一些数据进行脱敏处理。我们可以将返回数据中的部分内容替换为星号。以现有用户手机号为例:182****887。这样即使数据泄露了,也只是泄露了一部分,对于不法分子来说拿到这些数据是没有用的。14.完善的接口文档说实话,一份完善的API接口文档可以减少很多沟通成本,让对方在双方做接口对接的时候少走很多弯路。接口文档中需要包含以下信息:接口地址请求方法,如:postorget请求参数及字段介绍返回值及字段介绍返回码及错误信息加密或签名示例完整请求demo附加说明,如:打开ip白名单。接口文档中接口和字段名的命名风格最好统一,比如全部用驼峰命名。接口地址可以加一个版本号v1,比如:v1/query/getCategory,这样以后接口变化很大,升级版本也很方便。统一字段的类型和长度,例如:id字段使用Long类型,长度为20。status字段为int类型,固定长度为2等。统一时间格式字段,例如:时间使用String类型,格式为:yyyy-MM-ddHH:mm:ss。AK/SK和域名在接口文档中有说明,可以找人单独提供。
