背景

自己做了一点点的小尝试:基于前馈神经网络 LSTM 的个体出行目的地预测模型,基于个体历史出行数据,模型可以实现出行目的地的实时动态预测功能。

模型其实具有实际应用功能,为了对其应用场景进行探索,拟开发一个全栈的项目,在Web客户端实现用户出行的动态预测效果,同时能够提供数据可视分析等功能。

项目地址

可视化效果

服务端结构设计

结构设计如下图所示:

api 文件夹存放的是网页路由查询逻辑的实现代码。
py-script 存放的是python的脚本及训练好的深度学习模型。
tables 是基于Sequlize的数据库表模型。
config-default 定义了数据库连接信息。
db.js 定义了Sequlize对象模型。
func.js 定义了一些函数接口。
index.js 是服务器的启动入口,定义了路口链接的运行文件。
table.js 将tables文件中的表模型统一用模块接口暴露出来。

服务器搭建

服务端采用了基于Node.js运行的Web应用框架——Express,首选需要安装Express框架。(前提是电脑安装了Node.js,如需安装,请参考其它网上教程)

1
2
// 进入在server目录,执行下面命令安装Express
npm install express

数据库采用了MySQL数据库,首先需要安装连接数据库的库函数

1
npm install --save mysql

然后我们采用了Sequlize库函数方便管理SQL查询,接着安装Sequlize。

1
npm install --save sequelize

我们再安装Express的中间件 body-parser 帮助我们解析JSON、Raw、文本、URL-encoded格式的请求体。

1
npm install --save body-parser

然后再index.js编写我们的服务端启动入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// node server
// use ES6

require('babel-register')({
presets: ['env']
});

const bodyParser = require('body-parser');
const express = require('express');
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));

// 允许跨域访问
app.all('*', function (req, res, next) {

res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
res.header('X-Powered-By', ' 3.2.1');
res.header('Content-Type', 'application/json;charset=utf-8');

next();
});

app.get('/', (req, res, next) => res.send('Express Server'));
app.get('/api', (req, res, next) => res.send('API Pages'));

// 监听端口
app.listen(3000, () => console.log('success listen at port:3000......'));

在server文件夹下用node.js启动程序,node index, 命令窗口会显示 ‘success listen at port:3000……’ 字样,证明服务器启动成功!这时在浏览器中输入 localhost:3000/api发现浏览器输出 API Pages

连接数据库访问数据

首先在 config-default 文件内定义数据库连接信息,如下:

1
2
3
4
5
6
7
8
module.exports = {
host: 'localhost',
user: 'root',
password: '**********',
database: 'your_database',
port: '3306',
dialect: 'mysql'
};

然后 在db.js 文件中定义Sequlize模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const Sequelize = require('sequelize');
const config = require('./config-default');

let sequelize = new Sequelize(config.database, config.user, config.password, {
host: config.host,
dialect: config.dialect,
pool: {
max: 5,
min: 0,
idle: 10000
}
});

let defineModel = function (name, attributes) {
let attrs = {};
for (let key in attributes) {
let value = attributes[key];
// if the value is the mainKey
if (typeof value === 'object' && value['type']) {
value.allowNull = value.allowNull || false;
attrs[key] = value;
} else {
attrs[key] = {
type: value,
allowNull: false
};
}
}
return sequelize.define(name, attrs, {
tableName: name,
timestamps: false,
freezeTableName: true
});
};

let exp = {
defineModel: defineModel
};

module.exports = exp;

Sequlize 是一个基于Node.js的对象关系映射(Object Relational Mapping,ORM)实现,通过对SQL语言的封装,实现model与SQL的映射,让开发者可以用OOP的方式操作数据库,优雅的生成安全、可维护的SQL代码。

这里我们把Sequlize模型的定义做成了一个的模块接口,只要指定Sequlize参数,就可以生成一个Sequlize模型,这样针对一个数据库中不同的表就可以生成不同的Sequlize对象。

之后根据自己的数据库表设计定义对应的表模型,如项目中 tables 文件夹内的 User1 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const Sequelize = require('sequelize');
const db = require('../db');

module.exports = db.defineModel('user_1',{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false
},
weekday: Sequelize.INTEGER,
start_time: Sequelize.INTEGER,
dis_total: Sequelize.FLOAT,
sem_O: Sequelize.TEXT,
sem_D: Sequelize.TEXT,
destination: Sequelize.TEXT,
lngs: Sequelize.TEXT,
lats: Sequelize.TEXT,
travel_dis: Sequelize.TEXT,
spd: Sequelize.TEXT,
azimuth: Sequelize.TEXT,
norm_dict: Sequelize.TEXT,
key_point: Sequelize.TEXT,
sem_pt: Sequelize.TEXT,
time: Sequelize.TEXT
});

因为随着项目不断深入,数据表模型也不断增多。为了统一管理,建立 tables 模块将所有表模型统一打包,用户可按需导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tables.js 文件
// scan all models defined in models:
const fs = require('fs');
const user1 = require('./tables/User1');
const user2 = require('./tables/User2');
const user3 = require('./tables/User3');
const user4 = require('./tables/User4');

module.exports = {
'user1': user1,
'user2': user2,
'user3': user3,
'user4': user4,
};

之后再 api 文件夹中定义不同路由的处理逻辑,这里主要是对数据库的访问。

例如 searchAll.js 文件是对整张表进行访问,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const table = require('../table');
const express = require('express');
const router = express.Router();
import {jsonWrite} from '../func';

router.post('/', (req, res) => {
let params = req.body;
if(params['tableName'] === undefined){
jsonWrite(res, undefined);
return;
}
let user = table[params.tableName];
(async () => {
let traj = await user.findAll();
jsonWrite(res, traj);
})();
});

module.exports = router;

其中 jsonWrite 是我们在fun.js定义的根据请求结果返回res的函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
const jsonWrite = function (res, ret) {
if (typeof ret === 'undefined') {
res.json({
code: '1',
msg: '操作失败'
});
} else {
res.json(ret);
}
};

export {jsonWrite};

然后,前端发送post请求,请求体中输入待查询数据表名就可以实现整张表的查询。

这里的服务器代码不能实时更新效果,最后可以采用 nodemon 包检测服务器代码变化,实现代码的实时监测更新。

1
npm install -g  nodemon

然后利用 nodemon 启动 index.js入口文件,就可以实现服务器根据代码更新的自动重启,

1
nodemon index