diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..d667ac2 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b12c368 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d1fabd2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/weRequest.iml b/.idea/weRequest.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/weRequest.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..6119059 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + 1507876610387 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 6e40405..ce119da 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,22 @@ -# 登录时序图 -![](http://mp.weixin.qq.com/debug/wxadoc/dev/image/login.png) +logo + +# weRequest + +_解决繁琐的小程序会话管理,一款自带登录态管理的网络请求组件。_ + +[![Travis](https://img.shields.io/travis/rust-lang/rust.svg)]() +[![Github All Releases](https://img.shields.io/github/downloads/IvinWu/weRequest/total.svg)]() +[![GitHub forks](https://img.shields.io/github/forks/IvinWu/weRequest.svg?style=social&label=Fork)]() +[![GitHub stars](https://img.shields.io/github/stars/IvinWu/weRequest.svg?style=social&label=Stars)]() +[![GitHub watchers](https://img.shields.io/github/watchers/IvinWu/weRequest.svg?style=social&label=Watch)]() +[![Packagist](https://img.shields.io/packagist/l/doctrine/orm.svg)]() + +## 简介 + +![登录时序图](http://mp.weixin.qq.com/debug/wxadoc/dev/image/login.png) 上图是小程序官方文档中的**登录时序图**。此图涵盖了前后端,详细讲解了包括登录态的生成,维护,传输等各方面的问题。 -# 发起网络请求的流程图 + 具体到业务开发过程中的前端来说,我认为上图还不够完整,于是我画了下面这张以**前端逻辑**为出发点的、包含循环的**流程图**。 我认为前端每一次**发起网络请求**,跟后台进行数据交互,都适用于下图的**流程**: ![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow_login.png) @@ -10,6 +24,7 @@ - **hasChecked:** 用一状态标识本生命周期内是否执行过`wx.checkSession`,判断该标识,若否,开始执行`wx.checkSession`,若是,进入下一步 - **wx.checkSession():** 调用接口判断登录态是否过期,若是,重新登录;若否,进入下一步 > wx.checkSession()是小程序提供的检测登录态是否过期的接口,生命周期内只需调用一次即可。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明 + - **wx.getStorage(session):** 尝试获取本地的`session`。如果之前曾经登录过,则能获取到;否则,本地无`session` - **wx.login():** 小程序提供的接口,用于获取`code`(code有效期为5分钟) - **wx.request(code):** 将`code`通过后台提供的接口,换取`session` @@ -17,16 +32,13 @@ - **wx.request(session):** 真正发起业务请求,请求中带上`session` - **parse(data):** 对后台返回的数据进行预解析,若发现登录态失效,则重新执行登录;若成功,则真正获取到业务数据 -# 拓展小程序网络请求的能力 -只要遵循上图的流程,我们就无需在业务逻辑中关注登录态的问题了,相当于把登录态的管理问题**耦合**到了发起网络请求当中。 -一般情况下,我们程序设计都会遵循模块解耦的原则,尽可能将模块颗粒化到最小。这导致可能有些同学认为模块耦合不是好事情,但是我认为这是要分情况的: -- 小程序区别与传统的H5,不支持cookies,在代码层级上讲,这无形中就给登录态的管理增加了复杂度:cookies会在H5的每个请求中自动带上,但小程序的请求却每次都需要手动带上登录态参数 -- 小程序区别于基于公众号登录的H5来说,又存在一定的优势:登录授权时并不需要多次的页面跳转(Oauth),也正因为如此,小程序的请求在登录态失效时,需要具备重新登录并自动重试请求的能力(无页面刷新感,用户甚至都不能感知到进行了重新登录) +只要遵循上图的流程,我们就无需在业务逻辑中关注登录态的问题了,相当于把登录态的管理问题**耦合**到了发起网络请求当中,本组件则完成了上述流程的封装,让开发者不用再关心以上逻辑,把精力放回在业务的开发上。 + +## 目标 +让业务逻辑更专注,不用再关注底层登录态问题。小程序对比以往的H5,登录态管理逻辑要复杂很多。通过`weRequest`这个组件,希望能帮助开发者把更多精力放在业务逻辑上,而登录态管理问题只需通过一次简单配置,以后就不用再花精力管理了。 + +## 怎么使用 -以上两点虽然是登录态管理的问题,但从另外一个角度去理解,我更认为它是小程序网络请求的能力问题,所以,我认为**通过拓展小程序网络请求能力来实现登录态的自动管理**是非常合适的。 -# 通用组件——weRequest -一个通过拓展`wx.request`,从而实现自动管理登录态的组件。 -先来看看怎么使用: ```javascript var weRequest= require('../weRequest'); @@ -51,87 +63,213 @@ weRequest.request({ - 初始化组件配置 - **就像使用`wx.request`那样去使用它** -## 自动带上登录态参数 -我们来看看执行上面代码的DEMO效果: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/auto_session.png) +## 演示DEMO + +### 自动带上登录态参数 +![自动带上登录态参数](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/auto_session.png) 可以看到,通过`weRequest`发出的请求,将会自动带上登录态参数。 对应的流程为下图中**红色**的指向: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow1.png) +![自动带上登录态参数](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow1.png) -## 没有登录态时,自动登录 -那如果当前小程序并没有登录态的情况又会如何呢? -接下来我们来看看本地无登录态情况下的模拟: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/autoLogin.gif) +### 没有登录态时,自动登录 +![没有登录态时,自动登录](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/autoLogin.gif) 当本地没有登录态时,按照流程图,`weRequest`将会自动执行`wx.login()`后的一系列流程,得到`code`并调用后台接口换取`session`,储存在localStorage之后,重新发起业务请求。 对应的流程为下图中**红色**的指向: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow2.png) +![没有登录态时,自动登录](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow2.png) -## 登录态过期时,自动重新登录 -接下来我们再来看看,当本地储存的登录态过期之后,页面的行为如何: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/relogin.gif) +### 登录态过期时,自动重新登录 +![登录态过期时,自动重新登录](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/relogin.gif) 对后台数据进行预解析之后,发现登录态过期,于是重新执行登录流程,获取新的`session`之后,重新发起请求。 对应的流程为下图中**红色**的指向: -![](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow3.png) +![登录态过期时,自动重新登录](https://raw.githubusercontent.com/IvinWu/weRequest/master/image/flow3.png) + +## 文档 + +### .init(OBJECT) + +对组件进行初始化配置,使用组件发起请求前必须进行至少一次的配置 + +#### OBJECT参数说明 + +|参数名|类型|必填|默认值|说明| +| :-------- | :-------| :------ | :------ |:------ | +|sessionName|String|否|session|储存在localStorage的session名称,且CGI请求的data中会自动带上以此为名称的session值;可不配置,默认为session| +|urlPerfix|String|否||请求URL的固定前缀,如果配置了,后续请求的URL都会自动加上这个前缀| +|loginTrigger|Function|是||触发重新登录的条件;参数为CGI返回的数据,返回需要重新登录的条件| +|codeToSession|Object|是||用code换取session的CGI配置| +|reLoginLimit|Int|否|3|登录重试次数,当连续请求登录接口返回失败次数超过这个次数,将不再重试登录| +|successTrigger|Function|是||触发请求成功的条件;参数为CGI返回的数据,返回接口逻辑成功的条件| +|successData|Function|否||成功之后返回数据;参数为CGI返回的数据,返回逻辑需要使用的数据| +|errorTitle|String/Function|否|操作失败|接口逻辑失败时,错误弹窗的标题| +|errorContent|String/Function|否||接口逻辑失败时,错误弹窗的内容| +|errorCallback|Function|否||当出现接口逻辑错误时,会执行统一的回调函数,这里可以做统一的错误上报等处理| +|doNotCheckSession|Boolean|否|false|是否需要调用checkSession,验证小程序的登录态过期;若业务不需要使用到session_key,则可配置为true| +|reportCGI|Function|否||接口返回成功之后,会执行统一的回调函数,这里可以做统一的耗时上报等处理| +|mockJson|Object|否||可为接口提供mock数据| +|globalData|Object/Function|否||所有请求都会自动带上这里的参数| + +##### codeToSession参数说明 + +|参数名|类型|必填|默认值|说明| +| :-------- | :-------| :------ | :------ |:------ | +|url|String|是||CGI的url| +|method|String|否|GET|调用改CGI的方法| +|codeName|String|否|code|CGI中传参时,存放code的名称| +|data|Object|否||登录接口需要的其他参数| +|success|Function|是||接口返回成功的函数;需要返回session的值| +|fail|Function|否||code换取session的接口逻辑出错时,执行的函数,若配置了此函数,则不再默认弹窗报错| + +##### reportCGI返回参数说明 +|参数名|类型|说明| +| :-------- | :-------| :------ | +|name|String|调用的接口名字,可在request接口的report字段配置| +|startTime|Int|发起请求时的时间戳| +|endTime|Int|请求返回时的时间戳| +|request|Function|请求方法,可用于上报| + +#### 示例代码 -## 组件的配置项 -`weRequest`提供一个`init`方法,用于对组件的配置,以下展示所有的配置项: ```javascript weRequest.init({ - // 储存在localStorage的session名称,且CGI请求的data中会自动带上以此为名称的session值;可不传,默认为session sessionName: "session", - // 请求URL的固定前缀;可不传,默认为空 urlPerfix: "https://www.example.com/", // 触发重新登录的条件,res为CGI返回的数据 loginTrigger: function (res) { // 此处例子:当返回数据中的字段errcode等于-1,会自动触发重新登录 return res.errcode == -1; }, - // 用code换取session的CGI配置 codeToSession: { - // CGI的URL url: 'user/login', - // 调用改CGI的方法;可不传,默认为GET method: 'GET', - // CGI中传参时,存放code的名称,此处例子名称就是code;可不传,默认值为code codeName: 'code', + data: {}, // CGI中返回的session值 success: function (res) { // 此处例子:CGI返回数据中的字段session即为session值 return res.session; } }, - // 登录重试次数,当连续请求登录接口返回失败次数超过这个次数,将不再重试登录 reLoginLimit: 2, - // 触发请求成功的条件 successTrigger: function (res) { // 此处例子:当返回数据中的字段errcode等于0时,代表请求成功,其他情况都认为业务逻辑失败 return res.errcode == 0; }, - // 成功之后返回数据;可不传 successData: function (res) { // 此处例子:返回数据中的字段data为业务接受到的数据 return res.data; }, - // 当CGI返回错误时,弹框提示的标题文字 errorTitle: function(res) { // 此处例子:当返回数据中的字段errcode等于0x10040730时,错误弹框的标题是“温馨提示”,其他情况下则是“操作失败” return res.errcode == 0x10040730 ? '温馨提示' : '操作失败' }, - // 当CGI返回错误时,弹框提示的内容文字 errorContent: function(res) { // 此处例子:返回数据中的字段msg为错误弹框的提示内容文字 return res.msg + }, + errorCallback: function(obj, res) { + // do some report + }, + doNotCheckSession: true, + // 上报耗时的函数,name为上报名称,startTime为接口调用开始时的时间戳,endTime为接口返回时的时间戳 + reportCGI: function(name, startTime, endTime, request) { + // 这里可以自行上报耗时 + //wx.reportAnalytics(name, { + // time: endTime - startTime + //}); + //request({ + // url: 'reportCGI', + // data: { + // name: name, + // cost: endTime - startTime + // }, + // fail: function() { + // + // } + //}) + console.log(name + ":" + (endTime - startTime)); + }, + mockJson: require("../../mock.json"), + globalData: function() { + return { + version: getApp().version + } } }) ``` -# 让业务逻辑更专注,不用再关注底层登录态问题 -小程序对比以往的H5,登录态管理逻辑要复杂很多。通过`weRequest`这个组件,希望能帮助开发者把更多精力放在业务逻辑上,而登录态管理问题只需通过一次简单配置,以后就不用再花精力管理了。 -# FAQ -## 我希望在请求时候,页面能出现最简单的loading状态,该怎么办? +### .request(OBJECT) + +带上登录态发起一个请求,参数大部分与`wx.request`一致 + +#### OBJECT参数说明 + +|参数名|类型|必填|默认值|说明|是否与wx.request不一致| +| :-------- | :-------| :------ | :------ |:------ |:------ | +|url|String|是||开发者服务器接口地址,若在init()时有配置urlPerfix,则这里会自动拼接前缀|是| +|data|Object/String|否||请求的参数|| +|header|Object|否||设置请求的 header,header 中不能设置 Referer。|| +|method|String|否|GET|(需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT|| +|dataType|String|否|json|如果设为json,会尝试对返回的数据做一次 JSON.parse|| +|beforeSend|Function|否||发起请求前执行的函数|是| +|success|Function|否||收到开发者服务成功返回,且执行`successTrigger`成功后的回调函数,参数为`successData`返回的参数|是| +|fail|Function|否||接口调用失败,或执行`successTrigger`失败后的回调函数,若这里有配置,则不再默认弹窗报错|是| +|complete|Function|否||接口调用结束的回调函数(调用成功、失败都会执行)|| +|showLoading|Boolean|否|false|请求过程页面是否展示全屏的loading|是| +|report|String|否||接口请求成功后将自动执行init()中配置的reportCGI函数,其中的name字段值为这里配置的值|是| + +#### 示例代码 + +```javascript +weRequest.request({ + url: 'order/detail', + showLoading: true, + report: 'detail', + data: { + id: '123' + }, + success: function (data) { + console.log(data); + }, + fail: function(obj, res) { + } +}) +``` + +### .uploadFile(Object) + +带上登录态,将本地资源上传到开发者服务器,客户端发起一个 HTTPS POST 请求,其中 content-type 为 multipart/form-data,参数大部分与`wx.uploadFile`一致 + +#### OBJECT参数说明 + +|参数名|类型|必填|默认值|说明|是否与wx.uploadFile不一致| +| :-------- | :-------| :------ | :------ |:------ |:------ | +|url|String|是||开发者服务器接口地址,若在init()时有配置urlPerfix,则这里会自动拼接前缀|是| +|filePath|String|是||要上传文件资源的路径|| +|name|String|是||文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容|| +|header|Object|否||设置请求的 header,header 中不能设置 Referer。|| +|formData|Object|否||HTTP 请求中其他额外的 form data|| +|beforeSend|Function|否||发起请求前执行的函数|是| +|success|Function|否||收到开发者服务成功返回,且执行`successTrigger`成功后的回调函数,参数为`successData`返回的参数|是| +|fail|Function|否||接口调用失败,或执行`successTrigger`失败后的回调函数,若这里有配置,则不再默认弹窗报错|是| +|complete|Function|否||接口调用结束的回调函数(调用成功、失败都会执行)|| +|showLoading|Boolean|否|false|请求过程页面是否展示全屏的loading|是| +|report|String|否||接口请求成功后将自动执行init()中配置的reportCGI函数,其中的name字段值为这里配置的值|是| + +### .login() + +[不建议使用] 在不发起业务请求的情况下,单独执行登录逻辑 + +### .setSession(String) + +[不建议使用] 设置用户票据的值 + +## FAQ + +### 我希望在请求时候,页面能出现最简单的loading状态,该怎么办? + 只需要在请求的时候,加上参数`showLoading: true`即可,如: ```javascript weRequest.request({ @@ -147,7 +285,8 @@ weRequest.request({ ``` 当然,如果你希望使用个性化的loading样式,你可以直接使用beforeSend参数来进行自定义展示个性化的loading,并且在complete的时候将它隐藏。 -## 某些请求在返回错误时,我不希望触发通用的错误提示框,而想用特别的逻辑去处理,该怎么办? +### 某些请求在返回错误时,我不希望触发通用的错误提示框,而想用特别的逻辑去处理,该怎么办? + 只需要在请求的时候,加上参数`fail: function(){ ... }`即可,如: ```javascript weRequest.request({ @@ -166,7 +305,8 @@ weRequest.request({ ``` 此时,如果接口返回错误码,将触发这里定义的fail函数,且默认错误弹框将不会出现。 -## 为什么工具在发起请求之前,不主动去判断第三方session是否过期,而要通过接口结果来判断,这不是浪费了一次请求往返吗? +### 为什么工具在发起请求之前,不主动去判断第三方session是否过期,而要通过接口结果来判断,这不是浪费了一次请求往返吗? + 每个小程序对于自身生成的session都有自己的一套管理方案,微信官方也没有指明一套通用的方案来要求开发者,仅仅要求了**应该保证其安全性且不应该设置较长的过期时间**。 原文如下: >通过 wx.login() 获取到用户登录态之后,需要维护登录态。开发者要注意不应该直接把 session_key、openid 等字段作为用户的标识或者 session 的标识,而应该自己派发一个 session 登录态(请参考登录时序图)。对于开发者自己生成的 session,应该保证其安全性且不应该设置较长的过期时间。session 派发到小程序客户端之后,可将其存储在 storage ,用于后续通信使用。 diff --git a/build/weRequest.js b/build/weRequest.js index aaacf59..b07af43 100644 --- a/build/weRequest.js +++ b/build/weRequest.js @@ -1 +1 @@ -module.exports=function(t){function e(n){if(o[n])return o[n].exports;var c=o[n]={i:n,l:!1,exports:{}};return t[n].call(c.exports,c,c.exports,e),c.l=!0,c.exports}var o={};return e.m=t,e.c=o,e.i=function(t){return t},e.d=function(t,o,n){e.o(t,o)||Object.defineProperty(t,o,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var o=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(o,"a",o),o},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e){function o(){wx.showToast({title:"加载中",icon:"loading",mask:!0,duration:6e4})}function n(){wx.hideToast()}t.exports={show:o,hide:n}},function(t,e,o){function n(t,e){M?c(t,e):(e.count++,wx.checkSession({success:function(){M=!0},fail:function(){C=""},complete:function(){e.count--,c(t,e)}}))}function c(t,e){C||e.isLogin?"function"==typeof t&&t():b?setTimeout(function(){c(t,e)},300):(b=!0,e.count++,wx.login({complete:function(){e.count--,"function"==typeof e.complete&&0==e.count&&e.complete()},success:function(o){if(o.code){var n;n="function"==typeof g.data?g.data():g.data||{},n[g.codeName]=o.code,e.count++,l({url:g.url,data:n,method:g.method,isLogin:!0,success:function(e){C=e,M=!0,"function"==typeof t&&t(),wx.setStorage({key:m,data:C})},complete:function(){e.count--,"function"==typeof e.complete&&0==e.count&&e.complete(),b=!1}})}else wx.showModal({title:"登录失败",content:"请稍后重试",showCancel:!1}),console.error(o),b=!1},fail:function(t){wx.showModal({title:"登录失败",content:t.errMsg||"请稍后重试",showCancel:!1}),console.error(t),b=!1}}))}function i(t){return"function"==typeof t.beforeSend&&t.beforeSend(),void 0===t.reLoginLimit?t.reLoginLimit=0:t.reLoginLimit++,void 0===t.count&&(t.count=0),t.showLoading&&(h.show(),t.complete=function(t){return function(){h.hide(),"function"==typeof t&&t.apply(this,arguments)}}(t.complete)),t}function u(t){t.count++,t.data||(t.data={}),t.url!=g.url&&(t.data[m]=C),t.method=t.method||"GET";var e=t.url.startsWith("http")?t.url:x+t.url;"GET"!=t.method&&(e.indexOf("?")>=0?e+="&"+m+"="+C:e+="?"+m+"="+C),wx.request({url:e,data:t.data,method:t.method,header:t.header||{},dataType:t.dataType||"json",success:function(e){if(200==e.statusCode)if(t.isLogin){var o="";try{o=g.success(e.data)}catch(t){}o?t.success(o):r(t,e)}else if(y(e.data)&&t.reLoginLimit=0?o+="&"+h+"="+E:o+="?"+h+"="+E);for(var n in e)o.indexOf("?")>=0?o+="&"+n+"="+e[n]:o+="?"+n+"="+e[n]}t.report&&(t._reportStartTime=(new Date).getTime()),wx.request({url:o,data:t.data,method:t.method,header:t.header||{},dataType:t.dataType||"json",success:function(e){if(200==e.statusCode)if(t.report&&"function"==typeof b&&(t._reportEndTime=(new Date).getTime(),b(t.report,t._reportStartTime,t._reportEndTime,r)),t.isLogin){var o="";try{o=w.success(e.data)}catch(t){}o?t.success(o):u(t,e)}else if(T(e.data)&&t.reLoginLimit=0?o+="&"+h+"="+E:o+="?"+h+"="+E);for(var n in e)o.indexOf("?")>=0?o+="&"+n+"="+e[n]:o+="?"+n+"="+e[n];t.report&&(t._reportStartTime=(new Date).getTime()),wx.uploadFile({url:o,filePath:t.filePath||"",name:t.name||"",formData:t.formData,success:function(e){if(200==e.statusCode&&"uploadFile:ok"==e.errMsg){if(t.report&&"function"==typeof b&&(t.endTime=(new Date).getTime(),b(t.report,t._reportStartTime,t._reportEndTime,r)),"json"==t.dataType)try{e.data=JSON.parse(e.data)}catch(o){return u(t,e),!1}T(e.data)&&t.reLoginLimit= 0) { - url += '&' + sessionName + '=' + session; - } else { - url += '?' + sessionName + '=' + session; + + if(session) { + if(url.indexOf('?') >= 0) { + url += '&' + sessionName + '=' + session; + } else { + url += '?' + sessionName + '=' + session; + } } + + // 如果有全局参数,则在URL中添加 + for(var i in gd) { + if(url.indexOf('?') >= 0) { + url += '&' + i + '=' + gd[i]; + } else { + url += '?' + i + '=' + gd[i]; + } + } + } + + // 如果有上报字段配置,则记录请求发出前的时间戳 + if(obj.report) { + obj._reportStartTime = new Date().getTime(); } wx.request({ @@ -176,6 +219,13 @@ function request(obj) { dataType: obj.dataType || 'json', success: function (res) { if (res.statusCode == 200) { + + // 如果有上报字段配置,则记录请求返回后的时间戳,并进行上报 + if(obj.report && typeof reportCGI == "function") { + obj._reportEndTime = new Date().getTime(); + reportCGI(obj.report, obj._reportStartTime, obj._reportEndTime, request); + } + if (obj.isLogin) { // 登录请求 var s = ""; @@ -222,12 +272,8 @@ function request(obj) { } }, fail: function (res) { - wx.showModal({ - title: "请求失败", - content: res.errMsg, - showCancel: false - }) fail(obj, res); + console.error(res); }, complete: function () { obj.count --; @@ -244,15 +290,57 @@ function uploadFile(obj) { } obj.formData[sessionName] = session; + // 如果有全局参数,则添加 + var gd = {}; + if(typeof globalData == "function") { + gd = globalData(); + } else if(typeof globalData == "object") { + gd = globalData; + } + obj.formData = Object.assign({}, gd, obj.formData); + obj.dataType = obj.dataType || 'json'; + // 如果请求的URL中不是http开头的,则自动添加配置中的前缀 + var url = obj.url.startsWith('http') ? obj.url : (urlPerfix + obj.url); + + // 在URL中自动加上登录态和全局参数 + if(session) { + if(url.indexOf('?') >= 0) { + url += '&' + sessionName + '=' + session; + } else { + url += '?' + sessionName + '=' + session; + } + } + + // 如果有全局参数,则在URL中添加 + for(var i in gd) { + if(url.indexOf('?') >= 0) { + url += '&' + i + '=' + gd[i]; + } else { + url += '?' + i + '=' + gd[i]; + } + } + + // 如果有上报字段配置,则记录请求发出前的时间戳 + if(obj.report) { + obj._reportStartTime = new Date().getTime(); + } + wx.uploadFile({ - url: urlPerfix + obj.url, + url: url, filePath: obj.filePath || '', name: obj.name || '', formData: obj.formData, success: function (res) { if (res.statusCode == 200 && res.errMsg == 'uploadFile:ok') { + + // 如果有上报字段配置,则记录请求返回后的时间戳,并进行上报 + if(obj.report && typeof reportCGI == "function") { + obj.endTime = new Date().getTime(); + reportCGI(obj.report, obj._reportStartTime, obj._reportEndTime, request); + } + if(obj.dataType == 'json') { try { res.data = JSON.parse(res.data); @@ -284,12 +372,8 @@ function uploadFile(obj) { } }, fail: function (res) { - wx.showModal({ - title: "请求失败", - content: res.errMsg, - showCancel: false - }) fail(obj, res); + console.error(res); }, complete: function () { obj.count --; @@ -321,8 +405,8 @@ function fail(obj, res) { } wx.showModal({ - title: title || "操作失败", - content: content || "服务器异常,请稍后重试", + title: title, + content: content || "网络或服务异常,请稍后重试", showCancel: false }) } @@ -355,13 +439,14 @@ function getCache(obj, callback) { } } +function login(callback) { + checkSession(callback, {}) +} + function init(params) { sessionName = params.sessionName || 'session'; loginTrigger = params.loginTrigger || function () { return false }; - codeToSession = Object.assign({}, { - method: 'GET', - codeName: 'code' - }, params.codeToSession); + codeToSession = params.codeToSession || {}; successTrigger = params.successTrigger || function () { return true }; urlPerfix = params.urlPerfix || ""; successData = params.successData || function (res) { return res }; @@ -370,6 +455,9 @@ function init(params) { reLoginLimit = params.reLoginLimit || 3; errorCallback = params.errorCallback || null; sessionIsFresh = params.doNotCheckSession || false; + reportCGI = params.reportCGI || false; + mockJson = params.mockJson || false; + globalData = params.globalData || false; try { session = wx.getStorageSync(sessionName) || ''; @@ -378,11 +466,16 @@ function init(params) { function requestWrapper(obj) { obj = preDo(obj); - getCache(obj, function() { - checkSession(function () { - request(obj); - }, obj)} - ) + if(mockJson && mockJson[obj.url]) { + // mock 模式 + mock(obj); + } else { + getCache(obj, function() { + checkSession(function () { + request(obj); + }, obj)} + ) + } } function uploadFileWrapper(obj) { @@ -397,9 +490,23 @@ function setSession(s) { sessionIsFresh = true; } +function mock(obj) { + var res = { + data: mockJson[obj.url] + }; + if (successTrigger(res.data) && typeof obj.success == "function") { + // 接口返回成功码 + obj.success(successData(res.data)); + } else { + // 接口返回失败码 + fail(obj, res); + } +} + module.exports = { init: init, request: requestWrapper, uploadFile: uploadFileWrapper, - setSession: setSession + setSession: setSession, + login: login }; \ No newline at end of file