二十分钟封装,一个App前后台Http交互的实现

算法吟游者
• 阅读 1563

在React Native开发过程中,几乎所有的app都需要使用到Http请求,所以fetch的封装必不可少,由于不同app的请求参数,解析规则,token机制等完全不一样,所以在大多数App开发中,前后台Http请求的实现都是开发者自己封装的。

封装一个前后台Http请求实现需要多久?

可能有人回答是1小时,也有3、5小时甚至更长时间的,或者也有说先这样封装个大概,等到需求不满足的时候再改。

花费1小时的时间不一定短,花费3、5的时间也不一定算长,具体要看前后台交互的复杂程度与开发者对交互实现的封装程度。

那这里我们就引出了一个问题了,我们通常说的app的Http请求【封装】,到底封装的是什么,我们需要做哪些工作,能使用得app的接口请求更简单,易用且有较高的灵活性?在我看来这个“封装”主要分两个部分:

  1. 数据交换 层面的封装,即:

    • 实现前后台的互通,支持服务器要求的数据交换类型、格式等
    • 调用者可以自由设置请求的header、params等参数,程序根据不同的设置也能保证请求能正确的发送给服务端并返回相应的结果
    • 支持超时、日志打印等一些基本功能
  2. 业务逻辑 层面的封装,即:

    • 入参:公共部分header、params的参数处理,避免在具体接口请求是传入不必要与接口无关的参数
    • 出参:对后台返回的数据按约定好的规则做一层基础解析处理,避免在具体接口数据解析的时候做一些无意义的操作

从投入的时间上来看:
第一部分基本上要花掉开发者80%以上的时间来封装
第二部分需要消耗的时间可能不足20% 【以此推算,按上面1个小时的封装时间,用在逻辑封装部分的时间也就12分钟左右?】

我们再回头看一下,第一部分的【数据交换】封装是否涉及到具体业务逻辑呢?答案是:没有。

既然没有我们为什么不把第一部分的封装交给第三方框架呢,我们只需要做第二部分的封装多省事,有这样第三方框架么?

答案是:有的,react-native-easy-app 就可以实现【前后台数据交换】层面的封装,通过这个开源库,我们就只需要实现涉及【App业务逻辑】层面的封装即可。


为验证 react-native-easy-app 的实用性,在这里我们先来构想一个业务逻辑层面封装的需求:

  1. 请求接口的公共headers参数有:

    • version、channelCode、model、platform (所有接口)
    • accessToken、refreshToken、customerId (登录后额外增加)
  2. 请求接口的公共params参数有:

    • customerId (登录后额外增加)
  3. 后台返回的数据结构示例如下:
    { data: {}, successful:1, msg: 'request msg', code: 'xxx'}
  4. 请求状态码为503的时候表示accessToken过期,accessToken过期的情况下,需要重新获取新的accessToken并刷新因accessToken过期导致请求失败的接口

    1. accessToken、refreshToken在登录成功后的response的headers中返回。

对于以上业务逻辑层面的需求,看看通过 react-native-easy-app 我们可以怎么做。

XHttpConfig().initHttpLogOn(true)
    .initHeaderSetFunc((headers) => {
        headers['model'] = 'xiao mi';
        headers['version'] = '1.0.0';
        headers['platform'] = Platform.OS;
        headers['channelCode'] = 'channelOfficial';
        if (isLogin()) {
            headers['customerId'] = RNStorage.customerId;
            headers['accessToken'] = RNStorage.accessToken;
            headers['refreshToken'] = RNStorage.refreshToken;
        }
    })
    .initParamSetFunc(params => {
        if (isLogin()) {
            params['customerId'] = RNStorage.customerId;
        }
    })
    .initParseDataFunc((result, request, callback) => {
        let {success, json, message, status, response} = result;
        if (status === 503) {// accessToken过期标记
            this.refreshToken(request, callback);
        } else {
            let {data, successful, msg, code} = json;
            callback(success && successful === 1, data || {}, msg || message, code, response);
        }
    });

accessToken重新请求的实现及对失败接口的刷新:

refreshToken = (request, callback) => {
    if (global.hasQueryToken) {
        global.tokenExpiredList.push({request, callback});
    } else {
        global.hasQueryToken = true;
        global.tokenExpiredList = [{request, callback}];
        const refreshUrl = `${RNStorage.baseUrl}api/refreshToken?refreshToken=${RNStorage.refreshToken}`;
        fetch(refreshUrl).then(resp => {
            resp.json().then(({successful, data: {accessToken}}) => {
                if (successful === 1) {// 获取到新的accessToken
                    RNStorage.accessToken = accessToken;
                    global.tokenExpiredList.map(({request, callback}) => {
                        request.resendRequest(request, callback);
                    });
                    global.tokenExpiredList = [];
                } else {
                    console.log('Token 过期,退出登录');
                }
            });
        }).catch(err => {
            console.log('Token 过期,退出登录');
        }).finally(() => {
            global.hasQueryToken = false;
        });
    }
};

就这样对当前构想的app的逻辑层面的封装就实现了(实现上面的代码约70行,也许要超过20分钟 ??,但相较于以前从零开的封装,是不是节约了大量的时间呢?)是不是清晰明了。当然,这只是代码片段,没有实际操作,就没办法证明上面的代码实现是实际有效的。


