├── README.md ├── .gitignore ├── .editorconfig ├── ts ├── index.ts ├── util2.ts ├── wx-rest.ts └── rest-mysql.ts ├── tsconfig.json ├── tsd.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | a rest server for mysql -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | js 3 | node_modules 4 | typings -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /ts/index.ts: -------------------------------------------------------------------------------- 1 | export {WxRest} from './wx-rest'; 2 | export import WxUtil = require('./util2'); 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "noImplicitAny": false, 6 | "removeComments": false, 7 | "rootDir":"ts", 8 | "outDir":"js", 9 | "declaration":true, 10 | "noLib":false, 11 | "sourceMap": false 12 | }, 13 | "exclude": [ 14 | "js", 15 | "node_modules" 16 | ] 17 | } -------------------------------------------------------------------------------- /tsd.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v4", 3 | "repo": "borisyankov/DefinitelyTyped", 4 | "ref": "master", 5 | "path": "typings", 6 | "bundle": "typings/tsd.d.ts", 7 | "installed": { 8 | "express/express.d.ts": { 9 | "commit": "b4646ff708f39d2896bcb3e3974667f92e26c5a4" 10 | }, 11 | "serve-static/serve-static.d.ts": { 12 | "commit": "b4646ff708f39d2896bcb3e3974667f92e26c5a4" 13 | }, 14 | "node/node.d.ts": { 15 | "commit": "e05e1f6f83c6ac84e9d7ff8284e8089d53a788a6" 16 | }, 17 | "mime/mime.d.ts": { 18 | "commit": "b4646ff708f39d2896bcb3e3974667f92e26c5a4" 19 | }, 20 | "underscore/underscore.d.ts": { 21 | "commit": "b4646ff708f39d2896bcb3e3974667f92e26c5a4" 22 | }, 23 | "mysql/mysql.d.ts": { 24 | "commit": "e05e1f6f83c6ac84e9d7ff8284e8089d53a788a6" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-rest", 3 | "description": "a simple rest server for wx", 4 | "version": "1.0.27", 5 | "typings": "js/index", 6 | "author": "dongfuye ye = 4.2.0" 45 | }, 46 | "files": [ 47 | "Readme.md", 48 | "index.js", 49 | "js/", 50 | "hlp/", 51 | "ts/", 52 | "tsconfig.json", 53 | "tsd.json", 54 | "test/" 55 | ], 56 | "main": "js/index" 57 | } -------------------------------------------------------------------------------- /ts/util2.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as fs from 'fs'; 3 | var request = require('request'); 4 | import * as express from 'express'; 5 | var parser = require('xml2json'); 6 | var md5 = require('md5'); 7 | import * as _ from 'underscore'; 8 | 9 | export async function get(url) { 10 | return new Promise(function(resolve, reject) { 11 | console.log(`getting ${url}`); 12 | request(url, function(err, response, body) { 13 | console.log(`get ${url} body: ${body}`); 14 | err && reject(err) || resolve(body); 15 | }); 16 | }); 17 | } 18 | export async function getRaw(url) { 19 | return new Promise(function(resolve, reject) { 20 | let data = ''; 21 | console.log(`getting raw ${url}`); 22 | request(url).on('response', (response)=>{ 23 | response.setEncoding('binary'); 24 | response.on('data', chunk=>{ 25 | data += chunk; 26 | }) 27 | .on('end',()=> { 28 | console.log(`get raw ${url} body length: ${data.length}`); 29 | resolve(data); 30 | }); 31 | }); 32 | }); 33 | } 34 | export async function jget(url) { 35 | return get(url).then(function(res:any) { return Promise.resolve(JSON.parse(res)); }) 36 | } 37 | export async function jpost(url,data) { 38 | return post(url,JSON.stringify(data)).then(function(res:any) { return Promise.resolve(JSON.parse(res)); }) 39 | } 40 | export async function post(url, data) { 41 | return new Promise(function(resolve, reject) { 42 | console.log(`posting raw ${url} data length: ${data.length}`); 43 | request({ 44 | url: url, 45 | method: 'POST', 46 | body: data, 47 | }, (err, response, body) => { 48 | console.log(`post ${url} body ${JSON.stringify(data)} err ${err} body ${body}`); 49 | err && reject(err) || resolve(body); 50 | }); 51 | }); 52 | } 53 | export async function eachResult(result, cb) { 54 | if (_.isArray(result)) { 55 | for (let i in result) { 56 | await cb(result[i], i); 57 | } 58 | } else { 59 | await cb(result, 0); 60 | } 61 | } 62 | 63 | export async function postForm(url, formData) { 64 | return new Promise((resolve,reject) => { 65 | console.log(`posting ${url} form: ${JSON.stringify(formData)}`); 66 | request.post({url:url,formData:formData}, (err,response,body)=> { 67 | console.log(`post ${url} body ${JSON.stringify(formData)} err ${err} body ${body}`); 68 | err && reject(err) || resolve(body); 69 | }) 70 | }); 71 | } 72 | 73 | export function base64(img:string):boolean { return img && img.startsWith('data:'); } 74 | export function base64Buffer(img:string): Buffer { 75 | let pat = 'base64,' 76 | let pos = img.indexOf(pat); 77 | return new Buffer(img.slice(pos+pat.length), 'base64'); 78 | } 79 | export function base64Ext(img:string) { 80 | let regex = /^data:.+\/(.+);base64,(.*)$/; 81 | let matches = img.slice(0,30).match(regex); 82 | return matches[1]; 83 | } 84 | export function base64Save(img: string, filename:string) { 85 | console.log('begin saving base64'); 86 | let buffer = base64Buffer(img); 87 | console.log('writing to file ', filename, ' bytes: ', buffer.length); 88 | fs.writeFileSync(filename, buffer); 89 | } 90 | 91 | export function createNonceStr(length) { 92 | const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 93 | let str = ""; 94 | for (let i = 0; i < length; i++) { 95 | str += chars.charAt(Math.floor(Math.random()*chars.length)); 96 | } 97 | return str; 98 | } 99 | export function createNonceLower(length) { 100 | const chars = "abcdefghijklmnopqrstuvwxyz"; 101 | let str = ""; 102 | for (let i = 0; i < length; i++) { 103 | str += chars.charAt(Math.floor(Math.random()*chars.length)); 104 | } 105 | return str; 106 | } 107 | export function getTimeStamp(){ 108 | return Math.floor(new Date().getTime()/1000); 109 | } 110 | 111 | export function sha1(s1){ 112 | let cry = require('crypto'); 113 | let shasum= cry.createHash('sha1'); 114 | shasum.update(s1); 115 | return shasum.digest('hex'); 116 | } 117 | 118 | export function getIp(req) { 119 | let ip:string = req.headers['x-forwarded-for'] || req.connection.remoteAddress; 120 | if (ip.indexOf(':')>=0) { 121 | ip = ip.split(':').slice(-1)[0]; 122 | } 123 | if (ip.endsWith('127.0.0.1')) { 124 | ip = '118.244.254.29'; 125 | } 126 | return ip; 127 | } 128 | 129 | export function toXml(obj) { 130 | let xml = ''; 131 | for (let i in obj) { 132 | let v = obj[i]; 133 | if (typeof i == 'number') { 134 | xml += `<${i}>${v}`; 135 | } else { 136 | xml += `<${i}>`; 137 | } 138 | } 139 | xml += ''; 140 | console.log('xml is: ', xml); 141 | return xml; 142 | } 143 | export function fromXml(xml) { return JSON.parse(parser.toJson(xml)); } 144 | 145 | export function ascSign(obj, key:string) { 146 | let keys = _.keys(obj).sort(); 147 | let s = ''; 148 | for(let k of keys) { 149 | let v = obj[k]; 150 | if (k != 'sign' && v && typeof v != 'Array') { 151 | s += `${k}=${v}&`; 152 | } 153 | } 154 | s += `key=${key}`; 155 | console.log('str to sign:', s); 156 | s = md5(s); 157 | s = s.toUpperCase(); 158 | return s; 159 | } 160 | export function mysql_real_escape_string (str) { 161 | if (typeof str != 'string') return str; 162 | return str.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) { 163 | switch (char) { 164 | case "\0": 165 | return "\\0"; 166 | case "\x08": 167 | return "\\b"; 168 | case "\x09": 169 | return "\\t"; 170 | case "\x1a": 171 | return "\\z"; 172 | case "\n": 173 | return "\\n"; 174 | case "\r": 175 | return "\\r"; 176 | case "\"": 177 | case "'": 178 | case "\\": 179 | case "%": 180 | return "\\"+char; // prepends a backslash to backslash, percent, 181 | // and double/single quotes 182 | } 183 | }); 184 | } -------------------------------------------------------------------------------- /ts/wx-rest.ts: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | import {Rest} from './rest-mysql'; 5 | import {Request,Response} from 'express'; 6 | import * as fs from 'fs'; 7 | var request = require('request'); 8 | import * as express from 'express'; 9 | import * as util from './util2'; 10 | import * as qs from 'querystring'; 11 | 12 | export class WxRest extends Rest { 13 | constructor(public config: any) { 14 | super(config); 15 | let that = this; 16 | this.app.get('/api/wx/redirect', function(req,res) { 17 | let url = req.query.url; 18 | console.log('redirect to: ', url); 19 | res.redirect(url); 20 | }) 21 | .get('/api/wx/oauth', async function(req, res) { 22 | let query = req.query; 23 | console.log('query1 is1:', query); 24 | let code = req.query.code; 25 | let state = req.query.state; //state=epeijing.cn/app/ /tab/try 26 | state = state.replace(' ', '#'); 27 | let conf = config; 28 | if (query.app) { 29 | conf = await that.queryOne(`select * from n_wx_apps where app='${query.app}'`); 30 | } 31 | let tr:any = await util.jget(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${conf.app_id}&secret=${conf.app_secret}&grant_type=authorization_code&code=${code}`); 32 | console.log(`app: ${query.app} app_id: ${conf.app_id} oauth token. code: ${code} state: ${state} openid: ${tr.openid}`); 33 | let userinfo = await util.jget(`https://api.weixin.qq.com/sns/userinfo?access_token=${tr.access_token}&openid=${tr.openid}&lang=zh_CN`); 34 | let info = encodeURI(JSON.stringify(userinfo)); 35 | delete query.code; 36 | delete query.state; 37 | let result = query.result || 'userinfo'; 38 | let url = `${state}?${result}=${info}&${qs.stringify(query)}`; 39 | console.log('url is', url); 40 | res.redirect(url); 41 | return Promise.resolve(true); 42 | }) 43 | .get('/api/wx/weixin_token', async function(req, res) { 44 | let token = await that.getWeixinToken(); 45 | res.send({token:token}); 46 | }) 47 | .get('/api/wx/weixin_image', async function (req:Request,res:Response) { 48 | let id = req.query.id; 49 | let token = await that.getWeixinToken(); 50 | let url = `http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${token}&media_id=${id}`; 51 | console.log(`downloading weixin: ${url}`); 52 | let body:any = await util.getRaw(url); 53 | res.contentType('image/jpeg').end(body,'binary'); 54 | }) 55 | .get('/api/wx/weixin_ticket', async function(req, res) { 56 | let ticket = await that.getWeixinTicket(); 57 | res.send({ticket:ticket}); 58 | }) 59 | .get('/api/wx/get_sign', async function(req, res) { 60 | let sign = await that.getSign(req.query.url); 61 | console.log('sign is: ', sign); 62 | res.send(sign); 63 | }) 64 | } 65 | async cacheSet (key, value, expire) { 66 | await this.query(`delete from n_cache where key1='${key}' and expire < unix_timestamp()`); 67 | await this.query(`insert into n_cache(key1, value1, expire, create_time) values('${key}', '${value}', unix_timestamp()+${expire}, now())`); 68 | } 69 | async cacheGet (key) { 70 | let sql = `select value1 from n_cache where key1='${key}' and expire > unix_timestamp() limit 1`; 71 | let rs = await this.queryOne(sql); 72 | let res = null; 73 | if(rs && rs.hasOwnProperty('value1') && rs.value1) 74 | res = rs.value1; 75 | console.log('cacheGet:', key, res); 76 | return res; 77 | } 78 | async getWeixinToken () { 79 | if (!process.env.PRODUCT) { 80 | let r:any = await util.jget(`http://${this.config.product_host}/api/wx/weixin_token`); 81 | console.log(`weixin token get: `,r); 82 | return r.token; 83 | } 84 | let token = await this.cacheGet(`weixin-token`); 85 | if (token) { 86 | return token; 87 | } 88 | let app_id = this.config.app_id; 89 | let app_secret = this.config.app_secret; 90 | let url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${app_id}&secret=${app_secret}`; 91 | let get_res:any = await util.jget(url); 92 | console.log(`weixin get token: app_id: ${app_id} result: ${get_res}`); 93 | let weixin_token = get_res.access_token; 94 | await this.cacheSet( `weixin-token`, weixin_token, 7000); 95 | return Promise.resolve(weixin_token); 96 | } 97 | async getWeixinTicket () { 98 | if (!process.env.PRODUCT) { 99 | let r:any = await util.jget(`http://${this.config.product_host}/api/wx/weixin_ticket`); 100 | console.log(`weixin token get: ${r}`); 101 | return r.ticket; 102 | } 103 | let ticket = await this.cacheGet( "weixin-ticket"); 104 | console.log('cacheget ticket: ',ticket); 105 | if (ticket) { 106 | return ticket; 107 | } 108 | let token = await this.getWeixinToken(); 109 | let url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`; 110 | let get_res:any = await util.jget(url); 111 | ticket = get_res.ticket; 112 | console.log('new tickect ',ticket) 113 | await this.cacheSet( "weixin-ticket", ticket, 7000); 114 | return ticket; 115 | } 116 | async sendTemplateMsg(msg) { 117 | let token = await this.getWeixinToken(); 118 | let purl = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token='+token; 119 | let phr:any = await util.jpost(purl, msg); 120 | console.log(`send weixin msg: ${purl} `, msg, phr); 121 | return phr.errcode == 0; 122 | } 123 | async getSign(url){ 124 | const jsapiTicket = await this.getWeixinTicket(); 125 | const timestamp = util.getTimeStamp(); 126 | const nonceStr = util.createNonceStr(16); 127 | // 这里参数的顺序要按照 key 值 ASCII 码升序排序 128 | let str = `jsapi_ticket=${jsapiTicket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`; 129 | let signature = util.sha1(str); 130 | const signPackage = { 131 | appId: this.config.app_id, 132 | nonceStr: nonceStr, 133 | timestamp: timestamp, 134 | url: url, 135 | signature: signature, 136 | rawString: str 137 | }; 138 | return Promise.resolve(signPackage); 139 | } 140 | 141 | async unified_order(sn, wx_id, price, ip):Promise { 142 | let input = { 143 | appid: this.config.app_id, 144 | mch_id: this.config.wx_pay.mchid, 145 | nonce_str: util.createNonceStr(8), 146 | spbill_create_ip: ip, 147 | notify_url: "http://paysdk.weixin.qq.com/example/notify.php", 148 | trade_type:'JSAPI', 149 | openid: wx_id, 150 | body: "sn-"+sn, 151 | out_trade_no: sn, 152 | total_fee: price, 153 | sign:'', 154 | } 155 | input.sign = util.ascSign(input, this.config.wx_pay.key); 156 | let xml = util.toXml(input); 157 | let pr = await util.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xml); 158 | console.log(pr); 159 | let jr = util.fromXml(pr); 160 | console.log(jr); 161 | if (!jr.xml.prepay_id) { 162 | return Promise.resolve({success:0,message:jr.xml.err_code_des}); 163 | } 164 | let apiPay = { 165 | appId: this.config.app_id, 166 | timeStamp: `${util.getTimeStamp()}`, 167 | nonceStr: util.createNonceStr(8), 168 | package: 'prepay_id='+jr.xml.prepay_id, 169 | signType: 'MD5', 170 | } 171 | apiPay['paySign'] = util.ascSign(apiPay, this.config.wx_pay.key); 172 | // let r_sn = sn.endsWith('a') ? sn.slice(0, -1) : sn; 173 | // let table = req.query.type == 'corp' ? 'n_order_shop' : 'n_order'; 174 | // let sql = `update ${table} set pay_status='paying', update_time=now(),pay_query=unix_timestamp( ) where sn='${r_sn}'`; 175 | // rest.query(sql); 176 | return Promise.resolve(apiPay) 177 | } 178 | 179 | async order_query_raw(sn) { 180 | let input = { 181 | out_trade_no: sn, 182 | appid: this.config.app_id, 183 | mch_id: this.config.wx_pay.mchid, 184 | nonce_str: util.createNonceStr(8), 185 | } 186 | input['sign'] = util.ascSign(input, this.config.wx_pay.key); 187 | let xml = util.toXml(input); 188 | let pr = await util.post('https://api.mch.weixin.qq.com/pay/orderquery', xml); 189 | console.log(pr); 190 | let jr = util.fromXml(pr); 191 | return Promise.resolve(jr.xml.trade_state && jr.xml.trade_state=='SUCCESS'); 192 | } 193 | 194 | } 195 | 196 | -------------------------------------------------------------------------------- /ts/rest-mysql.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import express = require('express'); 4 | import {Request,Response} from 'express'; 5 | import mysql = require("mysql"); 6 | import _ = require("underscore"); 7 | import * as fs from 'fs'; 8 | var parser:any = require('body-parser'); 9 | require("date-format-lite"); 10 | require('debug-trace')(); 11 | var proxy = require('express-http-proxy'); 12 | import * as util2 from './util2'; 13 | 14 | console['format'] = function(c) { return c.date + ": " + require('path').basename(c.filename) + ":" + c.getLineNumber(); }; 15 | 16 | function printable(body:any) { 17 | if (!body) return null; 18 | let s = JSON.stringify(body); 19 | if (s.length > 10*1024) return ` ... ${s.length} bytes`; 20 | return s; 21 | } 22 | 23 | export function create(option?:any):Rest { 24 | return new Rest(option); 25 | } 26 | 27 | export class Rest { 28 | app = express(); 29 | conn:mysql.IConnection; 30 | custom:any = {}; 31 | options = { 32 | mysql: process.env.MYSQL || 'mysql://root:@localhost/test', 33 | timeFormat: 'YYYY-MM-DD hh:mm:ss', 34 | port: 80, 35 | api: '/api/', 36 | custom: '', //custom processor for table request 37 | raw: '', // raw processor for request 38 | www: '', // web root for static 39 | call: '', // handle file/method request 40 | proxy: { 41 | //'/call_proxy': 'www.example.com' 42 | }, 43 | bodyOption: {limit: '50mb'}, 44 | verbose: true, 45 | }; 46 | 47 | constructor(option?:any) { 48 | if (option.dir) { 49 | this.options.www = option.dir + '/../../www'; 50 | this.options.custom = option.dir + '/custom'; 51 | this.options.raw = option.dir + '/raw'; 52 | this.options.call = option.dir + '/call'; 53 | } 54 | _.extendOwn(this.options, option); 55 | Date['masks']['default'] = this.options.timeFormat; 56 | console.log('connecting to mysql: ', this.options.mysql); 57 | this.conn = mysql.createConnection(this.options.mysql); 58 | setInterval(()=>{ 59 | this.conn.query('select 1', (err,rows)=>{ 60 | if (err) throw err; 61 | console.log('heart beat to mysql'); 62 | }) 63 | }, 120*1000); 64 | let table = this.options.api + 'table'; 65 | console.log('table is at: ', table); 66 | this.app 67 | .use(require('morgan')('dev')) 68 | .use(require('connect-timeout')('20s')) 69 | for (let k in this.options.proxy) { 70 | let v = this.options.proxy[k]; 71 | console.log('proxying: ', k, ' to: ', v); 72 | this.app.use(k, proxy(v, { 73 | forwardPath: (req,res)=>{ console.log('proxying: ', k+req.url, ' => ', v); return k+req.url;} 74 | })); 75 | } 76 | //.use(parser.raw({type: '*/*'})) 77 | this.app.use(parser['json'](this.options.bodyOption)) 78 | .use(parser.urlencoded({ extended: true })) 79 | .get(this.options.api + 'ping', (req:express.Request, res:express.Response) => { 80 | res.header("Content-Type", "application/json"); 81 | res.send('true'); 82 | }) 83 | .all(table + '/:table/:id', this.handle) 84 | .all(table + '/:table', this.handle); 85 | if (this.options.call) { 86 | console.log(`call is at ${this.options.api}call for ${this.options.call}`); 87 | this.app.all(this.options.api+'call/:file/:method', (req: express.Request, res: express.Response) => { 88 | addJbody(req); 89 | console.log(`${req.method} ${req.url} ${JSON.stringify(req.query)} body: ` + printable(req['jbody'])); 90 | var module1 = require(`${this.options.call}/${req.params.file}.js`); 91 | this.outputPromise(res, module1[req.params.method](req, res, this)); 92 | }); 93 | } 94 | if (this.options.www) { 95 | console.log('static is at:', this.options.www); 96 | this.app.use(express.static(this.options.www)); 97 | } 98 | if (this.options.port) { 99 | console.log('listening at port:', this.options.port); 100 | this.app.listen(this.options.port); 101 | } 102 | if (this.options.custom) { 103 | console.log('custom is file: ', this.options.custom); 104 | this.custom = require(this.options.custom); 105 | } 106 | if (this.options.raw) { 107 | console.log('raw is: ', this.options.raw); 108 | require(this.options.raw)(this); 109 | } 110 | this.app.use((err:Error, req: express.Request, res: express.Response, next)=> { 111 | outputError(err); 112 | console.log(`timedout: ${req.method} ${req.url} ${JSON.stringify(req.query)} body: ` + printable(req['jbody'])); 113 | res.status(500).send({message: 'request timeout'}); 114 | }); 115 | } 116 | 117 | query = async (sql:string, args?:any):Promise => { 118 | if (!_.isArray(args)) args = _.isUndefined(args) ? [] : [args]; 119 | console.log('querying: ', sql, ' ', args.length?args:''); 120 | return new Promise((resolve, reject) => { 121 | this.conn.query(sql, args, function (err, rows) { 122 | if (err) { 123 | if (err.code == 'PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR' || err.fatal) 124 | throw err; 125 | reject(err); 126 | } else { 127 | transformRows(rows); 128 | resolve(rows); 129 | } 130 | }) 131 | }); 132 | }; 133 | queryOne = async (sql:string, args?:any):Promise => { 134 | var rows = await this.query(sql,args); 135 | return rows[0]; 136 | }; 137 | queryColumn = async (sql:string, args?:any):Promise => { 138 | var rows:any[] = await this.query(sql, args); 139 | return rows.map(getOnlyField); 140 | }; 141 | queryValue = async (sql:string, args?:any):Promise => { 142 | let col = await this.queryColumn(sql,args); 143 | return col[0]; 144 | }; 145 | outputResult = (res:express.Response, cont:any, status?:number):void => { 146 | status = status || 200; 147 | if (cont.stack) outputError(cont); 148 | else if (status / 100 != 2) console.log('\n', cont); 149 | 150 | if (typeof cont != 'string') { 151 | res.header("Content-Type", "application/json"); 152 | cont = JSON.stringify(cont) + '\n'; 153 | } 154 | if (this.options.verbose || status != 200) { 155 | let req:express.Request = res['req']; 156 | console.log(`${req.method} ${req.url} ${JSON.stringify(req.query)} body: ` + printable(req['jbody'])); 157 | console.log(' status: ', status, ' result: ', cont); 158 | } 159 | res.status(status).send(cont); 160 | }; 161 | outputPromise = (res:express.Response, p:Promise):void => { 162 | p.then((r) => { 163 | this.outputResult(res, r, 200); 164 | }, (err)=> { 165 | if (err instanceof Array) { 166 | this.outputResult(res, err[0], err[1]); 167 | } else { 168 | this.outputResult(res, err, 500); 169 | } 170 | }); 171 | }; 172 | handle = (req:express.Request, res:express.Response):void => { 173 | try { 174 | addJbody(req); 175 | console.log(`${req.method} ${req.url} ${JSON.stringify(req.query)} body: ` + printable(req['jbody'])); 176 | var table = req.params.table; 177 | if ('op_' in req.query && table in this.custom && 'op_' in this.custom[table]) { 178 | return this.outputPromise(res, this.custom[table].op_(req, res, this)); 179 | } 180 | if (table in this.custom && req.method in this.custom[table] && !req.query.op_) { 181 | return this.outputPromise(res, this.custom[table][req.method](req, res, this)); 182 | } 183 | this.outputPromise(res, this.handleDefault(req)); 184 | } catch (e) { 185 | this.outputResult(res, e, 500); 186 | } 187 | }; 188 | handleDefault = async (req:express.Request):Promise => { 189 | var table = req.params.table; 190 | var id = req.params.id || null; 191 | var query = req.query; 192 | let checkMsg = checkInput([id, query.order_, query.start_, query.limit_, query.op_,table]); 193 | if (checkMsg) { 194 | return Promise.reject([checkMsg, 400]); 195 | } 196 | let fs:any[] = await this.query('desc ' + table); 197 | var tableMeta = {}; 198 | let pris = []; 199 | for (let k = 0; k < fs.length; k++) { 200 | let f = fs[k]; 201 | tableMeta[f.Field] = f.Type; 202 | if (f.Key == 'PRI') { 203 | pris.push(f.Field); 204 | } 205 | } 206 | let pri = pris.length == 1 ? pris[0] : ''; 207 | if (pri) { 208 | tableMeta['id_'] = pri; 209 | } 210 | let where = query.where_ ? {sql:query.where_, args:[]} : buildCond(id, query, tableMeta); 211 | if (req.method == 'GET') { 212 | let order = query.order_ ? " order by " + query.order_ : ""; 213 | let sql = `select * from ${table} where ${where.sql} ${order} limit ${query.start_||0}, ${query.limit_||100}`; 214 | let args = [].concat(where.args); 215 | if (query.op_ == 'count') { 216 | sql = `select count(1) a from ${table} where ${where.sql}`; 217 | } 218 | let rows:any[] = await this.query(sql, args); 219 | if (id && rows.length == 0) { 220 | return Promise.reject(['', 404]); 221 | } 222 | let r:any = rows; 223 | if (query.op_ == 'count') { 224 | r = {count: rows[0]['a']}; 225 | } else if (id) { 226 | r = rows[0]; 227 | } 228 | return Promise.resolve(r); 229 | } else if (req.method == 'POST') { 230 | let jbody = req['jbody']; 231 | // id is null then init create_time , otherwise not 232 | if (!id){ 233 | jbody.create_time = jbody.create_time || new Date()['format'](); 234 | } 235 | jbody.update_time = new Date()['format'](); 236 | let qr = buildUpdate(table, id, jbody, tableMeta, !!req.query.force); 237 | let result:any = await this.query(qr.sql, qr.args); 238 | let nid = id || result.insertId; 239 | if (!nid) { 240 | return Promise.resolve(jbody); 241 | } 242 | let rows = await this.query(`select * from ${table} where ${pri}='${nid}'`) 243 | for (let k in rows[0]) { 244 | if (rows[0].hasOwnProperty(k)) { 245 | jbody[k] = rows[0][k]; 246 | } 247 | } 248 | return Promise.resolve(jbody); 249 | } else if (req.method == 'DELETE') { 250 | if (!id && !where.sql) { 251 | return Promise.reject(['delete should specify condition', 400]); 252 | } 253 | let limit = query.limit_ || 1; 254 | let sql = `delete from ${table} where ${where.sql} limit ${limit}`; 255 | let result:any = await this.query(sql, where.args); 256 | if (result.affectedRows == 0) { 257 | return Promise.reject(['', 404]); 258 | } 259 | return Promise.resolve(result); 260 | } 261 | }; 262 | genAngular = async (filename: string) => { 263 | function underscore2Camel(uname) { 264 | var cname = ''; 265 | var upper = true; 266 | for (let c of uname) { 267 | if (c == '_') { 268 | upper = true; 269 | } else { 270 | cname += upper ? c.toUpperCase() : c; 271 | upper = false; 272 | } 273 | } 274 | console.log('cname is: ', cname); 275 | return cname; 276 | } 277 | var cs = await this.queryColumn(`show tables`); 278 | var ngCont = "angular.module('restService', ['ngResource'])\n"; 279 | for (let i=0; i= 0 || type.search('date') >= 0 ? "'" + qv + "'" : qv; 304 | } 305 | 306 | function buildCond(id, query, table) { 307 | if (id) { 308 | return {sql: ` ${table.id_}=?`, args: [id]}; 309 | } 310 | var cond = ''; 311 | let args = []; 312 | for (var f in table) { 313 | if (table.hasOwnProperty(f) && query.hasOwnProperty(f)) { 314 | cond += ` and ${f}=?`; 315 | args.push(query[f]); 316 | } 317 | } 318 | return {sql: cond && cond.slice(4) || ' 1=1 ', args: args}; 319 | } 320 | 321 | function buildUpdate(tname, id, jbody, table, force) { 322 | let fs = [], vs = [], sets = [], args=[]; 323 | if (id) { //把id加入update 324 | jbody[table.id_] = id; 325 | } 326 | for (let k in table) { 327 | if (!table.hasOwnProperty(k) || k == 'id_') continue; 328 | if (jbody.hasOwnProperty(k) || force) { 329 | if ((table[k]).search('date') >= 0) { 330 | jbody[k] = new Date(jbody[k])['format'](); 331 | } 332 | fs.push(k); 333 | vs.push('?'); 334 | sets.push(`${k}=?`); 335 | args.push(jbody[k]); 336 | } 337 | } 338 | return {sql:`insert into ${tname}(${fs.join(',')}) values(${vs.join(',')}) on duplicate key update ${sets.join(',')}`,args:[].concat(args,args)}; 339 | } 340 | 341 | function getOnlyField(obj) { 342 | return _.values(obj)[0]; 343 | } 344 | 345 | function transformRows(rows) { 346 | if (rows instanceof Array) { 347 | console.log('total rows: ', rows.length); 348 | for (let i in rows) { 349 | for (let f in rows[i]) { 350 | if (rows[i][f] instanceof Date) { 351 | rows[i][f] = new Date(rows[i][f])['format'](); 352 | } 353 | } 354 | } 355 | } 356 | } 357 | 358 | function addJbody(req) { 359 | if (req.body) { 360 | req['jbody'] = req.body instanceof Buffer ? JSON.parse(req.body) : req.body; 361 | } 362 | } 363 | 364 | function outputError(err){ 365 | console.error(err.stack.split('\n').slice(0,5).join('\n')); 366 | let reg = /\/(.*?):(.*?):(\d+)/; 367 | let matches = err.stack.match(reg); 368 | if (matches) { 369 | let file = '/'+matches[1]; 370 | let line = parseInt(matches[2]); 371 | let column = parseInt(matches[3]); 372 | let lns = fs.readFileSync(file).toString('utf8').split('\n', 1000000); 373 | for(let l=line-4; l