Interfacer.js 13.3 KB
var util = require('util');
var Emitter = require('events');
var Request = require('request');
var async = require('async');
/*接口*/
var _ = require('lodash');

var Utils = require('./Utils');


var Interfacer = function (config, app) {
    this.config = config;
    this.CONSTS = app.get("Register");
    this.config.domain = this.config.domain || app.get("Register").domain;
    this.apis = {};
    Emitter.call(this);
};

util.inherits(Interfacer, Emitter);

Interfacer.prototype.register = function (mos) {
    var me = this, name = mos.namespace;
    if (!name) {
        console.info(" Interfacer name can not empty");
    }
    for (var key in mos.apis) {
        var name_key = name + "_" + key;
        if (me.isExisted(name_key)) {
            console.info("can not add repeat Interfacer key,please checkout");
        }
        /*需要进行验证判断*/
        me.apis[name_key] = mos.apis[key];
    }
};

Interfacer.prototype.isExisted = function (key) {
    return !!this.apis[key];
};
Interfacer.prototype.getInterfacerByNameSpace = function (namespace) {
    var me = this;
    if (!namespace) return {};
    var apis = {}, len = namespace.length;
    for (var key in me.apis) {
        if (key.slice(0, len + 1) === namespace + "_") {
            var name = key.slice(len + 1);
            if (typeof me.apis[key] !== "function") {
                apis[name] = me.apis[key];
            }
        }
    }
    return apis;
}

function __getArgs(str) {
    var arr = str.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].split(',');
    return (arr.length == 1 && arr[0] == "")?[]:arr;
}

function __requestApi(config, apiOpt, req, callback) {
    var me = this;
    if (typeof apiOpt == "function") {
        
        var args = __getArgs(apiOpt), o = {};
        if (args.length > 2) {
            args.slice(2).map(function (namespace) {
                namespace = _.trim(namespace);
                o[namespace] = {};
                var apis = me.getInterfacerByNameSpace(namespace);
                for (var action in apis) {
                    o[namespace][action] = (function (api, config, consts) {
                        var intermo = new Interfacer.create(api);
                        return function () {
                            var args = [].slice.call(arguments, 0), params = {};
                            intermo.apiOpt = api;
                            intermo.config = {
                                config: config,
                                consts: consts
                            };
                            
                            if (args.length === 1 && _.isPlainObject(args[0])) {
                                intermo.req.param(args[0]);
                            } else { 
                                api.params = api.params || {};
                                var os = Object.keys(api.params);
                                args.forEach(function (value, i) {
                                    params[os[i]] = value;
                                });
                                intermo.req.param(params);
                            }
                            if (!this.queue) {
                                this.queue = [];
                            }
                            this.queue.push(intermo);
                            this.__proto__ = new queue();
                            return this;
                        };
                    })(apis[action], config, me.CONSTS);
                }
            });
        }
        var fns = [];
        for (var i in o) {
            fns.push(o[i]);
        }
        return apiOpt.apply(0, [req, function (err, result) {
                req._yoheaders = req._yoheaders;
                return callback(null, result);
            }].concat(fns));
    }
    
    var options = __requestOption(req, apiOpt, config, me.CONSTS);
    __sendRequest(options, function (result) {
        return callback(null, result);
    }, function (result) {
        return callback(result, null);
    }, {len:1}, 0, []);
};

function __requestOption(req, apiOpt, config, consts) {
    var method = (apiOpt.method || "POST").toLocaleUpperCase(), errs = [];
    var data = {};
    if (apiOpt.params && (_.isArray(apiOpt.params) || _.isPlainObject(apiOpt.params))) {
        if (_.isArray(apiOpt.params)) {
            //兼容 yo之前的模式 后期废弃
            apiOpt.params.forEach(function (param) {
                var name = param.name;
                if (param.default) {
                    data[name] = param.default;
                }
                if (req.param(name)) {
                    if (param.type.toUpperCase() == "NUMBER") {
                        data[name] = Number(req.param(name));
                    } else {
                        data[name] = String(req.param(name));
                    }
                }
            });
        } else
            if (_.isPlainObject(apiOpt.params)) {
                var fns = [];
                for (var name in apiOpt.params) {
                    if (req.param("@" + name)) {
                        continue;
                    }
                    var param = apiOpt.params[name];
                    if (param.default) {
                        data[name] = param.type(param.default);
                    }
                    if (req.param(name)) {
                        data[name] = param.type(req.param(name));
                    }
                    if (param.coerce && typeof param.coerce == "function") { 
                        fns.push({ param: param,name:name});
                    }
                }
                fns.forEach(function (fn) {
                    var d = fn.param.coerce.call(data, req);
                    if (d) { 
                        data[fn.name] = param.type(d);
                    }
                });
                //验证
                for (var name in apiOpt.params) {
                    var param = apiOpt.params[name];
                    if (param.required && !data[name]) {
                        errs.push("params "+name + "is required!!!");
                    }
                    if (param.validator&&typeof param.validator=="function") {
                        var isSuc = param.validator.call(data, data[name]);

                        if (isSuc===false) { 
                            errs.push("params " +name + " validator error!!!");
                        }
                    }
                }
            }
    }
    var options = { method: method };
    options.errs = errs;
    options.title = apiOpt.title || '';
    options.outobj = apiOpt.outobj || '';
    if (options.method == "GET") {
        apiOpt.query = true;
    }

    if (/^https{0,1}:\/\//g.test(apiOpt.url)) {
        options.url = apiOpt.url;
    } else {
        options.url = (apiOpt.domain || config.domain) + apiOpt.url;
    }
    if (/\{\{.*\}\}/.test(options.url)) {
        var _qs = {};
        if (apiOpt.query) {
            _qs = data;
        }
        var a = _.merge(consts, _qs);
        options.url=Utils.template(options.url, a);
    }

    if (apiOpt.form) {
        if (typeof apiOpt.form === "string") {
            options.form = Utils.template(apiOpt.form, data);
        } else if (_.isPlainObject(apiOpt.form)) {
            options.form = {};
            for (var i in apiOpt.form) {
                options.form[i] = Utils.template(apiOpt.form[i], data)
            }
        }
        options.headers = {
            'Content-Type' : 'application/x-www-form-urlencoded'
        }
    } else if (apiOpt.query) {
        //不做任何事
    } else {
        options.body = JSON.stringify(data);
        options.headers = {
            'Content-Type' : 'application/json'
        }
    }
    if (apiOpt.headers && _.isPlainObject(apiOpt.headers)) {
        options.headers = {};
        for (var name in apiOpt.headers) {
            var param = apiOpt.headers[name];
            if (req.param("@" + name)) {
                if (param.default) {
                    options.headers[name] = param.type(param.default);
                }
                if (req.param("@" + name)) {
                    options.headers[name] = param.type(req.param("@" + name));
                }
            }
        }
    }
    if (req._yoheaders) {
        options.headers = _.merge(options.headers, req._yoheaders);
    }
    if(apiOpt.timeout){
        options.timeout=apiOpt.timeout;
    }
    console.log("*************************************");
    console.log("Http", options.url, data, options.headers);
    console.log("*************************************");
    return options;
}