为了演示,先用 react native init HttpTestDemo 创建一个RN项目:示例项目:HttpTestDemo 修改并删除不必要的布局或资源,结果如下:

二十分钟封装,一个App前后台Http交互的实现

假定有三个接口,分别为 api/login、api/userInfo 、api/refreshToken (为了省事,接口都以json文件替代

  • api/login 有两个必传参数:[userName、userPass];请求内容类型为:application/x-www-form-urlencoded;post请求
  • api/userInfo 无参数;请求内容类型为:application/json;get请求
  • api/refreshToken 必须参数refreshToken;请求内容类型为:application/json;get请求

https://react-native-easy-app...
https://react-native-easy-app...
https://react-native-easy-app...

  1. react-native-easy-app 的说明文档,安装库:npm install react-native-easy-app --save
  2. 定义一个持久化对象,用于保存accessToken,customerId等参数:
export const RNStorage = {// 持久化数据列表
    customerId: undefined,//客户ID
    accessToken: undefined,//OAuth2.0 accessToken
    refreshToken: undefined,//OAuth2.0 refreshToken
    baseUrl: undefined,
    userInfo: undefined,
    hasLogin: false,
};

3.在页面的构造方法时调用 RNStorage的初始化操作;初始化完成之后,调用Http请求XHttpConfig的【业务逻辑】层初始化方法,这样就完成了,现在就可以调用接口了。

  • 调用登录接口:(由于使用json文件的形式只能使用get请求)
import { XHttp } from 'react-native-easy-app';

login = () => {
    let params = {userName: 'zhangsan', userPass: '123456a'};
    XHttp().url('api/login').param(params).formEncoded().get((success, json, message, status, resonse) => {
        if (success) {
            if (resonse.headers && resonse.headers.map) {
                RNStorage.accessToken = resonse.headers.map['x-oss-meta-accesstoken'];
                RNStorage.refreshToken = resonse.headers.map['x-oss-meta-refreshtoken'];
            }
            RNStorage.customerId = json.customerId;
            RNStorage.hasLogin = true;
            this.setState({data: JSON.stringify(json)});
        } else {
            console.log('失败', message);
        }
    });
};

调用接口,通过框架自带的日志功能,可以看到,该拼的参数都拼接了,从header中也获取到了token

二十分钟封装,一个App前后台Http交互的实现

  • 调用获取用户个人信息接口:
import { XHttp } from 'react-native-easy-app';

queryUserInfo = () => {
    XHttp().url('api/userInfo').formJson().get((success, json, message) => {
        if (success) {
            RNStorage.userInfo = json;
            this.setState({data: JSON.stringify(json)});
        } else {
            console.log('失败', message);
        }
    });
};

调用接口,通过框架自带的日志功能,可以看到accessToken、refreshToken也正确的拼接了。

二十分钟封装,一个App前后台Http交互的实现

由于没有合适的服务器,token过期的情况就不演示了,只要请求refreshToken的接口正常请求就不会有问题。

至此一个完整的App 【业务逻辑】层面的封装就完全实现了,从Http请求的配置到,refreshToken的重新请求到刷新失败接口,一共大概只用了70行代码左右,是不是相较于之前从零开始的fetch封装简单容易多了,节约了大量的封装时间呢?

担心框架的灵活性?请参考 react-native-easy-app 详解与使用之(二) fetch 并且react-native-easy-app 开源库并不只有Http请求的封装,还有更多功能,有兴趣的同学可以查看此栏目的其它文章,你肯定会有更多收获。

当前示例项目链接:HttpTestDemo

如果有任何疑问,欢迎扫码加入RN技术QQ交流群

二十分钟封装,一个App前后台Http交互的实现

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
SVG跟随父级DIV自适应
后台返回过来的是这样的SVG标签<svgwidth"100%"height"100%"version"1.1"xmlns"http://www.w3.org/2000/svg"<gtransform"translate(00)"
Easter79 Easter79
3年前
SVG跟随父级DIV自适应
后台返回过来的是这样的SVG标签<svgwidth"100%"height"100%"version"1.1"xmlns"http://www.w3.org/2000/svg"<gtransform"translate(00)"
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringMvc接受特殊符号参数被转义
WEB开发时,在前端通过get/post方法传递参数的时候 如果实参附带特殊符号,后端接收到的值中特殊符号就会被转义例如该请求: http://localhost:10001/demo/index.do?name张三(1)注:中文()不会出现此种情况后台就收到的实际name值为:  张三&40;1&41;&40;其实为h
Wesley13 Wesley13
3年前
Unity横屏
Android下发现Unity里面的Player设置,并不能完全有效,比如打开了自动旋转,启动的时候还是会横屏,修改XML添加以下代码<applicationandroid:icon"@drawable/ic\_launcher"                    android:label"@string/app\_name"
Json格式Java封装天猫商品详情数据接口,实现海量商品采集业务
根据天猫的API文档,获取天猫商品详情的API是通过发送Http/Post/GET请求,其中itemID是具体的商品ID。以下是Python和Java封装获取天猫商品详情API(复制Taobaoapi2014)的示例代码:1.请求方式:HTTPPOSTGE