当前位置: 首页 > news >正文

南康网站制作海口cms建站系统

南康网站制作,海口cms建站系统,网站首页设计制作费用,百度手机网页版前言 前段时间老板心血来潮#xff0c;要我们前端组对整个的项目都做一下接口防止重复请求的处理#xff08;似乎是有用户通过一些快速点击薅到了一些优惠券啥的#xff09;。。。听到这个需求#xff0c;第一反应就是#xff0c;防止薅羊毛最保险的方案不还是在服务端加… 前言 前段时间老板心血来潮要我们前端组对整个的项目都做一下接口防止重复请求的处理似乎是有用户通过一些快速点击薅到了一些优惠券啥的。。。听到这个需求第一反应就是防止薅羊毛最保险的方案不还是在服务端加限制吗前端加限制能够拦截的毕竟有限。可老板就是执意要前端搞一下子行吧搞就搞吧。 虽然大部分的接口处理我们都是加了loading的但又不能确保真的是每个接口都加了的可是如果要一个接口一个接口的排查那这维护了四五年的系统成百上千的接口肯定要耗费非常多的精力根本就是不现实的所以就只能去做全局处理。下面就来总结一下这次的防重复请求的实现方案 方案一 这个方案是最容易想到也是最朴实无华的一个方案通过使用axios拦截器在请求拦截器中开启全屏Loading然后在响应拦截器中将Loading关闭。 这个方案固然已经可以满足我们目前的需求但不管三七二十一直接搞个全屏Loading还是不太美观何况在目前项目的接口处理逻辑中还有一些局部Loading就有可能会出现Loading套Loading的情况两个圈一起转头皮发麻。  方案二 加Loading的方案不太友好而对于同一个接口如果传参都是一样的一般来说都没有必要连续请求多次吧。那我们可不可以通过代码逻辑直接把完全相同的请求给拦截掉不让它到达服务端呢这个思路不错我们说干就干。 首先我们要判断什么样的请求属于是相同请求 一个请求包含的内容不外乎就是请求方法地址参数以及请求发出的页面hash。那我们是不是就可以根据这几个数据把这个请求生成一个key来作为这个请求的标识呢 // 根据请求生成对应的key function generateReqKey(config, hash) {const { method, url, params, data } config;return [method, url, JSON.stringify(params), JSON.stringify(data), hash].join(); } 有了请求的key我们就可以在请求拦截器中把每次发起的请求给收集起来后续如果有相同请求进来那都去这个集合中去比对如果已经存在了说明就是一个重复的请求我们就给拦截掉。当请求完成响应后再将这个请求从集合中移除。合理nice! 具体实现如下 是不是觉得这种方案还不错万事大吉 这个方案虽然理论上是解决了接口防重复请求这个问题但是它会引发更多的问题。 比如我有这样一个接口处理 那么当我们触发多次请求时  这里我连续点击了4次按钮可以看到的确是只有一个请求发送出去可是因为在代码逻辑中我们对错误进行了一些处理所以就将报错消息提示了3次这样是很不友好的而且如果在错误捕获中有做更多的逻辑处理那么很有可能会导致整个程序的异常。 而且这种方案还会有另外一个比较严重的问题 我们在上面在生成请求key的时候把hash考虑进去了(如果是history路由可以将pathname加入生成key)这是因为项目中会有一些数据字典型的接口这些接口可能有不同页面都需要去调用如果第一个页面请求的字典接口比较慢第二个页面的接口就被拦截了最后就会导致第二个页面逻辑错误。那么这么一看我们生成key的时候加入了hash讲道理就没问题了呀。 可是倘若我这两个请求是来自同一个页面呢 比如一个页面同时加载两个组件而这两个组件都需要调用某个接口时 那么此时后调接口的组件就无法拿到正确数据了。啊这真是难顶 方案三 方案二的路子我们发现确实问题重重那么接下来我们来看第三种方案也是我们最终采用的方案。 延续我们方案二的前面思路仍然是拦截相同请求但这次我们可不可以不直接把请求挂掉而是对于相同的请求我们先给它挂起等到最先发出去的请求拿到结果回来之后把成功或失败的结果共享给后面到来的相同请求。 思路我们已经明确了但这里有几个需要注意的点 我们在拿到响应结果后返回给之前我们挂起的请求时我们要用到发布订阅模式日常在面试题中看到这次终于让我给用上了 对于挂起的请求我们需要将它拦截不能让它执行正常的请求逻辑所以一定要在请求拦截器中通过return Promise.reject()来直接中断请求并做一些特殊的标记以便于在响应拦截器中进行特殊处理。 最后直接附上完整代码 import axios from axioslet instance axios.create({baseURL: /api/ })// 发布订阅 class EventEmitter {constructor() {this.event {}}on(type, cbres, cbrej) {if (!this.event[type]) {this.event[type] [[cbres, cbrej]]} else {this.event[type].push([cbres, cbrej])}}emit(type, res, ansType) {if (!this.event[type]) returnelse {this.event[type].forEach(cbArr {if(ansType resolve) {cbArr[0](res)}else{cbArr[1](res)}});}} } // 根据请求生成对应的key function generateReqKey(config, hash) {const { method, url, params, data } config;return [method, url, JSON.stringify(params), JSON.stringify(data), hash].join(); }// 存储已发送但未响应的请求 const pendingRequest new Set(); // 发布订阅容器 const ev new EventEmitter()// 添加请求拦截器 instance.interceptors.request.use(async (config) {let hash location.hash// 生成请求Keylet reqKey generateReqKey(config, hash)if(pendingRequest.has(reqKey)) {// 如果是相同请求,在这里将请求挂起通过发布订阅来为该请求返回结果// 这里需注意拿到结果后无论成功与否都需要return Promise.reject()来中断这次请求否则请求会正常发送至服务器let res nulltry {// 接口成功响应res await new Promise((resolve, reject) {ev.on(reqKey, resolve, reject)})return Promise.reject({type: limiteResSuccess,val: res})}catch(limitFunErr) {// 接口报错return Promise.reject({type: limiteResError,val: limitFunErr})}}else{// 将请求的key保存在configconfig.pendKey reqKeypendingRequest.add(reqKey)}return config;}, function (error) {return Promise.reject(error);});// 添加响应拦截器 instance.interceptors.response.use(function (response) {// 将拿到的结果发布给其他相同的接口handleSuccessResponse_limit(response)return response;}, function (error) {return handleErrorResponse_limit(error)});// 接口响应成功 function handleSuccessResponse_limit(response) {const reqKey response.config.pendKeyif(pendingRequest.has(reqKey)) {let x nulltry {x JSON.parse(JSON.stringify(response))}catch(e) {x response}pendingRequest.delete(reqKey)ev.emit(reqKey, x, resolve)delete ev.reqKey} }// 接口走失败响应 function handleErrorResponse_limit(error) {if(error.type error.type limiteResSuccess) {return Promise.resolve(error.val)}else if(error.type error.type limiteResError) {return Promise.reject(error.val);}else{const reqKey error.config.pendKeyif(pendingRequest.has(reqKey)) {let x nulltry {x JSON.parse(JSON.stringify(error))}catch(e) {x error}pendingRequest.delete(reqKey)ev.emit(reqKey, x, reject)delete ev.reqKey}}return Promise.reject(error); }export default instance; 补充 到这里这么一通操作下来上面的代码讲道理是万无一失了但不得不说线上的情况仍然是复杂多样的。而其中一个比较特殊的情况就是文件上传。 可以看到我在这里是上传了两个不同的文件的但只调用了一次上传接口。按理说是两个不同的请求可为什么会被我们前面写的逻辑给拦截掉一个呢 我们打印一下请求的config 可以看到请求体data中的数据是FormData类型而我们在生成请求key的时候是通过JSON.stringify方法进行操作的而对于FormData类型的数据执行该函数得到的只有{}。所以对于文件上传尽管我们上传了不同的文件但它们所发出的请求生成的key都是一样的这么一来就触发了我们前面的拦截机制。 那么我们接下来我们只需要在我们原来的拦截逻辑中判断一下请求体的数据类型即可如果含有FormData类型的数据我们就直接放行不再关注这个请求就是了。 function isFileUploadApi(config) {return Object.prototype.toString.call(config.data) [object FormData] } 最后 到这里整个的需求总算是完结啦不用一个个接口的改代码又可以愉快的打代码了nice
http://www.hkea.cn/news/14330603/

