微信小程序 + Node.js + MongoDB + VPS(Linux)

这段时间由于公司没有项目做,确实闲出蛋了,于是给自己找了点事做。听说最近 Node.js 火得不行,我也赶一波潮流,照着官方网站学习了下,对于有编程基础的同学来说入门还是比较容易的。

学习新东西还是比较有激情的吧,很快把 Node.js、Express(一个基于 Node.js 的 Web 应用框架)和 MongoDB 都大概学了一下,恰好这个时间我准备开始健身,就想着做一个小应用来管理自己每天的饮食、训练和身体数据。由于考虑到本地 App 需要适应不同平台,前端选择了最新微信新出的一个公众服务平台:小程序。

好了,随便唠两句,还是进入正题吧,一个一个来。

Node.js

环境配置

首先得在电脑上安装 Node.js 环境,直接去官网下载就好了,注意最好下载最近的 LTS 版本,比较稳定,我的版本是 v6.10.1。

关于 Node.js 的基本知识和用法就不介绍了,中文官网上都有,直接上 Express。

Express

简介

Express 是一个简洁而灵活的 Node.js Web 应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。使用 Express 可以快速地搭建一个完整功能的网站,它有一套健壮的特性,可用于开发单页、多页和混合Web应用。

安装 Express 代码生成器

对于一个 Express 应用来说,其代码结构大致一样(框架的统一性),所以官方随着 Express 一起也发布了一个代码生成器,打开终端,输入如下命令安装:

1
$ npm install -g express-generator

NPM 是随同 Node.js 一起安装的包管理工具,可以解决 Node.js 代码部署的很多问题,通俗地讲就是用来下载 Node.js 代码库的。

上面的 -g 参数表示全局安装,即在任何目录下都可以使用 express-generator 模块。

新建一个工程

打开终端,输入如下命令:

1
$ express -e fighting          // -e 是指定页面渲染模版为 ejs,默认为 jade

你会发现在当前目录下生成了一个 figting 目录,这是 Express 给我们生成了一个基本的框架代码,哇,刺激!下面咱们来看看这些框架代码有什么东西。

工程结构

直接通过文本编辑器打开,可以看到如下目录结构:


app.js:工程的主控文件,或者说是进行全局配置的地方。

package.json:存储着工程的信息及模块依赖,当在 dependencies 中添加依赖的模块时,运行 npm install,NPM 会检查当前目录下的 package.json,并自动安装所有指定的模块。

node_modules:存放 package.json 中安装的模块,当你在 package.json 添加依赖的模块并安装后,存放在这个文件夹下。

public:存放 image、css、js 等文件。

routes:存放路由文件。

views:存放视图文件或者说模版文件。

bin:存放可执行文件。

app.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

// 引用路由文件
var index = require('./routes/index');
var users = require('./routes/users');

// 初始化一个 express 实例
var app = express();

// 设置视图文件夹为 views,__dirname 为全局变量,存储当前正在执行的脚本所在的目录(这里也可以用"./views")
app.set('views', path.join(__dirname, 'views'));

// 设置视图渲染模版为 ejs
app.set('view engine', 'ejs');

// 各种中间件的添加
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

// 设置 public 文件夹为存放静态文件的目录
app.use(express.static(path.join(__dirname, 'public')));

// 路由控制器
app.use('/', index);
app.use('/users', users);

// 捕获 404 错误,并转发到错误处理器
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// 开发环境下的错误处理器,将错误信息渲染 error 模版并显示到浏览器中
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});

module.exports = app;

bin/www

这个 www 文件可以理解为程序的入口,同样直接看代码:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env node

var app = require('../app');
var debug = require('debug')('fighting:server');
var http = require('http');

// 创建 http 服务器,并监听 3000 端口
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
var server = http.createServer(app);
server.listen(port);

// 事件监听
server.on('error', onError);
server.on('listening', onListening);

// 是否在运行时指定端口
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
}

// 错误处理
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

// 监听端口成功
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

