api.js 5.94 KB
/**
 * 接口公共方法
 * @author: xuqi<qi.xu@yoho.cn>
 * @date: 2016/4/25
 */

'use strict';
const rp = require('request-promise');
const qs = require('querystring');
const md5 = require('md5');
const _ = require('lodash');
const log = require('./logger');
const cache = require('./cache');
const Timer = require('./timer');
const sign = require('./sign');
const config = require('../config/common');
const api = config.domains.api;
const serviceApi = config.domains.service;
const searchApi = config.domains.search;

// 错误返回
const API_BAD_RETSULT = {
    code: 500,
    message: 'API result is not JSON string or null.'
};

// 调用失败
const API_CALL_FAIL = {
    code: 500,
    message: 'Call API failed.'
};

// all 方法错误的传参
const API_ALL_METHOD_ERROR = 'the parameters of api all method should be Array!';

// 获取缓存数据失败
const SLAVE_CACHE_FAIL = 'get slave cache fail';
const MASTER_CACHE_FAIL = 'get master cache fail';

// 获取缓存数据成功
const SLAVE_CACHE_SUCCESS = 'get slave cache success';
const MASTER_CACHE_SUCCESS = 'get master cache success';

class Http {

    constructor(baseUrl) {
        this.ApiUrl = baseUrl;
    }

    /**
     * 获取请求 ID
     */
    _getReqId(options) {
        return md5(`${options.url}?${qs.stringify(options.qs || options.form)}`);
    }

    /**
     * 调用接口
     */
    _requestFromAPI(options, cacheOption, reqId) {
        const timer = new Timer();
        const method = options.method || 'get';

        timer.put('getApi');// 统计时间开始
        return rp(options).then((result) => {
            const duration = timer.put('getApi');// 统计时间结束

            // 数据校验
            if (!result) {
                log.error(`error: ${API_BAD_RETSULT.message}`);
                return Promise.reject(API_BAD_RETSULT);
            }

            // 写缓存, 否则返回 Slave 缓存服务器的数据
            if (config.useCache && cacheOption) {
                const cacheTime = _.isNumber(cacheOption) ? cacheOption : 60;
                const catchErr = (err) => {
                    log.error(`cache: ${err.toString()}`);
                };

                reqId = reqId || this._getReqId(options);
                cache.set(`apiCache:${reqId}`, result, cacheTime).catch(catchErr);
                cache.setSlave(`apiCache:${reqId}`, result, 86400).catch(catchErr); // 二级缓存存储一天
            }

            let reqData = options.qs || options.form;

            log.info(`use: ${duration}ms for ${method} api: ${options.url}?${qs.stringify(reqData)} `);
            return result;
        }).catch((err)=> {
            const duration = timer.put('getApi');// 统计时间结束

            log.error(`${method} api fail: use: ${duration}ms, code:${err.statusCode}, error: ${err.message}`);
            log.error(`API: ${options.url}?${qs.stringify(options.qs)}`);

            // 使用缓存的时候,读取二级缓存
            if (config.useCache && cacheOption) {
                return this._requestFromCache(options, true);
            }
            return Promise.resolve(API_CALL_FAIL);
        });
    }

    /**
     * 读取缓存
     * @param  {[object]} options
     * @param  {[boolean]} slave true: 读取二级缓存
     * @return {[type]}
     */
    _requestFromCache(options, slave) {
        const reqId = this._getReqId(options);
        const getCache = slave ? cache.getFromSlave : cache.get;

        log.info(`get ${slave ? 'slave' : 'master'} cache: ${reqId}, url: ${options.url}?${qs.stringify(options.qs)}`);
        return getCache(`apiCache:${reqId}`).then((result) => {
            if (!_.isNil(result)) {
                try {
                    result = JSON.parse(result);
                } finally {
                    log.info(slave ? SLAVE_CACHE_SUCCESS : MASTER_CACHE_SUCCESS);
                    return result;
                }
            }

            // 读取缓存失败,并且不是二级缓存的时候,调用 API
            if (!slave) {
                return this._requestFromAPI(options, true, reqId);
            }
        }).catch(() => {
            log.error(slave ? SLAVE_CACHE_FAIL : MASTER_CACHE_FAIL);

            // 读取缓存失败,并且不是二级缓存的时候,调用 API
            if (!slave) {
                return this._requestFromAPI(options, true, reqId);
            }

            return Promise.resolve(API_CALL_FAIL);
        });
    }

    /**
     * 使用 get 请求获取接口
     * @param  {[string]} url
     * @param  {[object]} data
     * @param  {[bool or number]} cacheOption 使用数字时,数字表示缓存时间
     * @return {[type]}
     */
    get(url, data, cacheOption) {
        const options = {
            url: `${this.ApiUrl}${url}`,
            qs: data.client_secret ? data : sign.apiSign(data),
            json: true,
            gzip: true,
            timeout: 3000
        };

        // 从缓存获取数据
        if (config.useCache && cacheOption) {
            return this._requestFromCache(options);
        }

        return this._requestFromAPI(options, cacheOption);
    }

    /**
     * post
     * @param url String
     * @param data Obejct
     */
    post(url, data) {
        const options = {
            url: `${this.ApiUrl}${url}`,
            form: data.client_secret ? data : sign.apiSign(data),
            method: 'post',
            json: true,
            timeout: 3000
        };

        return this._requestFromAPI(options);
    }

    all(list) {
        if (_.isArray(list)) {
            return Promise.all(list);
        } else {
            return Promise.reject(Error(API_ALL_METHOD_ERROR));
        }
    }
}

class API extends Http {
    constructor() {
        super(api);
    }
}

class ServiceAPI extends Http {
    constructor() {
        super(serviceApi);
    }
}

class SearchAPI extends Http {
    constructor() {
        super(searchApi);
    }
}

exports.API = API;
exports.ServiceAPI = ServiceAPI;
exports.SearchAPI = SearchAPI;