Express+MongoDB后端与微信小程序前端建构最小端到端系统

服务端搭建


一、环境准备

1.操作系统与网络

本文以Arch Linux为例,在VirtualBox虚拟机下运行,通过SSH与宿主机连接。
由于虚拟机通过NAT联网,要想访问在虚拟机中部署的Web服务需要进行端口转发:

  • 打开VirtualBox管理器,进入Arch虚拟机的设置页,在网络选项卡中点击高级
  • 点击端口转发,我们新建一个端口转发规则。协议为TCP;主机IP填写VirtualBox虚拟网卡的地址,在Windows宿主机通过ipconfig命令可查看,默认为192.168.56.1;子系统IP填写虚拟机IPv4地址,在Arch虚拟机中通过ip addr命令可查看(图中红框所示处);子系统端口为我们即将部署的Node服务的监听端口;主机端口为转发到主机后的端口,我们应该避开常用或已被占用的端口(如21、22、25、80、443),为了方便我们可以转发到原端口,本文以3000端口为例
  • 这样设置完毕后虚拟机的3000端口就可以通过192.168.56.1:3000访问了

    2.Node.js

    由于该虚拟机此前已安装Node.js、MongoDB等,首先将其卸载再做演示:
    1
    $ pacman -Rsc nodejs mongodb  #删除Node.js、MongoDB和所有依赖这两个软件包的程序

更多关于pacman(Arch官方软件包管理器)的用法请见这里
通过pacman安装Node.js:

1
$ pacman -S nodejs npm  #安装node及包管理器npm

或者从Node.js官网下载二进制文件:

1
2
3
$ wget https://nodejs.org/dist/v8.12.0/node-v8.12.0-linux-x64.tar.xz
$ xz -d node-v8.12.0-linux-x64.tar.xz
$ tar -xvf node-v8.12.0-linux-x64.tar

3.MongoDB

  • 方法一:
1
2
3
$ pacman -S mongodb  #从官方软件仓库安装mongodb
$ systemctl start mongodb.service #启动mongodb服务
$ systemctl enable mongodb.service #设置开机启动服务

Arch Linux的默认dbpath是/var/lib/mongodb/,启动服务后可以通过mongo命令进入Mongo Shell

  • 方法二:
1
2
3
$ cd /home
$ wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.2.tgz #从官网下载二进制包
$ tar -xzvf mongodb-linux-x86_64-4.0.2.tgz

为了快速使用mongodb命令,可以配置环境变量。编辑~/.profile/etc/profile文件,将mongodb/bin路径加入即可:

1
$ sudo vim /etc/profile

在文件末尾添加一行:

1
export PATH=$PATH:/home/mongodb-linux-x86_64-4.0.2/bin

保存修改后,在终端运行以下命令使环境变量生效:

1
$ source /etc/profile

启动mongod

1
2
$ mkdir /data/db  #创建数据存放目录
$ mongod --dbpath /data/db

此时mongod已经启动,我们可以在另一终端中通过mongo命令进入交互程序

二、部署服务

1.建立应用并安装依赖

首先建立一个工作目录:

1
2
$ mkdir wx
$ cd wx

通过npm init初始化一个应用,此时会要求你输入应用的名称、版本、入口文件等信息,可以按Enter键选择默认值,最终npm会为你生成一个package.json文件

1
$ npm init

下面通过Node包管理器安装Express、Mongoose:

1
$ npm install express mongoose --save

安装成功后会出现+ express+ mongoose等字样,这两个包及其所需依赖都被安装在/node_modules中并被写入package.json


除了上述方法,我们也可以通过全局安装来达到一劳永逸的目的:
1
$ npm install express mongoose -g

2.建立HWMongo模块

编辑hwmongo.js

1
vim hwMongo.js

将数据库连接与定义Schema部分写进hwMongo.js,以便后续调用:

1
2
3
4
5
6
7
8
9
10
11
//hwmongo.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test'); //与数据库建立连接

var helloWorldSchema = mongoose.Schema({
country: String,
helloWorld: String,
}); //定义数据架构helloWorldSchema
var HWMongo = mongoose.model('HWs', helloWorldSchema);

module.exports = HWMongo; //导出HWMongo

3.初始化数据

编辑initData.js

1
vim initData.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
var HWMongo = require('./hwMongo');

new HWMongo({
country: 'China',
helloWorld: '你好,世界!'
}).save();

new HWMongo({
country: 'US',
helloWorld: 'Hello World!'
}).save();

new HWMongo({
country: 'Danmark',
helloWorld: 'Hallo, Verden!'
}).save();

new HWMongo({
country: 'Germany',
helloWorld: 'Hallo, Welt!'
}).save();

new HWMongo({
country: 'France',
helloWorld: 'Bonjour, tout le monde'
}).save();

运行程序:

1
node initData.js

如果出现“当前URL解析器被弃用”的警告,我们可以修改hwMongo.js的数据库连接部分(第3行),使用新的解析器:

1
mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true});  //Line 3

数据初始化完成之后我们可以通过mongo进入Mongo Shell,在交互程序中通过use wx切换至数据库“wx”,通过show collections可以列出数据库中的全部集合,通过db.hws.find()可以进行查询:

