├── .gitignore ├── README.md ├── wechat_service1 ├── index.js ├── package.json └── src │ ├── common │ ├── config.example.js │ ├── constant.js │ ├── ctrls.js │ ├── errors.js │ ├── func.js │ ├── global.js │ └── models.js │ ├── ctrls │ ├── active_message.js │ ├── api.js │ ├── js_sdk.js │ ├── media.js │ ├── menu.js │ ├── process_passive.js │ ├── qr_code.js │ ├── user_info.js │ └── weixin.js │ ├── models │ ├── base.js │ ├── qr_url.js │ └── user_qrcode.js │ ├── schemas │ ├── qr_url.js │ ├── test.js │ └── user_qrcode.js │ └── server │ ├── router.js │ ├── run.js │ └── server.js └── wechat_service2 ├── wechat.helper.js └── wechat.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | *.rdb 5 | src/common/config.js 6 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nodejs开发的两个微信公众号开发的示例,满足所有基本需求。 -------------------------------------------------------------------------------- /wechat_service1/index.js: -------------------------------------------------------------------------------- 1 | require('./src/server/server'); 2 | -------------------------------------------------------------------------------- /wechat_service1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smartstudy-weixin-service", 3 | "description": "smartstudy-weixin-service", 4 | "dependencies": { 5 | "body-parser": "^1.13.3", 6 | "co": "^4.6.0", 7 | "compression": "^1.5.2", 8 | "connect-timeout": "^1.6.2", 9 | "debug": "^2.2.0", 10 | "express": "^4.13.3", 11 | "fase": "0.0.3", 12 | "hiredis": "^0.4.0", 13 | "lodash": "^3.10.1", 14 | "moment": "^2.10.6", 15 | "mysql": "^2.8.0", 16 | "redis": "^0.12.1", 17 | "request": "^2.60.0", 18 | "sequelize": "^3.5.1", 19 | "sha1": "^1.1.1", 20 | "thunkify-wrap": "^1.0.4", 21 | "underscore": "^1.8.3", 22 | "validator": "^4.0.2", 23 | "wechat": "^2.0.1", 24 | "wechat-toolkit": "^0.2.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /wechat_service1/src/common/config.example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | // 监听端口 5 | port: 3015, 6 | 7 | // 默认超时时间 8 | timeout: 10, 9 | 10 | // 错误码前缀 11 | errorPrefix: 'WX', 12 | 13 | authorized: [ 14 | // 智课 15 | "N0LXYPFL03O4TU2WQEU536NDJR7IJKII", 16 | // 批改 17 | "KVN182L4M59FP1KFJ1IWRJ192UFNV648", 18 | // 备考 19 | "IO193UNVKDF711LFLGIDS81JVMLAFKEI", 20 | // 微信 21 | 'YXHQKS48XXT0SMREP570DBAPQJ02MI69', 22 | ], 23 | 24 | db: { 25 | dbname: 'weixin_service', 26 | username: 'weixin_service', 27 | password: 'weixin_service', 28 | host: 'dev.smartstudy.com', 29 | port: 3306, 30 | pool: 10 31 | }, 32 | 33 | // 缓存 34 | cache: { 35 | host: '127.0.0.1', 36 | port: 6379 37 | }, 38 | 39 | kaowei: { 40 | host: 'http://app.smartstudy.com/kaowei/kaowei_wap' 41 | }, 42 | 43 | wap: { 44 | host: 'http://dev.m.smartstudy.com' 45 | }, 46 | 47 | user: { 48 | host: 'http://172.16.3.237:5000' 49 | }, 50 | 51 | weixin: { 52 | appId: 'wx45bfa5b4213fa863', 53 | appSecret: '6c5b42a215a7c90e2ed0d2ebb2780dad', 54 | token: 'ss_test', 55 | encodingAESKey: 'qbpnfwNeMuIAho2fktz4n9rWjDwudSZ6fmfXfaoaAPs', 56 | 57 | template: { 58 | kaowei: '98QYgiCPxyNbeeVvpWSs_l5SQiF2fYm1ORQMwXJifME', 59 | } 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /wechat_service1/src/common/constant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | NOT_SEND_JSON: 'not_send_json_1vkla011889fdoq', 5 | 6 | WEIXIN_WRONG_ACC_TOKEN: 40001, 7 | WEIXIN_RETRIES: 3, 8 | 9 | URL: { 10 | WX_OAUTH_INI: 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=', 11 | KAOWEI: encodeURI(config.kaowei.host), 12 | KOUYU: config.wap.host + '/activity/speak', 13 | TEACHERS: config.wap.host + '/teachers/toefl', 14 | COURSES: config.wap.host + '/courses/toefl', 15 | USER_BIND: config.user.host + '/weixin_mp/bind', 16 | DOWNLOAD_APP: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.smartstudy.zhike', 17 | WENWEN: 'http://wen.beikaodi.com', 18 | }, 19 | 20 | MEDIA: { 21 | PIC: { 22 | DOWNLOAD_APP: 'https://mmbiz.qlogo.cn/mmbiz/iaBmDVE8BLHf9gveoTvsKt3PhevjudvQGSf0achZCvBnsZWdobEoywiayRKAvSbzrKeUqic2gpU5xlrkGCm4sevxg/0?wx_fmt=jpeg' 23 | } 24 | }, 25 | 26 | WEIXIN_KEY_SORT: { 27 | 'HY': 'welcome', 28 | 'HYZH': 'welcome', 29 | 'ZH': 'bind', 30 | 'ZB': 'live' 31 | }, 32 | 33 | CACHE_KEYS: { 34 | MEDIA: 'MEDIA_oZtL7s4yXR3zkQMGN4AFIiMwOs7M' 35 | }, 36 | 37 | }; -------------------------------------------------------------------------------- /wechat_service1/src/common/ctrls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引用 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | // 加载所有模型 8 | var exports = {}; 9 | var dir = fs.readdirSync(__dirname + '/../ctrls'); 10 | for (var i = 0; i < dir.length; i++) { 11 | if (path.extname(dir[i]) !== '.js') continue; 12 | exports[_.toCamel(path.basename(dir[i], '.js'))] = require(__dirname + '/../ctrls/' + dir[i]); 13 | } 14 | 15 | // 导出 16 | module.exports = exports; 17 | -------------------------------------------------------------------------------- /wechat_service1/src/common/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 1000: '未知错误', 5 | 1001: '资源未找到', 6 | 1002: '请求超时', 7 | 1003: '权限不足', 8 | 1004: '操作失败', 9 | 1005: '尚未登录', 10 | 11 | 1101: 'api token错误', 12 | 13 | 2000: '消息模块未知错误', 14 | 2001: '消息模块token错误', 15 | 2002: '消息模块数据错误', 16 | 2010: '消息模块拒绝寄方', 17 | 2020: '消息模块拒绝收方', 18 | 19 | 3000: '微信未知错误', 20 | 3001: '微信签名错误', 21 | 3002: '微信输入错误', 22 | 3003: '微信AccToken错误', 23 | 3010: '微信更新AccToken错误', 24 | 3020: '微信更新JSSDK注入错误', 25 | 3030: '微信oAuth未知错误', 26 | 3031: '微信oAuth错误, code不合法', 27 | 3080: '微信删除菜单错误', 28 | 3081: '微信修改菜单错误', 29 | 30 | 3100: '微信发送消息错误', 31 | 32 | 4000: '微信二维码错误', 33 | 4001: '微信二维码参数错误', 34 | 4002: '微信二维码参数不足', 35 | }; 36 | -------------------------------------------------------------------------------- /wechat_service1/src/common/func.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Func = function() {}; 4 | var that = module.exports = new Func(); 5 | 6 | // 将下划线命名转换为驼峰命名 7 | _.toCamel = function (name) { 8 | var newName = ''; 9 | var underline = false; 10 | for (var i = 0; i < name.length; i++) { 11 | if (name[i] === '_' || name[i] === '-') { 12 | underline = true; 13 | } else { 14 | newName += underline ? name[i].toUpperCase() : name[i]; 15 | underline = false; 16 | } 17 | } 18 | ; 19 | return newName; 20 | }; 21 | 22 | Func.prototype.cacheGet = thunkify(function (key, next) { 23 | cache.get(key, next); 24 | }); 25 | 26 | Func.prototype.cacheSet = thunkify(function (key, value, next) { 27 | cache.set(key, value, next); 28 | }); 29 | 30 | Func.prototype.cacheSetex = thunkify(function (key, value, timeout, next) { 31 | cache.setex(key, value, timeout, next); 32 | }); 33 | 34 | Func.prototype.cacheDel = thunkify(function (key, next) { 35 | cache.del(key, next); 36 | }); 37 | 38 | Func.prototype.sendGetRequest = thunkify(function (uri, data, next){ 39 | request({ 40 | uri: uri, 41 | method: 'GET', 42 | postData: data, 43 | timeout: 10000, 44 | }, function (err, ret, body) { 45 | if (err) { 46 | next(err, false); 47 | } 48 | else { 49 | let retObj = JSON.parse(body); 50 | next(null, retObj); 51 | } 52 | }); 53 | }); 54 | 55 | Func.prototype.sendPostRequest = thunkify(function (uri, data, next){ 56 | request({ 57 | uri: uri, 58 | method: 'POST', 59 | body: JSON.stringify(data), 60 | timeout: 10000, 61 | }, function (err, ret, body) { 62 | if (err) { 63 | next(null, -1); 64 | } 65 | else { 66 | let retObj = JSON.parse(body); 67 | next(null, retObj); 68 | } 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /wechat_service1/src/common/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 全局变量 4 | global.config = require('./config'); 5 | global.constant = require('./constant'); 6 | global._ = require('lodash'); 7 | global.co = require('co'); 8 | global.thunkify = require('thunkify-wrap'); 9 | global.Sequelize = require('sequelize'); 10 | global.validator = require('validator'); 11 | global.request = require('request'); 12 | global.$ = require('fase'); 13 | global.func = require('./func'); 14 | 15 | //数据库 16 | global.db = new Sequelize(config.db.dbname, config.db.username, config.db.password, { 17 | dialect: 'mysql', 18 | host: config.db.host, 19 | port: config.db.port, 20 | timezone: '+08:00', 21 | logging: undefined, 22 | pool: { 23 | maxConnections: config.db.pool 24 | } 25 | }); 26 | 27 | //cache 28 | var redis = require("redis") 29 | global.cache = redis.createClient( 30 | config.cache.port, 31 | config.cache.host, 32 | {enable_offline_queue: false} 33 | ); 34 | 35 | global.cache.on("error", function (err) { 36 | console.log("Redis Client Error " + err); 37 | }); 38 | 39 | // 全局错误 40 | global.errors = require('./errors'); 41 | global.Exception = function(code, msg) { 42 | this.code = code; 43 | this.msg = msg || errors[code]; 44 | this.stack = new Error(this.code + ': ' + this.msg).stack; 45 | }; 46 | 47 | // 控制器 48 | global.ctrls = require('./ctrls'); 49 | 50 | // 模型 51 | global.models = require('./models'); 52 | 53 | -------------------------------------------------------------------------------- /wechat_service1/src/common/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引用 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | // 加载所有模型 8 | var exports = {}; 9 | var dir = fs.readdirSync(__dirname + '/../models'); 10 | for (var i = 0; i < dir.length; i++) { 11 | if (path.extname(dir[i]) !== '.js') continue; 12 | exports[_.toCamel(path.basename(dir[i], '.js'))] = require(__dirname + '/../models/' + dir[i]); 13 | } 14 | 15 | module.exports = exports; 16 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/active_message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/31. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var wx = require("wechat-toolkit"); 12 | var request = require('request'); 13 | var debug = require('debug')('weixin'); 14 | var fase = require('fase'); 15 | var async = fase.async; 16 | 17 | var weixin = require('./weixin.js'); 18 | 19 | Ctrl.prototype.sendExamSeatMsg = function* (req, res) { 20 | if (config.authorized.indexOf(req.body.token) === -1) { 21 | throw new Exception(1101); 22 | } 23 | 24 | if (!req.body.toUser || !req.body.exam || !req.body.date || !req.body.school) { 25 | throw new Exception(3002); 26 | } 27 | 28 | if (!config.weixin.accToken) { 29 | throw new Exception(3003); 30 | } 31 | 32 | let currentTime = new Date(); 33 | let wxOpenId = req.body.toUser; 34 | let uri = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' + config.weixin.accToken; 35 | let data = { 36 | touser: wxOpenId, 37 | template_id: config.weixin.template.kaowei, 38 | url: '', 39 | topcolor: '#0000FF', 40 | data: { 41 | first: { 42 | value: '您查询的考场有新考位', 43 | color: "#173177", 44 | }, 45 | keyword1: { 46 | value: req.body.exam, 47 | color: "#173177", 48 | }, 49 | keyword2: { 50 | value: req.body.date, 51 | color: "#173177", 52 | }, 53 | keyword3: { 54 | value: req.body.school, 55 | color: "#173177", 56 | }, 57 | keyword4: { 58 | value: currentTime.toLocaleString(), 59 | color: "#173177", 60 | }, 61 | remark:{ 62 | value: '谢谢', 63 | color: "#173177", 64 | } 65 | } 66 | }; 67 | let retMsg = yield func.sendPostRequest(uri, data); 68 | if (retMsg.errorcode) { 69 | throw new Exception(3100); 70 | } 71 | 72 | return retMsg; 73 | } 74 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/30. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var request = require('request'); 12 | var debug = require('debug')('weixin'); 13 | var fase = require('fase'); 14 | var async = fase.async; 15 | 16 | var weixin = require('./weixin.js'); 17 | 18 | Ctrl.prototype.getAccToken = function* (req, res) { 19 | if (config.authorized.indexOf(req.body.token) === -1) { 20 | throw new Exception(1101); 21 | } 22 | 23 | let result = { 24 | oldAccToken: config.weixin.oldAccToken, 25 | accToken: config.weixin.accToken, 26 | lastUpdate: config.weixin.lastUpdate, 27 | }; 28 | 29 | return result; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/js_sdk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/30. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var request = require('request'); 12 | var debug = require('debug')('weixin'); 13 | var fase = require('fase'); 14 | var async = fase.async; 15 | 16 | var weixin = require('./weixin.js'); 17 | 18 | var sha1 = require('sha1'); 19 | var _ = require('underscore'); 20 | 21 | Ctrl.prototype.getParams = function* (req, res) { 22 | if (config.authorized.indexOf(req.body.token) === -1) { 23 | throw new Exception(1101); 24 | } 25 | 26 | let param = {}; 27 | param.url = req.body.url; 28 | param.appId = config.weixin.appId; 29 | param.timestamp = new Date().getTime() + ""; 30 | param.timestamp = param.timestamp.slice(0, param.timestamp.length-3); 31 | param.nonceStr = $.func.randString(16); 32 | 33 | let sorted = _.sortBy( 34 | [ 35 | 'noncestr=' + param.nonceStr, 36 | 'jsapi_ticket=' + config.weixin.jsApiTicket, 37 | 'timestamp=' + param.timestamp, 38 | 'url=' + param.url 39 | ], function(err){ 40 | if (err) { 41 | return err; 42 | } 43 | }); 44 | 45 | let origin = sorted.join("&"); 46 | debug('origin: ' + origin); 47 | param.signature = sha1(origin); 48 | return param; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/media.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/30. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | let Ctrl = function() {}; 8 | let that = module.exports = new Ctrl(); 9 | 10 | let CONSTANT = require('../common/constant'); 11 | let wx = require("wechat-toolkit"); 12 | let request = require('request'); 13 | let debug = require('debug')('weixin'); 14 | let fase = require('fase'); 15 | let async = fase.async; 16 | 17 | let weixin = require('./weixin.js'); 18 | 19 | let getTags = function (str) { 20 | if (!str) { 21 | return false; 22 | } 23 | let getStr = str.match(/\[\[.+?-.+?\]\]/g); 24 | let getUrl = str.match(/\[\[\[.+?\]\]\]/g); 25 | if (!getStr) { 26 | return false; 27 | } 28 | else { 29 | let tags = getStr[0]; 30 | if(tags.length){ 31 | let key = tags.match(/([\u4E00-\u9FFF]|[\u0061-\u007A]|[\u0041-\u005A])+/g)[0]; 32 | let num = tags.match(/[0-9]+/g)[0]; 33 | let scene_type = tags.match(/([\u4E00-\u9FFF]|[\u0061-\u007A]|[\u0041-\u005A]|[0-9])+/g)[2]; 34 | if(!getUrl){ 35 | return { 36 | key: key, 37 | num: parseInt(num), 38 | scene_type: scene_type 39 | }; 40 | } 41 | else { 42 | let url = getUrl[0]; 43 | url = url.replace('[[[', ''); 44 | url = url.replace(']]]', ''); 45 | return { 46 | key: key, 47 | num: parseInt(num), 48 | scene_type: scene_type, 49 | urlRedirectFromBind: url 50 | }; 51 | } 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | let generateMediaObj = function (mediaArray) { 58 | let mediaObj = {}; 59 | mediaArray.forEach(function(media){ 60 | let tags = getTags(media.content); 61 | if (tags) { 62 | media.key = tags.key; 63 | media.num = tags.num; 64 | media.scene_type = tags.scene_type; 65 | if (tags.key === 'HYZH') { 66 | media.url = CONSTANT.URL.WX_OAUTH_INI + config.weixin.appId 67 | + '&redirect_uri=' + encodeURIComponent(CONSTANT.URL.USER_BIND) 68 | + '&response_type=code' 69 | + '&scope=snsapi_userinfo' 70 | + '&state=' + 'bindAccount' + '#wechat_redirect'; 71 | } 72 | if (tags.scene_type && tags.urlRedirectFromBind) { 73 | media.url = CONSTANT.URL.WX_OAUTH_INI + config.weixin.appId 74 | + '&redirect_uri=' + encodeURIComponent(CONSTANT.URL.USER_BIND + '?redirect=' + encodeURIComponent(tags.urlRedirectFromBind)) 75 | + '&response_type=code' 76 | + '&scope=snsapi_userinfo' 77 | + '&state=' + 'bindAccount' + '#wechat_redirect'; 78 | } 79 | if (!media.scene_type) { 80 | media.scene_type = 'default'; 81 | } 82 | mediaObj[CONSTANT.WEIXIN_KEY_SORT[media.key] + '_' + media.scene_type] = mediaObj[CONSTANT.WEIXIN_KEY_SORT[media.key] + '_' + media.scene_type] || []; 83 | mediaObj[CONSTANT.WEIXIN_KEY_SORT[media.key] + '_' + media.scene_type].push(media); 84 | } 85 | }); 86 | for (let key in mediaObj) { 87 | mediaObj[key].sort(function(obj1,obj2){ 88 | if (obj1.num > obj2.num) { 89 | return 1; 90 | } 91 | else if (obj1.num < obj2.num) { 92 | return -1; 93 | } 94 | else { 95 | return 0; 96 | } 97 | }); 98 | } 99 | return mediaObj; 100 | } 101 | 102 | Ctrl.prototype.mediaFromWeixin = function* (type, MaxNum) { 103 | let uri = 'https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token='; 104 | uri += config.weixin.accToken; 105 | 106 | let data = { 107 | type: type, 108 | offset: 0, 109 | count: 20 110 | }; 111 | 112 | let retMedia = { 113 | item_count: 0, 114 | item: [] 115 | }; 116 | 117 | for(let offset = 0; offset < MaxNum;) { 118 | data.offset = offset; 119 | let media = yield func.sendPostRequest(uri,data); 120 | 121 | if (!media || media.errcode || !parseInt(media.item_count) || parseInt(media.item_count) < 0) { 122 | console.log('err getting media ' + media.errcode); 123 | break; 124 | } 125 | 126 | retMedia.total_count = media.total_count; 127 | retMedia.item_count += media.item_count; 128 | for (let i = 0; i < media.item.length; i++ ) { 129 | retMedia.item.push(media.item[i]); 130 | } 131 | offset += media.item_count; 132 | if (offset >= media.total_count) { 133 | break; 134 | } 135 | } 136 | 137 | return retMedia; 138 | } 139 | 140 | Ctrl.prototype.refreshMediaObject = function* () { 141 | 142 | let newsMedia = yield that.mediaFromWeixin('news',300); 143 | let imgMedia = yield that.mediaFromWeixin('image', 300); 144 | 145 | let retArray = []; 146 | let mediaObj = {}; 147 | 148 | if (newsMedia.item_count && newsMedia.item_count > 0) { 149 | //debug(imgMedia); 150 | //debug(newsMedia.item[0].content.news_item[0]); 151 | let count = 152 | config.weixin.countMedia < newsMedia.item_count ? 153 | config.weixin.countMedia : 154 | newsMedia.item_count; 155 | for (let iNews = 0; iNews < count; ++iNews) { 156 | let newObj= { 157 | title: newsMedia.item[iNews].content.news_item[0].title, 158 | description: newsMedia.item[iNews].content.news_item[0].digest, 159 | url: newsMedia.item[iNews].content.news_item[0].url, 160 | content: newsMedia.item[iNews].content.news_item[0].content, 161 | }; 162 | let newsThumbId = newsMedia.item[iNews].content.news_item[0].thumb_media_id; 163 | if (imgMedia.item_count && imgMedia.item_count > 0){ 164 | for (let iImg = 0; iImg < imgMedia.item_count; ++ iImg) { 165 | if (imgMedia.item[iImg].media_id === newsThumbId) { 166 | newObj.picurl = imgMedia.item[iImg].url; 167 | break; 168 | } 169 | } 170 | } 171 | 172 | retArray[iNews] = newObj; 173 | } 174 | } 175 | else { 176 | retArray = [ 177 | { 178 | title: '【欢迎2】小鹏哥雅思写作真题解析及预测', 179 | description: '考生在雅思写作过程中往往面临三种困境:没有观点、没有语言、表达不精彩。怎么办?你需要是精彩的语料库!9月15日晚,经验丰富的小鹏哥针对2015年9月的雅思写作考试精准预测,根据考情预测趋势,分享精彩语料库,让你轻松应对雅思写作!', 180 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 181 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 182 | content: '啊哈哈哈啊[[HY-1-yaya2]][[[http://dev.smartstudy.com:9009/reg/banshee?hmsr=test3]]]' 183 | }, 184 | { 185 | title: '【欢迎4】小鹏哥雅思写作真题解析及预测', 186 | description: '考生在雅思写作过程中往往面临三种困境:没有观点、没有语言、表达不精彩。怎么办?你需要是精彩的语料库!9月15日晚,经验丰富的小鹏哥针对2015年9月的雅思写作考试精准预测,根据考情预测趋势,分享精彩语料库,让你轻松应对雅思写作!', 187 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 188 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 189 | content: '啊哈哈哈啊[[HY-4-GRE1]]' 190 | }, 191 | { 192 | title: '【欢迎4】小鹏哥雅思写作真题解析及预测', 193 | description: '考生在雅思写作过程中往往面临三种困境:没有观点、没有语言、表达不精彩。怎么办?你需要是精彩的语料库!9月15日晚,经验丰富的小鹏哥针对2015年9月的雅思写作考试精准预测,根据考情预测趋势,分享精彩语料库,让你轻松应对雅思写作!', 194 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 195 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 196 | content: '啊哈哈哈啊[[HY-6-GRE1]]' 197 | }, 198 | { 199 | title: '【欢迎7】小鹏哥雅思写作真题解析及预测', 200 | description: '考生在雅思写作过程中往往面临三种困境:没有观点、没有语言、表达不精彩。怎么办?你需要是精彩的语料库!9月15日晚,经验丰富的小鹏哥针对2015年9月的雅思写作考试精准预测,根据考情预测趋势,分享精彩语料库,让你轻松应对雅思写作!', 201 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 202 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 203 | content: '啊哈哈哈啊[[HY-2-GRE3]]' 204 | }, 205 | { 206 | title: '【欢迎4】小鹏哥雅思写作真题解析及预测', 207 | description: '考生在雅思写作过程中往往面临三种困境:没有观点、没有语言、表达不精彩。怎么办?你需要是精彩的语料库!9月15日晚,经验丰富的小鹏哥针对2015年9月的雅思写作考试精准预测,根据考情预测趋势,分享精彩语料库,让你轻松应对雅思写作!', 208 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 209 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 210 | content: '啊哈哈哈啊[[HY-4]]' 211 | }, 212 | { 213 | title: '【绑账号1】', 214 | description: '接收通知', 215 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 216 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 217 | content: '啊[[HYZH-1-HYZH]]<>' 218 | }, 219 | { 220 | title: '【欢迎3】', 221 | description: '欢迎', 222 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 223 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 224 | content: '啊[[HY-3-Toefl]]<>' 225 | }, 226 | { 227 | title: '【欢迎5】', 228 | description: '欢迎', 229 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 230 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 231 | content: '啊[[HY-5-Toefl]]<>' 232 | }, 233 | { 234 | title: '【直播】', 235 | description: '直播', 236 | url: 'http://mp.weixin.qq.com/s?__biz=MzA5ODIzMDI2NQ==&mid=216246151&idx=1&sn=7c055208f9daae2f97823bd1f85fc1d1#rd', 237 | picurl: 'http://mmbiz.qpic.cn/mmbiz/iaBmDVE8BLHfvOVEyNdGUTQVqPNCyKAHibvluBYDFOTfJpKNmMFLXMxfFZSyp18jMmrL5UpA2aAjNjqrsDh0997g/640?wx_fmt=jpeg&tp=webp&wxfrom=5', 238 | content: '啊[[ZB-3]]' 239 | } 240 | ]; 241 | } 242 | mediaObj = yield generateMediaObj(retArray); 243 | for(let key in mediaObj) { 244 | yield func.cacheSet(key, JSON.stringify(mediaObj[key])); 245 | } 246 | return mediaObj; 247 | }; 248 | 249 | Ctrl.prototype.refreshMediaInfo = function* (req, res) { 250 | if (config.authorized.indexOf(req.body.token) === -1) { 251 | throw new Exception(1101); 252 | } 253 | return yield that.refreshMediaObject(); 254 | }; 255 | 256 | Ctrl.prototype.getNewsMedia = function* (getCacheKey) { 257 | try{ 258 | let cacheStr = yield func.cacheGet(getCacheKey); 259 | let retObj = JSON.parse(cacheStr); 260 | if (!retObj) { 261 | let mediaObj = yield that.refreshMediaObject(); 262 | retObj = mediaObj[getCacheKey]; 263 | } 264 | if (retObj instanceof Array) { 265 | return retObj; 266 | } 267 | return false; 268 | } 269 | catch(e) { 270 | return false; 271 | } 272 | } 273 | 274 | Ctrl.prototype.getWelcome = function* () { 275 | try{ 276 | let cacheStr = yield func.cacheGet(CONSTANT.CACHE_KEYS.MEDIA); 277 | let retObj = JSON.parse(cacheStr); 278 | if (!retObj) { 279 | retObj = yield that.refreshMediaObject(); 280 | } 281 | if (retObj.welcome instanceof Array) { 282 | return retObj.welcome 283 | } 284 | return false; 285 | } 286 | catch(e) { 287 | return false; 288 | } 289 | } 290 | 291 | Ctrl.prototype.getBind = function* () { 292 | try{ 293 | let cacheStr = yield func.cacheGet(CONSTANT.CACHE_KEYS.MEDIA); 294 | let retObj = JSON.parse(cacheStr); 295 | if (!retObj) { 296 | retObj = yield that.refreshMediaObject(); 297 | } 298 | if (retObj.bind instanceof Array) { 299 | return retObj.bind 300 | } 301 | return false; 302 | } 303 | catch(e) { 304 | return false; 305 | } 306 | } 307 | 308 | Ctrl.prototype.getLive = function* () { 309 | try{ 310 | let cacheStr = yield func.cacheGet(CONSTANT.CACHE_KEYS.MEDIA); 311 | let retObj = JSON.parse(cacheStr); 312 | if (!retObj) { 313 | retObj = yield that.refreshMediaObject(); 314 | } 315 | if (retObj.live instanceof Array) { 316 | return retObj.live 317 | } 318 | return false; 319 | } 320 | catch(e) { 321 | return false; 322 | } 323 | } -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/menu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/30. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var wx = require("wechat-toolkit"); 12 | var request = require('request'); 13 | var debug = require('debug')('weixin'); 14 | var fase = require('fase'); 15 | var async = fase.async; 16 | 17 | var weixin = require('./weixin.js'); 18 | 19 | var createMenu = thunkify(function (accToken, menuObj, next) { 20 | wx.createMenu(accToken, menuObj, function(err, error_code, error_message){ 21 | if (err) { 22 | next(err, -1); 23 | } 24 | else { 25 | next(null, error_code); 26 | } 27 | }); 28 | }); 29 | 30 | Ctrl.prototype.deleteMenu = function* (req, res) { 31 | if (config.authorized.indexOf(req.body.token) === -1) { 32 | throw new Exception(1101); 33 | } 34 | 35 | if(config.weixin.accToken){ 36 | let data = {}; 37 | let retObj = {}; 38 | for (let i = 0; i 3) { 148 | retArray.length = 3; 149 | } 150 | return retArray; 151 | } 152 | else { 153 | return false; 154 | } 155 | } 156 | 157 | Ctrl.prototype.getUserQrcode = function* (weiXinData) { 158 | if (!weiXinData) { 159 | return false; 160 | } 161 | let eventKey = weiXinData.event_key; 162 | let scene_id = eventKey.match(/[0-9]+/g); 163 | let keywordObj = yield models.qrUrl.find({scene_id: scene_id, status: 1}); 164 | let keywordId; 165 | 166 | if (keywordObj && keywordObj[0]) { 167 | keywordId = keywordObj[0].id; 168 | } 169 | else { 170 | keywordId = 0; 171 | } 172 | 173 | let retUserQrcode; 174 | 175 | let where = { 176 | open_id: weiXinData.open_id, 177 | keyword_id: keywordId 178 | }; 179 | let userQrcodeObj = yield models.userQrcode.find(where); 180 | 181 | if (userQrcodeObj && userQrcodeObj[0]) { 182 | retUserQrcode = yield models.userQrcode.update(where); 183 | } 184 | else { 185 | let values = { 186 | event: weiXinData.event, 187 | open_id: weiXinData.open_id, 188 | event_fired_at: weiXinData.event_fired_at, 189 | keyword_id: keywordId 190 | }; 191 | retUserQrcode = yield models.userQrcode.save(values); 192 | } 193 | 194 | return retUserQrcode; 195 | } 196 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/qr_code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yangtao on 16/2/15. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | let Ctrl = function() {}; 8 | let that = module.exports = new Ctrl(); 9 | 10 | let CONSTANT = require('../common/constant'); 11 | let wx = require("wechat-toolkit"); 12 | let request = require('request'); 13 | let debug = require('debug')('weixin'); 14 | let fase = require('fase'); 15 | let async = fase.async; 16 | 17 | let weixin = require('./weixin.js'); 18 | 19 | Ctrl.prototype.getQrCode = function* (req, res) { 20 | if (!req.query.action_name || !req.query.scene_id) { 21 | throw new Exception(4002); //参数不足 22 | } 23 | 24 | let data = {}; 25 | data.action_info = {}; 26 | data.action_info.scene = {}; 27 | 28 | if (req.query.expire_seconds) { 29 | data.expire_seconds = parseInt(req.query.expire_seconds); 30 | } 31 | 32 | data.action_name = req.query.action_name; 33 | data.action_info.scene.scene_id = parseInt(req.query.scene_id); 34 | 35 | if (data.action_name == 'QR_SCENE') { 36 | if (data.expire_seconds) { 37 | if (data.expire_seconds >2592000 || data.expire_seconds < 0) { 38 | throw new Exception(4001); //expire_seconds 错误 39 | } 40 | } 41 | if (data.action_info.scene.scene_id > 1 && data.action_info.scene.scene_id < 100000) { 42 | throw new Exception(4001); //scene_id值范围错误 43 | } 44 | } 45 | if (data.action_name == 'QR_LIMIT_SCENE') { 46 | if (data.expire_seconds) { 47 | throw new Exception(4001); //永久二维码不该有该字段 48 | } 49 | if (data.action_info.scene.scene_id > 100000 || data.action_info.scene.scene_id < 1) { 50 | throw new Exception(4001); //scene_id值范围错误 51 | } 52 | } 53 | 54 | let ticketMsg = yield that.getQrCodeTicket(data); 55 | if (ticketMsg) { 56 | if (ticketMsg.expire_seconds !== data.expire_seconds){ 57 | console.log('expire_seconds mismatch: ' + data.expire_seconds + ' vs. ' + ticketMsg.expire_seconds) 58 | throw new Exception(4000); //前后超时时间不一致 59 | } 60 | let ticket = ticketMsg.ticket; //ticket是否需要进行UrlEncode转换 61 | let uri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='; 62 | let url = uri + ticket; 63 | return url; 64 | } 65 | else { 66 | throw new Exception(4000); 67 | } 68 | } 69 | 70 | Ctrl.prototype.getQrCodeTicket = function* (data) { 71 | if (!data) { 72 | return false; 73 | } 74 | let ticketMsg; 75 | let uri = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token='; 76 | uri += config.weixin.accToken; 77 | ticketMsg = yield func.sendPostRequest(uri,data); 78 | if (ticketMsg.errcode) { 79 | console.log('err getting ticketMsg: ' + ticketMsg.errcode); 80 | throw new Exception(4000); 81 | } 82 | return ticketMsg; 83 | } 84 | 85 | Ctrl.prototype.getKeyUrlArray = function* (req, res) { 86 | if (!req.body.keywordArray) { 87 | return false; 88 | } 89 | 90 | let keywordArray = JSON.parse(req.body.keywordArray); 91 | let keywordUrlArray = []; 92 | let keywordUrlObj = {}; 93 | for(let i = 0; i < keywordArray.length; i++) { 94 | if (keywordArray[i] == ''){ 95 | continue; 96 | } 97 | keywordUrlObj = yield models.qrUrl.find({keyword: keywordArray[i], status: 1}, {attributes: ['keyword', 'qr_url']}); 98 | if (keywordUrlObj && keywordUrlObj[0] && keywordUrlObj[0].keyword === keywordArray[i]) { 99 | keywordUrlArray.push(keywordUrlObj[0]); 100 | } 101 | else { 102 | keywordUrlObj = {}; 103 | let values = {}; 104 | let sceneIds = yield models.qrUrl.find({},{attributes: ['scene_id']}); 105 | let scene_id = Math.floor(Math.random() * 100000 + 1); 106 | let retValues = yield that.getUrl(scene_id, sceneIds); 107 | 108 | values.keyword = keywordArray[i]; 109 | values.scene_id = retValues.scene_id; 110 | values.qr_url = retValues.url; 111 | values.status = retValues.status; 112 | 113 | let obj = yield models.qrUrl.save(values); 114 | 115 | keywordUrlObj.keyword = obj.dataValues.keyword; 116 | keywordUrlObj.qr_url = obj.dataValues.qr_url; 117 | keywordUrlArray.push(keywordUrlObj); 118 | } 119 | } 120 | 121 | return keywordUrlArray; 122 | } 123 | 124 | Ctrl.prototype.getUrl = function* (scene_id, sceneIds) { 125 | if (!scene_id) { 126 | return false; 127 | } 128 | let retValues = {}; 129 | let data = {}; 130 | data.action_name = "QR_LIMIT_SCENE"; 131 | data.action_info = {}; 132 | data.action_info.scene = {}; 133 | 134 | while (scene_id) { 135 | if (sceneIds.indexOf(scene_id) == -1) { 136 | break; 137 | } 138 | else { 139 | scene_id = Math.floor(Math.random() * 100000 + 1); 140 | } 141 | } 142 | 143 | retValues.scene_id = scene_id; 144 | data.action_info.scene.scene_id = scene_id; 145 | let tickeyMsg = yield that.getQrCodeTicket(data); 146 | if (tickeyMsg) { 147 | let ticket = tickeyMsg.ticket; 148 | let uri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='; 149 | let url = uri + ticket; 150 | retValues.url = url; 151 | retValues.status = 1; 152 | } 153 | if (tickeyMsg.errcode) { 154 | console.log('err getting ticketMsg: ' + tickeyMsg.errcode); 155 | throw new Exception(4000); 156 | } 157 | 158 | return retValues; 159 | } 160 | 161 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/user_info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/31. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var request = require('request'); 12 | var debug = require('debug')('weixin'); 13 | var fase = require('fase'); 14 | var async = fase.async; 15 | 16 | var wx = require("wechat-toolkit"); 17 | 18 | var cacheGet = thunkify(function (key, next) { 19 | cache.get(key, next); 20 | }); 21 | 22 | var cacheSet = thunkify(function (key, value, next) { 23 | cache.set(key, value, next); 24 | }); 25 | 26 | var getOAuthToken = thunkify(function (code, next){ 27 | wx.exchangeAccessToken(config.weixin.appId, config.weixin.appSecret, code, function(err, result){ 28 | if (err) { 29 | console.log(err); 30 | next(null, err); 31 | } 32 | else { 33 | next(null, result); 34 | } 35 | }); 36 | }); 37 | 38 | var getOAuthUserInfo = thunkify(function (token, openid, next){ 39 | wx.getUserInfo(token, openid, function(err, result){ 40 | if (err) { 41 | console.log(err); 42 | next(null, err); 43 | } 44 | else { 45 | next(null, result); 46 | } 47 | }); 48 | }); 49 | 50 | Ctrl.prototype.getUserInfo = thunkify(function (openid, next){ 51 | wx.getFanInfo(config.weixin.accToken, openid, function(err, result){ 52 | if (err) { 53 | console.log(err); 54 | next(null, err); 55 | } 56 | else { 57 | next(null, result); 58 | } 59 | }); 60 | }); 61 | 62 | Ctrl.prototype.getOAuth = function* (req, res) { 63 | if (config.authorized.indexOf(req.body.token) === -1) { 64 | throw new Exception(1101); 65 | } 66 | 67 | if (!req.body.code) { 68 | throw new Exception(3002); 69 | } 70 | 71 | let code = req.body.code; 72 | let oAuthToken = yield getOAuthToken(code); 73 | 74 | if (oAuthToken.errcode === 40029) { 75 | throw new Exception(3031); 76 | } 77 | else if (oAuthToken.errcode) { 78 | throw new Exception(3030); 79 | } 80 | 81 | let userInfo = yield getOAuthUserInfo(oAuthToken.access_token, oAuthToken.openid); 82 | if (userInfo.errcode) { 83 | throw new Exception(3030); 84 | } 85 | 86 | if (oAuthToken.openid) { 87 | let location = JSON.parse(yield cacheGet('location_openId: ' + oAuthToken.openid)); 88 | if (location) { 89 | userInfo.location = { 90 | latitude: location.latitude, 91 | longitude: location.longitude, 92 | precision: location.precision, 93 | }; 94 | if (oAuthToken.unionid) { 95 | userInfo.unionid = oAuthToken.unionid; 96 | } 97 | } 98 | } 99 | 100 | return userInfo; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /wechat_service1/src/ctrls/weixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by CDENG on 15/8/13. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | var Ctrl = function() {}; 8 | var that = module.exports = new Ctrl(); 9 | 10 | var CONSTANT = require('../common/constant'); 11 | var wx = require("wechat-toolkit"); 12 | var request = require('request'); 13 | var debug = require('debug')('weixin'); 14 | var fase = require('fase'); 15 | var async = fase.async; 16 | 17 | var sha1 = require('sha1'); 18 | var _ = require('underscore'); 19 | 20 | var processPassive = require('./process_passive'); 21 | 22 | Ctrl.prototype.enableDev = function * (req, res) { 23 | 24 | } 25 | 26 | Ctrl.prototype.processRecvPost = function* (req, res) { 27 | if (req.weixin.MsgType !== 'event' 28 | && (yield func.cacheGet('customerService_openid:' + req.weixin.FromUserName)) 29 | ) {//redirect to customer service 30 | debug('transfering to customerService'); 31 | res.reply({type: 'transfer_customer_service'}); 32 | return; 33 | } 34 | 35 | switch(req.weixin.MsgType){ 36 | case 'event': 37 | yield that.processEvent(req, res); 38 | break; 39 | case 'text': 40 | yield processPassive.text(req, res); 41 | break; 42 | default: 43 | res.reply({type: "text", content: 'Hello world!'}); 44 | break; 45 | } 46 | } 47 | 48 | Ctrl.prototype.processEvent = function* (req, res) { 49 | switch (req.weixin.Event) { 50 | case 'subscribe': 51 | yield processPassive.subscribe(req, res); 52 | break; 53 | case 'SCAN': 54 | yield processPassive.scan(req, res); 55 | break; 56 | case 'LOCATION' :{ 57 | let location = {}; 58 | location.latitude = req.weixin.Latitude; 59 | location.longitude = req.weixin.Longitude; 60 | location.precision = req.weixin.Precision; 61 | yield func.cacheSet('location_openId: ' + req.weixin.FromUserName, JSON.stringify(location)); 62 | break; 63 | } 64 | case 'CLICK' : { 65 | yield processPassive.buttonClick(req, res); 66 | } 67 | case 'VIEW' : { 68 | } 69 | default: 70 | debug(req.weixin); 71 | break; 72 | } 73 | return 'nothing bad happened'; 74 | } 75 | 76 | Ctrl.prototype.updateJsApiTicket = function (next){ 77 | let data = {}; 78 | data.access_token = config.weixin.accToken; 79 | data.type = 'jsapi'; 80 | request( 81 | { 82 | uri: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket', 83 | method: 'GET', 84 | qs: data, 85 | timeout: 10000 86 | }, function(err, res, body) { 87 | if (err) { 88 | debug('err: ' + err); 89 | next(err); 90 | } 91 | else{ 92 | debug('update jsapi body: ' + body); 93 | var resultObj = JSON.parse(body); 94 | if (parseInt(resultObj.errcode !== 0)) { 95 | console.log('something wrong getting JSAPI Ticket: ' + resultObj.errmsg); 96 | next(resultObj.errmsg); 97 | } 98 | else { 99 | config.weixin.jsApiTicket = resultObj.ticket; 100 | next(null); 101 | } 102 | } 103 | }); 104 | } 105 | 106 | Ctrl.prototype.updateAccToken = function (next) { 107 | let data = {}; 108 | data.appid = config.weixin.appId; 109 | data.secret = config.weixin.appSecret; 110 | data.grant_type = 'client_credential'; 111 | 112 | request( 113 | { 114 | uri: 'https://api.weixin.qq.com/cgi-bin/token', 115 | method: 'GET', 116 | qs: data, 117 | timeout: 10000 118 | }, function(err, res, body) { 119 | if (err) { 120 | debug('err: ' + err); 121 | next(err); 122 | } 123 | else { 124 | debug('update token body: ' + body); 125 | var resultObj = JSON.parse(body); 126 | if (resultObj.access_token) { 127 | config.weixin.oldAccToken = (config.weixin.accToken) ? config.weixin.accToken : ''; 128 | config.weixin.accToken = resultObj.access_token; 129 | next(null); 130 | } 131 | else { 132 | console.log('something wrong in updateAccToken'); 133 | next('something wrong in updateAccToken'); 134 | } 135 | } 136 | }); 137 | } 138 | 139 | //由定时器触发,每小时更新一次accToken和JDK相关 140 | //若bForceUpdate为真的话无视上次更新时间,否则距上次更新一小时以上时更新 141 | Ctrl.prototype.timedUpdate = function* (bForceUpdate, next) { 142 | let currentTime = new Date(); 143 | if (bForceUpdate || !config.weixin.lastUpdate || currentTime - new Date(config.weixin.lastUpdate) >= 60*60*1000-1000 ) { 144 | that.updateAccToken(function(err){ 145 | if (err) { 146 | next(err, false); 147 | } 148 | else { 149 | that.updateJsApiTicket(function(err){ 150 | if (err) { 151 | next(err, false); 152 | } 153 | else { 154 | config.weixin.lastUpdate = currentTime; 155 | cache.set('weixinConfig', JSON.stringify(config.weixin), function(err, next){ 156 | if (err) { 157 | next('sth wrong writing token to cache: ' + err); 158 | } else { 159 | 160 | } 161 | }); 162 | next(null, true); 163 | } 164 | }); 165 | } 166 | }); 167 | } 168 | } 169 | 170 | setTimeout(function () { 171 | cache.get('weixinConfig', function(err, ret){ 172 | if (err) { 173 | console.log('err reading cached: ' + err); 174 | } 175 | else { 176 | if (ret) { 177 | let retObj = JSON.parse(ret); 178 | if (retObj.lastUpdate) config.weixin.lastUpdate = retObj.lastUpdate; 179 | if (retObj.oldAccToken) config.weixin.oldAccToken = retObj.oldAccToken; 180 | if (retObj.accToken) config.weixin.accToken = retObj.accToken; 181 | if (retObj.jsApiTicket) config.weixin.jsApiTicket = retObj.jsApiTicket; 182 | debug('update from cached token, lastUpdate: ' + retObj.lastUpdate); 183 | debug('accToken: ' + retObj.accToken); 184 | async(that.timedUpdate, [false], function(err){ 185 | if (err) { 186 | console.log('something wrong during timed update'); 187 | } 188 | }); 189 | } 190 | else { 191 | async(that.timedUpdate, [true], function(err){ 192 | if (err) { 193 | console.log('something wrong during timed update'); 194 | } 195 | }); 196 | } 197 | } 198 | }); 199 | }, 10 * 1000); 200 | 201 | //定时器,每5分钟跳一次 202 | setInterval(function(){ 203 | async(that.timedUpdate, [false], function(err){ 204 | if (err) { 205 | console.log('something wrong during timed update'); 206 | } 207 | }); 208 | }, 5*60*1000); 209 | 210 | -------------------------------------------------------------------------------- /wechat_service1/src/models/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 基础模型 5 | */ 6 | var Model = module.exports = function() {}; 7 | 8 | /** 9 | * 获取 10 | * 11 | * @param {Object} where 筛选条件 12 | * @param {Object} options 其它条件 13 | * @return {Object} 文档 14 | */ 15 | Model.prototype.get = function*(where, options) { 16 | options = options || {}; 17 | 18 | // 生成查询条件 19 | var queryOptions = {where: where}; 20 | for (var i in options) { 21 | if (i === 'oneToMany') continue; 22 | if (i === 'manyToMany') continue; 23 | queryOptions[i] = options[i]; 24 | } 25 | queryOptions.raw = true; 26 | 27 | // 获取数据 28 | var data = yield this.orm.findOne(queryOptions); 29 | 30 | // 获取一对多的关联数据 31 | if (options.oneToMany) { 32 | data = yield this.getOneToMany(this.orm.name, data, options.oneToMany); 33 | } 34 | 35 | // 获取多对多关联数据 36 | if (options.manyToMany) { 37 | data = yield this.getManyToMany(this.orm.name, data, options.manyToMany); 38 | } 39 | 40 | // 返回 41 | return data; 42 | }; 43 | 44 | /** 45 | * 通过ID获取 46 | * 47 | * @param {Number} id 编号 48 | * @param {Object} options 其它条件 49 | * @return {Object} 数据 50 | */ 51 | Model.prototype.getById = function*(id, options) { 52 | var res = yield this.get({id: id}, options); 53 | return res; 54 | }; 55 | 56 | /** 57 | * 通过字段获取用户信息 58 | * 59 | * @param {String} field 字段 60 | * @param {String} value 值 61 | * @param {Object} options 其它条件 62 | * @return {Object} 数据 63 | */ 64 | Model.prototype.getByField = function*(field, value, options) { 65 | var where = {}; 66 | where[field] = value; 67 | 68 | var res = yield this.get(where, options); 69 | return res; 70 | }; 71 | 72 | /** 73 | * 查找 74 | * 75 | * @param {Object} where 筛选条件 76 | * @param {Object} options 其它条件 77 | * @return {Array} 文档 78 | */ 79 | Model.prototype.find = function*(where, options) { 80 | options = options || {}; 81 | 82 | // 生成查询条件 83 | var queryOptions = {where: where}; 84 | for (var i in options) { 85 | if (i === 'oneToMany') continue; 86 | if (i === 'manyToMany') continue; 87 | queryOptions[i] = options[i]; 88 | } 89 | queryOptions.raw = true; 90 | 91 | // 获取数据 92 | var data = yield this.orm.findAll(queryOptions); 93 | 94 | // 获取一对多的关联数据 95 | if (options.oneToMany) { 96 | data = yield this.getOneToMany(this.orm.name, data, options.oneToMany); 97 | } 98 | 99 | // 获取多对多关联数据 100 | if (options.manyToMany) { 101 | data = yield this.getManyToMany(this.orm.name, data, options.manyToMany); 102 | } 103 | 104 | // 返回 105 | return data; 106 | }; 107 | 108 | /** 109 | * 删除 110 | * 111 | * @param {Number} id 编号 112 | */ 113 | Model.prototype.delete = function*(id) { 114 | var res = yield this.orm.destroy({where: {id: id}}); 115 | if (!res) { 116 | throw new Exception(10006); 117 | } 118 | }; 119 | 120 | /** 121 | * 统计数量 122 | * 123 | * @param {Object} where 筛选条件 124 | * @param {Object} other 其它条件 125 | * @return {Array} 文档 126 | */ 127 | Model.prototype.count = function*(where, other) { 128 | var options = {where: where}; 129 | for (var i in other) { 130 | options[i] = other[i]; 131 | } 132 | options.raw = true; 133 | 134 | var res = yield this.orm.count(options); 135 | return res; 136 | }; 137 | 138 | // 获取一对多关联数据 139 | Model.prototype.getOneToMany = function*(model, data, oneToMany) { 140 | for (var i = 0; i < oneToMany.length; i++) { 141 | if (typeof oneToMany[i] === 'string') { 142 | var associationData = yield this.getAssociationDataByOneToMany(model, oneToMany[i], data); 143 | data = yield this.mergeAssociationDataByOneToMany(model, oneToMany[i], data, associationData); 144 | } else { 145 | var associationData = yield this.getAssociationDataByOneToMany(model, oneToMany[i].model, data); 146 | if (oneToMany[i].oneToMany) { 147 | associationData = yield this.getOneToMany(oneToMany[i].model, associationData, oneToMany[i].oneToMany); 148 | } 149 | if (oneToMany[i].manyToMany) { 150 | associationData = yield this.getManyToMany(oneToMany[i].model, associationData, oneToMany[i].manyToMany); 151 | } 152 | data = yield this.mergeAssociationDataByOneToMany(model, oneToMany[i].model, data, associationData); 153 | } 154 | } 155 | 156 | return data; 157 | }; 158 | 159 | // 获取多对多关联数据 160 | Model.prototype.getManyToMany = function*(model, data, manyToMany) { 161 | for (var i = 0; i < manyToMany.length; i++) { 162 | if (typeof manyToMany[i] === 'string') { 163 | var associationInfo = yield this.getAssociationInfo(model, manyToMany[i], data); 164 | var associationData = yield this.getAssociationData(manyToMany[i], associationInfo); 165 | data = yield this.mergeAssociationData( 166 | model, manyToMany[i], data, associationData, associationInfo 167 | ); 168 | } else { 169 | var associationInfo = yield this.getAssociationInfo(model, manyToMany[i].model, data); 170 | var associationData = yield this.getAssociationData(manyToMany[i].model, associationInfo); 171 | if (manyToMany[i].oneToMany) { 172 | associationData = yield this.getOneToMany(manyToMany[i].model, associationData, manyToMany[i].oneToMany); 173 | } 174 | if (manyToMany[i].manyToMany) { 175 | associationData = yield this.getManyToMany(manyToMany[i].model, associationData, manyToMany[i].manyToMany); 176 | } 177 | data = yield this.mergeAssociationData( 178 | model, manyToMany[i].model, data, associationData, associationInfo 179 | ); 180 | } 181 | } 182 | 183 | return data; 184 | }; 185 | 186 | // 获取关联信息 187 | Model.prototype.getAssociationInfo = function*(modelA, modelB, data) { 188 | // 初始化 189 | var modelAB = 190 | models[modelA + modelB[0].toUpperCase() + modelB.substr(1)] 191 | ? modelA + modelB[0].toUpperCase() + modelB.substr(1) 192 | : modelB + modelA[0].toUpperCase() + modelA.substr(1); 193 | var idA = modelA + 'Id'; 194 | var idB = modelB + 'Id'; 195 | 196 | // 获取编号列表 197 | 198 | if (data instanceof Array) { 199 | var idsA = data.map(function(item) { 200 | return item.id; 201 | }); 202 | } else { 203 | var idsA = [data.id]; 204 | } 205 | 206 | // 获取关联信息 207 | var where = {}; 208 | where[idA] = {in: idsA}; 209 | var associations = yield models[modelAB].find(where); 210 | 211 | return associations; 212 | }; 213 | 214 | // 获取关联数据 215 | Model.prototype.getAssociationData = function*(model, associationInfo) { 216 | var id = model + 'Id'; 217 | 218 | var ids = associationInfo.map(function(item) { 219 | return item[id]; 220 | }); 221 | 222 | var data = yield models[model].find({ 223 | id: {in: ids} 224 | }); 225 | 226 | // 返回 227 | return data; 228 | } 229 | 230 | // 获取关联数据 231 | Model.prototype.getAssociationDataByOneToMany = function*(modelA, modelB, associationInfo) { 232 | var idA = modelA + 'Id'; 233 | 234 | if (associationInfo instanceof Array) { 235 | var ids = associationInfo.map(function(item) { 236 | return item.id; 237 | }); 238 | } else { 239 | var ids = [associationInfo.id]; 240 | } 241 | 242 | var where = {}; 243 | where[idA] = {in: ids}; 244 | var data = yield models[modelB].find(where); 245 | 246 | // 返回 247 | return data; 248 | } 249 | 250 | // 组合关联数据 251 | Model.prototype.mergeAssociationData = function*(modelA, modelB, data, associationData, associationInfo) { 252 | var idA = modelA + 'Id'; 253 | var idB = modelB + 'Id'; 254 | 255 | var find = data instanceof Array; 256 | if (!find) { 257 | data = [data]; 258 | } 259 | 260 | // 关联 261 | data = data.map(function(item) { 262 | item[modelB + 's'] = []; 263 | 264 | for (var i = 0; i < associationData.length; i++) { 265 | for (var j = 0; j < associationInfo.length; j++) { 266 | if (item.id === associationInfo[j][idA] && associationData[i].id === associationInfo[j][idB]) { 267 | item[modelB + 's'].push(associationData[i]); 268 | break; 269 | } 270 | } 271 | } 272 | 273 | return item; 274 | }); 275 | 276 | // 返回 277 | return find ? data : data[0]; 278 | }; 279 | 280 | // 组合关联数据 281 | Model.prototype.mergeAssociationDataByOneToMany = function*(modelA, modelB, data, associationData) { 282 | var idA = modelA + 'Id'; 283 | var idB = modelB + 'Id'; 284 | 285 | var find = data instanceof Array; 286 | if (!find) { 287 | data = [data]; 288 | } 289 | 290 | // 关联 291 | data = data.map(function(item) { 292 | item[modelB + 's'] = []; 293 | 294 | for (var i = 0; i < associationData.length; i++) { 295 | if (item.id === associationData[i][idA]) { 296 | item[modelB + 's'].push(associationData[i]); 297 | } 298 | } 299 | 300 | return item; 301 | }); 302 | 303 | // 返回 304 | return find ? data : data[0]; 305 | }; 306 | -------------------------------------------------------------------------------- /wechat_service1/src/models/qr_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yangtao on 16/2/23. 3 | */ 4 | 'use strict'; 5 | 6 | //引用 7 | var util = require('util'); 8 | var Base = require('./base'); 9 | 10 | var Model = function () { 11 | this.orm = require('../schemas/qr_url'); 12 | } 13 | util.inherits(Model, Base); 14 | var me = module.exports = new Model(); 15 | 16 | Model.prototype.save = function* (data) { 17 | var values = { 18 | scene_id: data.scene_id, 19 | keyword: data.keyword, 20 | status: data.status, 21 | qr_url: data.qr_url 22 | }; 23 | 24 | var ret = yield me.orm.create(values); 25 | return ret; 26 | }; -------------------------------------------------------------------------------- /wechat_service1/src/models/user_qrcode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yangtao on 16/2/18. 3 | */ 4 | 'use strict'; 5 | 6 | //引用 7 | var util = require('util'); 8 | var Base = require('./base'); 9 | 10 | var Model = function () { 11 | this.orm = require('../schemas/user_qrcode'); 12 | } 13 | util.inherits(Model, Base); 14 | var me = module.exports = new Model(); 15 | 16 | Model.prototype.save = function* (data) { 17 | var values = { 18 | event: data.event, 19 | open_id: data.open_id, 20 | event_fired_at: data.event_fired_at, 21 | keyword_id: data.keyword_id 22 | }; 23 | 24 | var ret = yield me.orm.create(values); 25 | return ret; 26 | }; 27 | 28 | Model.prototype.update = function* (data) { 29 | var ret = yield me.orm.update({ 30 | },{ 31 | where: { 32 | open_id: data.open_id, 33 | keyword_id: data.keyword_id 34 | } 35 | }); 36 | 37 | return ret; 38 | } -------------------------------------------------------------------------------- /wechat_service1/src/schemas/qr_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yangtao on 16/2/23. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var schema = db.define('qrUrl',{ 8 | //编号 9 | id: { 10 | type: Sequelize.INTEGER, 11 | primaryKey: true, 12 | autoIncrement: true 13 | }, 14 | //随机产生的scene_id 15 | scene_id: { 16 | type: Sequelize.INTEGER.UNSIGNED 17 | }, 18 | //传进来的keyword 19 | keyword: { 20 | type: Sequelize.STRING 21 | }, 22 | //状态码 23 | status: { 24 | type: Sequelize.INTEGER, 25 | defaultValue: 0 26 | }, 27 | //微信返回的url 28 | qr_url: { 29 | type: Sequelize.STRING 30 | } 31 | },{ 32 | tableName: 'qr_url', 33 | createdAt: 'created_at', 34 | updatedAt: 'updated_at' 35 | }); 36 | 37 | schema.sync(); 38 | module.exports = schema; -------------------------------------------------------------------------------- /wechat_service1/src/schemas/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/threerocks/wechat_server/140fc34d838472cec96ff9fa8a4aaa03dce15f0f/wechat_service1/src/schemas/test.js -------------------------------------------------------------------------------- /wechat_service1/src/schemas/user_qrcode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yangtao on 16/2/17. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var schema = db.define('userQrcode',{ 8 | //编号 9 | id: { 10 | type: Sequelize.INTEGER, 11 | primaryKey: true, 12 | autoIncrement: true 13 | }, 14 | //关注或扫描等事件 15 | event: { 16 | type: Sequelize.STRING 17 | }, 18 | //发送方账号(一个OpenID) 19 | open_id: { 20 | type: Sequelize.STRING 21 | }, 22 | //消息创建时间 23 | event_fired_at: { 24 | type: Sequelize.DATE 25 | }, 26 | //scene_id字典映射后的keywordId 27 | keyword_id: { 28 | type: Sequelize.INTEGER 29 | } 30 | },{ 31 | tableName: 'user_qrcode', 32 | createdAt: 'created_at', 33 | updatedAt: 'updated_at' 34 | }); 35 | 36 | schema.sync(); 37 | module.exports = schema; -------------------------------------------------------------------------------- /wechat_service1/src/server/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引用 4 | var run = require('./run'); 5 | 6 | // 路由 7 | module.exports = function(app) { 8 | 9 | app.get('/', run(ctrls.weixin.enableDev)); 10 | app.post('/',run(ctrls.weixin.processRecvPost)); 11 | 12 | app.post('/api/menu/del', run(ctrls.menu.deleteMenu)); 13 | app.post('/api/menu/create', run(ctrls.menu.createMenu)); 14 | app.post('/api/menu/createdefault', run(ctrls.menu.createDefaultMenu)); 15 | 16 | app.post('/api/user/oauth', run(ctrls.userInfo.getOAuth)); 17 | app.post('/api/activemsg/post', run(ctrls.activeMessage.sendExamSeatMsg)); 18 | app.post('/api/media/info', run(ctrls.media.refreshMediaInfo)); 19 | 20 | app.post('/api/jssdk/get', run(ctrls.jsSdk.getParams)); 21 | app.post('/api/accToken/get', run(ctrls.api.getAccToken)); 22 | 23 | //生成单个二维码 24 | //app.get('/api/qrcode/',run(ctrls.qrCode.getQrCode)); 25 | 26 | //批量生成二维码 27 | app.post('/api/qrcode/',run(ctrls.qrCode.getKeyUrlArray)); 28 | 29 | 30 | // 404 31 | app.all('*', function(req, res) { 32 | res.send({ 33 | code: config.errorPrefix + 1001, 34 | msg: errors[1001] 35 | }); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /wechat_service1/src/server/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引用 4 | var log = require('util').log; 5 | 6 | // 运行 7 | module.exports = function (func) { 8 | return function (req, res, next) { 9 | // 访问日志 10 | log(req.method + ' ' + req.url); 11 | 12 | // 执行 13 | co(func(req, res)).then(function (data) { 14 | if (data === constant.NOT_SEND_JSON) return; 15 | 16 | res.send({ 17 | code: 0, 18 | data: data 19 | }); 20 | }, function (err) { 21 | console.log(err.stack); 22 | 23 | err.code = err.code || 1000; 24 | res.send({ 25 | code: config.errorPrefix + err.code, 26 | msg: err.msg || '未知错误' 27 | }); 28 | }); 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /wechat_service1/src/server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引用 4 | require('../common/global'); 5 | var express = require('express'); 6 | var router = require('./router'); 7 | //var wx = require("wechat-toolkit"); 8 | 9 | var wechat = require('wechat'); 10 | var wxConfig = { 11 | token: config.weixin.token, 12 | appid: config.weixin.appId, 13 | encodingAESKey: config.weixin.encodingAESKey, 14 | }; 15 | 16 | // express配置 17 | var app = express(); 18 | app.use(require('compression')()); 19 | app.use(require('connect-timeout')(config.timeout * 1000, {respond: false})); 20 | app.use(require('body-parser').urlencoded({extended: false})); 21 | app.use(function(req, res, next) { 22 | // 超时处理 23 | req.on('timeout', function () { 24 | res.send({ 25 | code: config.errorPrefix + 1002, 26 | msg: errors[1002] 27 | }); 28 | process.nextTick(function () { 29 | res.send = res.end = function () { 30 | }; 31 | }); 32 | }); 33 | 34 | next(); 35 | }); 36 | 37 | //微信解析中间件 38 | app.all('/', wechat(wxConfig, function (req, res, next) { 39 | next(); 40 | })); 41 | 42 | // 路由 43 | router(app); 44 | 45 | // 错误 46 | process.on('uncaughtException', function (err) { 47 | console.error('Global:'); 48 | console.error(err); 49 | process.exit(0); 50 | }); 51 | 52 | // 监听 53 | app.listen(config.port); 54 | console.log('wx-service server start: ' + config.port); 55 | -------------------------------------------------------------------------------- /wechat_service2/wechat.helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('./../../common/config'); 4 | const crypto = require('crypto'); 5 | const wx = require("wechat-toolkit"); 6 | const wechat = require('wechat'); 7 | const request = require('request'); 8 | const wechatConfig = { 9 | token: config.wechat.token, 10 | appid: config.wechat.appId, 11 | encodingAESKey: config.wechat.encodingAESKey 12 | }; 13 | 14 | exports.sha1 = function (str) { 15 | var md5sum = crypto.createHash("sha1"); 16 | md5sum.update(str); 17 | str = md5sum.digest("hex"); 18 | return str; 19 | }; 20 | exports.getMenuToken = function () { 21 | return new Promise((resolve, reject) => { 22 | wx.getAccessToken(config.wechat.appId, config.wechat.appSecret, (err, access_token) => { 23 | if (err) reject(err); 24 | resolve(access_token); 25 | }); 26 | }) 27 | }; 28 | exports.getWechatMsg = function (req, res) { 29 | return new Promise((resolve, reject) => { 30 | wechat(wechatConfig, (req, res, next) => { 31 | // 微信输入信息都在req.weixin上 32 | const msg = req.weixin; 33 | const eventObj = processMsgType(req, res, msg); 34 | resolve(eventObj) 35 | } 36 | )(req, res) 37 | }) 38 | }; 39 | function processMsgType(req, res, msg) { 40 | if ((msg.MsgType == 'event') && (msg.Event == 'subscribe')) { 41 | return {type: 'subscribe', req: req, res: res, msg: msg}; 42 | } 43 | else if ((msg.MsgType == 'event') && (msg.Event == 'unsubscribe')) { 44 | return {type: 'unsubscribe', req: req, res: res, msg: msg}; 45 | } 46 | else if(msg.MsgType == 'text'){ 47 | return {type: 'text', req: req, res: res, msg: msg}; 48 | } 49 | else{ 50 | return {type: 'other', req: req, res: res, msg: msg}; 51 | } 52 | } 53 | 54 | exports.getToken = function (code) { 55 | let reqUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token?'; 56 | let params = { 57 | appid: config.wechat.appId, 58 | secret: config.wechat.appSecret, 59 | code: code, 60 | grant_type: 'authorization_code' 61 | }; 62 | return new Promise((resolve, reject) => { 63 | wx.exchangeAccessToken(params.appid, params.secret, params.code, (err, result) => { 64 | if (err) { 65 | reject(err); 66 | } 67 | resolve(result); 68 | }) 69 | }) 70 | }; 71 | 72 | //根据openId找到userId 73 | exports.getUserInfo = function* (model, openId) { 74 | const data = yield model.findOne({ 75 | where: { 76 | openId: openId, 77 | } 78 | }); 79 | return data; 80 | }; 81 | 82 | //根据userId找到studentId 83 | exports.getStudentId = function* (model, userId) { 84 | const data = yield model.findOne({ 85 | where: { 86 | userId: userId, 87 | } 88 | }); 89 | return data.id; 90 | }; 91 | 92 | //根据userId找到teacherId 93 | exports.getTeacherId = function* (teacherModel, staffModel, userId) { 94 | const staff = yield staffModel.findOne({ 95 | where: { 96 | userId: userId, 97 | } 98 | }); 99 | const teacher = yield teacherModel.findOne({ 100 | where: { 101 | staffId: staff.id 102 | } 103 | }); 104 | return teacher.id; 105 | }; 106 | 107 | //feedbacks数组依据create_at的快速排序 108 | exports.quickSort = sort; 109 | function sort(array, start, end) { 110 | if (!array || start >= end) { 111 | return; 112 | } 113 | var i = start; 114 | var j = end; 115 | var tmp = array[i].createAt; 116 | var tmpObj = array[i]; 117 | while (i < j) { 118 | while (i < j && array[j].createAt >= tmp) { 119 | j--; 120 | } 121 | if (i < j) { 122 | array[i++] = array[j]; 123 | } 124 | while (i < j && array[i].createAt <= tmp) { 125 | i++; 126 | } 127 | if (i < j) { 128 | array[j--] = array[i]; 129 | } 130 | } 131 | array[i] = tmpObj; 132 | sort(array, start, i - 1); 133 | sort(array, i + 1, end); 134 | } 135 | 136 | //解绑事件 137 | exports.unbind = function* (model, openId){ 138 | try { 139 | yield model.destroy({ 140 | where: { 141 | openId: openId, 142 | } 143 | }); 144 | return true; 145 | }catch (err){ 146 | throw err; 147 | } 148 | }; 149 | 150 | 151 | //菜单选项设置 152 | let reqUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?'; 153 | let params = { 154 | appid: config.wechat.appId, 155 | redirect_url: 'http://xxxxxx.com/api/OAuth2', 156 | response_type: 'code', 157 | scope: 'snsapi_base', 158 | }; 159 | 160 | exports.defaultMenu = { 161 | url: '/api/wechat/defaultMenu', 162 | menuObj: { 163 | "button": [ 164 | { 165 | "name": config.wechat.menu[0].main, 166 | "sub_button": [ 167 | { 168 | "type": "view", 169 | "name": config.wechat.menu[0].subMenu[0].name, 170 | "url": `${reqUrl}appid=${params.appid}&redirect_uri=${params.redirect_url}&response_type=${params.response_type}&scope=${params.scope}&state=studentfeedbacks#wechat_redirect`, 171 | }, 172 | { 173 | "type": "view", 174 | "name": config.wechat.menu[0].subMenu[1].name, 175 | "url": `${reqUrl}appid=${params.appid}&redirect_uri=${params.redirect_url}&response_type=${params.response_type}&scope=${params.scope}&state=studenttimetable#wechat_redirect`, 176 | } 177 | ] 178 | }, 179 | { 180 | "name": config.wechat.menu[1].main, 181 | "sub_button": [ 182 | { 183 | "type": "view", 184 | "name": config.wechat.menu[1].subMenu[0].name, 185 | "url": `${reqUrl}appid=${params.appid}&redirect_uri=${params.redirect_url}&response_type=${params.response_type}&scope=${params.scope}&state=teachertimetable#wechat_redirect`, 186 | }, 187 | { 188 | "type": "view", 189 | "name": config.wechat.menu[1].subMenu[1].name, 190 | "url": `${reqUrl}appid=${params.appid}&redirect_uri=${params.redirect_url}&response_type=${params.response_type}&scope=${params.scope}&state=managestudent#wechat_redirect`, 191 | } 192 | ] 193 | } 194 | ] 195 | } 196 | }; -------------------------------------------------------------------------------- /wechat_service2/wechat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const wechatHelper = require('./common/wechat.helper'); 4 | const subjectHelper = require('./common/subject_helper'); 5 | const url = require('url'); 6 | const config = require('./../common/config'); 7 | const wx = require('wechat-toolkit'); 8 | const user = require('./../services/user'); 9 | const backendConstants = require('../common/constants'); 10 | const request = require('request'); 11 | const ONE_HOUR = 60 * 60 * 1000; 12 | 13 | module.exports = class Model extends BaseModel { 14 | // 构造函数 15 | constructor() { 16 | super(); 17 | this.orm = this.load(schemas.wechat); 18 | this.ormTask = this.load(schemas.studentTask); 19 | this.ormStudent = this.load(schemas.student); 20 | this.ormStaff = this.load(schemas.organizationStaff); 21 | this.ormTeacher = this.load(schemas.teacher); 22 | this.ormClassroom = this.load(schemas.classroom); 23 | this.vipCourse = this.load(schemas.vipCourse); 24 | this.groupCourse = this.load(schemas.groupCourse); 25 | this.ormContractTransfer = this.load(schemas.contractTransfer); 26 | 27 | this.vipCourse.belongsTo(this.ormTeacher, { foreignKey: 'teacherId' }) 28 | this.vipCourse.belongsTo(this.ormStudent, { foreignKey: 'studentId' }) 29 | this.vipCourse.belongsTo(this.ormClassroom, { foreignKey: 'classroomId' }) 30 | this.ormTeacher.belongsTo(this.ormStaff, { foreignKey: 'staffId', as: 'teacherStaff', constraints: false }) 31 | } 32 | 33 | // 服务器确认 34 | *wechatVerification(req, res) { 35 | var query = url.parse(req.url, true).query; 36 | var signature = query.signature; 37 | var echostr = query.echostr; 38 | var timestamp = query['timestamp']; 39 | var nonce = query.nonce; 40 | var oriArray = new Array(); 41 | oriArray[0] = nonce; 42 | oriArray[1] = timestamp; 43 | oriArray[2] = config.wechat.token; 44 | oriArray.sort(); 45 | var original = oriArray.join(''); 46 | console.log("Original str : " + original); 47 | console.log("Signature : " + signature); 48 | var scyptoString = wechatHelper.sha1(original); 49 | if (signature == scyptoString) { 50 | res.end(echostr); 51 | console.log("Confirm and send echo back"); 52 | } else { 53 | res.end("false"); 54 | console.log("Failed!"); 55 | } 56 | } 57 | 58 | // 默认菜单 59 | *defaultMenu() { 60 | const token = yield wechatHelper.getMenuToken(); 61 | console.log(token); 62 | wx.createMenu(token, wechatHelper.defaultMenu.menuObj, function (err, error_code, error_message) { 63 | if (err) { 64 | console.log(err); 65 | return; 66 | } 67 | console.log(wechatHelper.defaultMenu.menuObj); 68 | console.log(error_code); 69 | console.log(error_message) 70 | }) 71 | } 72 | 73 | // 设置模板消息行业信息 74 | *setIndustry() { 75 | const token = yield wechatHelper.getMenuToken(); 76 | const url = `https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=${token}`; 77 | const formData = { 78 | "industry_id1": "1", 79 | "industry_id2": "16" 80 | }; 81 | request.post({ 82 | url: url, 83 | formData: formData 84 | }, function optionalCallback(err, httpResponse, body) { 85 | if (err) { 86 | return console.error('upload failed:', err); 87 | } 88 | console.log('Upload successful! Server responded with:', body); 89 | }); 90 | } 91 | 92 | // 获取模板消息行业信息 93 | *getIndustryInfo() { 94 | const token = yield wechatHelper.getMenuToken(); 95 | const url = `https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=${token}`; 96 | request(url, function (error, response, body) { 97 | if (!error && response.statusCode == 200) { 98 | console.log(body) // Show the HTML for the Google homepage. 99 | } 100 | }); 101 | } 102 | 103 | // 获取模板信息 104 | *getTemplete() { 105 | const token = yield wechatHelper.getMenuToken(); 106 | const url = `https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=${token}`; 107 | request(url, function (error, response, body) { 108 | if (!error && response.statusCode == 200) { 109 | console.log(body) // Show the HTML for the Google homepage. 110 | } 111 | }); 112 | } 113 | 114 | // 删除模板 115 | *deleteTemplete(params) { 116 | const token = yield wechatHelper.getMenuToken(); 117 | const url = `https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token=${token}`; 118 | const formData = { 119 | "template_id": params.templateId 120 | }; 121 | request.post({ 122 | url: url, 123 | formData: formData 124 | }, function optionalCallback(err, httpResponse, body) { 125 | if (err) { 126 | return console.error('upload failed:', err); 127 | } 128 | console.log('Upload successful! Server responded with:', body); 129 | }); 130 | } 131 | 132 | // 推送模板信息 133 | *autoPush(taskId, studentId, feedback) { 134 | const student = yield this.ormStudent.findById(studentId); 135 | if (!student) throw new Exception(2303); 136 | const bindingInfo = yield this.orm.findOne({ 137 | where: { 138 | userId: (student.userId).toString(), 139 | } 140 | }); 141 | if (!bindingInfo) return false; 142 | const task = yield this.ormTask.findById(taskId); 143 | const time = new Date(feedback.createAt); 144 | 145 | const token = yield wechatHelper.getMenuToken(); 146 | const openId = bindingInfo.openId; 147 | var data = { 148 | first: { 149 | value: "您好,您有反馈信息!", 150 | color: "#888888" 151 | }, 152 | "keyword1": { 153 | value: task.name, 154 | color: "#0A0A0A" 155 | }, 156 | "keyword2": { 157 | value: feedback.content, 158 | color: "#0A0A0A" 159 | }, 160 | "remark": { 161 | value: '反馈教师: ' 162 | + feedback.userName + ' ' 163 | + '\n反馈时间: ' 164 | + time.getFullYear() + '.' 165 | + time.getMonth() + '.' 166 | + time.getDate() + ' ' 167 | + time.getHours() + ':' 168 | + time.getMinutes(), 169 | color: "#888888" 170 | } 171 | }; 172 | var obj = { 173 | access_token: token, 174 | fan_open_id: openId, 175 | template_id: 'XXXXXXXXXXXXX', 176 | top_color: "#000000", 177 | data: data 178 | }; 179 | wx.sendTemplateMessage(obj, function (err, code, message) { 180 | if (err) { 181 | console.log(err); 182 | return; 183 | } 184 | console.log(code); 185 | console.log(message); 186 | }); 187 | } 188 | 189 | // 用户交互事件处理 190 | *postProcessRecv(req, res, body) { 191 | const eventObj = yield wechatHelper.getWechatMsg(req, res, body); 192 | const type = eventObj.type, 193 | wReq = eventObj.req, 194 | wRes = eventObj.res, 195 | msg = eventObj.msg; 196 | switch (type) { 197 | case 'subscribe': 198 | wRes.reply('感谢您的关注!'); 199 | break; 200 | case 'unsubscribe': 201 | const flag = yield wechatHelper.unbind(this.orm, msg.FromUserName); 202 | if (flag) console.log(msg.FromUserName + ' 完成解绑'); 203 | break; 204 | case 'text': 205 | if (msg.Content !== '解绑' && msg.Content !== 'jiebang' && msg.Content !== 'jb') { 206 | wRes.reply('无法识别的关键字,仅支持jb、jiebang、解绑,三个关键字用于账号解绑') 207 | } else { 208 | const flag = yield wechatHelper.unbind(this.orm, msg.FromUserName); 209 | if (flag) console.log(msg.FromUserName + ' 完成解绑'); 210 | wRes.reply('解绑成功') 211 | } 212 | break; 213 | case 'other': 214 | wRes.reply('无法识别的关键字'); 215 | break; 216 | default: 217 | wRes.reply('无法识别'); 218 | break; 219 | } 220 | } 221 | }; 222 | --------------------------------------------------------------------------------