/*
* {mos} 接口key 数组 比如[key1,key2] 后面可能要兼容字符串 比如 传key1
*/
Interfacer.prototype.require = function (mos, req, res, cb) {
    var me = this, funcs = [], names = [], errName = [];
    
    mos.forEach(function (name) {
        
        if (me.apis.hasOwnProperty(name)) {
            names.push(name);
            if (me.config.mock) {
                funcs.push(me.apis[name].output);
            } else {
                funcs.push(function (callback) {
                    __requestApi.call(me, me.config, me.apis[name], req, callback);
                });
            }
        } else {
            console.error(name+"不存在");
            errName.push(name);
        }
    });
    
    if (funcs.length != mos.length) {
        var err = new Error();
        err.message = "某个" + errName.join("->") + " 可能不存在!";
        cb(err, funcs, names);
        return;
    }
    if (me.config.mock) {
        cb(null, funcs, names);
        return;
    }
    async.parallel(funcs, function (err, results) {
        if (err) {
            console.info("Async Error");
            console.error(err);
            console.error(results);
            return cb(err, results, names);
        }
        return cb(null, results, names);
    });
};

Interfacer.create = function (apis) {
    return {
        req: {
            __params__: null,
            param: function (value) {
                if (typeof value == "object") {
                    this.__params__ = value;
                } else {
                    return this.__params__[value];
                }
            }
        },
        apiOpt: null,
        config: {},
        
        error: function () { 
            
        }
    }
}
function queue() { }
queue.prototype = {
    done: function (success, fail) {
        var that = this;
        if (typeof success !== 'function') return;
        var len = that.queue.length, watchlen = { len: len };
        var args = new Array(3);
        var wlen=Object.defineProperty({}, 'len', {
            value: len,
            writable: true,
            enumerable: false,
            configurable: true
        });
        for (var i = 0; i < len; i++) {
            (function (queue,args) {
                var options = __requestOption(queue.req, queue.apiOpt, queue.config.config, queue.config.consts);
                __sendRequest(options, success, fail, wlen, i,args);
            })(this.queue[i],args);
        }
        that.queue = null;
        //var options = __requestOption(this.req, this.apiOpt);
        //this.__sendRequest(options, success, fail);
        return this;
    },
    
}
function __sendRequest(options, success, fail, wlen, i, args) {
    var _err_ = new Error(), obj;
    if (options.errs instanceof Array&&options.errs.length) {
        _err_.message = options.errs.join(',');
        console.info("Error [options" + options.url + "]:" + options.title);
        console.error(_err_);
        wlen.len = 0;
        fail && fail(_err_);
        return;
    }
    Request(options, function (error, response, body) {
        if (!wlen.len) { 
            return;
        }
        if (error) {
            console.info("Error [request"+options.url+"]:" + options.title);
            console.error(error);
            wlen.len = 0;
            fail && fail(error);
            return;
        }
        try {
            if (response && (response.statusCode === 200||response.statusCode === 302)) {
                if (!options.outobj) {
                    obj = JSON.parse(body)
                    if (!(typeof obj == "object")) {
                        _err_.message = "Error[json parse@" + options.title + "--"+options.url+"]:" + body;
                        console.info(_err_.message);
                        console.error(_err_);
                        wlen.len = 0;
                        fail && fail(_err_);
                        return;
                    }
                } else {
                    obj = options.outobj.toLocaleUpperCase().indexOf("BODY")>-1?body:response;
                }
            } else {
                _err_.message = "Error[response state @" + options.title + "--"+options.url+"]:" + response.statusCode;
                console.info(_err_.message);
                console.error(_err_);
                wlen.len = 0;
                fail && fail(_err_);
                return;
            }
            
        } catch (err) {
            console.info("Error[response to json @" + options.title + "--"+options.url+"]");
            console.info(response);
            console.error(err);
            wlen.len = 0;
            fail && fail(err);
            return;
        }
        args[i]=obj;
        wlen.len--;
        if (!wlen.len) { 
            success && success.apply(0, args)
        }
        return;
    });
}

module.exports = Interfacer;