前言
上篇文章vue+express+mongodb——个人全栈博客项目(一)简要介绍了一些项目的大致情况,这次接着上篇文章讲一下服务端搭建项目的具体流程
初始化项目结构
首先创建目录(我是在整个项目文件下新建一个server
文件夹)并初始化,初始化node项目一般是用
npm init -y
安装express
npm i express
生成package.json
文件,然后在server
目录下新建入口文件app.js
const express = require('express');
app = express();
app.listen(3000, () => {
console.log('http://localhost:3000');
})
为了接下去运行项目方便,在package.json
中添加启动命令(个人习惯,统一成与vue运行命令相同)
"scripts": {
"serve": "nodemon app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
连接MongoDB数据库
首先安装MongoDB: 菜鸟教程MongoDB安装
安装之后根据不同平台的对应方法启动服务。
然后安装Mongoose:
npm i mongoose
Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具,使用起来要比底层的 MongoDB Node 驱动更方便。
在server目录下新建一个plugins
文件夹,再在plugins
文件夹下新建数据库连接文件db.js
module.exports = app => {
const mongoose = require("mongoose")
mongoose.connect('mongodb://127.0.0.1:27017/vue-blog', {
useNewUrlParser: true,
useUnifiedTopology: true
})
}
接着在入口文件app.js
中将其
引用进来
require('./plugins/db')(app)
创建数据模型
在项目根目录建立一个models
文件夹,用来存储数据模型,定义要存的数据片段,数据类型是什么。以文章为例,在models
文件夹下新建一个Article.js
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
//标题
title: { type: String },
//简介
introduction: { type: String },
//封面
cover: { type: String },
//内容(markdown)
body: { type: String },
//html
content: { type: String },
//所含标签
tags: { type: Array },
//标签id
tagid: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Tag' }
],
//更新日期
date: { type: String },
//点击量
click: {type: Number},
//评论数量
comment: {type: String},
//作者
author: { type: mongoose.SchemaTypes.ObjectId, ref: 'User'}
})
//导出model模块
module.exports = mongoose.model('Article', schema)
建立路由接口
在server
文件夹下创建一个routes
文件夹,在此文件夹下创建admin
和web
两个文件夹,分别为后台管理和前台博客的路由
后台管理路由(通用CURD)
在server
下新建一个middleware
文件夹用于存放中间件,在此文件夹下新建一个resource.js
文件
此时需要安装一个inflection
的包来将小写的资源名称转换为大写的类名
npm i inflection
module.exports = options => {
return async (req, res, next) => {
const modelName = require('inflection').classify(req.params.resource)
req.Model = require(`../models/${modelName}`)
next()
}
}
在admin
文件夹下新建一个index.js
文件
module.exports = app => {
const express = require('express')
const router = express.Router()
// 创建资源
router.post('/', async (req, res) => {
const model = await req.Model.create(req.body)
res.send(model)
})
// 更新资源
router.put('/:id', async (req, res) => {
const model = await req.Model.findByIdAndUpdate(req.params.id, req.body)
res.send(model)
})
// 删除资源
router.delete('/:id', async (req, res) => {
await req.Model.findByIdAndDelete(req.params.id)
res.send({
success: true
})
})
// 资源列表
router.get('/', async (req, res) => {
const queryOptions = {}
const items = await req.Model.find().setOptions(queryOptions).limit(100)
res.send(items)
})
// 资源详情
router.get('/:id', async (req, res) => {
const model = await req.Model.findById(req.params.id)
res.send(model)
})
//中间件
const resourceMiddleware = require('../../middleware/resource')
app.use('/admin/api/rest/:resource', resourceMiddleware(), router)
}
因为没有任何一个路由是写死的,这样一套接口可以给所有资源的增删改查去使用,但有些需要关联查询的地还是要分开处理的
图片上传
在server
目录下创建uploads
文件夹,用于存放后台富文本编辑器中上传的图片(现在正尝试改为上传到七牛云), 然后安装 multer 中间件用于处理文件上传:
npm i multer
初始化 multer 中间件,然后将其添加到上传图片的路由中
const multer = require('multer')
const upload = multer({dest: __dirname + '/../../uploads'})
app.post('/admin/api/upload',upload.single('file'), async(req, res) => {
const file = req.file
file.url = `http://localhost:3000/uploads/${file.filename}`
res.send(file)
})
在app.js
中托管静态文件
app.use('/uploads', express.static(__dirname + '/uploads'))
前台博客路由
正常的资源列表、资源详情等就不过多废话了,主要说一下文章搜索以及用户登录的实现
使用正则的模糊查询
//搜索一些文章
router.get('/search', (req, res) => {
let regexp = new RegExp(req.query.key, 'i')
Article.find(
{
$or: [
{ title: { $regex: regexp } },
{ introduction: { $regex: regexp } },
{ content: { $regex: regexp } },
{ tags: { $regex: regexp } },
{ date: { $regex: regexp } },
]
}, (err, doc) => {
if (err) {
console.log(err)
res.send({
code: 400,
msg: "查询失败"
})
}
if (doc) {
res.send({
code: 200,
msg: "查询成功",
data: doc
})
}
})
})
登录方面,首先要注意的是在用户模型中的密码要进行散列加密,需要安装一个bcrypt
模块
npm i bcrypt
set
自定义保存的值,hashSync()
的第二个参数为散列的指数,可以理解为加密指数,指数越高越安全也越耗时,一般10-12比较合理,select
设置为false表示密码默认是取不到的,
const mongoose = require('mongoose')
const schema = new mongoose.Schema({
username: { type: String },
password: {
type: String,
select:false,
set(val) {
return require('bcrypt').hashSync(val, 10)
} },
nickname: { type: String },
email: { type: String },
avatar: { type: String },
isAdmin: { type: Boolean, default: false }
})
module.exports = mongoose.model('User', schema)
登录路由需要安装jsonwebtoken
(token值)和http-assert
(简化错误返回)两个模块,然后在routes/web/index.js
中引入
const jwt = require('jsonwebtoken')
const assert = require('http-assert')
jwt.sign()
接受两个参数,一个是传入的对象,一个是自定义的密钥,简单点这个秘钥我就直接在app.js
里设置了
app.set('secret', 'i2u34y12oi3u4y8')
assert
第一个参数为条件,第二个参数为如果不满足抛出的错误状态码,第三个参数为文字信息
compareSync
解密匹配,第一个参数是明文,第二个参数是密文,返回 boolean 值
app.post('/admin/api/login', async (req, res) => {
const { username, password } = req.body
// 1.根据用户名找用户
const user = await User.findOne({ username }).select('+password')
assert(user, 422, '用户不存在')
// 2.校验密码
const isValid = require('bcrypt').compareSync(password, user.password)
assert(isValid, 422, '密码错误')
// 3.返回token
const token = jwt.sign({ id: user._id }, app.get('secret'))
res.send({ userinfo: user, token: token })
})
最后记得在app.js
中应用这两个路由
require('./routes/admin')(app)
require('./routes/web')(app)
处理跨域情况
因为express
服务是在localhost:3000
端口启动的,而vue-cli创建的项目是在默认端口localhost:8080
启动的,所以肯定会涉及到跨域的情况
在server
目录下安装cors
模块
npm i cors
在app.js
中应用,就能实现跨域,这是最简单的配置
app.use(require('cors')())
接口调试
服务端在写的过程中可以使用Postman进行接口测试,简单实用大方美观,文档:Postman官方文档
总结
个人在写node服务端的时候确实是遇到了不少问题,毕竟第一次写,期间也有认真解决所遇到的问题,尽管写的东西比较简单,但也让我了解到了一些服务端的知识以及思想,算是有所收获吧