Browse Source

optimize router && add Wechat api

HonorLee 6 years ago
parent
commit
057c972624

+ 10 - 1
config.js

@@ -54,9 +54,18 @@ Database:{
         host:'localhost',
         port:11211
     }
+},
 
+Wechat:{
+    on:true,
+    token:'HonorLee',
+    appId:'wxe55b22a89a7743f2',
+    appSecret:'c33d9654d442dc48a6686309838513f0',
+    apiDomain:'api.weixin.qq.com',
+    handler_path:'/wechat',
+    StoreType:'Memcache'
 },
-    
+
 //If Debug on,log && info logs will output in console;except Error!
 debug:true,
 //if write file on,logs will write in log files

+ 22 - 0
package-lock.json

@@ -461,6 +461,14 @@
                 "graceful-fs": "4.1.11"
             }
         },
+        "klaw-sync": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-4.0.0.tgz",
+            "integrity": "sha512-go/5tXbgLkgwxQ2c2ewaMen6TpQtI9fTzzmTdlSGK8XxKcFSsJvn/Sgn75Vg+mOJwkKVPrqLw2Xq7x/zP1v7PQ==",
+            "requires": {
+                "graceful-fs": "4.1.11"
+            }
+        },
         "lodash": {
             "version": "4.17.10",
             "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
@@ -725,6 +733,15 @@
             "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
             "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
         },
+        "sha1": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
+            "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=",
+            "requires": {
+                "charenc": "0.0.2",
+                "crypt": "0.0.2"
+            }
+        },
         "simple-lru-cache": {
             "version": "0.0.2",
             "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz",
@@ -758,6 +775,11 @@
                 "tweetnacl": "0.14.5"
             }
         },
+        "string-random": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/string-random/-/string-random-0.1.0.tgz",
+            "integrity": "sha1-HaDsWqdufFrF5Hsgsbish/k5ilM="
+        },
         "string_decoder": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",

+ 4 - 1
package.json

@@ -11,6 +11,7 @@
         "formidable": "^1.1.1",
         "fs-extra": "^0.30.0",
         "js-base64": "^2.1.9",
+        "klaw-sync": "^4.0.0",
         "md5": "^2.2.1",
         "memcached": "^2.2.2",
         "mime-types": "^2.1.11",
@@ -20,7 +21,9 @@
         "mysql": "^2.11.1",
         "path": "^0.12.7",
         "querystring": "^0.2.0",
-        "request": "^2.72.0",
+        "request": "^2.85.0",
+        "sha1": "^1.1.1",
+        "string-random": "^0.1.0",
         "tracer": "^0.8.3"
     },
     "engines": {

+ 30 - 3
system/core.js

@@ -35,20 +35,31 @@ global.MD5          = require('md5');
 global.Formidable   = require('formidable');
 global.MIME         = require('mime-types');
 global.Path         = require('path');
-global.Request      = require('request');
 global.Tracer       = require('tracer').dailyfile({root:Core.Path.Log,format : "{{timestamp}} <{{title}}> {{file}}:{{line}} {{message}}", dateformat : "HH:MM:ss.L"});
 
+String.random       = require('string-random');
+Core.Request        = require('request');
+FILE.walkSync       = require('klaw-sync');
+
 //System Library load
 let CoreLibFiles = FILE.readdirSync(Core.Path.CoreLib);
 CoreLibFiles.forEach(function(filename){
     let nameWithOutMimeType = (filename.split('.')[0]).toUpperCase();
+    let coreClass;
     try {
-        global[nameWithOutMimeType] = require(Core.Path.CoreLib + '/' + filename);
-        if(typeof global[nameWithOutMimeType] == 'object' && global[nameWithOutMimeType]['__construct']) global[nameWithOutMimeType]['__construct']();
+        coreClass = require(Core.Path.CoreLib + '/' + filename);
+        
     }catch(e){
         console.log('[Core] Core library file ['+filename+'] load error!');
+        console.log(e);
         Tracer.error('[Core] Core library file ['+filename+'] load error!');
     }
+    if(coreClass.hasOwnProperty('_name') && coreClass['_name']){
+        global[coreClass['_name']] = coreClass;
+    }else{
+        global[nameWithOutMimeType] = coreClass;
+    }
+    if(typeof coreClass == 'object' && coreClass.hasOwnProperty('__construct')) coreClass['__construct']();
 });
 CoreLibFiles = null;
 //System Library load end
@@ -122,4 +133,20 @@ for(let path in global.Core.Path){
     }catch(e){
         FILE.mkdirsSync(global.Core.Path[path]);
     }
+}
+
+if(Config && Config.Wechat.on){
+    global.Wechat = require(Core.Path.ExtraLib + '/wechat/wechat.js');
+    try{
+        FILE.statSync(Core.Path.Handler + Config.Wechat.handler_path +'/index.js');
+    }catch(e){
+        FILE.copy(Core.Path.ExtraLib + '/wechat/handler/wechat.js',Core.Path.Handler + Config.Wechat.handler_path +'/index.js')
+    }
+}
+
+let HandlerFiles = FILE.walkSync(Core.Path.Handler, {nodir: true});
+CACHE.Handlers = [];
+for(let i in HandlerFiles){
+    let path = HandlerFiles[i]['path'];
+    CACHE.Handlers.push(path.replace(Core.Path.Handler,''));
 }

