فهرست منبع

Initial commit

Allen 8 سال پیش
کامیت
129f016521
21فایلهای تغییر یافته به همراه3368 افزوده شده و 0 حذف شده
  1. 11 0
      Dockerfile
  2. 5 0
      README.md
  3. 46 0
      app.js
  4. 49 0
      common/mysqlConnection.js
  5. 34 0
      conf/database.js
  6. 11 0
      docker-compose.yml
  7. 15 0
      package.json
  8. 108 0
      public/css/index.css
  9. 127 0
      public/css/loader.css
  10. 14 0
      public/index.html
  11. 78 0
      public/scr/cookies.js
  12. 1510 0
      public/scr/index.js
  13. 23 0
      routes/captcha.js
  14. 353 0
      routes/databaseList.js
  15. 24 0
      routes/index.js
  16. 95 0
      routes/signin.js
  17. 16 0
      routes/signout.js
  18. 122 0
      routes/signup.js
  19. 323 0
      routes/userList.js
  20. 8 0
      run.sh
  21. 396 0
      yarn.lock

+ 11 - 0
Dockerfile

@@ -0,0 +1,11 @@
+FROM node:8
+
+WORKDIR /usr/src/app
+
+COPY . .
+
+RUN npm install --registry=https://registry.npm.taobao.org
+
+EXPOSE 80
+
+CMD ["npm", "start"]

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+## 如果不明白 `public/scr/index.js` 中 `mdui` 的作用,请参阅 `https://www.mdui.org/` 的开发文档。
+
+## 部署请直接直接运行该目录下的 run.sh
+
+Happy coding :)

+ 46 - 0
app.js

@@ -0,0 +1,46 @@
+const express = require('express');
+const bodyParser = require('body-parser');
+const session = require('express-session');
+const path = require('path');
+const app = express();
+const captcha = require('./routes/captcha');
+const signup = require('./routes/signup');
+const signin = require('./routes/signin');
+const signout = require('./routes/signout');
+const index = require('./routes/index');
+const userList = require('./routes/userList');
+const databaseList = require('./routes/databaseList');
+
+//解决跨域问题
+app.all('*', (req, res, next) => {
+    res.header("Access-Control-Allow-Origin", req.headers.origin); //设置来源
+    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
+    res.header("Access-Control-Max-Age", "604800000");
+    res.header("Access-Control-Allow-Credentials", true);
+    res.header("X-Powered-By", ' 3.2.1');
+    // res.header("Content-Type", "application/json;charset=utf-8");
+    next();
+});
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+//设置并使用session中间件
+app.use(session({
+    secret: 'Nobody will know this secret shit, else me and the God',
+    cookie: {
+        maxAge: 1000 * 60 * 60 * 24 * 7
+    },
+    saveUninitialized: true,
+    name: 'CSA_Uid'
+}));
+
+app.use(express.static(path.join(__dirname, 'public')));
+app.use('/v1/captcha', captcha);
+app.use('/v1/signup', signup);
+app.use('/v1/signin', signin);
+app.use('/v1/signout', signout);
+app.use('/v1/index', index);
+app.use('/v1/index/userList', userList);
+app.use('/v1/index/databaseList', databaseList);
+
+app.listen(80);

+ 49 - 0
common/mysqlConnection.js

@@ -0,0 +1,49 @@
+const mysql = require('mysql');
+const dbConfig = require('../conf/database');
+
+const adminDatabasePool = mysql.createPool(dbConfig.mysql.adminDatabase);
+const userInfoDatabasePool = mysql.createPool(dbConfig.mysql.userInfoDatabase);
+const userDatabasePool = mysql.createPool(dbConfig.mysql.userDatabase);
+
+
+/**
+ * 封装使用占位符的query语句
+ * @param {*} pool
+ * @param {string} sql 
+ * @param {*} args 
+ * @param {function} callback 
+ */
+function queryArgs(pool, sql, args, callback) {
+    pool.getConnection((err, connection) => {
+        if (err) {
+            console.error('error connecting: ' + err.stack);
+            callback({
+                err: err,
+                errInfo: '数据库连接错误,请重试'
+            });
+            return;
+        }
+        console.log('connected as id ' + connection.threadId);
+        connection.query(sql, args, (err, results, fields) => {
+            if (err) {
+                callback({
+                    err: err,
+                    errInfo: '数据库连接错误,请重试'
+                });
+            } else {
+                callback(err, results, fields);
+            }
+            //释放链接
+            connection.release();
+        });
+    });
+}
+
+module.exports = {
+    pool: {
+        adminDatabasePool: adminDatabasePool,
+        userInfoDatabasePool: userInfoDatabasePool,
+        userDatabasePool: userDatabasePool
+    },
+    queryArgs: queryArgs
+}

+ 34 - 0
conf/database.js

@@ -0,0 +1,34 @@
+//MYSQL配置信息
+mysql = {
+    adminDatabase: {
+        connectionLimit: 100,
+        host: "10.3.14.20",
+        user: "root",
+        password: "usestudio-1",
+        port: "14062",
+        database: "CloudSqlAdmin",
+        multipleStatements: true,
+        dateStrings: true
+    },
+    userInfoDatabase: {
+        connectionLimit: 100,
+        host: "10.20.5.88",
+        user: "root",
+        password: "usestudio-1",
+        port: "14068",
+        database: "cloudsql",
+        multipleStatements: true,
+        dateStrings: true
+    },
+    userDatabase: {
+        connectionLimit: 100,
+        host: "10.20.5.88",
+        user: "root",
+        password: "usestudio-1",
+        port: "14064",
+        multipleStatements: true,
+        dateStrings: true
+    }
+}
+
+exports.mysql = mysql;

+ 11 - 0
docker-compose.yml

