全栈开发实战:基于Vue+Node.js+Mongoodb开发前后端项目

2,327 阅读6分钟

项目简介

前一段时间领导临时给了一个小需求,要做一个简单的会议报表,要满足会议表格内容的增删改查、文件的上传下载以及用户登录验证,于是趁着划水的时间写了一下,看着网上针对这一块的教程不是很多,所以写下了这篇文章,也算是入门全栈开发的一篇基础文章,大佬勿喷。

以下是项目地址,希望给个 star,鼓励一下:

github地址

效果预览:

l37qmt.gif

技术栈

项目前端基于Vue,后端基于Nodejs-Express,数据库采用Mongodb。

前端

主要是基于 Vue-Cli

UI 框架选的是:element-ui

css采用 scss

登录页的用的是element-ui的表单组件,没有单独写页面,原谅我的懒。。。

前端就不过多介绍了,也就是老一套,具体的可以看代码。

项目结构(大致)如下:

├── pulic         
│   └── index.html 
├── src
│   ├── assets   
│   ├── components    // 主组件    
│   └── plugin       // element插件
│   └── App.vue      
│   └──http.js       // 对 axios进行 二次封装
│   └── main.js       
├── .env              // 环境变量配置文件

Tip: 结构很简单,主要是axios封装,组件element+bootstrap。

后端

后端采用Nodejs,框架采用的Express,这个可以根据个人兴趣来,Koa,nestjs,Hapi都可。不做过多评价。

后端项目当中用到的第三方库,主要是列一下最主要的。

  "dependencies": {
     // 加密用户密码(数据库没有存明文密码)这里强调提下windows最好用bcryptjs,mac可以用bcrypt。
    "bcryptjs": "^2.4.3",
     //解决跨域
    "cors": "^2.8.5",
      //这里用的是express下一版本
    "express": "^5.0.0-alpha.7",
      //
    "http-assert": "^1.4.1",
      //目前最流行的跨域认证
    "jsonwebtoken": "^8.5.1",
      //一款为异步工作环境设计的 MongoDB 对象建模工具
    "mongoose": "^5.7.13",
    "morgan": "^1.9.1",
      //express 中间件 上传下载文档
    "multer": "^1.4.2",
  }

后端的项目结构如下,这是自己搭建的,最好还是按照规范来。

│   ├── middleware// 自定义中间件目录
│   ├── models    //定义的表结构
│   ├── plugin    // 数据库配置文件
│   │    
│   ├── routes    // 路由文件
│   │   
│   └── uploads    // 文件上传目录
│   │  
├── index.js        // 项目入口文件

数据库

数据库这里采用的是mongodb,具体的就不过多介绍了。

表结构如下:

表单数据模型

const mongoose = require('mongoose')
//创建模型
const schema = new mongoose.Schema({
  date: { type: Date },
  iteminfo: [
    {
      road_name: { type: String },
      road_lat: { type: Number },
      road_lng: { type: Number },
      ys_state: { type: String },
      ws_state: { type: String },
      issus_list: { type: String },
      list_before_url: [{ name: { type: String }, url: { type: String } }],
      list_after_url: [{ name: { type: String }, url: { type: String } }],
      remark: { type: String }
    }
  ]
})

//导出模型
module.exports = mongoose.model('FightInfo', schema)

用户模型

const mongoose = require('mongoose')

const schema = new mongoose.Schema({
  username: { type: String },
  password: {
    type: String,
    set(val) {
      return require('bcryptjs').hashSync(val, 10)
    }
  }
})

module.exports = mongoose.model('User', schema)

Tip: 表结构很简单,两张表结构模型都在model文件夹下面

前后端分离