routes/index.js

路由文件,来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var express = require('express');
var router = express.Router();

// 路径是"/",渲染一个名叫 index 的文件作为返回内容(目录在 app.js 中指定过),并且传递了一个 title 参数
// 如果是想返回 json 数据,这里改成 res.json({}); 即可
// 这里是 get 请求,如果是提供给客户端使用的 Api 接口,也可以使用 post 请求

router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});

module.exports = router;

// 关于请求参数的解释:
// req.query : 处理 get 请求,获取 get 请求参数
// req.params : 处理 /:xxx 形式的 get 或 post 请求,获取请求参数
// req.body : 处理 post 请求,获取 post 请求体
// req.param() : 处理 get 和 post 请求,但查找优先级由高到低为 req.params → req.body → req.query

views/index.ejs

前端真正显示的内容,这里很简单,和一般的 Html 基本相似,只是使用了 <%= title% > 这个东西,其实这就是把之前传递过来的 title 变量全部替换成了 Express 而已。

关于模版引擎不作详解,这篇博客的 Node.js 重点在后台服务。

启动第一个工程

打开终端,cd 到 fighting 目录,执行如下命令:

1
$ npm start

如果控制台没有报什么错误的话,就已经启动成功了。打开浏览器,输入 http://localhost:3000/,可以看到如下页面:


到了这里,写一些基于 Express 的路由接口应该没什么问题了,但是一般的后台服务程序不可能一直把数据存在内存里面吧,所以使用数据库进行数据的管理是必不可少的,下面咱们来看看和 Node.js 形影不离的 MongoDB~~~

MongoDB

简介

MongoDB 是一个基于分布式文件存储的 NoSQL(非关系型数据库)的一种,由 C++ 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 支持的数据结构非常松散,是类似 json 的 bjson 格式,因此可以存储比较复杂的数据类型。MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB 没有关系型数据库中行和表的概念,不过有类似的文档和集合的概念。文档是 MongoDB 最基本的单位,每个文档都会以唯一的 _id 标识,文档的属性为 key/value 的键值对形式,文档内可以嵌套另一个文档,因此可以存储比较复杂的数据类型。集合是许多文档的总和,一个数据库可以有多个集合,一个集合可以有多个文档。

下面是一个 MongoDB 文档的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{ 
"_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),
"name" : "nswbmw",
"age" : 22,
"email" : [ "xxx@126.com", "xxx@gmail.com" ],
"family" : {
"mother" : { ... },
"father" : { ... },
"sister : {
"name" : "miaomiao",
"age" : 27,
"email" : "xxx@163.com",
"family" : {
"mother" : { ... },
"father" : { ... },
"brother : { ... },
"husband" : { ... },
"son" : { ... }
}
}
}
}

更多有关 MongoDB 的知识可以查阅:http://www.mongodb.org/

安装 MongoDB

安装 MongoDB 很简单,去官网下载对应系统的 MongoDB 压缩包解压即可。

启动 MongoDB

刚安装上是没办法使用 MongoDB 的,需要启动服务。打开终端,运行如下命令:

1
$ mongod --dbpath (你的数据路径) --logpath (你的日志路径)

这里容易发生出现两个小问题。一个是启动失败返回 100 错误码,这里需要把 /data 目录下的 mongod.lock 删除,再次启动;另一个是默认端口被占用,那么修改端口或者结束占用端口的进程便可以解决!

端口号也可以指定,使用 -port 命令,MongoDB 的默认端口号是 27017。

这里一般不会有什么问题,运行后再执行 mongo,会发现进入了 Mongo 的命令控制台,这样表明启动成功啦!

启动后,当然是要玩儿了!这里列一些比较常用的命令吧:

1
2
3
4
5
6
7
8
9
$ db                                   // 查看当前使用的数据库名称,默认是 test

$ show dbs // 列出当前连接的 MongoDB 的所有数据库名称

$ use (db) // 切换数据库,若数据库不存在,则创建