@@ -0,0 +1,11 @@
+version: "3"
+
+services:
+  cloudsqladminpanel-node:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    network_mode: "host"
+    ports:
+      - "80:80"
+    restart: always

+ 15 - 0
package.json

@@ -0,0 +1,15 @@
+{
+  "name": "CloudSqlAdminPanel",
+  "version": "0.0.1",
+  "private": "true",
+  "scripts": {
+    "start": "node ./app.js"
+  },
+  "dependencies": {
+    "ejs": "^2.5.7",
+    "express": "^4.16.2",
+    "express-session": "^1.15.6",
+    "mysql": "^2.15.0",
+    "svg-captcha": "^1.3.11"
+  }
+}

+ 108 - 0
public/css/index.css

@@ -0,0 +1,108 @@
+.index {
+    padding: 0;
+    margin: 0;
+    height: 100%;
+}
+main .main-section {
+    padding: 115px 0 115px 0;
+    height: 100%;
+}
+
+.main-section .main-section-tittle {
+    padding: 0 16.666667% 0 16.666667%;
+}
+
+main .main-sub-section {
+    float: none;
+    border: 1px solid #dedede;
+}
+
+main .main-sub-section .main-input-row {
+    margin: 25px 0 25px 0;
+    float: none;
+}
+
+main .main-sub-section .main-a-row {
+    margin: 15px 0 15px 0;
+    float: none;
+}
+
+main .main-sub-section .main-button-row {
+    margin: 25px 0 25px 0;
+    float: none;
+}
+
+.main-sub-section .main-a-row a {
+    list-style: none;
+}
+
+main .captcha-row {
+    display: flex!important;
+}
+
+main .captcha-row img{
+    cursor: pointer;
+}
+
+
+footer {
+    display: block;
+    padding: 32px 0 33px 0;
+    background-color: #424242;
+    color: #9e9e9e;
+}
+footer .footer-copyright {
+    text-align: center;
+}
+
+footer a {
+    color: inherit;
+    text-decoration: none;
+}
+
+@media (min-width: 480px) {
+    .main-container {
+      padding-top: 60px;
+      padding-bottom: 70px;
+    }
+}
+
+@media (min-width: 840px) {
+    .main-container {
+      padding-top: 80px;
+      padding-bottom: 100px;
+    }
+}
+
+.main-container .main-status-col .icon-row i {
+    width: 74px;
+    height: 74px;
+    font-size: 74px;
+}
+
+.main-container .main-status-col .info-row .name-col {
+    padding: 10px 5px 10px 5px;
+    font-size: 18px;
+}
+
+.main-container .main-status-col .info-row .value-col {
+    font-size: 24px;
+    padding: 0 5px 0 5px;
+}
+
+.main-container .cssload-fond {
+    margin: 20px 0;
+}
+
+.main-divider {
+    margin-top: 50px;
+    margin-bottom: 50px;
+}
+
+.table-search-row {
+    max-width: 300px;
+}
+
+.main-container .page-button-row {
+    
+}

+ 127 - 0
public/css/loader.css

@@ -0,0 +1,127 @@
+.cssload-fond{
+	position:relative;
+	margin: auto;
+}
+
+.cssload-container-general
+{
+	animation:cssload-animball_two 1.15s infinite;
+		-o-animation:cssload-animball_two 1.15s infinite;
+		-ms-animation:cssload-animball_two 1.15s infinite;
+		-webkit-animation:cssload-animball_two 1.15s infinite;
+		-moz-animation:cssload-animball_two 1.15s infinite;
+	width:43px; height:43px;
+}
+.cssload-internal
+{
+	width:43px; height:43px; position:absolute;
+}
+.cssload-ballcolor
+{
+	width: 19px;
+	height: 19px;
+	border-radius: 50%;
+}
+.cssload-ball_1, .cssload-ball_2, .cssload-ball_3, .cssload-ball_4
+{
+	position: absolute;
+	animation:cssload-animball_one 1.15s infinite ease;
+		-o-animation:cssload-animball_one 1.15s infinite ease;
+		-ms-animation:cssload-animball_one 1.15s infinite ease;
+		-webkit-animation:cssload-animball_one 1.15s infinite ease;
+		-moz-animation:cssload-animball_one 1.15s infinite ease;
+}
+.cssload-ball_1
+{
+	background-color:rgb(203,32,37);
+	top:0; left:0;
+}
+.cssload-ball_2
+{
+	background-color:rgb(248,179,52);
+	top:0; left:23px;
+}
+.cssload-ball_3
+{
+	background-color:rgb(0,160,150);
+	top:23px; left:0;
+}
+.cssload-ball_4
+{
+	background-color:rgb(151,191,13);
+	top:23px; left:23px;
+}
+
+
+
+
+
+@keyframes cssload-animball_one
+{
+	0%{ position: absolute;}
+	50%{top:12px; left:12px; position: absolute;opacity:0.5;}
+	100%{ position: absolute;}
+}
+
+@-o-keyframes cssload-animball_one
+{
+	0%{ position: absolute;}
+	50%{top:12px; left:12px; position: absolute;opacity:0.5;}
+	100%{ position: absolute;}
+}
+
+@-ms-keyframes cssload-animball_one
+{
+	0%{ position: absolute;}
+	50%{top:12px; left:12px; position: absolute;opacity:0.5;}
+	100%{ position: absolute;}
+}
+
+@-webkit-keyframes cssload-animball_one
+{
+	0%{ position: absolute;}
+	50%{top:12px; left:12px; position: absolute;opacity:0.5;}
+	100%{ position: absolute;}
+}
+
+@-moz-keyframes cssload-animball_one
+{
+	0%{ position: absolute;}
+	50%{top:12px; left:12px; position: absolute;opacity:0.5;}
+	100%{ position: absolute;}
+}
+
+@keyframes cssload-animball_two
+{
+	0%{transform:rotate(0deg) scale(1);}
+	50%{transform:rotate(360deg) scale(1.3);}
+	100%{transform:rotate(720deg) scale(1);}
+}
+
+@-o-keyframes cssload-animball_two
+{
+	0%{-o-transform:rotate(0deg) scale(1);}
+	50%{-o-transform:rotate(360deg) scale(1.3);}
+	100%{-o-transform:rotate(720deg) scale(1);}
+}
+
+@-ms-keyframes cssload-animball_two
+{
+	0%{-ms-transform:rotate(0deg) scale(1);}
+	50%{-ms-transform:rotate(360deg) scale(1.3);}
+	100%{-ms-transform:rotate(720deg) scale(1);}
+}
+
+@-webkit-keyframes cssload-animball_two
+{
+	0%{-webkit-transform:rotate(0deg) scale(1);}
+	50%{-webkit-transform:rotate(360deg) scale(1.3);}
+	100%{-webkit-transform:rotate(720deg) scale(1);}
+}
+
+@-moz-keyframes cssload-animball_two
+{
+	0%{-moz-transform:rotate(0deg) scale(1);}
+	50%{-moz-transform:rotate(360deg) scale(1.3);}
+	100%{-moz-transform:rotate(720deg) scale(1);}
+}