1
2
3
4
5
6
7
8
9
10
> use wx
switched to db wx
> show collections
hws
> db.hws.find()
{ "_id" : ObjectId("5bb597c822106c03e8e75b72"), "country" : "US", "helloWorld" : "Hello World!", "__v" : 0 }
{ "_id" : ObjectId("5bb597c822106c03e8e75b73"), "country" : "Danmark", "helloWorld" : "Hallo, Verden!", "__v" : 0 }
{ "_id" : ObjectId("5bb597c822106c03e8e75b74"), "country" : "Germany", "helloWorld" : "Hallo, Welt!", "__v" : 0 }
{ "_id" : ObjectId("5bb597c822106c03e8e75b75"), "country" : "France", "helloWorld" : "Bonjour, tout le monde", "__v" : 0 }
{ "_id" : ObjectId("5bb597c822106c03e8e75b71"), "country" : "China", "helloWorld" : "你好,世界!", "__v" : 0 }

4.建立HTTP服务器

编辑入口文件index.js

1
vim index.js

index.js中编写简易HTTP服务器与查询服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express');
const HWMongo = require('./hwMongo.js'); //调用模块
const app = express();
const port = 3000; //http服务器端口号

app.get('/country', function(req,res) {
HWMongo.findOne({'country': req.query.country}, function(err, doc) {
if (err) {
console.log("数据库出错");
} else if (doc==null) {
res.send("输入信息有误,请重试");
console.log(req.query.country + ":" + doc);
} else {
res.send(doc.helloWorld); //应答报文
console.log(req.query.country + ":" +doc.helloWorld);
}
});
});

app.listen(port, function() {
console.log('Server is running at http://localhost:3000/.');
});

HTTP服务器跑起来后宿主机所在局域网内的设备可以通过http://192.168.56.1:3000来访问:

1
node index.js


至此,服务端已搭建完成


前端小程序实现


一、开发工具

这里下载并安装微信web开发者工具

二、项目与项目配置文件

新建一个空白项目,编辑项目配置文件project.config.json,为了调试方便,我们将urlCheck一项改为false:

1
2
3
4
5
6
7
8
{
...
"setting": {
"urlCheck": false,
...
},
...
}

更多关于项目配置文件的描述请见这里

三、文件结构

从微信小程序官方文档中我们可以知道小程序的文件组成结构,具体内容如下

小程序主体部分(项目根目录下):

文件 必需 作用
app.js 小程序逻辑
app.json 小程序公共配置
app.wxss 小程序公共样式表

小程序页面:

文件类型 必需 作用
js 页面逻辑
wxml 页面结构
json 页面配置
wxss 页面样式表

因此我们在项目目录中新建app.jsapp.json,并编辑app.json

1
2
3
4
5
{
"pages": [
"pages/index/index"
]
}

此时开发工具会自动建立pages/index目录及其页面组成文件,至此只包含一个页面的最小项目的框架搭建完成,其文件结构如下:

1
2
3
4
5
6
7
8
.
├── app.js
├── app.json
├── pages
│   └── index
│   ├── index.js
│   └── index.wxml
└── project.config.json

四、页面逻辑及生命周期

每个页面的JS文件中通过Page(Object)函数来注册页面,它接受一个对象类型参数,指定页面的初始数据、生命周期回调、事件处理函数等。
我在此列举几个主要、常用的对象参数(详见这里):

属性 类型 说明
data 对象 页面第一次渲染使用的初始数据,JSON类型
onLoad 函数 页面加载时触发,一个页面只会调用一次
onShow 函数 页面显示/切入前台时触发
onReady 函数 页面初次渲染完成时触发,一个页面只会调用一次
onHide 函数 页面隐藏/切入后台时触发
其他 任意 开发者添加任意函数或数据

其中所有函数类型的参数均为生命周期回调函数
我们编辑pages/index的页面逻辑文件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
33
34
35
36
37
Page({
//页面的初始数据
data: {
hidden: true,
data_from_mongodb: ""
},

//开发者自定义函数
query: function() {
var index = Math.floor(Math.random() * 5) //产生0-4的随机数
var wxcountry = ""
if (index == 0) {
wxcountry = "China"
} else if (index == 1) {
wxcountry = "US"
} else if (index == 2) {
wxcountry = "Danmark"
} else if (index == 3) {
wxcountry = "Germany"
} else if (index == 4) {
wxcountry = "France"
}
var that = this
wx.request({
url: "http://192.168.56.1:3000/country",
data: {
country: wxcountry
},
success: function(res) {
that.setData({
hidden: false,
data_from_mongodb: wxcountry + " : " + res.data
})
},
}) //从后端请求数据
},
})

由于微信小程序支持ECMAScript 6ES6)标准,因此JS代码可以省略语句末的分号;,并且微信官方所有示例程序均采用省略,因此我们也应该遵循微信小程序的编码规范,语句末省略分号;

五、编译与运行

点击开发者工具工具栏中的编译按钮,即可在左侧的模拟器中看到运行效果:


至此,前端部分建构完成


将服务部署至VPS/云服务器


我们可以把我们建构好的服务部署至服务器以实现公网访问,我将该Node+Express+MongoDB组成的服务端部署在Vultr的VPS上(新加坡机房,用来搭梯子的),由于操作系统是CentOS,在部署过程中部分操作略有不同,在此不再赘述。
部署后将微信小程序中pages/index/index.js的查询函数中的URL修改为VPS/云服务器的公网IP:端口号,实现了全网访问服务:


至此,最小端到端系统实验完成


总结


本实验使我巩固了Node.js、MongoDB、Linux运维、网络原理等多方面知识,并对微信小程序的开发有了一定了解,增强了我对新事物的探索能力。我将在继续深入钻研既学技术的同时努力接触新技术,让自己的技术栈更加充实