$ show collections // 列出当前数据库存在的集合名称

$ db.(collection).find().pretty() // 列出指定集合名称的所有文档数据,并且格式化

使用命令行可能对于某些人不太习惯(事实上大部分人都不会习惯),这里我使用了一款客户端软件 MongoChef,非常好用!

使用 Mongoose 模块

对于 Node.js 操作 MongoDB 有很多框架,这里推荐一个 Mongoose,特点是简单好用,给一个入门网站

配置 mongoose 模块

使用前当然是要安装 mongoose 模块了,cd 到 fighting 目录,输入如下命令:

1
$ npm install mongoose --save    // --save 指定安装时同时写入 package.json 的 dependencies 标签下

写代码1

安装后当然是要使用了,在 fighting 目录下建立一个目录 models,专门存放所有和数据库有关的对象。首先写一个连接对象,名为 db.js,代码如下:

1
2
3
4
5
6
7
8
9
10
var mongoose = require('mongoose');
mongoose.Promise = Promise;

// 连接数据库,数据库名为 fighting,端口号为 27017
var db = mongoose.createConnection('mongodb://localhost:27017/fighting');
// 监听连接的情况,并在控制台输出响应内容
db.on('error', (error) => console.log('连接数据库错误 => ' + error));
db.on('open', () => console.log('连接数据库成功'));

module.exports = db;

上面中的代码有一个 Promise,简单解释下,这是 ES6 用来传递异步操作消息对象,简单点说就是可以把耗时操作放在后台线程中执行,给一个学习网站吧——Javascript 中的神器——Promise

接着咱们创建一个实体对象 user.js,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mongoose = require('mongoose');
var db = require('./db');

var userSchema = new mongoose.Schema(
{
username: String,
password: String,
head: String,
nick: String,
registerTime: String,
lastLoginTime: String
}
);

module.exports = db.model('user', userSchema);

很简单,就是指定名为 user 文档中的字段和类型,然后导出这个模块。

mongoose 有一个有趣的东西,就是你指定的文档名他会变为复数形式,比如这里的 user 会变成 users!

写代码2

连接对象和实体对象都创建完成了,接下来是不是要对其进行增删改查了呢?

1
2
3
4
5
6
7
8
// 定义一个要插入的数据库对象
var userEntity = {username: 'admin', password: '123456', nick: '管理员'};

// 使用 user 模块并插入
var userModel = require('../models/user');
userModel.create(userEntity).then(user => {
// 这里返回的 user 便是插入成功的对象
});

1
2
3
4
5
6
7
8
// 删除条件,这里指定 username
var userEntity = {username: username};

// 使用 user 模块并删除
var userModel = require('../models/user');
userModel.delete(userEntity).then(() => {
// 删除成功
});

1
2
3
4
5
6
7
8
// 定义修改的数据库对象
var userEntity = {username: 'admin', password: '123', nick: '金梧'};

// 使用 user 模块并更新
var userModel = require('../models/user');
userModel.update(userEntity).then(() => {
// 更新成功
});

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义修改的数据库对象,这里指定 username
var userEntity = {username: 'admin'};

// 使用 user 模块
var userModel = require('../models/user');
// 找列表
userModel.find(userEntity).then(users => {
// 这里返回的 users 便是查找成功的对象
});
// 找唯一对象
userModel.findOne(userEntity).then(user => {
// 这里返回的 user 便是查找成功的对象
});

关于 MongoDB 以及如何在 Node.js 中使用就简单介绍到这里,如果有兴趣,去官网看文档是最好的方式。

动手写项目

有了上面的基础之后,就可以开始写项目了。之前说过想做一个关于健身数据管理的小应用,那么首先是需要把客户端需要的接口先写出来,直接开干!

需要哪些接口

由于客户端的功能很简单,仅仅是一个用户每天的数据需要显示和上传,那么只需要五个接口:用户数据字段获取、用户注册、用户登录、获取数据、上传数据。

用户数据字段获取