+ 14 - 0
public/index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CloudSQL 管理员后台</title>
+	<meta charset="UTF-8">
+	<link rel="stylesheet" href="//cdn.bootcss.com/mdui/0.3.0/css/mdui.min.css">
+	<link href="./css/index.css" rel="stylesheet" type="text/css">
+	<link href="./css/loader.css" rel="stylesheet" type="text/css">
+	<script src="//cdn.bootcss.com/mdui/0.3.0/js/mdui.min.js"></script>
+	<!-- <script type="text/javascript" charset="utf-8" src="http://www.1473.cn/uform.js"></script> -->
+	<script type="text/javascript" src="./scr/index.js"></script>
+</head>
+<body></body>
+</html>

+ 78 - 0
public/scr/cookies.js

@@ -0,0 +1,78 @@
+/*\
+|*|
+|*|	:: cookies.js ::
+|*|
+|*|	A complete cookies reader/writer framework with full unicode support.
+|*|
+|*|	Revision #3 - July 13th, 2017
+|*|
+|*|	https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie (中文版本)
+|*|	https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
+|*|	https://developer.mozilla.org/User:fusionchess
+|*|	https://github.com/madmurphy/cookies.js
+|*|
+|*|	This framework is released under the GNU Public License, version 3 or later.
+|*|	http://www.gnu.org/licenses/gpl-3.0-standalone.html
+|*|
+|*|	Syntaxes:
+|*|
+|*|	* docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
+|*|	* docCookies.getItem(name)
+|*|	* docCookies.removeItem(name[, path[, domain]])
+|*|	* docCookies.hasItem(name)
+|*|	* docCookies.keys()
+|*|
+\*/
+
+var docCookies = {
+	getItem: function (sKey) {
+		if (!sKey) { return null; }
+		return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
+	},
+	setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
+		if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
+		var sExpires = "";
+		if (vEnd) {
+			switch (vEnd.constructor) {
+				case Number:
+					sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
+					/*
+					Note: Despite officially defined in RFC 6265, the use of `max-age` is not compatible with any
+					version of Internet Explorer, Edge and some mobile browsers. Therefore passing a number to
+					the end parameter might not work as expected. A possible solution might be to convert the the
+					relative time to an absolute time. For instance, replacing the previous line with:
+					*/
+					/*
+					sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; expires=" + (new Date(vEnd * 1e3 + Date.now())).toUTCString();
+					*/
+					break;
+				case String:
+					sExpires = "; expires=" + vEnd;
+					break;
+				case Date:
+					sExpires = "; expires=" + vEnd.toUTCString();
+					break;
+			}
+		}
+		document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
+		return true;
+	},
+	removeItem: function (sKey, sPath, sDomain) {
+		if (!this.hasItem(sKey)) { return false; }
+		document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
+		return true;
+	},
+	hasItem: function (sKey) {
+		if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
+		return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
+	},
+	keys: function () {
+		var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
+		for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
+		return aKeys;
+	}
+};
+
+if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
+	module.exports = docCookies;
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1510 - 0
public/scr/index.js


+ 23 - 0
routes/captcha.js

@@ -0,0 +1,23 @@
+const express = require('express');
+const router = express.Router();
+const svgCaptcha = require('svg-captcha');
+
+const c = svgCaptcha.create();
+
+router.get('/', (req, res, next) => {
+    let captcha = svgCaptcha.create({
+        size: 4,
+        ignoreChars: '0o1ilI',
+        noise: 2,
+        color: true,
+        fontSize: 50,
+        width: 100,
+        height: 36
+    });
+    req.session.captcha = captcha.text.toLowerCase();
+
+    res.type('svg');
+    res.status(200).send(captcha.data);
+});
+
+module.exports = router;

+ 353 - 0
routes/databaseList.js

