如何做一个api接口?
379
2022-09-12
本文关于接口服务是什么?api接口服务质量分析包括哪些方面?
api接口是应用程序中的一种衔接,在很多软件中都有使用,api接口对于程序软件工作来说,非常重要。api接口服务是什么?api接口服务质量分析包括哪些方面?很多人对此都不清楚,下面小编就来给大家详细回答下吧。
一、api接口服务是什么?
应用程序API接口,就是软件系统不同组成部分衔接的约定。由于近年来软件的规模日益庞大,常常需要把复杂的系统划分成小的组成部分,编程接口的设计十分重要。程序设计的实践中,编程接口的设计首先要使软件系统的职责得到合理划分。
api接口服务质量分析
二、api接口有什么作用?
良好的接口设计可以降低系统各部分的相互依赖,提高组成单元的内聚性,降低组成单元间的耦合程度,从而提高系统的维护性和扩展性。应用程序接口是一组数量上千、极其复杂的函数和副程序,可让程序员做很多任务作,譬如“读取文件”、“显示菜单”、“在视窗中显示网页”等等。,数据平台提供各种针对不同类型的企业或创业者需要的数据,针对性比较强,可以逐一进入去根据自身需求,选择对应的数据api接口。
三、api接口服务质量分析包括哪些方面?
创建项目
根据 RESTful 规范,接口地址是POST /api/projects。虽然我们是脱离 UI 界面在开发接口,但页面的交互逻辑是必须清楚的(一般来说,会有交互设计稿)。对于创建项目来说,一般就是一个让用户填写项目信息的表单,里面有项目的名称和描述,用户填写完后,点击“提交”按钮提交表单数据,后端接收到数据后,在数据库中插入一条记录。这个就是创建项目的过程。
查询项目
项目创建完后,肯定要在页面上显示出来,有可能是单独的项目详情页面,也有可能是项目列表页面,都需要用到项目数据。所以需要有查询项目的接口,最常见的就是按项目 id 查单个项目,还有就是显示用户的项目列表。
api接口服务质量分析
修改项目
首先还是分析业务需求,修改这种操作,和具体的业务逻辑关系非常大,比如可以限制只有项目创建者可以修改项目,也可以让所有项目成员都可以修改项目。我们的需求最开始也已经描述过了“只有项目的管理员和创建者可以修改项目”。
根据project表,项目的名称和描述可以被修改
删除项目
根据前面的分析,删除项目的接口地址是DELETE /api/projects/:id,并且我们不是物理删除记录,而是去给deletedAt字段赋值。这个字段有值表示项目已经被删除。
api接口服务是什么?api接口服务质量分析包括哪些方面?大家看完上文小编的介绍,对于api接口服务有了基本认识,api接口服务对于程序软件来说比较关键。
接口无非就是客户端请求你的接口地址,并传入一堆该接口定义好的参数,通过接口自身的逻辑处理,返回接口约定好的数据以及相应的数据格式。
接口由于本身的性质,由于和合作方对接数据,所以有以下几点需要在开发的时候注意:
1.定义接口入参:写好接口文档
2.定义接口返回数据类型:一般都需要封装成一定格式,确定返回json还是xml报文等
见如下返回数据定义格式:
package com.caiex.vb.model; import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD)@XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" })public class Result implements Serializable { private static final long serialVersionUID = 10L; protected int resultCode; protected String resultMsg; public int getResultCode() { return this.resultCode; } public void setResultCode(int value) { this.resultCode = value; } public String getResultMsg() { return this.resultMsg; } public void setResultMsg(String value) { this.resultMsg = value; }}
package com.caiex.vb.model; import java.io.Serializable; public class Response implements Serializable { private static final long serialVersionUID = 2360867989280235575L; private Result result; private Object data; public Result getResult() { if (this.result == null) { this.result = new Result(); } return result; } public void setResult(Result result) { this.result = result; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
3.确定访问接口的方式,get or post等等,可以根据restful接口定义规则RESTful API:RESTful API
4.定义一套全局统一并通用的返回码,以帮助排查问题;
public static int NO_AGENT_RATE = 1119; //未找到兑换率 public static int SCHEME_COMMIT_FAIL = 4000; //方案提交失败 public static int SCHEME_CONFIRMATION = 4001; //方案确认中 public static int SCHEME_NOT_EXIST = 4002; //方案不存在 public static int SCHEME_CANCEL= 4005; //方案不存在 //。。。。
5.统一的异常处理:应该每个系统都需要一套统一的异常处理
package com.caiex.vb.interceptor; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody; import com.caiex.vb.model.Response; @ControllerAdvice@ResponseBodypublic class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 所有异常报错 * @param request * @param exception * @return * @throws Exception */ @ExceptionHandler(value=Exception.class) public Response allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception { logger.error("拦截到异常:", exception); Response response = new Response(); response.setData(null); response.getResult().setResultCode(9999); response.getResult().setResultMsg("系统繁忙"); return response; } }
6.拦截器链设置:合作方访问接口的时候,会根据你接口定义好的传参访问你的接口服务器,但是会存在接口参数类型错误或者格式不对,必传参数没传的问题,甚至一些恶意请求,都可以通过拦截器链进行前期拦截,避免造成接口服务的压力。
还有很重要的一点,加签验签也可以在拦截器设置。继承WebMvcConfigurerAdapter实现springboot的拦截器链。实现HandlerInterceptor方法编写业务拦截器。
package com.caiex.vb.interceptor; import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSON;import com.caiex.redis.service.api.RedisApi;import com.caiex.vb.model.Response;import com.caiex.vb.utils.CaiexCheckUtils; @Componentpublic class SignInterceptor extends BaseValidator implements HandlerInterceptor{ private Logger logger = LogManager.getLogger(this.getClass()); @Resource private RedisApi redisApi; public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub } public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { if(isTestIpAddr(arg0)){ return true; } String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid")); if(StringUtils.isEmpty(securityKey)){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(8001); response.getResult().setResultMsg("缺少私钥, 渠道号:" + arg0.getParameter("agentid")); logger.error("缺少私钥, 渠道号:" + arg0.getParameter("agentid")); InterceptorResp.printJson(arg1, response); return false; } if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(3203); response.getResult().setResultMsg("参数签名认证失败"); logger.error("参数签名认证失败:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey); InterceptorResp.printJson(arg1, response); return false; }else{ return true; } } }
package com.caiex.oltp.config; import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.caiex.oltp.interceptor.APILimitRateValidator;import com.caiex.oltp.interceptor.CommonValidator;import com.caiex.oltp.interceptor.DDSAuthValidator;import com.caiex.oltp.interceptor.QueryPriceParamsValidator;import com.caiex.oltp.interceptor.TradeParamsValidator; @EnableWebMvc@Configuration@ComponentScanpublic class WebAppConfigurer extends WebMvcConfigurerAdapter { @Bean CommonValidator commonInterceptor() { return new CommonValidator(); } @Bean DDSAuthValidator ddsAuthInterceptor() { return new DDSAuthValidator(); } @Bean QueryPriceParamsValidator queryPriceParamsInterceptor() { return new QueryPriceParamsValidator(); } @Bean TradeParamsValidator tradeParamsInterceptor() { return new TradeParamsValidator(); } @Bean APILimitRateValidator aPILimitRateInterceptor() { return new APILimitRateValidator(); } @Override public void addInterceptors(InterceptorRegistry registry) { //访问速率限制 registry.addInterceptor(aPILimitRateInterceptor()) .addPathPatterns("/*/*"); //.addPathPatterns("/price/getPriceParam"); //参数签名认证 registry.addInterceptor(ddsAuthInterceptor()) .addPathPatterns("/tradeState/*") .addPathPatterns("/recycle/*") .addPathPatterns("/matchInfo/*") .addPathPatterns("/price/tradeTicketParam"); //公共参数检查 registry.addInterceptor(commonInterceptor()) .addPathPatterns("/price/tradeTicketParam") .addPathPatterns("/tradeState/*") .addPathPatterns("/recycle/*"); //询价参数校验 registry.addInterceptor(queryPriceParamsInterceptor()) .addPathPatterns("/price/getPriceParam"); //交易参数检查 registry.addInterceptor(tradeParamsInterceptor()) .addPathPatterns("/price/tradeTicketParam"); super.addInterceptors(registry); }}
7.token令牌和sign数字签名实现数据保密性。
为保证请求的合法性,我们提供第三方创建令牌接口,某些接口需要通过token验证消息的合法性,以免遭受非法攻击。
token过期时间目前暂时定为1天,由于考虑到合作方往往是分布式环境,多台机器都有可能申请token,为了降低合作方保证token一致性的难度,调用接口创建token成功以后一分钟以内,再次请求token返回的数据是一样的。
获取用于数字签名的私钥,第三方获取的私钥需妥善保存,并定期更新,私钥只参与数字签名,不作为参数传输。
数字签名方式:
参数签名;签名方式:所有值不为null的参数(不包括本参数)均参与数字签名,按照“参数名+参数值+私钥”的格式得到一个字符串,再将这个字符串MD5一次就是这个参数的值。(示例:h15adc39y9ba59abbe56e057e60f883g
),所以需要先获取私钥。
验签方式:
将用户的所有非null参数放入定义好排序规则的TreeSet中进行排序,再用StringBuilder按照按照“参数名+参数值+私钥”的格式得到一个字符串(私钥从redis拿),再将这个字符串MD5一次就是这个参数的值。将这个值与用户传来的sign签名对比,相同则通过,否则不通过。
private String createToken(){ String utk = "Msk!D*"+System.currentTimeMillis()+"UBR&FLP"; logger.info("create token --- "+Md5Util.md5(utk)); return Md5Util.md5(utk); }
8.接口限流
有时候服务器压力真的太大,以防交易接口被挤死,就可以对一些其他不影响主要业务功能并且计算量大的接口做限流处理。RateLimit--使用guava来做接口限流,当接口超过指定的流量时,就不处理该接口的请求。详细可看RateLimit。也可参考其他限流框架。
9.协议加密,http升级成https;
为什么要升级呢,为了保证数据的安全性。当使用https访问时,数据从客户端到服务断,服务端到客户端都加密,即使黑客抓包也看不到传输内容。当然还有其他好处,这里不多讲。但这也是开发接口项目需要注意的一个问题。
接口开发好了,接下来就讨论接口的可用性问题。首先我们要将高并发和高可用区分一下,毕竟高可用是在可用的情况,只是很慢或者效率不高。其实也可以归为一类问题,但是不重要啦,重要的是怎么提高你写的接口的访问速度和性能。
当访问一个接口获取数据时,发现返回很慢,或者总是超时,如果排除网络的原因,那就是接口服务器压力太大,处理不过来了。在世界杯期间,我们查看后台日志总是connection by reset和borker pipe和一些超时问题。
这时候,你可能遇到了高并发和高可用问题。但是,不管遇到什么问题,都不能臆断和乱改,你得需要找到慢的原因,才能对症下药,乱改可能会导致其他问题的出现。首先,解决高并发问题的三个方向是负载均衡,缓存和集群。
我们使用的是阿里云服务器的负载均衡,后台分布式服务管理,我们运维小哥哥搭建了一套k8s,可以自由在k8s上扩展服务节点,各个服务结点也能随内存的使用自动漂移,不用多说,k8s真的很厉害,感兴趣的同学可以详细去学。那么问题来了,阿里云的负载均衡怎么对应到k8s的负载均衡呢?
这个涉及到了k8s的service暴露的一些特点,简单说就是k8s把所有集群的服务都通过指定的内部负载均衡,在指定的服务器上暴露,然后我们又把这几个服务器接在阿里云负载均衡下,这个涉及的细节和配置很多。当然,除nginx外,还有其他负载均衡解决方案,软件硬件都有,硬件如f5等。
阿里云的nginx负载均衡,我们使用的是加权轮询策略,其实轮询是最低效的方式;
这就是最基本的负载均衡实例,但这不足以满足实际需求;目前Nginx服务器的upstream模块支持6种方式的分配:
负载均衡策略
轮询 | 默认方式 |
---|---|
weight | 权重方式 |
ip_hash | 依据ip分配方式 |
least_conn | 最少连接方式 |
fair(第三方) | 响应时间方式 |
url_hash(第三方) | 依据URL分配方式 |
首先,通过排查问题,发现是oltpapi接口服务处理请求很慢,大量请求过来,总是超时和中断连接,这时候,我们想着最简单的方法就是加机器,给oltp接口服务多加几台机器。
嗯,一切都很完美,如预期进行,但是加到一定数量,你发现,怎么不起效果,异步响应还是很慢,或者更直观的说,消息队列出现了严重的消息堆积。这时候,你发现出现了新的问题或者瓶颈,这个问题已经不是说加oltp服务器能解决了,那么,就需要去重新定位问题。发现是消息堆积,消息堆积就是生产者过快,导致消费者消费不过来,这时候,你就需要增加消费者的消费数量。给风控系统多加几台机器,让消费者和生产者达到一定平衡。
这里有个误区,你可能以为是rocketmq的broker数量过少,增加broker数量,其实当消费者和生产者保持一样的速度时,消息肯定不对堆积,按照原始的broker数量就足够。但是增加broker也会使得消息得到尽快的处理,提升一定效率。
当加机器不能解决问题时,或者说没那么多服务器可使用时,那么就要重代码层面解决高并发问题。Redis 是一个高性能的key-value数据库,当获取数据从数据库拿很慢时,就可以存储到redis,从redis取值。
用ConcurrentHashMap缓存对象,并设置过期时间
redis缓存数据,结合spring定时任务定时获取不会经常改动的key
提高使用redis的效率:比如使用mGet一次获取多个key
....等
高可用问题应该上升到整个服务的架构问题上,就是说在搭建整体系统是就应该考虑到。高可用问题是以单点故障,访问速度慢的问题为主导。见 服务高可用
redis主从分布式(redis的单点故障和访问速度的提高和主从备份)
分布式dubbo服务的zookeeper主从集群
strom的主从集群
...等
下面对接口开发服务做一些总结:
当接口作为数据源时,还要考虑数据是让合作方主动过来拉还是数据有变化就推送呢,当然是推的效果更好,但是如何有效的推数据,不推重复数据等都是需要根据实际业务考虑的问题。
当接口服务和合作方都处于分布式情况下,就很容易出现一个订单号申请多次交易请求,但是根据幂等性,一张彩票只能交易一次,并且每次不管何时请求,结果都应该一样不会改变。这种情况下,我们怎么保证唯一性呢,我们需要把该订单和订单状态存redis,每次请求时去看是否订单已存在。但可能这次交易不成功,下次这张票还可以继续交易,可以生成新的订单号啊。
redis的setNX是一个很好的解决方案,意思是当存在该key时,返回false,当没有时,该key和value插入成功。用作检查订单是否正在提交,如果是,则阻塞本次请求,避免重复提交 ,可以设置过期时间3s。提交之前锁定订单,防止重复提交。
总之,博主发现,在高并发场景下,导致服务崩溃的原因还是redis和数据库,可能是redis读写太慢,或者数据库的一些sql使用不当,或者没建索引导致读写很慢。
总之,这是一条很漫长的路,我们都需要慢慢积累经验和学习前人更优秀的解决办法。
上述就是小编为大家整理的接口服务是什么?api接口服务质量分析包括哪些方面的相关内容。
国内(北京、上海、广州、深圳、成都、重庆、杭州、西安、武汉、苏州、郑州、南京、天津、长沙、东莞、宁波、佛山、合肥、青岛)api space软件分析、比较及推荐。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~