为什么会有这个接口?

用户数据指的是用户每天针对身体、训练及饮食情况的具体数据,比如体重、做俯卧撑的情况等,这些字段如果直接在客户端写死会很不灵活,如果想增加新的字段需要更新客户端。而如果通过在后台配置这些字段,前台动态获取,这样能解决因新增字段过于频繁更新客户端的麻烦。

那应该怎么做呢?首先应该定义好一个配置文件,里面放置前台需要的字段。新建 files 目录,在目录下新建一个文件,名为 fighting.config,写入下面的配置信息:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[
{
"name": "身体情况",
"items": [
{
"name": "称重(kg)",
"hint": "请输入重量(无任何负重)",
"field": "weight",
"value": ""
}
]
},
{
"name": "饮食情况",
"items": [
{
"name": "早餐",
"hint": "请输入早餐",
"field": "breakfast",
"value": ""
},
{
"name": "午餐",
"hint": "请输入午餐",
"field": "noon",
"value": ""
},
{
"name": "晚餐",
"hint": "请输入晚餐",
"field": "dinner",
"value": ""
}
]
},
{
"name": "运动情况",
"items": [
{
"name": "跑步",
"hint": "请输入跑步时间和里程",
"field": "run",
"value": ""
},
{
"name": "杠铃卧推",
"hint": "请输入组数和每组个数",
"field": "wotui",
"value": ""
},
{
"name": "杠铃推举",
"hint": "请输入组数和每组个数",
"field": "tuiju",
"value": ""
}
]
}
]

这里只列出了一小部分,也只是最简单的定义,对每个字段还进行了类别的划分。有了配置文件之后,便可以写出对应的接口了,在 routes 目录下新建 config.js 文件,写入代码:

1
2
3
4
5
6
7
8
9
10
11
var fs = require('fs');
var express = require('express');
var router = express.Router();

router.get('/getFightingConfig', (req, res, next) => {
// 读取对应的配置文件,并返回给客户端
var config = fs.readFileSync('./files/fighting.config', 'utf8');
res.json({status: 200, data: JSON.parse(config)});
});

module.exports = router;

这里的路径指定了 /getFightingConfig,那么可以通过访问 http://localhost:3000/getFightingConfig/ 获取对应的数据(关于如何运行代码上面讲过了,之后不再赘述)。

用户注册

还记得之前使用 mongoose 定义的一个 user 对象吧,直接拿来使用,新建 register.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var express = require('express');
var userModel = require('../models/user');
var router = express.Router();

router.get('/register', (req, res, next) => {
var username = req.query.username;
var password = req.query.password;
var nick = req.query.nick;

// 参数检查
if (username == null) {
res.json({status: 201, msg: '缺少参数username'});
return;
}
if (password == null) {
res.json({status: 201, msg: '缺少参数password'});
return;
}
if (nick == null) {
res.json({status: 201, msg: '缺少参数nick'});
return;
}

getUser(username, user => {
// 检查用户是否存在
if (user != null) {
res.json({status: 201, msg: '该用户已存在'});
return;
}

// 正确注册
var userEntity = {username: username, password: password, nick: nick, registerTime: utils.getDateTime()};
userModel.create(userEntity).then(user => {
res.json({status: 200, data: user});
});
});

});

// 获取用户信息
function getUser(username, callback) {
userModel.findOne({username: username}).then(user => {
callback(user);
});
}

module.exports = router;

这里代码也是很简单,首先获取客户端传入的参数,检查其是否齐全。然后先通过 username 参数从数据库查询,如果存在这个用户告诉客户端不能注册,否则往数据库里面插入一条用户数据,成功后通知客户端注册成功。这样一个注册接口也写好了!

接下来可以顺利地写下用户登录、获取数据和上传数据的接口,都是对数据库的增删改查,非常地简单~~~

有一个小坑:Https