@@ -0,0 +1,353 @@
+const express = require('express');
+const router = express.Router();
+const crypto = require('crypto');
+const events = require('events');
+const db = require('../common/mysqlConnection');
+
+/**
+ * 获取数据库列表
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getDatabaseList(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            pageIndex = req.query.pageIndex - 1,
+            orderBy = req.query.orderBy,
+            searchValue = req.query.search;
+
+        emitter.on('ok', (results, count) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results,
+                    'count': count
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectDatabseInfoFromDbOwnTable', () => {
+            switch (orderBy) {
+                case 'userId':
+                    orderBy = 'dbOwnUuid';
+                    break;
+
+                case 'databaseName':
+                    orderBy = 'dbName';
+                    break;
+
+                case 'createDate':
+                    orderBy = 'createDate';
+                    break;
+
+                default:
+                    orderBy = 'dbOwnUuid';
+                    break;
+            }
+
+            if (pageIndex > 0) {
+                pageIndex = parseInt(pageIndex, 10) * 20;
+            } else {
+                pageIndex = 0;
+            }
+
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT SQL_CALC_FOUND_ROWS * FROM (SELECT db.dbName, db.dbOwnUuid, user.account, db.createDate FROM dbOwn_test AS db, user_test AS user WHERE db.dbOwnUuid = user.uuid ORDER BY ? ASC) a LIMIT ?, 20; SELECT FOUND_ROWS()',
+                value = [orderBy, pageIndex];
+
+            if (searchValue) {
+                // sqlString = 'SELECT SQL_CALC_FOUND_ROWS * FROM (SELECT db.dbName, db.dbOwnUuid, user.account, db.createDate FROM dbOwn_test AS db, user_test AS user WHERE db.dbOwnUuid = user.uuid AND db.dbName LIKE ? OR db.dbOwnUuid LIKE ? OR user.account LIKE ? OR db.createDate LIKE ? ORDER BY ? ASC) a LIMIT ?, 20; SELECT FOUND_ROWS()';
+                sqlString = 'SELECT SQL_CALC_FOUND_ROWS * FROM (SELECT * FROM (SELECT db.dbName, db.dbOwnUuid, user.account, db.createDate FROM dbOwn_test AS db, user_test AS user WHERE db.dbOwnUuid = user.uuid) AS r WHERE r.dbName LIKE ? OR r.dbOwnUuid LIKE ? OR r.account LIKE ? OR r.createDate LIKE ? ORDER BY ? ASC) a LIMIT ?, 20; SELECT FOUND_ROWS()';
+                searchValue = '%' + searchValue + '%';
+                value = [searchValue, searchValue, searchValue, searchValue, orderBy, pageIndex];
+            }
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results[0], results[1][0]['FOUND_ROWS()']);
+                }
+            });
+        });
+
+        emitter.emit('selectDatabseInfoFromDbOwnTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 获取数据库总数
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getDatabaseCount(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter();
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectUserCountFromUserTable', () => {
+
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT COUNT(1) FROM dbOwn_test',
+                value = null;
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results[0]['COUNT(1)']);
+                }
+            });
+        });
+
+        emitter.emit('selectUserCountFromUserTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 获取数据库所有表
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getDatabaseTables(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            dbName = req.query.dbName;
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectDatabseTables', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userDatabasePool,
+                sqlString = 'SELECT * FROM information_schema.`tables` a WHERE a.TABLE_SCHEMA = ?',
+                value = [dbName];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results);
+                }
+            });
+        });
+
+        emitter.emit('selectDatabseTables');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 删除数据库
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function dropDatabase(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            dbName = req.body.dbName;
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '删除成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('deleteRow', () => {
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'DELETE FROM dbOwn_test WHERE dbName = ?',
+                value = [dbName];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, 'sql执行出错');
+                } else {
+                    emitter.emit('ok', results);
+                }
+            });
+        })
+
+        emitter.on('dropDb', () => {
+            //定义SQL语句
+            let pool = db.pool.userDatabasePool,
+                sqlString = 'DROP DATABASE ' + dbName,
+                value = null;
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, 'sql执行出错');
+                } else {
+                    emitter.emit('deleteRow', results);
+                }
+            });
+        });
+
+        emitter.emit('dropDb');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 清空数据库
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function emptyDatabase(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            dbName = req.body.dbName;
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '删除成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('dropTable', (results) => {
+            let pool = db.pool.userDatabasePool,
+                value = null;
+
+            results.forEach((item, index) => {
+                let sqlString = 'USE ' + dbName + '; ' + item['CONCAT("DROP TABLE ", table_name, ";")'];
+
+                db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                    if (err) {
+                        emitter.emit('err', err.err, 'sql执行出错');
+                    } else {
+                        emitter.emit('ok', results);
+                    }
+                });
+            }, this);
+        });
+
+        emitter.on('getAllTable', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userDatabasePool,
+                sqlString = 'SELECT CONCAT("DROP TABLE ", table_name, ";") FROM information_schema.tables WHERE table_schema = ?',
+                value = [dbName];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, 'sql执行出错');
+                } else {
+                    if (results.length === 0) {
+                        emitter.emit('err', null, '该数据库无数据表');
+                    } else {
+                        emitter.emit('dropTable', results);
+                    }
+                }
+            });
+        });
+
+        emitter.emit('getAllTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+router.get('/', getDatabaseList);
+router.get('/getDatabaseCount', getDatabaseCount);
+router.get('/getDatabaseAllTables', getDatabaseTables);
+router.post('/dropDatabase', dropDatabase);
+router.post('/emptyDatabase', emptyDatabase);
+
+
+module.exports = router

+ 24 - 0
routes/index.js

@@ -0,0 +1,24 @@
+const express = require('express');
+const router = express.Router();
+const crypto = require('crypto');
+const events = require('events');
+const db = require('../common/mysqlConnection');
+
+let emitter = new events.EventEmitter();
+
+router.get('/signStatus', (req, res, next) => {
+    if (req.session.signinStatus) {
+        res.send({
+            status: 'signedin',
+            info: '已登录'
+        });
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+});
+
+module.exports = router;

+ 95 - 0
routes/signin.js

@@ -0,0 +1,95 @@
+const express = require('express');
+const router = express.Router();
+const crypto = require('crypto');
+const events = require('events');
+const db = require('../common/mysqlConnection');
+
+router.post('/', (req, res, next) => {
+    if (req.session.signinStatus) {
+        res.send({
+            status: 'signedin',
+            info: '已登录',
+            url: '#/index'
+        });
+    } else {
+        if (!req.body) {
+            res.send({
+                status: 'error',
+                info: '参数错误,请重试'
+            });
+        } else {
+            let captchaCode = req.body.captcha;
+            if (req.session.captcha !== captchaCode) {
+                res.status(200).send({
+                    status: 'error',
+                    info: '验证码错误'
+                });
+            } else {
+                let emitter = new events.EventEmitter(),
+                    userId = req.body.userId,
+                    password = req.body.password;
+    
+                emitter.on('ok', () => {
+                    //更改登录状态
+                    req.session.signinId = userId;
+                    req.session.signinStatus = true;
+
+                    res.send({
+                        status: 'signedin',
+                        info: '登陆成功',
+                        url: '#/index'
+                    });
+                });
+    
+                emitter.on('err', (err, errInfo) => {
+                    res.json({
+                        status: 'error',
+                        info: errInfo,
+                        error: err
+                    });
+    
+                });
+
+                //从User表中取对应ID的密码和盐值
+                emitter.on('selectUserPasswordAndSaltFromUserTable', () => {
+                    //定义SQL语句查询
+                    let pool = db.pool.adminDatabasePool;
+                        sqlString = 'SELECT password, salt FROM CloudSqlAdmin_Users WHERE userId = ?',
+                        value = [userId];
+    
+                    db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                        if (err) {
+                            emitter.emit('err', err.err, '数据库查询出错');
+                        } else {
+                            let passwordHashOnDb = results[0].password,
+                                salt = results[0].salt;
+    
+                            emitter.emit('checkUserPassword', passwordHashOnDb, salt);
+                        }
+                    });
+                })
+    
+                //检测用户密码是否正确
+                emitter.on('checkUserPassword', (passwordHashOnDb, salt) => {
+                    crypto.pbkdf2(password, salt, 4096, 256, 'SHA256', (err, hash) => {
+                        if (err) {
+                            emitter.emit('err', err, '后端逻辑出错,请联系管理员修复该问题');
+                            throw err;
+                        }
+                        hash = hash.toString('hex'); //生成密文
+    
+                        if (hash != passwordHashOnDb) {
+                            emitter.emit('err', null, '用户名/邮箱或密码错误,请重试。');
+                        } else {
+                            emitter.emit('ok')
+                        }
+                    })
+                });
+
+                emitter.emit('selectUserPasswordAndSaltFromUserTable');
+            }
+        }
+    }
+});
+
+module.exports = router

+ 16 - 0
routes/signout.js

@@ -0,0 +1,16 @@
+const express = require('express');
+const router = express.Router();
+
+router.get('/', (req, res, next) => {
+    //更改登录状态
+    delete req.session.signinId;
+    delete req.session.signinStatus;
+
+    res.status(200).send({
+        status: 'successed',
+        info: '已登出',
+        url: '#/signin'
+    });
+});
+
+module.exports = router;

+ 122 - 0
routes/signup.js

@@ -0,0 +1,122 @@
+const express = require('express');
+const router = express.Router();
+const crypto = require('crypto');
+const events = require('events');
+const db = require('../common/mysqlConnection');
+
+router.post('/', (req, res, next) => {
+    if (!req.body) {
+        res.status(200).send({
+            status: 'error',
+            info: '参数错误'
+        });
+    } else {
+        let captchaCode = req.body.captcha;
+        if (req.session.captcha !== captchaCode) {
+            res.status(200).send({
+                status: 'error',
+                info: '验证码错误'
+            });
+        } else {
+            let emitter = new events.EventEmitter(),
+                userId = req.body.userId,
+                password = req.body.password;
+
+            emitter.on('ok', () => {
+                res.status(200).send({
+                    status: 'successed',
+                    info: '注册成功',
+                    url: '#/signin'
+                });
+            });
+
+            emitter.on('err', (err, errInfo) => {
+                res.status(200).send({
+                    status: 'error',
+                    info: errInfo,
+                    error: err
+                });
+            });
+
+            //检测UserId是否存在
+            emitter.on('selectUserIdFromUserTable', () => {
+                //定义SQL语句查询UserID是否存在
+                let pool = db.pool.adminDatabasePool;
+                    sqlString = 'SELECT userId FROM CloudSqlAdmin_Users WHERE userId = ?',
+                    value = [userId];
+
+                db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                    if (err) {
+                        emitter.emit('err', err.err, err.errInfo);
+                    } else {
+                        if (results.length !== 0) {
+                            emitter.emit('err', null, '邮箱或用户名已存在');
+                        } else {
+                            emitter.emit('createSalt');
+                        }
+                    }
+                });
+            })
+
+            //生成salt
+            emitter.on('createSalt', () => {
+                crypto.randomBytes(128, (err, salt) => {
+                    if (err) { throw err; }
+                    salt = salt.toString('hex');
+
+                    emitter.emit('createPassword', salt);
+                });
+            });
+
+            //生成密码
+            emitter.on('createPassword', (salt) => {
+                crypto.pbkdf2(password, salt, 4096, 256, 'SHA256', (err, hash) => {
+                    if (err) {
+                        emitter.emit('err', err, '后端逻辑出错,请联系管理员修复该问题');
+                        throw err;
+                    }
+                    hash = hash.toString('hex'); //生成密文
+
+                    emitter.emit('insertUserInfoToUserTable', hash, salt);
+                })
+            });
+
+            //向User表插入数据
+            emitter.on('insertUserInfoToUserTable', (hash, salt) => {
+                //定义向Users表插入用户账户信息的SQL语句
+                let pool = db.pool.adminDatabasePool;
+                    sqlString = 'INSERT INTO CloudSqlAdmin_Users (uid, userId, password, salt, createTime, lastUpdateTime) VALUES (uuid(), ?, ?, ?, NOW(), NOW())',
+                    value = [userId, hash, salt];
+
+                db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                    if (err) {
+                        emitter.emit('err', err.err, err.errInfo);
+                    } else {
+                        emitter.emit('insertUserInfoToUserProfileTable');
+                    }
+                });
+            });
+
+            //向UserProfile表插入数据
+            emitter.on('insertUserInfoToUserProfileTable', () => {
+
+                //定义向UsersProfile表插入用户账户信息的SQL语句
+                let pool = db.pool.adminDatabasePool;
+                    sqlString = 'INSERT INTO CloudSqlAdmin_UsersProfile (uid, userId, inviteCode, userStatus, createTime, lastUpdateTime) VALUES (uuid(), ?, ?, 1, NOW(), NOW())',
+                    value = [userId, '$$$TempPassCode'];
+
+                db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                    if (err) {
+                        emitter.emit('err', err.err, err.errInfo);
+                    } else {
+                        emitter.emit('ok');
+                    }
+                });
+            });
+
+            emitter.emit('selectUserIdFromUserTable');
+        }
+    }
+});
+
+module.exports = router;

+ 323 - 0
routes/userList.js

@@ -0,0 +1,323 @@
+const express = require('express');
+const router = express.Router();
+const crypto = require('crypto');
+const events = require('events');
+const db = require('../common/mysqlConnection');
+
+/**
+ * 获取用户列表
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getUserList(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            pageIndex = req.query.pageIndex - 1,
+            orderBy = req.query.orderBy,
+            searchValue = req.query.search;
+
+        emitter.on('ok', (results, count) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results,
+                    'count': count
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectUserInfoFromUserTable', () => {
+            switch (orderBy) {
+                case 'userId':
+                    orderBy = 'uuid';
+                    break;
+
+                case 'userNickName':
+                    orderBy = 'account';
+                    break;
+
+                case 'createDate':
+                    orderBy = 'createDate';
+                    break;
+
+                default:
+                    orderBy = 'uuid';
+                    break;
+            }
+
+            if (pageIndex > 0) {
+                pageIndex = parseInt(pageIndex, 10) * 20;
+            } else {
+                pageIndex = 0;
+            }
+            
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT SQL_CALC_FOUND_ROWS * FROM (SELECT account, uuid, dbpassword, createDate FROM user_test ORDER BY ? ASC) a LIMIT ?, 20; SELECT FOUND_ROWS()',
+                value = [orderBy, pageIndex];
+
+            if (searchValue) {
+                sqlString = 'SELECT SQL_CALC_FOUND_ROWS * FROM (SELECT account, uuid, dbpassword, createDate FROM user_test WHERE account LIKE ? OR uuid LIKE ? OR createDate LIKE ? ORDER BY ? ASC) a LIMIT ?, 20; SELECT FOUND_ROWS()';
+                searchValue = '%' + searchValue + '%';
+                value = [searchValue, searchValue, searchValue, orderBy, pageIndex];
+            }
+    
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results[0], results[1][0]['FOUND_ROWS()']);
+                }
+            });
+        });
+
+        emitter.emit('selectUserInfoFromUserTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 获取用户总数
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getUserCount(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter();
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectUserCountFromUserTable', () => {
+
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT COUNT(1) FROM user_test',
+                value = null;
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results[0]['COUNT(1)']);
+                }
+            });
+        });
+
+        emitter.emit('selectUserCountFromUserTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 获取用户拥有的数据库
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function getUserOwnDb(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            uuid = req.query.userId;
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '获取成功',
+                data: {
+                    'results': results
+                }
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('selectDbOwnInfoFromDbOwnTable', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT dbName FROM dbOwn_test WHERE dbOwnUuid = ?',
+                value = [uuid];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('ok', results);
+                }
+            });
+        });
+
+        emitter.emit('selectDbOwnInfoFromDbOwnTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+/**
+ * 删除用户
+ * @param {*} req 
+ * @param {*} res 
+ * @param {function} next 
+ */
+function removeUser(req, res, next) {
+    if (req.session.signinStatus) {
+        let emitter = new events.EventEmitter(),
+            uuid = req.body.userId,
+            userNickName = req.body.userNickName;
+
+        emitter.on('ok', (results) => {
+            res.send({
+                status: 'success',
+                info: '删除成功'
+            });
+        });
+
+        emitter.on('err', (err, errInfo) => {
+            res.json({
+                status: 'error',
+                info: errInfo,
+                error: err
+            });
+        });
+
+        emitter.on('deleteFromDbOwnTable', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'DELETE FROM dbOwn_test WHERE dbOwnUuid = ?',
+                value = [userNickName];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '删除用户信息表记录时出错');
+                } else {
+                    emitter.emit('ok');
+                }
+            });
+        });
+
+        emitter.on('deleteFromUserTable', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'DELETE FROM user_test WHERE uuid = ?',
+                value = [uuid];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '用户数据表删除数据出错');
+                } else {
+                    emitter.emit('deleteFromDbOwnTable');
+                }
+            });
+        });
+
+        emitter.on('dropUser', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userDatabasePool,
+                sqlString = 'USE mysql; DELETE FROM user WHERE User = ?',
+                value = [uuid];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '用户数据表删除数据出错');
+                } else {
+                    emitter.emit('deleteFromUserTable');
+                }
+            });
+        });
+
+        emitter.on('dropDatabase', (results) => {
+            let pool = db.pool.userDatabasePool,
+                value = null;
+
+            results.forEach((item, index) => {
+                let sqlString = 'DROP DATABASE ' + item.dbName;
+
+                db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                    if (err) {
+                        emitter.emit('err', err.err, '删除数据库时出错');
+                    } else {
+                        emitter.emit('dropUser');
+                    }
+                });
+            }, this);
+        });
+
+        emitter.on('selectDbOwnInfoFromDbOwnTable', () => {
+            //定义SQL语句查询
+            let pool = db.pool.userInfoDatabasePool,
+                sqlString = 'SELECT dbName FROM dbOwn_test WHERE dbOwnUuid = ?',
+                value = [uuid];
+
+            db.queryArgs(pool, sqlString, value, (err, results, fields) => {
+                if (err) {
+                    emitter.emit('err', err.err, '数据库查询出错');
+                } else {
+                    emitter.emit('dropDatabase', results);
+                }
+            });
+        });
+
+        emitter.emit('selectDbOwnInfoFromDbOwnTable');
+    } else {
+        res.send({
+            status: 'signout',
+            info: '未登录',
+            url: '#/signin'
+        });
+    }
+}
+
+router.get('/', getUserList);
+router.get('/getUserCount', getUserCount);
+router.get('/getUserOwnDb', getUserOwnDb);
+router.post('/removeUser', removeUser);
+
+
+
+module.exports = router