项目采用前后端分离的方式进行开发,Express提供API接口,进行访问过滤(路由)、验证(JSON-WEB-TOKEN)以及mongoose操作mongoodb数据库的一些知识和技巧。

  1. 启动Express 主文件如下

    const express = require('express')
    const logger = require('morgan')
    const app = express()
    
    //请求logger  便于调试
    app.use(logger('dev'))
    //支持json
    app.use(express.json())
    //解决跨域
    app.use(require('cors')())
    
    //静态文件夹 上传下载文件
    app.use('/uploads', express.static(__dirname + '/uploads'))
    //静态文件夹
    app.use('/json', express.static(__dirname + '/plugin/json'))
    
    //连接数据库文件
    require('./plugin/db/config')(app)
    //表单的具体操作方法
    require('./routes/fightinfo')(app)
    //用户登录验证
    require('./routes/user')(app)
    
    
    app.use(async (err, req, res, next) => {
      res.status(err.statusCode || 500).send({
        message: err.message
      })
    })
    
    app.listen(3001, () => {
      console.log('http://127.0.0.1:3001/')
    })
    
  2. 数据库连接

    //mongodb数据库配置连接
    module.exports = app => {
      const mongoose = require('mongoose')
      mongoose.connect('mongodb://localhost/vue-fight-direct', {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        useFindAndModify: false
      })
    }
    
  3. 编写用户验证的路由

    Tip: 这里是用户登录注册的路由,由于没有写注册页面(我这里只是为了实现一个交互功能,好像是为了我的懒找借口。。),大家可以用第三方工具PostMan进行注册验证,字段可以根据用户模型来即可,也就是(username,password)。如果不注册的话 数据增删改查都不行,需要进行登录验证。

    module.exports = app => {
      const express = require('express')
      const router = express.Router()
      const jwt = require('jsonwebtoken')
      const bcryptjs = require('bcryptjs')
      //引入用户模型文件
      const User = require('../../models/User')
      app.set('secret', 'ashjhdjhasjdidh')
        //用户登录
      router.post('/login', async (req, res) => {
        const { username, password } = req.body
        //判断用户是否存在
        const user = await User.findOne({ username })
        if (!user) {
          return res.status(422).send({
            message: '用户不存在'
          })
        }
        //判断密码是否正确
        const isValid = bcryptjs.compareSync(password, user.password)
        if (!isValid) {
          return res.status(422).send({
            message: '密码错误'
          })
        }
        //发送token
        const token = jwt.sign({ id: user._id }, app.get('secret'))
        res.send({ token: token, message: '登陆成功' })
      })
       
      //用户注册
      router.post('/register', async (req, res) => {
        const model = await User.create(req.body)
        res.send(model)
      })
    
      app.use('/api/rest/user', router)
    }
    
    
  4. 编写表单具体操作(CRUD)路由

    module.exports = app => {
      const express = require('express')
      const multer = require('multer')
      const FightInfo = require('../../models/FightInfo')
      const router = express.Router({
        mergeParams: true
      })
    
      const storage = multer.diskStorage({
        destination: (req, file, cb) => {
          cb(null, './uploads')
        },
        filename: (req, file, cb) => {
          cb(null, file.originalname)
        }
      })
      const upload = multer({
        storage: storage
      })
    
      //用户验证中间件
      const authMiddleWare = require('../../middleware/auth')
    
      //添加表单
      router.post('/', authMiddleWare(), async (req, res) => {
        await FightInfo.create(req.body)
        res.send({ message: '表单成功添加' })
      })
    
      //修改表单信息
      router.put('/:id', authMiddleWare(), async (req, res) => {
        await FightInfo.findByIdAndUpdate(req.params.id, req.body)
        res.send({ message: '保存成功' })
      })
    
      //获取单个表单
      router.get('/:id', async (req, res) => {
        const items = await FightInfo.findById(req.params.id)
        res.send(items)
      })
    
      //获取所有表单
      router.get('/', async (req, res) => {
        const items = await FightInfo.find().sort({ date: -1 })
        res.send(items)
      })
    
      //文件上传
      app.post('/api/rest/upload', upload.single('file'), async (req, res) => {
        req.file.url = `http://localhost:3001/uploads/${req.file.filename}`
        await res.send(req.file)
      })
    
      app.use('/api/rest/fightinfo', router)
    }
    
    
  5. 用户验证中间件

    module.exports = options => {
      return async (req, res, next) => {
        const jwt = require('jsonwebtoken')
        const token = String(req.headers.authorization || '').split(' ').pop()
        //判断有无token
        if (!token) {
          return res.status(401).send({
            message: '无效的token'
          })
        }
        //判断用户是否存在
        const userid = jwt.verify(token, req.app.get('secret')).id
        if (!userid) {
          return res.status(401).send({
            message: '用户不存在'
          })
        }
        next()
      }
    }
    
    

项目详述

新增表单

用户可以选择新的表单进行会议记录,但是前提必须是要登录,才有权限。

用户初次登录的时候并没有表单信息,可以选择新增表单进行添加内容。

新增作战对象

用户可以选择在表单中新增单个对象进行会议记录,这里不需要登录,但是保存还是需要权限认证。

登录

用户登录认证,前端登录后台返回token,存入session中,保存和新增操作都需要进行认证。

其他功能

表单中可以进行内容编辑,每次编辑过后要进行保存入库,也可以进行上传文件和下载。

不足

上传文件和下载 这里没有进行权限认证,也就是不登录也可以上传,当然也可以加上具体的权限认证可以参照element-ui中组件上传,这里就不做过多的赘述了。

总结

其实整个项目很简单,无非就是增删改查,但是这也算是一个完整的全栈实战了,其中也包括用户登录注册,接口权限验证等。技术栈(Vue、Express、mongodb)也都是比较火的,对于初学者还是很有意义的。

希望你不吝赐教,可以的话给颗 star 鼓励一下吧:

github地址