本人在完成后台一系列的流程后,对接微信小程序时发现:如果创建项目时指定了 AppID(必须指定AppID才能审核、发布),那么访问的接口必须是 Https 协议,并且要把服务器的地址配置在其后台才能使用。所以,之前上面使用的基于 Http 协议的服务需要改动才能供微信小程序使用,这里方法有很多,可以使用 OpenSSL,也可以去网上找。这里我使用了阿里云的证书服务,免费、简单、快速,给一个教程链接

远程部署项目

当配置完 Https 协议的证书和相关代码后,接下来需要把项目部署在真正的服务器上面。

对于个人开发者而言,服务器不需要太好,所以我使用的是一个名叫搬瓦工的 VPS(虚拟专用服务器),很便宜,而且速度不错,没事挂点自己的小服务在上面是非常好的!

拷贝项目

有了服务器之后,然后把该装的环境都装上,接着就是要把项目都拷贝到服务器上去,有几种做法:

1、通过 SSH 远程连接 VPS,通过拷贝命令拷贝文件:

1
2
3
$ ssh -p (port) root@(IP)                 // 连接指定 IP 指定端口号的 VPS,以 root 权限进入

$ scp root@(IP):/home/A /home/B // 将本地 home/A 拷贝至目标主机的 home,并命名为 B

2、通过 Git 上传代码,服务器下载。这种方式较推荐,需要在服务器实现安装 Git Shell。

3、在服务器搭建 FTP 服务,通过 FTP 客户端直接操作文件。本人用的就是这种方式,另外用的客户端软件灰常好用,推荐 FileZilla,下面是 FileZilla 连接上服务器后的界面,是不是很直观呢!


运行项目

接下来是要在服务器运行项目,和本地运行不同的是,项目需要在服务器的后台一直运行,不能说是一退出就没了!另外有可能端口号被占用,这里介绍几条需要用到的命令( Linux 命令):

1
2
3
4
5
# forever start(可执行程序名)           // 永远将指定程序运行在后台

# pkill (进程名) // 杀死指定进程名的进程

# lsof -i:(port) // 列出指定端口被占用的进程情况

至此,关于后台的所有流程都走完了,终于要到微信小程序出场了,很激动有木有!

微信小程序

按照惯例,这里是不是应该介绍一把,算了,自己去微信公众平台了解吧,很详细!

准备工作

首先得去微信公众平台网站注册一个小程序账号,按照流程一步一步来,没有什么坑要过,注意类型写个人的就好了!

接着创建一个小程序,也是按照其流程一步一步走,创建成功后会得到一个 AppID,之后会有用。

然后下载并安装微信 web 开发工具,打开后点击“添加项目”,会有如下界面:


填入 AppID 和项目相关信息,进入后便开始写代码。

玩转开发工具

关于微信小程序的代码没打算介绍,因为官网上的介绍已经非常全面了,而且是最新的,对着文档学习一到两个小时就可以开始写代码了(前提是得有 Html、CSS 和 JavaScript 的基础)。这里主要来看一看开发工具怎么玩的!

编辑

这里主要是代码编辑,简单明了:


关于收起和展开需要根据自己显示的大小合适调整,另外手机型号和网络情况都可以进行选择。

调试

主要是页面预览和控制台相关的信息,还包括网络、内存、闪存、GPS模拟型号等信息,另外左下角还有对缓存清除的按钮:


项目

这里主要是在代码功能编写完成后,对项目进行发布的流程之一,如下图:


发布

当代码上传成功后,进入微信公众平台-小程序-开发管理,可以看到刚刚提交的代码信息,如图:


接着要进行代码审核,点击“提交审核”,便可根据流程提交审核,需要注意的是配置的页面和类别一定要对应,不然审核会过不去。

大概等一到两天吧,审核通过后点击“发布”,即可在微信小程序中搜索自己的小程序啦!

结尾

奉上我自己的小程序吧,功能很简单,不要吐槽,哈哈哈!!!这只是第一个版本,还在升级中,UP!UP!UP!

坚持原创技术分享,您的支持将鼓励我继续创作!