服务端技术栈介绍
初始化服务器端项目
# 创建项目文件夹
mkdir ecommerce-end
cd ecommerce-end
# 生成 package.json
npm init -y
创建初始目录结构
├─ config # 存放应用配置文件
├─ controllers # 存放 MongoDB 数据管理方法
├─ helpers # 存放辅助函数
├─ models # 存放 MongoDB 连接、Model(mongoose 概念)
├─ pem # 存放支付宝密钥
├─ routes # 路由接口配置
├─ validator # 存放路由接口前置校验回调
├─ .env # dotenv 环境变量
├─ app.js # 应用入口文件
├─ .gitignore # git 忽略文件配置
├─ 接口文档.md
└─ package.json
安装依赖
npm i express express-jwt express-validator cors body-parser cookie-parser morgan formidable mongoose mongoose-unique-validator nodemon config dotenv cross-env jsonwebtoken order-id uuid lodash alipay-sdk
- 服务器相关
express
:基于 Node.js 的 web 应用开发框架express-jwt
:用于解析 JWT 的中间件express-validator
:校验中间件cors
:CORS 中间件body-parser
:HTTP 请求体解析中间件cookie-parser
:请求体 Cookie 解析中间件morgan
:HTTP 请求日志中间件formidable
:解析表单数据的工具,尤其是文件上传
- 数据库相关
mongoose
:在 Node.js 异步环境下对 MongoDB 进行便捷操作的对象模型工具mongoose-unique-validator
:mongoose 插件,为 Mongoose Schemoa 中的唯一标识属性添加预保存验证
- 应用相关
nodemon
:自动检测并重启应用的工具config
:Node.js 应用配置工具dotenv
:.env
文件设置环境变量cross-env
:跨平台使用脚本设置环境变量的工具
- 安全权限相关
jsonwebtoken
:JWT 生成工具order-id
:唯一订单 id 生成器uuid
:UUID 生成器,生成用于密码加密的盐(Salt)
- 其它工具
lodash
:JS 工具库alipay-sdk
:支付宝服务端 SDK(通用版)(NodeJS 版)
忽略 node_module(.gitignore
):
node_modules
创建 web 服务器
配置环境变量 - 服务器监听端口
dotenv 将环境变量从 .env
文件加载到 process.env
中。
# .env
# 服务启动监听端口
APP_PORT=80
创建 web 服务和配置中间件
// app.js
const express = require('express')
const cors = require('cors')
const morgan = require('morgan')
const bodyParse = require('body-parse')
const cookieParser = require('cookie-parser')
// 创建 web 服务器
const app = express()
// 添加中间件
// 开启 CORS
app.use(cors())
// morgan 预定义的 dev 格式的请求日志
app.use(morgan('dev'))
// 解析 form 请求
app.use(bodyParser.json())
// 使用 qs 库解析 URL 编码数据请求
app.use(bodyParser.urlencoded({ extended: true }))
// 解析请求体中的 cookie
app.use(cookieParser())
// 请求接口
app.get('/', (req, res) => {
res.send('请求成功')
})
const APP_PORT = process.env.APP_PORT || 80
app.listen(APP_PORT, () => {
console.log(`服务器启动成功,监听 ${APP_PORT} 端口`)
})
配置启动脚本
编程方式加载 dotenv,需要在应用程序中尽早 require
并配置。
// 在 app.js 的顶部加载 dotenv
// `config` 会读取 `.env` 文件,解析内容,将其分配给 `process.env`。
require('dotenv').config()
也可以使用命令行方式预加载 dotevn,使用 --require
或 -r
命令行选项预加载 dotenv,这样做就不需要在应用程序代码中加载 require('dotenv')
。
package.json
:
"scripts": {
"start": "nodemon -r dotenv/config app.js"
},
运行 npm start
访问 http://localhost/
,终端会打印请求日志:
GET / 200 2.413 ms - 12GET /favicon.ico 404 1.086 ms - 150
创建本地数据库
安装工具
启动 MongoDB
# 启动 MongoDB 服务
# 管理员身份打开命令行工具
# `MongoDB` 是安装 MongoDB 时默认的服务名称
net start MongoDB
创建数据库
创建本地 MongoDB 连接
这里使用管理员身份连接本地 MongoDB,只需设置连接名称 Name
即可。
点击 Connect
连接后,右键左侧的连接 - Create Database
,创建数据库,命名为 ecommerce
。
配置数据库连接信息
node-config 模块介绍
node-config(npm 名是 config) 模块用于管理应用的配置,根据环境变量 NODE_ENV
获取对应环境下的配置,也可以在配置中使用环境变量。
配置文件可以是
.json
/.json5
/.js
文件或其它类型的文件。JSON5 是对 JSON 的扩展,可以在文件中像 JS 一样编写 JSON 对象和注释。
更多参考 Configuration Files
node-config 会自动获取根目录下 config
(默认) 文件夹中的配置文件,合并生成对应环境下的配置信息:
default.json
:默认配置参数,其它文件可能会覆盖这些参数- 覆盖是在每个参数的基础上完成,不同与对象引用的合并
- node-config 运行时会先匹配
default.json
,后续匹配的文件覆盖它,如果不存在default.json
,则继续匹配后面的文件
<NODE_ENV>.json
:env 环境配置,覆盖默认配置,根据NODE_ENV
使用同名的配置文件NODE_ENV=production
:使用product.json
文件NODE_ENV=development
:使用development.json
文件NODE_ENV=demo
:使用demo.json
文件- 当未配置
NODE_ENV
,node-config 默认其值为development
custom-environment-variables.json
:使用环境变量的配置,优先级仅低于命令行配置- 值映射的环境变量的名称,node-config 会检查环境变量,如果有则覆盖到配置中
- 一般将敏感信息存储在环境变量中
- Environment Variables · Custom Environment Variables
例如:
# .env
APP_PORT=80
DB_NAME=mydatabase
// config/default.json
{
"Customer": {
"appConfig": {
"lang": "简体中文",
"theme": "default",
"port": 1000
},
"dbConfig": {
"host": "localhost"
}
}
}
// config/development.json
{
"Customer": {
"appConfig": {
"title": "开发环境",
"theme": "blue"
}
}
}
// config/production.json
{
"Customer": {
"appConfig": {
"title": "生产环境",
"theme": "green"
},
"dbConfig": {
"host": "127.0.0.1"
}
}
}
// config/custom-environment-variables.json
{
"Customer": {
"appConfig": {
"port": "APP_PORT"
},
"dbConfig": {
"name": "DB_NAME"
}
}
}
require('config').get('Customer')
结果:
// 开发环境
{
appConfig: { lang: '简体中文', theme: 'blue', port: '80', title: '开发环境' },
dbConfig: { host: 'localhost', name: 'mydatabase' }
}
// 生产环境
{
appConfig: { lang: '简体中文', theme: 'green', port: '80', title: '生产环境' },
dbConfig: { host: '127.0.0.1', name: 'mydatabase' }
}
配置项目数据库信息
# .env
# 服务启动监听端口
APP_PORT=80
# 本地数据库连接信息
# 连接地址
DB_HOST=localhost
# 数据库名称
DB_NAME=ecommerce
# 用户名(默认为空)
DB_USER=
# 密码(默认为空)
DB_PASS=
# 端口(默认27017)
DB_PORT=27017
// config\custom-environment-variables.json
{
"dbConfig": {
"host": "DB_HOST",
"name": "DB_NAME",
"user": "DB_USER",
"pass": "DB_PASS",
"port": "DB_PORT"
}
}
连接数据库
Mongoose 介绍
Mongoose 可以连接 MongoDB 数据库。
var mongoose = require('mongoose')
// var db = mongoose.connect('mongodb://<host>:<port>/<databaseName>'[, options])
// 示例
var db = mongoose.connect('mongodb://localhost:27017/mydatabase', {
user,
pass,
// 下面的参数的作用是使用新版的功能替换已弃用(即将在未来版本中删除)的功能
// 如果不设置,就会在终端输出警告提示,并不影响使用
// 详细参考 https://mongoosejs.com/docs/deprecations.html
useNewUrlParser: true, // URL 字符串解析器
useUnifiedTopology: true // 服务器发现和监视引擎
})
Mongoose 一切始于 Schema,每个 schema 都会映射到一个 MongoDB collection,定义并约束这个 collection 里 document 的构成。
// 创建一个 Schema
var blogSchema = new mongoose.Schema({
title: String, // 约束类型为 String
date: {
type: Date, // 使用 type 属性指定约束类型
default: Date.now // 默认值
},
comments: [{ title: String, date: Date }]
})
将 Schema 编译成 Model,Model 是用来创建 document 实例的 Class,也可以用来查询 documents。
// 编译一个 Model
// 第一个参数是 Model 名称,是对应 collection 名称的`单数`形式(不区分大小写)
// MongoDB 会自动找到名称是 Model 名字`复数`形式的 collection(匹配全小写)
// 例如 Blog 就是对应数据库中 blogs 这个 collection 的 Model 名称;Category 对应 categories
// 第二个参数就是依据的 Schema
// `.model()` 就是对 Schema 做了拷贝(生成了 model)
// 要确保在调用 `.model()` 之前把所有需要的东西都加进 Schema 里
// 注意:直到 Model 使用的数据库连接打开后,才可以操作 collection
// 如果 collection 不存在,保存 save 操作会自动创建这个 collection
// 如果 collection 不存在,查询 find 操作会返回 `[]`
var BlogModel = mongoose.model('Blog', blogSchema)
// 创建一个 document 实例
var blog = new BlogModel({ title: '测试文章'})
// 保存文章
blog.save()
通过 Model 的静态方法 find 查询:
// 立即执行查询:传入 error-first 回调函数参数
BlogModel.find((error, data) => {
console.log(data)
})
// 不立即执行查询:不传入回调函数参数
// 返回一个 Query 查询实例
var query = BlogModel.find()
// 调用 Query 对象的执行查询方法:回调方式
query.exec((error, data) => {
console.log(data)
})
// 调用 Query 对象的执行查询方法:Promise 方式
query.exec().then(console.log)
更多文档请参考 mongoose 英文原版文档
创建数据库连接
// models\connect.js
const mongoose = require('mongoose')
const config = require('config')
const { host, port, user, pass, name } = config.get('dbConfig')
mongoose
.connect(`mongodb://${host}:${port}/${name}`, {
useNewUrlParser: true, // URL 字符串解析器
useCreateIndex: true, // 定义索引的函数使用 `createIndex` 方法替换废弃的 `ensureIndex`
useUnifiedTopology: true, // 服务器发现和监视引擎
useFindAndModify: false, // 使用 MongoDB 的 `findOneAndUpdate` 方法替换过早的 `findAndModify` 方法
user,
pass
})
.then(() => console.log('数据库连接成功'))
.catch(() => console.log('数据库连接失败'))
在启动 web 服务前引入这个文件:
// app.js
const express = require('express')
const cors = require('cors')
const morgan = require('morgan')
const bodyParse = require('body-parse')
const cookieParser = require('cookie-parser')
// 连接数据库
require('./models/connect')
// 创建 web 服务器
const app = express()
// 添加中间件
// 开启 CORS
app.use(cors())
// morgan 预定义的 dev 格式的请求日志
app.use(morgan('dev'))
// 解析 form 请求
app.use(bodyParser.json())
// 使用 qs 库解析 URL 编码数据请求
app.use(bodyParser.urlencoded({ extended: true }))
// 解析请求体中的 cookie
app.use(cookieParser())
// 请求接口
app.get('/', (req, res) => {
res.send('请求成功')
})
const APP_PORT = process.env.APP_PORT || 80
app.listen(APP_PORT, () => {
console.log(`服务器启动成功,监听 ${APP_PORT} 端口`)
})