相关文章:

  • 云南建网站工程找队伍信息网
  • 大鹏外贸网站建设呼和浩特做网站的
  • 山东建设工程上传原件的网站企业网站建设方案 完整版
  • 在哪查找网站的建设者西部数码WordPress开启伪静态
  • 微信分销网站建设多少钱单屏网站设计
  • 如何做招聘网站统计表短故事网站模板
  • 柳市做公司网站saas小程序开发费用
  • 网站建设岗位职责怎么写成都工商注册
  • 网站搭建app可以做软件的网站有哪些功能吗
  • 网页制作与网站建设完全学习手册pdfpython培训学校
  • 网站开发资料现在出入邯郸最新规定
  • 网站地图怎么设置企业网页制作方案
  • 谁做视频网站商城网站建设公司招聘
  • 宁波象山网站建设网站制作与网页制作
  • 建设英文版网站网站设计属于什么分类号
  • 招投标网站开发公司seo短视频网页入口引流网站推荐
  • 热点新闻深圳网站优化运营
  • 沧州兼职网站建设保险平台有哪些
  • 广东网站开发需要多少钱网站ip改变 备案
  • 做卡贴的网站网站建设seo优化价格
  • vs2015 做网站主题字体wordpress
  • 网站建设报价比较表陕西企业营销型网站
  • 网站开发工具 枫子科技已有网站怎么做后台
  • wordpress post template南通优化网站费用
  • 网站开发费入账大连百度推广公司有几家
  • 一个网站建设流程图seo外链
  • 广西网站建设制作网站建设合同 含维护费
  • 网站网站制作网手机系统优化是什么意思
  • 驻马店住房和城乡建设部网站国内特效网站
  • 成都网站建设龙兵网络电子商务及网站建设