+ 77 - 40
system/lib/core/handler.js

@@ -3,46 +3,83 @@
  * @Version 1.0 (2018-05-04)
  * @License MIT
  */
-var Handler = {
-    // getViewPath:function(path){
-    //     var request = URL.parse(VIEWSPATH+path,true);
-    //     var FileName = request.pathname+'.html';
+'use strict'
+module.exports = function(req,res){
+    this.Request  = req;
+    this.Response = res;
+    this.COOKIE   = req._Cookie;
+    this.GET      = req._GET;
+    this.POST     = req._POST;
+    this.UPLOAD   = req._UPLOAD;
 
-    //     if(FILE.existsSync(FileName)){
-    //         return FileName;
-    //     }else{
-    //         return null;
-    //     }
-    // },
-    // getView:function(path){
-    //     var FileName = this.getViewPath(path);
-    //     if(FileName){
-    //         return FILE.readFileSync(FileName,'utf8');
-    //     }else{
-    //         return null;
-    //     }
-
-    // },
-    response:function(res,data){
-        if(!res) return;
-        res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
-        res.write(data);
-        res.end();
-    },
-    apiResponse:function(res,data){
-        if(!res) return;
-        data = data?{success:1,result:data}:{success:1};
-        res.writeHead(200, {'Content-Type': 'text/json; charset=UTF-8'});
-        res.write(JSON.stringify(data));
-        res.end();
-    },
-    apiErrorResponse:function(res,msg){
-        if(!res) return;
-        msg = msg?{success:0,msg:msg}:{success:0};
-        res.writeHead(200, {'Content-Type': 'text/json; charset=UTF-8'});
-        res.write(JSON.stringify(msg));
-        res.end();
+    /**
+     * [response description]
+     * @return {[type]} [description]
+     */
+    this.end = this.response = function(){
+        let mimeType = 'text/plain';
+        let status = 200;
+        let endContent = '';
+        switch(arguments.length){
+            case 1:
+                endContent = arguments[0];
+                break;
+            case 2:
+                status = arguments[0];
+                endContent = arguments[1];
+                break;
+            case 3:
+                status = arguments[0];
+                endContent = arguments[1];
+                mimeType = arguments[2];
+                break;
+        }
+        this.Response.writeHead(status, { 'Content-Type': `${mimeType}; charset=UTF-8`});
+        this.Response.write(endContent);
+        this.Response.end();
+    }
+    /**
+     * [responseInJSON description]
+     * @param  {[type]} somthing [String,Number,Object]
+     */
+    this.endJSON = this.responseInJSON = function(somthing){
+        let endContent;
+        if(typeof somthing == 'string' || typeof somthing == 'number'){
+            endContent = {data:somthing};
+        }else if(typeof somthing == 'object'){
+            endContent = somthing;
+        }else{
+            return LOGGER.error('Function endInJSON argument type must be string,number or object');
+        }
+        this.end(200,JSON.stringify(endContent),'text/json');
     }
-}
+    /**
+     * [responseAPI description]
+     * @param  {[type]} statusCode [description]
+     * @param  {[type]} somthing   [description]
+     */ 
+    this.endAPI = this.responseAPI = function(statusCode,somthing){
+        let endContent = {statusCode:statusCode,data:null};
+        if(typeof somthing == 'string' || typeof somthing == 'number' || typeof somthing == 'object'){
+            endContent.data = somthing;
+            this.end(200,JSON.stringify(endContent),'text/json');
+        }else{
+            return LOGGER.error('Function endAPI argument type must be string,number or object');
+        }
 
-module.exports = Handler;
+    }
+    /**
+     * [responseView description]
+     * @param  {[type]} viewName [description]
+     * @param  {[type]} data     [description]
+     * @return {[type]}          [description]
+     */
+    this.endView = this.responseView = function(viewName,data){
+        let view = new VIEW(viewName,data);
+        if(view && view.html){
+            this.end(200,view.html,'text/html');
+        }else{
+            this.end(403,`View [${viewName}] not found`);
+        }
+    }
+}

+ 33 - 35
system/lib/core/router.js

@@ -18,49 +18,47 @@ var Router ={
         });
     },
     goHandler:function(path,req,res){
-        let handlerFile = Core.Path.Handler + path + '.js';
         let pathArr = path.split('/');
-        let method = pathArr.pop();
-
-        if(!method) method = 'index';
-
-        if(CACHE.router[handlerFile]){
-            Router.runHandler(handlerFile,method,req,res);
-            return;
+        let method  = 'index';
+        let match   = false;
+        if(path=='/') path = '/index';
+        let expArr = [],methodMark = {};
+        let handlerFile;
+        //
+        handlerFile = path + '/index.js';
+        expArr.push(handlerFile);
+        methodMark[handlerFile] = 'index';
+        //
+        handlerFile = path + '.js';
+        expArr.push(handlerFile);
+        methodMark[handlerFile] = 'index';
+        //
+        if(path!='/index'){
+            method = pathArr.pop();
+            handlerFile = pathArr.join('/') + '/index.js';
+            expArr.push(handlerFile);
+            methodMark[handlerFile] = method;
+            //
+            handlerFile = pathArr.join('/') + '.js';
+            expArr.push(handlerFile);
+            methodMark[handlerFile] = method;
         }
-        FILE.stat(handlerFile,function(err,status){
-            if(err || !status.isFile()){
-                if(pathArr.length<=1) return Router._error('No such handler ['+handlerFile+']',res);
-                handlerFile = Core.Path.Handler + pathArr.join('/') + '.js';
-                FILE.stat(handlerFile,function(err,status){
-                    if(err || !status.isFile()){
-                        Router._error('No such handler ['+handlerFile+']',res);
-                    }else{
-                        Router.runHandler(handlerFile,method,req,res);
-                    }
-                });
-            }else{
-                Router.runHandler(handlerFile,method,req,res);
-            }
-        });
-    },
-    runHandler:function(handlerFile,method,req,res){
+        
+        match = new RegExp(expArr.join('|')).exec(CACHE.Handlers.join('|'));
+        if(!match) return Router._error('No such handler ['+handlerFile+']',res);
+        
+        method = methodMark[match];
+        handlerFile = Core.Path.Handler + match;
+
         let handler;
         try {
             handler = require(handlerFile);
         }catch(e){
             Router._error(e.stack,res);
         }
+        let baseClass = new HANDLER(req,res);
+        let newHandlerClass = Object.assign(baseClass,handler);
 
-        let newHandlerClass = Object.assign({
-            'Request'  : req,
-            'Response' : res,
-            'COOKIE'   : req._Cookie,
-            'GET'      : req._GET,
-            'POST'     : req._POST,
-            'UPLOAD'   : req._UPLOAD
-        },handler);
-        
         if(!CACHE.router[handlerFile]){ CACHE.router[handlerFile] = true;}
 
         if(newHandlerClass.hasOwnProperty(method) && typeof newHandlerClass[method]==='function'){
@@ -79,7 +77,7 @@ var Router ={
 };
 
 module.exports = function(req,res){
-    let URI       = req.url=='/'?'/index':req.url;
+    let URI       = req.url;
     let URLParse  = URL.parse(URI,true);
     let URLArr    = URLParse.pathname.split('/');
     let enterURI  = String(URLArr[1])==''?'index':String(URLArr[1]);

+ 7 - 3
system/lib/core/session.js

@@ -3,9 +3,9 @@
  * @Version 1.0 (2018-05-04)
  * @License MIT
  */
-'use strict';
+'use strict'
 var Session = {
-    set:function(key,value,sessionid){
+    set:function(key,value,arg1,arg2){
         if(!key || !value) return null;
         let sessionData;
         if(sessionid){
@@ -25,7 +25,7 @@ var Session = {
         FILE.writeFileSync(Core.Path.Session + '/' + sessionid,JSON.stringify(sessionData),'UTF-8');
         return sessionid;
     },
-    get:function(sessionid,key){
+    get:function(sessionid,key,callback){
         if(!sessionid) return null;
         let sessionData;
         try{
@@ -57,4 +57,8 @@ module.exports = Session;
 
 var FileManager = {
 
+}
+
+var MemcacheManager = {
+
 }

+ 18 - 12
system/lib/core/view.js

@@ -16,20 +16,26 @@ var View = function(src,params){
     try{
         FILE.statSync(viewPath);
     }catch(e){
-        LOGGER.error('No such view template ['+src+']');
-        return null;
+        viewPath = Core.Path.View + '/' + src;
+        try{
+            FILE.statSync(viewPath);
+        }catch(e){
+            LOGGER.error('No such view template ['+src+']');
+            return null;    
+        }
     }
-
-    try{
-        var data = FILE.readFileSync(viewPath,'UTF-8');
-        this.html = EJS.render(data,this.params);
-    }catch(e){
-        LOGGER.error('View template render error ['+src+']');
-        LOGGER.error(e);
-        return null;
+    let content = FILE.readFileSync(viewPath,'UTF-8');
+    if(this.params){
+        try{
+            this.html = EJS.render(content,this.params);
+        }catch(e){
+            LOGGER.error('View template render error ['+src+']');
+            LOGGER.error(e);
+            return null;
+        }
+    }else{
+        this.html = content;
     }
-    
-    return this;
 }
 
 module.exports = View;

+ 161 - 0
system/lib/extra/wechat/errMsg.js

@@ -0,0 +1,161 @@
+/**
+ * @Author  HonorLee (deve@honorlee.me)
+ * @Version 1.0 (2018-05-05)
+ * @License MIT
+ */
+module.exports = {
+    '-1':"系统繁忙,此时请开发者稍候再试",
+    0:"请求成功",
+    40001:"获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口",
+    40002:"不合法的凭证类型",
+    40003:"不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID",
+    40004:"不合法的媒体文件类型",
+    40005:"不合法的文件类型",
+    40006:"不合法的文件大小",
+    40007:"不合法的媒体文件 id",
+    40008:"不合法的消息类型",
+    40009:"不合法的图片文件大小",
+    40010:"不合法的语音文件大小",
+    40011:"不合法的视频文件大小",
+    40012:"不合法的缩略图文件大小",
+    40013:"不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写",
+    40014:"不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口",
+    40015:"不合法的菜单类型",
+    40016:"不合法的按钮个数",
+    40017:"不合法的按钮个数",
+    40018:"不合法的按钮名字长度",
+    40019:"不合法的按钮 KEY 长度",
+    40020:"不合法的按钮 URL 长度",
+    40021:"不合法的菜单版本号",
+    40022:"不合法的子菜单级数",
+    40023:"不合法的子菜单按钮个数",
+    40024:"不合法的子菜单按钮类型",
+    40025:"不合法的子菜单按钮名字长度",
+    40026:"不合法的子菜单按钮 KEY 长度",
+    40027:"不合法的子菜单按钮 URL 长度",
+    40028:"不合法的自定义菜单使用用户",
+    40029:"不合法的 oauth_code",
+    40030:"不合法的 refresh_token",
+    40031:"不合法的 openid 列表",
+    40032:"不合法的 openid 列表长度",
+    40033:"不合法的请求字符,不能包含 \\uxxxx 格式的字符",
+    40035:"不合法的参数",
+    40038:"不合法的请求格式",
+    40039:"不合法的 URL 长度",
+    40050:"不合法的分组 id",
+    40051:"分组名字不合法",
+    40060:"删除单篇图文时,指定的 article_idx 不合法",
+    40117:"分组名字不合法",
+    40118:"media_id 大小不合法",
+    40119:"button 类型错误",
+    40120:"button 类型错误",
+    40121:"不合法的 media_id 类型",
+    40132:"微信号不合法",
+    40137:"不支持的图片格式",
+    40155:"请勿添加其他公众号的主页链接",
+    41001:"缺少 access_token 参数",
+    41002:"缺少 appid 参数",
+    41003:"缺少 refresh_token 参数",
+    41004:"缺少 secret 参数",
+    41005:"缺少多媒体文件数据",
+    41006:"缺少 media_id 参数",
+    41007:"缺少子菜单数据",
+    41008:"缺少 oauth code",
+    41009:"缺少 openid",
+    42001:"access_token 超时,请检查 access_token 的有效期,请参考基础支持 - 获取 access_token 中,对 access_token 的详细机制说明",
+    42002:"refresh_token 超时",
+    42003:"oauth_code 超时",
+    42007:"用户修改微信密码, accesstoken 和 refreshtoken 失效,需要重新授权",
+    43001:"需要 GET 请求",
+    43002:"需要 POST 请求",
+    43003:"需要 HTTPS 请求",
+    43004:"需要接收者关注",
+    43005:"需要好友关系",
+    43019:"需要将接收者从黑名单中移除",
+    44001:"多媒体文件为空",
+    44002:"POST 的数据包为空",
+    44003:"图文消息内容为空",
+    44004:"文本消息内容为空",
+    45001:"多媒体文件大小超过限制",
+    45002:"消息内容超过限制",
+    45003:"标题字段超过限制",
+    45004:"描述字段超过限制",
+    45005:"链接字段超过限制",
+    45006:"图片链接字段超过限制",
+    45007:"语音播放时间超过限制",
+    45008:"图文消息超过限制",
+    45009:"接口调用超过限制",
+    45010:"创建菜单个数超过限制",
+    45011:"API 调用太频繁,请稍候再试",
+    45015:"回复时间超过限制",
+    45016:"系统分组,不允许修改",
+    45017:"分组名字过长",
+    45018:"分组数量超过上限",
+    45047:"客服接口下行条数超过上限",
+    46001:"不存在媒体数据",
+    46002:"不存在的菜单版本",
+    46003:"不存在的菜单数据",
+    46004:"不存在的用户",
+    47001:"解析 JSON/XML 内容错误",
+    48001:"api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限",
+    48002:"粉丝拒收消息(粉丝在公众号选项中,关闭了 “ 接收消息 ” )",
+    48004:"api 接口被封禁,请登录 mp.weixin.qq.com 查看详情",
+    48005:"api 禁止删除被自动回复和自定义菜单引用的素材",
+    48006:"api 禁止清零调用次数,因为清零次数达到上限",
+    48008:"没有该类型消息的发送权限",
+    50001:"用户未授权该 api",
+    50002:"用户受限,可能是违规后接口被封禁",
+    50005:"用户未关注公众号",
+    61451:"参数错误 (invalid parameter)",
+    61452:"无效客服账号 (invalid kf_account)",
+    61453:"客服帐号已存在 (kf_account exsited)",
+    61454:"客服帐号名长度超过限制 ( 仅允许 10 个英文字符,不包括 @ 及 @ 后的公众号的微信号 )(invalid kf_acount length)",
+    61455:"客服帐号名包含非法字符 ( 仅允许英文 + 数字 )(illegal character in kf_account)",
+    61456:"客服帐号个数超过限制 (10 个客服账号 )(kf_account count exceeded)",
+    61457:"无效头像文件类型 (invalid file type)",
+    61450:"系统错误 (system error)",
+    61500:"日期格式错误",
+    65301:"不存在此 menuid 对应的个性化菜单",
+    65302:"没有相应的用户",
+    65303:"没有默认菜单,不能创建个性化菜单",
+    65304:"MatchRule 信息为空",
+    65305:"个性化菜单数量受限",
+    65306:"不支持个性化菜单的帐号",
+    65307:"个性化菜单信息为空",
+    65308:"包含没有响应类型的 button",
+    65309:"个性化菜单开关处于关闭状态",
+    65310:"填写了省份或城市信息,国家信息不能为空",
+    65311:"填写了城市信息,省份信息不能为空",
+    65312:"不合法的国家信息",
+    65313:"不合法的省份信息",
+    65314:"不合法的城市信息",
+    65316:"该公众号的菜单设置了过多的域名外跳(最多跳转到 3 个域名的链接)",
+    65317:"不合法的 URL",
+    9001001:"POST 数据参数不合法",
+    9001002:"远端服务不可用",
+    9001003:"Ticket 不合法",
+    9001004:"获取摇周边用户信息失败",
+    9001005:"获取商户信息失败",
+    9001006:"获取 OpenID 失败",
+    9001007:"上传文件缺失",
+    9001008:"上传素材的文件类型不合法",
+    9001009:"上传素材的文件尺寸不合法",
+    9001010:"上传失败",
+    9001020:"帐号不合法",
+    9001021:"已有设备激活率低于 50% ,不能新增设备",
+    9001022:"设备申请数不合法,必须为大于 0 的数字",
+    9001023:"已存在审核中的设备 ID 申请",
+    9001024:"一次查询设备 ID 数量不能超过 50",
+    9001025:"设备 ID 不合法",
+    9001026:"页面 ID 不合法",
+    9001027:"页面参数不合法",
+    9001028:"一次删除页面 ID 数量不能超过 10",
+    9001029:"页面已应用在设备中,请先解除应用关系再删除",
+    9001030:"一次查询页面 ID 数量不能超过 50",
+    9001031:"时间区间不合法",
+    9001032:"保存设备与页面的绑定关系参数错误",
+    9001033:"门店 ID 不合法",
+    9001034:"设备备注信息过长",
+    9001035:"设备申请参数不合法",
+    9001036:"查询起始值 begin 不合法"
+}

+ 29 - 0
system/lib/extra/wechat/handler/wechat.js

@@ -0,0 +1,29 @@
+'use strict'
+module.exports = {
+    checkSignature:function(){
+        let signature = this.GET['signature'],
+            timestamp = this.GET['signtimestampature'],
+            nonce     = this.GET['nonce'],
+            echostr   = this.GET['echostr'];
+        if(signature && timestamp && nonce && echostr){
+            if(Wechat.checkSignature(signature,timestamp,nonce)){
+                this.end(echostr);
+                return;
+            }
+        }
+        this.end('Error');
+    },
+    getAccessToken:function(){
+        Wechat.getAccessToken.call(this,function(err,accessToken){
+            this.end(accessToken);
+        });
+    },
+    getSignature:function(){
+        if(!this.GET['url'] || !this.GET['url'].match(/(https?|ftp|file):\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/gi)) return this.endAPI(-1,'Please send a current url address');
+        Wechat.getSignature.call(this,this.GET['url'],function(err,signatureObj){
+            if(err) return this.endAPI(-1,'getSignature error!');
+            signatureObj.appId = Config.Wechat.appId;
+            this.endAPI(0,signatureObj);
+        });
+    }
+}

+ 180 - 0
system/lib/extra/wechat/wechat.js

@@ -0,0 +1,180 @@
+/**
+ * @Author  HonorLee (deve@honorlee.me)
+ * @Version 1.0 (2018-05-05)
+ * @License MIT
+ */
+'use strict'
+const sha1 = require('sha1');
+const tmpTokenFile = Core.Path.Temp + '/wechat_AccessToken.txt';
+const tmpTicketFile = Core.Path.Temp + '/wechat_JsApiTicket.txt';
+const WechatDomain = `https://${Config.Wechat.apiDomain}`;
+const errCode = require('./errMsg.js');
+const WechatApiURL = {
+    getAccessToken:`${WechatDomain}/cgi-bin/token?grant_type=client_credential&appid=${Config.Wechat.appId}&secret=${Config.Wechat.appSecret}`,
+    getJsApiTicket:`${WechatDomain}/cgi-bin/ticket/getticket?type=jsapi&access_token=`
+}
+
+let Wechat = {
+    checkSignature:function(signature,timestamp,nonce){
+        let arr = [Config.Wechat.token, timestamp, nonce];
+        arr.sort();
+        let content = arr.join('');
+        return sha1(content) === signature;
+    },
+    getAccessToken:function(callback){
+        let _this = this;
+        getTokenFromCache(function(data){
+            if(data){
+                callback.call(_this,null,data);
+            }else{
+                Core.Request({url:WechatApiURL.getAccessToken,encoding:'UTF-8',json:true},function(err,response,body){
+                    if(err || !body){
+                        Logger.error('Wechat getAccessToken error!');
+                        Logger.error(err);
+                        callback.call(_this,new Error('Wechat getAccessToken error!'),null);
+                        return;
+                    }
+                    if(body.errcode){
+                        callback(_this,errcode,errcode[body.errcode]);
+                    }
+                    let token = body.access_token;
+                    let expires = body.expires_in;
+                    setTokenToCahe(token,expires);
+                    callback.call(_this,null,token);
+                })
+            }
+        });
+    },
+    getJsApiTicket:function(callback){
+        let _this = this;
+        Wechat.getAccessToken(function(err,token){
+            if(!err){
+                getTicketFromCache(function(data){
+                    if(data){
+                        callback.call(_this,null,data);
+                    }else{
+                        let url = WechatApiURL.getJsApiTicket + token;
+                        Core.Request({url:url,encoding:'UTF-8',json:true},function(err,response,body){
+                            if(err || !body){
+                                Logger.error('Wechat getJsApiTicket error!');
+                                Logger.error(err);
+                                callback.call(_this,new Error('Wechat getJsApiTicket error!'),null);
+                                return;
+                            }
+                            console.log(body);
+                            if(body.errcode){
+                                callback(_this,errcode,errcode[body.errcode]);
+                            }
+                            let ticket = body.ticket;
+                            let expires = body.expires_in;
+                            setTicketToCahe(ticket,expires);
+                            callback.call(_this,null,ticket);
+                        })
+                    }
+                });
+            }else{
+                Logger.error('Wechat getJsApiTicket error!');
+                Logger.error(err);
+            }
+        });
+    },
+    getSignature:function(url,callback){
+        if(!url) return callback.call(this,new Error('URL is empty!'),null);
+        let _this = this;
+        let noncestr = String.random(16);
+        let timestamp = Math.floor(Moment().valueOf()/1000);
+        Wechat.getJsApiTicket(function(err,ticket){
+            if(err){
+                Logger.error('Wechat getSignature error!');
+                Logger.error(err);
+                callback.call(_this,err,null);
+                return;
+            }
+            let combineStr = `jsapi_ticket=${ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
+            let signature = sha1(combineStr);
+            callback.call(_this,null,{noncestr:noncestr,timestamp:timestamp,signature:signature});
+        });
+    }
+}
+
+module.exports = Wechat;
+
+function getTokenFromCache(callback){
+    if(Config.Wechat.StoreType=='Memcache'){
+        Memcache.get('wechat_AccessToken',function(err,data){
+            if(!err && data){
+                callback(data);
+            }else{
+                callback();
+            }
+        });
+        return;
+    }
+
+    try{
+        FILE.statSync(tmpTokenFile);
+    }catch(e){
+        callback(null);
+        return;
+    }
+    let obj = JSON.parse(FILE.readFileSync(tmpTokenFile,'UTF-8'));
+    let now = Math.floor(Moment().valueOf()/1000)+60;
+    if(now < obj.expires){
+        callback(obj.token);
+    }else{
+        callback(null);
+    }
+    
+}
+
+function setTokenToCahe(token,expires){
+    expires = expires - 60;
+    if(Config.Wechat.StoreType=='Memcache'){
+        Memcache.set('wechat_AccessToken',token,expires,function(err){
+            if(err) console.log(err);
+        })
+        return;
+    }
+    let expiresTime = Moment().valueOf() + expires;
+    FILE.writeFileSync(tmpTokenFile,JSON.stringify({token:token,expires:expiresTime}),'UTF-8');
+}
+
+function getTicketFromCache(callback){
+    if(Config.Wechat.StoreType=='Memcache'){
+        Memcache.get('wechat_JsApiTicket',function(err,data){
+            if(!err && data){
+                callback(data);
+            }else{
+                callback();
+            }
+        });
+        return;
+    }
+
+    try{
+        FILE.statSync(tmpTicketFile);
+    }catch(e){
+        callback(null);
+        return;
+    }
+    let obj = JSON.parse(FILE.readFileSync(tmpTicketFile,'UTF-8'));
+    let now = Math.floor(Moment().valueOf()/1000)+60;
+    if(now < obj.expires){
+        callback(obj.ticket);
+    }else{
+        callback(null);
+    }
+    
+}
+
+function setTicketToCahe(ticket,expires){
+    expires = expires - 5;
+    if(Config.Wechat.StoreType=='Memcache'){
+        Memcache.set('wechat_JsApiTicket',ticket,expires,function(err){
+            if(err) console.log(err);
+        })
+        return;
+    }
+    let expiresTime = Moment().valueOf() + expires;
+    FILE.writeFileSync(tmpTicketFile,JSON.stringify({ticket:ticket,expires:expiresTime}),'UTF-8');
+}