Code coverage report for wechat-enterprise/lib/api_common.js

Statements: 85.51% (59 / 69)      Branches: 74.19% (23 / 31)      Functions: 92.86% (13 / 14)      Lines: 85.51% (59 / 69)      Ignored: none     

All files » wechat-enterprise/lib/ » api_common.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216    1 1 1   1 86 43   43                                                                           1 13 13 13 13 13 34   13 9 9     9   13                                                 1 11 11 11 11 2   9 9 9     9     11                             1 33 33   33 33       33   33 33 33   33     33     33 33 33                                                                       1 3   3 3 1     2 1     1                           1 8 51 1   50       1   1  
// 本文件用于wechat API,基础文件,主要用于Token的处理和mixin机制
 
var urllib = require('urllib');
var util = require('./util');
var wrapper = util.wrapper;
 
var AccessToken = function (accessToken) {
  if (!(this instanceof AccessToken)) {
    return new AccessToken(accessToken);
  }
  this.accessToken = accessToken;
};
 
/**
 * 根据appid和appsecret创建API的构造函数。
 *
 * 如需跨进程跨机器进行操作Wechat API(依赖access token),access token需要进行全局维护
 * 使用策略如下:
 *
 * 1. 调用用户传入的获取token的异步方法,获得token之后使用
 * 2. 使用appid/appsecret获取token。并调用用户传入的保存token方法保存
 *
 * Examples:
 * ```
 * var API = require('wechat').API;
 * var api = new API('appid', 'secret', 'agentid');
 * ```
 * 以上即可满足单进程使用。
 * 当多进程时,token需要全局维护,以下为保存token的接口。
 * ```
 * var api = new API('corpid', 'secret', 'agentid', function (callback) {
 *   // 传入一个获取全局token的方法
 *   fs.readFile('access_token.txt', 'utf8', function (err, txt) {
 *     if (err) {return callback(err);}
 *     callback(null, JSON.parse(txt));
 *   });
 * }, function (token, callback) {
 *   // 请将token存储到全局,跨进程、跨机器级别的全局,比如写到数据库、redis等
 *   // 这样才能在cluster模式及多机情况下使用,以下为写入到文件的示例
 *   fs.writeFile('access_token.txt', JSON.stringify(token), callback);
 * });
 * ```
 * @param {String} corpid 在公众平台上申请得到的corpid
 * @param {String} corpsecret 在公众平台上申请得到的app secret
 * @param {String} agentid 企业应用的id
 * @param {Function} getToken 可选的。获取全局token对象的方法,多进程模式部署时需在意
 * @param {Function} saveToken 可选的。保存全局token对象的方法,多进程模式部署时需在意
 */
var API = function (corpid, corpsecret, agentid, getToken, saveToken) {
  this.corpid = corpid;
  this.corpsecret = corpsecret;
  this.agentid = agentid;
  this.store = null;
  this.getToken = getToken || function (callback) {
    callback(null, this.store);
  };
  this.saveToken = saveToken || function (token, callback) {
    this.store = token;
    Iif (process.env.NODE_ENV === 'production') {
      console.warn('Don\'t save token in memory, when cluster or multi-computer!');
    }
    callback(null);
  };
  this.prefix = 'https://qyapi.weixin.qq.com/cgi-bin/';
};
 
/*!
 * 根据创建API时传入的appid和appsecret获取access token
 * 进行后续所有API调用时,需要先获取access token
 * 详细请看:<http://mp.weixin.qq.com/wiki/index.php?title=获取access_token>
 *
 * 应用开发者无需直接调用本API。
 *
 * Examples:
 * ```
 * api.getAccessToken(callback);
 * ```
 * Callback:
 *
 * - `err`, 获取access token出现异常时的异常对象
 * - `result`, 成功时得到的响应结果
 *
 * Result:
 * ```
 * {"access_token": "ACCESS_TOKEN"}
 * ```
 * @param {Function} callback 回调函数
 */
API.prototype.getAccessToken = function (callback) {
  var that = this;
  var url = this.prefix + 'gettoken?corpid=' + this.corpid + '&corpsecret=' + this.corpsecret;
  urllib.request(url, {dataType: 'json'}, wrapper(function (err, data) {
    if (err) {
      return callback(err);
    }
    var token = AccessToken(data.access_token);
    that.saveToken(token, function (err) {
      Iif (err) {
        return callback(err);
      }
      callback(err, token);
    });
  }));
  return this;
};
 
/*!
 * 需要access token的接口调用如果采用preRequest进行封装后,就可以直接调用。
 * 无需依赖getAccessToken为前置调用。
 * 应用开发者无需直接调用此API。
 *
 * Examples:
 * ```
 * api.preRequest(method, arguments);
 * ```
 * @param {Function} method 需要封装的方法
 * @param {Array} args 方法需要的参数
 */
API.prototype.preRequest = function (method, args, retryed) {
  var that = this;
  var callback = args[args.length - 1];
  // 调用用户传入的获取token的异步方法,获得token之后使用(并缓存它)。
  that.getToken(function (err, token) {
    Iif (err) {
      return callback(err);
    }
    // 有token并且token有效直接调用
    Eif (token) {
      // 暂时保存token
      that.token = AccessToken(token.accessToken);
      Eif (!retryed) {
        var retryHandle = function (err, data, res) {
          // 42001 重试
          Iif (data && data.errcode && data.errcode === 42001) {
            return that.preRequest(method, args, true);
          }
          callback(err, data, res);
        };
        // 替换callback
        var newargs = Array.prototype.slice.call(args, 0, -1);
        newargs.push(retryHandle);
        method.apply(that, newargs);
      } else {
        method.apply(that, args);
      }
    } else {
      // 使用appid/appsecret获取token
      that.getAccessToken(function (err, token) {
        // 如遇错误,通过回调函数传出
        if (err) {
          return callback(err);
        }
        // 暂时保存token
        that.token = token;
        method.apply(that, args);
      });
    }
  });
};
 
/**
 * 获取最新的token。
 *
 * - 如果还没有请求过token,则发起获取Token请求。
 * - 如果请求过,则调用getToken从获取之前保存的token
 *
 * Examples:
 * ```
 * api.getLatestToken(callback);
 * ```
 * Callback:
 *
 * - `err`, 获取access token出现异常时的异常对象
 * - `token`, 获取的token
 *
 * @param {Function} callback 回调函数
 */
API.prototype.getLatestToken = function (callback) {
  var that = this;
  // 调用用户传入的获取token的异步方法,获得token之后使用(并缓存它)。
  that.getToken(function (err, token) {
    if (err) {
      return callback(err);
    }
    // 有token
    if (token) {
      callback(null, AccessToken(token.accessToken));
    } else {
      // 使用corpid/corpsecret获取token
      that.getAccessToken(callback);
    }
  });
};
 
/**
 * 用于支持对象合并。将对象合并到API.prototype上,使得能够支持扩展
 * Examples:
 * ```
 * // 媒体管理(上传、下载)
 * API.mixin(require('./lib/api_media'));
 * ```
 * @param {Object} obj 要合并的对象
 */
API.mixin = function (obj) {
  for (var key in obj) {
    if (API.prototype.hasOwnProperty(key)) {
      throw new Error('Don\'t allow override existed prototype method. method: '+ key);
    }
    API.prototype[key] = obj[key];
  }
};
 
API.AccessToken = AccessToken;
 
module.exports = API;