vue+express+mongodb——个人全栈博客项目(二)

2,523 阅读4分钟

前言

上篇文章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文件夹,在此文件夹下创建adminweb 两个文件夹,分别为后台管理和前台博客的路由

后台管理路由(通用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服务端的时候确实是遇到了不少问题,毕竟第一次写,期间也有认真解决所遇到的问题,尽管写的东西比较简单,但也让我了解到了一些服务端的知识以及思想,算是有所收获吧