+ 8 - 0
run.sh

@@ -0,0 +1,8 @@
+curl -fsSL get.docker.com -o get-docker.sh
+sh get-docker.sh --mirror Aliyun
+apt install vim git nload bridge-utils python-pip -y
+curl -L https://github.com/docker/compose/releases/download/1.20.0-rc2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
+chmod +x /usr/local/bin/docker-compose
+curl -L https://raw.githubusercontent.com/docker/compose/1.19.0/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose
+docker-compose run cloudsqladminpanel-node -d
+echo Service is running.

+ 396 - 0
yarn.lock

@@ -0,0 +1,396 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+accepts@~1.3.5:
+  version "1.3.5"
+  resolved "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
+  dependencies:
+    mime-types "~2.1.18"
+    negotiator "0.6.1"
+
+array-flatten@1.1.1:
+  version "1.1.1"
+  resolved "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+bignumber.js@4.0.4:
+  version "4.0.4"
+  resolved "http://registry.npm.taobao.org/bignumber.js/download/bignumber.js-4.0.4.tgz#7c40f5abcd2d6623ab7b99682ee7db81b11889a4"
+
+body-parser@1.18.2:
+  version "1.18.2"
+  resolved "http://registry.npm.taobao.org/body-parser/download/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
+  dependencies:
+    bytes "3.0.0"
+    content-type "~1.0.4"
+    debug "2.6.9"
+    depd "~1.1.1"
+    http-errors "~1.6.2"
+    iconv-lite "0.4.19"
+    on-finished "~2.3.0"
+    qs "6.5.1"
+    raw-body "2.3.2"
+    type-is "~1.6.15"
+
+bytes@3.0.0:
+  version "3.0.0"
+  resolved "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+
+content-disposition@0.5.2:
+  version "0.5.2"
+  resolved "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@~1.0.4:
+  version "1.0.4"
+  resolved "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+
+cookie-signature@1.0.6:
+  version "1.0.6"
+  resolved "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+
+cookie@0.3.1:
+  version "0.3.1"
+  resolved "http://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
+core-util-is@~1.0.0:
+  version "1.0.2"
+  resolved "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+crc@3.4.4:
+  version "3.4.4"
+  resolved "http://registry.npm.taobao.org/crc/download/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b"
+
+debug@2.6.9:
+  version "2.6.9"
+  resolved "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  dependencies:
+    ms "2.0.0"
+
+depd@1.1.1:
+  version "1.1.1"
+  resolved "http://registry.npm.taobao.org/depd/download/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
+
+depd@~1.1.1, depd@~1.1.2:
+  version "1.1.2"
+  resolved "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+
+destroy@~1.0.4:
+  version "1.0.4"
+  resolved "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+ejs@^2.5.7:
+  version "2.5.7"
+  resolved "http://registry.npm.taobao.org/ejs/download/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
+
+encodeurl@~1.0.2:
+  version "1.0.2"
+  resolved "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+
+escape-html@~1.0.3:
+  version "1.0.3"
+  resolved "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
+etag@~1.8.1:
+  version "1.8.1"
+  resolved "http://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+
+express-session@^1.15.6:
+  version "1.15.6"
+  resolved "http://registry.npm.taobao.org/express-session/download/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a"
+  dependencies:
+    cookie "0.3.1"
+    cookie-signature "1.0.6"
+    crc "3.4.4"
+    debug "2.6.9"
+    depd "~1.1.1"
+    on-headers "~1.0.1"
+    parseurl "~1.3.2"
+    uid-safe "~2.1.5"
+    utils-merge "1.0.1"
+
+express@^4.16.2:
+  version "4.16.3"
+  resolved "http://registry.npm.taobao.org/express/download/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
+  dependencies:
+    accepts "~1.3.5"
+    array-flatten "1.1.1"
+    body-parser "1.18.2"
+    content-disposition "0.5.2"
+    content-type "~1.0.4"
+    cookie "0.3.1"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "~1.1.2"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.1.1"
+    fresh "0.5.2"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.3"
+    qs "6.5.1"
+    range-parser "~1.2.0"
+    safe-buffer "5.1.1"
+    send "0.16.2"
+    serve-static "1.13.2"
+    setprototypeof "1.1.0"
+    statuses "~1.4.0"
+    type-is "~1.6.16"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
+finalhandler@1.1.1:
+  version "1.1.1"
+  resolved "http://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
+  dependencies:
+    debug "2.6.9"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    statuses "~1.4.0"
+    unpipe "~1.0.0"
+
+forwarded@~0.1.2:
+  version "0.1.2"
+  resolved "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+
+fresh@0.5.2:
+  version "0.5.2"
+  resolved "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+
+http-errors@1.6.2, http-errors@~1.6.2:
+  version "1.6.2"
+  resolved "http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+  dependencies:
+    depd "1.1.1"
+    inherits "2.0.3"
+    setprototypeof "1.0.3"
+    statuses ">= 1.3.1 < 2"
+
+iconv-lite@0.4.19:
+  version "0.4.19"
+  resolved "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
+inherits@2.0.3, inherits@~2.0.3:
+  version "2.0.3"
+  resolved "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+ipaddr.js@1.6.0:
+  version "1.6.0"
+  resolved "http://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
+
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "http://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "http://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+merge-descriptors@1.0.1:
+  version "1.0.1"
+  resolved "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+methods@~1.1.2:
+  version "1.1.2"
+  resolved "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+mime-db@~1.33.0:
+  version "1.33.0"
+  resolved "http://registry.npm.taobao.org/mime-db/download/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+
+mime-types@~2.1.18:
+  version "2.1.18"
+  resolved "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+  dependencies:
+    mime-db "~1.33.0"
+
+mime@1.4.1:
+  version "1.4.1"
+  resolved "http://registry.npm.taobao.org/mime/download/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+mysql@^2.15.0:
+  version "2.15.0"
+  resolved "http://registry.npm.taobao.org/mysql/download/mysql-2.15.0.tgz#ea16841156343e8f2e47fc8985ec41cdd9573b5c"
+  dependencies:
+    bignumber.js "4.0.4"
+    readable-stream "2.3.3"
+    safe-buffer "5.1.1"
+    sqlstring "2.3.0"
+
+negotiator@0.6.1:
+  version "0.6.1"
+  resolved "http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
+on-finished@~2.3.0:
+  version "2.3.0"
+  resolved "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+  dependencies:
+    ee-first "1.1.1"
+
+on-headers@~1.0.1:
+  version "1.0.1"
+  resolved "http://registry.npm.taobao.org/on-headers/download/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
+opentype.js@^0.7.3:
+  version "0.7.3"
+  resolved "http://registry.npm.taobao.org/opentype.js/download/opentype.js-0.7.3.tgz#40fb8ce18bfd60e74448efdfe442834098397aab"
+  dependencies:
+    tiny-inflate "^1.0.2"
+
+parseurl@~1.3.2:
+  version "1.3.2"
+  resolved "http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
+
+path-to-regexp@0.1.7:
+  version "0.1.7"
+  resolved "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+
+process-nextick-args@~1.0.6:
+  version "1.0.7"
+  resolved "http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+proxy-addr@~2.0.3:
+  version "2.0.3"
+  resolved "http://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
+  dependencies:
+    forwarded "~0.1.2"
+    ipaddr.js "1.6.0"
+
+qs@6.5.1:
+  version "6.5.1"
+  resolved "http://registry.npm.taobao.org/qs/download/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
+random-bytes@~1.0.0:
+  version "1.0.0"
+  resolved "http://registry.npm.taobao.org/random-bytes/download/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
+
+range-parser@~1.2.0:
+  version "1.2.0"
+  resolved "http://registry.npm.taobao.org/range-parser/download/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+
+raw-body@2.3.2:
+  version "2.3.2"
+  resolved "http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
+  dependencies:
+    bytes "3.0.0"
+    http-errors "1.6.2"
+    iconv-lite "0.4.19"
+    unpipe "1.0.0"
+
+readable-stream@2.3.3:
+  version "2.3.3"
+  resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~1.0.6"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.0.3"
+    util-deprecate "~1.0.1"
+
+safe-buffer@5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.1"
+  resolved "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+send@0.16.2:
+  version "0.16.2"
+  resolved "http://registry.npm.taobao.org/send/download/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
+  dependencies:
+    debug "2.6.9"
+    depd "~1.1.2"
+    destroy "~1.0.4"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    fresh "0.5.2"
+    http-errors "~1.6.2"
+    mime "1.4.1"
+    ms "2.0.0"
+    on-finished "~2.3.0"
+    range-parser "~1.2.0"
+    statuses "~1.4.0"
+
+serve-static@1.13.2:
+  version "1.13.2"
+  resolved "http://registry.npm.taobao.org/serve-static/download/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
+  dependencies:
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    parseurl "~1.3.2"
+    send "0.16.2"
+
+setprototypeof@1.0.3:
+  version "1.0.3"
+  resolved "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+
+setprototypeof@1.1.0:
+  version "1.1.0"
+  resolved "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+
+sqlstring@2.3.0:
+  version "2.3.0"
+  resolved "http://registry.npm.taobao.org/sqlstring/download/sqlstring-2.3.0.tgz#525b8a4fd26d6f71aa61e822a6caf976d31ad2a8"
+
+"statuses@>= 1.3.1 < 2", statuses@~1.4.0:
+  version "1.4.0"
+  resolved "http://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+
+string_decoder@~1.0.3:
+  version "1.0.3"
+  resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+  dependencies:
+    safe-buffer "~5.1.0"
+
+svg-captcha@^1.3.11:
+  version "1.3.11"
+  resolved "http://registry.npm.taobao.org/svg-captcha/download/svg-captcha-1.3.11.tgz#f16e9d68ca0b189627c0557ef8e6a085f7e87ee9"
+  dependencies:
+    opentype.js "^0.7.3"
+
+tiny-inflate@^1.0.2:
+  version "1.0.2"
+  resolved "http://registry.npm.taobao.org/tiny-inflate/download/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7"
+
+type-is@~1.6.15, type-is@~1.6.16:
+  version "1.6.16"
+  resolved "http://registry.npm.taobao.org/type-is/download/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
+  dependencies:
+    media-typer "0.3.0"
+    mime-types "~2.1.18"
+
+uid-safe@~2.1.5:
+  version "2.1.5"
+  resolved "http://registry.npm.taobao.org/uid-safe/download/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
+  dependencies:
+    random-bytes "~1.0.0"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+  version "1.0.0"
+  resolved "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+util-deprecate@~1.0.1:
+  version "1.0.2"
+  resolved "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+utils-merge@1.0.1:
+  version "1.0.1"
+  resolved "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+
+vary@~1.1.2:
+  version "1.1.2"
+  resolved "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"