api.js 5.39 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 config = require('../config/common');
const api = config.domains.api;
const serviceApi = config.domains.service;

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';

        log.info(`${method} api: ${options.url}?${qs.stringify(options.qs)}`);
        timer.put('getApi');// 统计时间开始
        return rp(options).then((result) => {
            const duration = timer.put('getApi');// 统计时间结束

            // 数据校验
            if (!result || !result.code) {
                log.error('error: 接口返回的数据结构错误,非 JSON');
                return Promise.reject({
                    statusCode: 500,
                    message: '接口返回内容格式错误'
                });
            }

            // 写缓存, 否则返回 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); // 二级缓存存储一天
            }

            log.info(`get api success: use: ${duration}ms`);
            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({
                code: 500,
                message: '接口调用失败'
            });
        });
    }

    /**
     * 读取缓存
     * @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 ? 'get slave cache success' : 'get master cache success');
                    return result;
                }
            }

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

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

            return Promise.resolve({
                code: 500,
                message: '接口调用失败'
            });
        });
    }

    /**
     * 使用 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,
            json: 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,
            method: 'post',
            json: true,
            timeout: 3000
        };

        return this._requestFromAPI(options);
    }

    all(list) {
        if (_.isArray(list)) {
            return Promise.all(list);
        } else {
            return Promise.reject(Error('the parameters of api all method should be Array!'));
        }
    }
}

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

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

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