node + express + mongodb 博客接口开发

1,487 阅读7分钟

首页

前言

此 blog-node 项目是基于 express 搭建,采用了主流的前后端分离思想,提供符合 RESTful 风格的 API 接口(后续部分功能在基于koa中版本实现)

实现功能

  • 文章管理
  • 评论管理
  • 评论回复管理
  • 菜单管理
  • 功能管理
  • 友情链接管理
  • 留言管理
  • 项目管理
  • 角色管理
  • 励志语言管理
  • 统计管理
  • 标签管理
  • 上传资源管理
  • 用户管理

技术库依赖

 express: ~4.16.1,  
 http-errors: ~1.6.3,  
 bcryptjs: ^2.4.3, (密码加密)  
 body-parser: ^1.19.0, (解析请求json和表单数据)  
 connect-history-api-fallback: ^1.6.0, (支持前端单页面history模式)  
 jsonwebtoken: ^8.5.1, (token生成与解析)  
 log4js: ^6.2.0,  (日志记录)  
 moment: ^2.24.0,  (日期处理) 
 mongoose: ^5.8.1, (mongodb对象模型) 
 multer: ^1.4.2, (文件上传) 
 nodemailer: ^6.4.2, (邮件发送)  
 svg-captcha: ^1.4.0 (随机验证码)

目录结构

- config
  - connect   mongodb数据库连接
  - constant  常量数据
  - globalHandle  cors允许跨域以及路由拦截验证token
  - jwt   token生成以及校验封装
  - logConfig   日志提示封装
  - utils   响应回复、数据库操作请求、发送邮件、验证码和时间格式化等一些常用方法封装
- routes
  - article 文章增删改查、详情和点赞
  - comment 一级评论增删改查和置顶评论
  - functionOper 功能列表增删改查
  - index 引入所有路由接口
  - link 友情链接增删改查
  - menu 菜单功能增删改查以及权限列表树形结构数据
  - message 留言增删改查
  - project 项目增删改查
  - replyComment 回复评论增删改查
  - role 角色增删改查、获取和设置角色权限列表、批量导入和移除用户
  - statement 前端博客显示励志语句增改查
  - statistics 访客、用户、文章、留言按年月日周或时间段统计以及排名
  - tag 文章标签增删改查
  - upload 上传资源增删查以及下载
  - user 用户增删改查、登录注册和邮件发送、验证码获取
- models 模式类型,定义文档的字段属性以及校验
- mongodb mongodb数据集合备份(初始化恢复数据,包括菜单、角色和test用户)
- static 图片和资源
- app.js 初始化以及配置

主文件 app.js

 let createError = require('http-errors');  
 let express = require('express');  
 let path = require('path');  
 const bodyParser = require("body-parser");  
 const HTTP_CODE = require("./config/constant").HTTP_CODE;  
 const utils = require('./config/utils');  
 let history = require('connect-history-api-fallback');  
 let app = express();  
  // 前端支持history模式,上线之后使用的nginx  
 app.use(history({    htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']  }))  
  //设置静态文件托管放于全局接口拦截之前,避免验证token,可放置html文件  
  app.use(express.static(path.join(__dirname, 'views')));  
  // 静态文件资源static  
  app.use('/static',express.static(path.join(__dirname,"/static")))  
  // 请求数据中间件  
  app.use(bodyParser.json({limit: '30mb'}));   //处理json数据  
  app.use(bodyParser.urlencoded({limit: '30mb', extended:true}));  //处理 form 表单数据  
  //记录日志  
  const log = require('./config/logConfig.js')  
  log.use(app)  
   // 配置  
   require("./config/globalHandle")(app);   // 全局接口拦截来设置cors跨域和检查token  
   require("./config/connect");   // MongoDB数据库连接  
   //将路由文件引入  
   const route = require('./routes/index');  
   //初始化所有路由  route(app);  
   // catch 404 and forward to error handler  
   app.use(function(req, res, next) {    
     next(createError(HTTP_CODE.notFound));  
    });  
    // error handler  
    app.use(function(err, req, res, next) {    
      // set locals, only providing error in development    
      res.locals.message = err.message;    
      res.locals.error = req.app.get('env') === 'development' ? err : {};    
      // render the error page    
      console.log(err)    
      utils.severErr(err, res)     
      // res.status(err.status || HTTP_CODE.severError);    
      // res.render('error');  
    });  
module.exports = app;

config 配置文件

connect.js mongodb 数据库连接,当 mongodb 开启了权限验证,需要加入账号和密码,注意项目更换数据库账号,密码和表名

constant.js 常量静态数据,如http状态码,错误状态码

globalHandle.js 设置cros允许跨域,token拦截验证以及解析token获取用户信息

jwt.js token生成和校验解析

logConfig.js 请求日志和错误日志输出文件到logs文件

utils.js 公共函数封装

    const nodemailer = require('nodemailer');
    const moment = require('moment');
    const CONSTANT = require('./constant');
    const svgCaptcha = require("svg-captcha");
    const RES_CODE = CONSTANT.RES_CODE;
    const HTTP_CODE = CONSTANT.HTTP_CODE;
    const logger = require('./logConfig').logger;

    // 响应客户端
    function responseClient(res, code = RES_CODE.dataFail, msg = "服务器错误", data = null,httpCode = HTTP_CODE.ok) {
      let responseData = {};
      responseData.code = code;
      responseData.msg = msg;
      responseData.data = data;
      res.status(httpCode).json(responseData);
    }
    // 错误请求返回
    function severErr(err, res) {
      console.log(err)
      logger.error(err)
      let errObj = {
        msg: '服务器错误',
        code: HTTP_CODE.severError,
        data: err
      }
      res.status(HTTP_CODE.severError).json(errObj);
    }
    // 分页,排序判断
    let ignoreAttr = ['currentPage', 'pageSize', 'sortBy', 'sortOrders']
    // 列表查询,循环对象判断字段属性是否存在,模糊查询
    function blurSelect(obj){
      let conditions = {}
      if(JSON.stringify(obj) !== '{}'){
        for(let attr in obj){
          if(!ignoreAttr.includes(attr)){
            if(attr === 'id'){
              obj[attr]? conditions['_id'] = obj[attr] : null
            }else if(attr === 'createTime' || attr === 'startTime' || attr === 'endTime'){
              let arr = obj[attr].split(',')
              if(arr.length === 2){
                conditions[attr] = {
                  "$gte": arr[0],
                  "$lte": arr[1]
                }
              }
            }else{
              if(obj[attr]){
                if(obj[attr].length === 24){
                  conditions[attr] = obj[attr]
                }else{
                  let reg = new RegExp(obj[attr], 'i')
                  conditions[attr] = {
                    $regex: reg
                  }
                }
              }
            }
          }
        }
      }
      return conditions
    }

    // 循环对象判断字段属性是否存在,精准匹配
    function completeSelect(obj){
      let conditions = {}
      if(JSON.stringify(obj) !== '{}'){
        for(let attr in obj){
          if(!ignoreAttr.includes(attr)){
            if(attr === 'id'){
              // obj[attr]? conditions['_id'] = obj[attr] : null
            }else{
              obj[attr]? conditions[attr] = obj[attr] : null
            }
          }
        }
      }
      return conditions
    }
    //返回分页查询对象
    function pageSelect(obj){
      let pageSize = Math.max(parseInt(obj.pageSize),1) || 10;
      let currentPage = Math.max(parseInt(obj.currentPage),1) || 1;
      let pageObj = {
        limit: pageSize,
        skip: (currentPage-1) * pageSize
      }
      if(obj.sortBy){
        let sortBy = obj.sortBy;
        let sortOrders = obj.sortOrders;
        let order = 'desc';
        if(sortOrders === '1' || sortOrders === 1 || sortOrders === 'asc' || sortOrders === 'ascending'){
          order = 'asc';
        }
        pageObj.sort = {
          [sortBy]: order
        }
      }
      return pageObj
    }
    // 当前时间
    function currentDayDate(type = 'time'){
      if(type === 'day'){
        return moment().format('YYYY-MM-DD')
      }else{
        return moment().format('YYYY-MM-DD HH:mm:ss')
      }
    }
    // 获取本周星期一和星期天
    function weekFirstLast(date){
      let week = moment(date).format('E')
      let arr = [moment(date).subtract(week-1, 'days').format('YYYY-MM-DD'),moment(date).add(7-week, 'days').format('YYYY-MM-DD')]
      return arr
    }
    // 获取本月第一天和最后一天
    function monthFirstLast(date){
      let arr = [moment(date).startOf('month').format('YYYY-MM-DD'),moment(date).endOf('month').format('YYYY-MM-DD')]
      return arr
    }
    // 获取本年第一天和最后一天
    function yearFirstLast(date){
      let arr = [moment(date).startOf('year').format('YYYY-MM-DD'),moment(date).endOf('year').format('YYYY-MM-DD')]
      return arr
    }
    // 获取传递时间当前周数组7个日期
    function weekArry(date){
      let week = moment(date).format('E')
      let arr = []
      for(let i = 1; i < 8; i++){
        let data = moment(date).subtract(week-i, 'days').format('YYYY-MM-DD')
        arr.push(data)
      }
      return arr
    }
    // 时间差
    function timeDiff(startTime, endTime, type = 'minute'){
      let sTime = moment(startTime)
      let eTime = moment(endTime)
      return eTime.diff(sTime, type)
    }
    // 获取文章字数
    function getPostWordCount(text) {
      let len = 0;
      // 先将回车换行符做特殊处理
      text = text.replace(/(rn+|s+|  +)/g,"蓓"); // 书 = 云
      // 处理英文字符数字,连续字母、数字、英文符号视为一个单词
      text = text.replace(/[x00-xff]/g,"m");    
      // 合并字符m,连续字母、数字、英文符号视为一个单词
      text = text.replace(/m+/g,"*");
         // 去掉回车换行符
      text = text.replace(/蓓+/g,"");
      // 返回字数
      len = text.length
      return len;
    }

    // 邮件发送
    //创建发送邮件的请求对象
    let transporter = nodemailer.createTransport({
      host: 'smtp.163.com',    //发送端邮箱类型(163邮箱)
      port: 465,      //端口号
      secure: true, 
      auth: {
          user: 'test@163.com', // 发送方的邮箱地址(自己的)
          pass: 'sdj527' // mtp 验证码
      }
    });
    function sendEmail(mail,code) {
      let mailObj = {
          from: '"test" <test@163.com>',   // 邮件名称和发件人邮箱地址
          to: 'test@163.com, ' + mail,   //收件人邮箱地址(这里的mail是封装后方法的参数,代表收件人的邮箱地址),出现504.可以抄送一份给自己
          // to: mail,   //收件人邮箱地址(这里的mail是封装后方法的参数,代表收件人的邮箱地址)
          subject: '邮箱验证码',   //邮件标题
          // html: '',
          text: '您的验证码:'+ code + ' ( 有效期十分钟 )'
      }
      // 发送邮件(封装成一个promise对象),方便后面调用该方法
      return new Promise((resolve, reject)=>{
          transporter.sendMail(mailObj, (err, data) => {
              if(err){
                  reject(err)    //出错
              }else{
                  resolve(data)    //成功
              }
          })
      })
    }

    // 随机验证码svg图片
    function createCode() {
      return svgCaptcha.create({
          size: 4,
          ignoreChars: "0o1iIl",
          noise: 1,
          color: true,
          background: "transparent",
          fontSize: 60
      });
    }
    module.exports = {
      responseClient,
      blurSelect,
      completeSelect,
      pageSelect,
      sendEmail,
      currentDayDate,
      timeDiff,
      weekArry,
      weekFirstLast,
      monthFirstLast,
      yearFirstLast,
      getPostWordCount,
      objProp,
      createCode,
      severErr
    };

models Mongoose数据模型

user用户模型,模型有设置默认值default,校验required、validate和match,枚举enum,

    const mongoose = require('mongoose');
    const moment = require('moment');
    const Schema = mongoose.Schema;
    const userSchema = new Schema({
      name: {
        type: String,
        required: true,
        validate: (val)=> {
          return val.length < 10
        }
      },
      email: {
        type: String,
        required: true,
        match: /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
      },
      phone: {
        type: String,
        match: /^1[3|4|5|6|7|8|9][0-9]\d{4,8}$/   //正则校验
      },
      password: {
        type: String,
        select: false,    //   返回对象数据中不会显示这个字段信息
        required: true
      },
      info: {
        type: String,
        validate: (val)=> {
          return val.length < 40
        }
      },
      status: {
        type: String,
        default: '1',
        enum: ['0', '1']
      },
      avatarId: {
        type: String,
        default: ''
      },
      mark: {
        type: String,
        default: 'xxxxxx4xxxyxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        })
      },
      roleId: { 
        type: mongoose.Schema.Types.ObjectId,   
        ref: 'Role',  //角色对象ID类型
      },
      createTime: {
        type: String,
        default: () => moment().format('YYYY-MM-DD HH:mm:ss')
      },
      updateTime: {
        type: String,
        default: () => moment().format('YYYY-MM-DD HH:mm:ss')
      }
    }, {
      versionKey: false,
      collection: 'user'  //生成 collection 的自定义名称,默认会以复数形式
      // timestamps: { createdAt: 'createTime', updatedAt: 'updateTime' }
    })

    module.exports = mongoose.model('User', userSchema);

routes 路由

index.js总路由,如引入用户模块

const user = require('./user');
module.exports = app => {
  let baseUrl = '/blogAdmin'
  // 用户模块
  app.get(baseUrl + '/user/list', user.userList)
  app.get(baseUrl + '/user/accessList', user.accessUserList)
  app.post(baseUrl + '/user/register', user.register)
  app.post(baseUrl + '/user/add', user.userAdd)
  app.put(baseUrl + '/user/update', user.userUpdate)
  app.post(baseUrl + '/user/login', user.login)
  app.post(baseUrl + '/user/resetPwd', user.resetPwd)
  app.post(baseUrl + '/user/modifyPwd', user.modifyPwd)
  app.post(baseUrl + '/user/sendEmail', user.sendEmail)
  app.get(baseUrl + '/user/getCode', user.getCode)
  app.delete(baseUrl + '/user/del/:id', user.userDel)
}

用户管理

     1、使用map结构来缓存随机验证码和邮箱验证码,设置有效期为十分钟
      //随机验证码验证
      function randomCodeFind(req) {
        let { randomCode } = req.body;
        let result = true;
        if (randomCode) {
          let getRandomCode = randomCodeList.get(randomCode.toUpperCase())
          if (getRandomCode && utils.timeDiff(getRandomCode.createTime, utils.currentDayDate()) <= 10) {
            result = false
          } else {
            randomCodeList.delete(randomCode.toUpperCase())
          }
        }
        return result
      }
    2、使用bcrypt对密码进行加密
    function pwdBcrypt(password) {
        return new Promise((resolve, reject) => {
          bcrypt.genSalt(10, function (error, salt) {
            bcrypt.hash(password, salt, function (err, hashPassword) {
              //store hash in your password DB.
              if (err) {
                reject(err)
                throw new Error('加密失败');
              }
              resolve(hashPassword)
            });
          })
        })
      }
    3、用户注册判断用户名和邮箱重复
      User.findOne({ $or: [{ name }, { email }] }).exec(function (err, user) {
        if (err) {
          return utils.severErr(err, res)
        }
        if (user) {
          if (user.name === name) {
            utils.responseClient(res, RES_CODE.dataAlready, "用户名已存在")
          } else if (user.email === email) {
            utils.responseClient(res, RES_CODE.statusFail, "邮箱已存在")
          }
        }
      })  
    4、用户列表,根据参数进行模糊匹配,分页和排序,如果角色不是超级用户则只能获取属于该账号下用户列表
      exports.userList = (req, res) => {
        let conditions = utils.blurSelect(req.query)
        let pageObj = utils.pageSelect(req.query)
        let userMessage = req.tokenMessage.userMessage
        if (userMessage.roleId !== ROLE_TYPE.superRole) {
          conditions.mark = userMessage.mark
        }
        User.countDocuments(conditions, function (err, count) {
          if (err) {
            return utils.severErr(err, res)
          }
          User.find(conditions, null, pageObj, function (error, docs) {
            if (error) {
              return utils.severErr(error, res)
            }
            if (docs) {
              let data = {
                count,
                data: docs
              }
              utils.responseClient(res, RES_CODE.reqSuccess, "获取用户列表成功", data)
            } else {
              utils.responseClient(res, RES_CODE.dataFail, "获取用户列表失败")
            }
          }).populate([
            { path: 'roleId' }
          ])
        })
      }

文章管理

      1、使用populate填充文章标签和创建用户对象
      Article.find(conditions, fields, pageObj, function (error, docs){
        if(error){
          return utils.severErr(error, res)
        }
        if (docs) {
          let data = {
            count,
            data: docs
          }
          utils.responseClient(res, RES_CODE.reqSuccess, "获取文章列表成功", data)
        } else {
          utils.responseClient(res, RES_CODE.dataFail, "获取文章列表失败")
        }
      }).populate([
        { path: 'tags', select: '_id name bgColor' },
        { path: 'createUser', select: '_id name mark' }
      ]) 
     2、使用$inc来减少或增加数量,$pull或$push来对数组元素进行删除或添加,如增加文章一级评论时,如要在文章模型中修改对应数据
      let newComment = new Comment(conditions)
      newComment.save(function (err, addResult) {
        if (err) {
          return utils.severErr(err, res)
        }
        if (addResult) {
          if(addResult.status === '1'){
            Article.findByIdAndUpdate(articleId, {$inc: { 'meta.commentTotal': 1}, $push: {commentList: addResult._id }}, { new: true }, function (errs, updateResult) {
              if (errs) {
                return utils.severErr(errs, res)
              }
              updateResult ? utils.responseClient(res, RES_CODE.reqSuccess, "文章评论新增成功", updateResult) : utils.responseClient(res, RES_CODE.dataFail, "文章新增评论失败")
            })
          }else{
            utils.responseClient(res, RES_CODE.reqSuccess, "文章评论新增成功", addResult)
          }
        } else {
          utils.responseClient(res, RES_CODE.dataFail, "文章新增评论失败")
        }
      })
    }
    3、文章评论置顶,通过isTop(Bool类型)和topUpdateTime排序,默认置顶排序
    let pageObj = utils.pageSelect(req.query)
    if(!pageObj.sort){
      pageObj.sort = {
        isTop: '-1',
        topUpdateTime: '-1'
      }
    }

菜单功能

    1、菜单功能扁平数据结构转化成树形数据结构返回
      function treeData(source){
        let cloneData = JSON.parse(JSON.stringify(source))    // 对源数据深度克隆
        return cloneData.filter(father=>{               
          let branchArr = cloneData.filter(child=>father._id == child.parentId)    //返回每一项的子级数组
          branchArr.length>0 ? father.children = branchArr : ''   //如果存在子级,则给父级添加一个children属性,并赋值
          return father.parentId=='0';      //返回第一层
        })
      }
    2、对用户权限进行接口拦截处理,暂时通过写死数据,后续通过中间件进行处理
      let userMessage = req.tokenMessage.userMessage
      if(!userMessage.functionList.includes('5e834ff2fb69305aa091e836')){
        return utils.responseClient(res, RES_CODE.dataFail, "无该功能权限")
      }
    3、通过update批量导入或移除角色用户
      User.update(conditions,doc,{multi: true},function (error, docs){})

数据统计

	1、按日、周、月、年或自定义时间段统计数据
     exports.articleStatistics = (req, res) => {
       let { type, startTime, endTime } = req.query
       startTime = startTime || utils.currentDayDate('day')
       let options = {}
       if (type === 'day') {
         options.startTime = startTime
         options.endTime = startTime
         options.substrData = ["$createTime", 11, 2]
       } else if (type === 'week') {
         let weekArr = utils.weekFirstLast(startTime)
         options.startTime = weekArr[0]
         options.endTime = weekArr[1]
         options.substrData = ["$createTime", 0, 10]
       } else if (type === 'month') {
         let monthArr = utils.monthFirstLast(startTime)
         options.startTime = monthArr[0]
         options.endTime = monthArr[1]
         options.substrData = ["$createTime", 0, 10]
       } else if (type === 'year') {
         let yearArr = utils.yearFirstLast(startTime)
         options.startTime = yearArr[0]
         options.endTime = yearArr[1]
         options.substrData = ["$createTime", 5, 2]
       } else {
         options.startTime = startTime
         options.endTime = endTime
         options.substrData = ["$createTime", 0, 10]
       }
       Article.aggregate([
         {
           $match: {
             status: '1',
             createTime: { '$gte': options.startTime + ' 00:00:00', '$lt': options.endTime + ' 23:59:59' }
           }
         },
         {
           $project: {
             hour: {
               $substr: options.substrData
             }
           },
         },
         {
           $group: {
             _id: "$hour",
             count: { $sum: 1 }
           }
         },
         {
           $sort: { _id: 1 }
         }
       ]).exec(function (err, data) {
         if (err) {
           return utils.severErr(err, res)
         }
         utils.responseClient(res, RES_CODE.reqSuccess, "获取文章统计", data)
       })
     }
     2、按排名最多排序,时间字符串转换时间格式,使用new Date("$time")全部会转换成1970-01-01T00:00:00.000Z
     exports.articleList = (req, res) => {
       let { type } = req.query
       let date = null
       if(type === 'day'){
         date = {
           $substr: ["$createTime", 11, 2]
         }
       }else if(type === 'month'){
         date = {
           $substr: ["$createTime", 5, 2]
         }
       }else{
         date = {
           $isoDayOfWeek: {
             $dateFromString: {
               dateString: "$createTime"
             }
           }
         }
       }
       Article.aggregate([
         {
           $match: {
             status: '1'
           }
         },
         {
           $project: {
             date,
             createTime: 1
           }
         },
         {
           $group: {
             _id: "$date",
             count: { $sum: 1 }
           }
         },
         {
           $sort: { count: -1 }
         }
       ]).exec(function (err, data) {
         if (err) {
           return utils.severErr(err, res)
         }
         utils.responseClient(res, RES_CODE.reqSuccess, "获取文章排名统计", data)
       })
     }
    3、$lookup联表查询,$group分组字段需要放到_id中
    exports.tagList = (req, res) => {
      Article.aggregate([
        {
          $match: {
            status: '1'
          }
        },
        {
          $lookup: {
            from: 'tag',
            localField: 'tags',
            foreignField: '_id',
            as: 'articleTag'
          }
        },
        {
          $unwind: "$articleTag"
        },
        {
          $project: {
            title: 1,
            'articleTag._id': 1,
            'articleTag.name': 1,
            'articleTag.bgColor': 1
          }
        },
        {
          $group: {
            _id: {
              "id": "$articleTag._id",
              "name": "$articleTag.name",
              "bgColor": "$articleTag.bgColor"
            },
            count: { $sum: 1 }
          }
        },
        {
          $sort: { count: -1 }
        }
      ]).exec(function (err, data) {
        if (err) {
          return utils.severErr(err, res)
        }
        utils.responseClient(res, RES_CODE.reqSuccess, "获取文章标签统计", data)
      })
    }

文件上传和下载

	1、通过multer模块来进行上传管理
      app.post(baseUrl + '/file/upload',multer({
          //设置文件存储路径
         dest: './static/img'   //img文件如果不存在则会自己创建一个,single上传单个文件
      }).single('file'), upload.uploadFile);  
    2、先fs.readFile读取文件,然后fs.writeFile写入文件,最后数据库保存路径
      exports.uploadFile = (req, res) => {
        if(req.file){
          fs.readFile(req.file.path, (err, data) => {
            //读取失败,说明没有上传成功
            if (err) {
              return utils.severErr(err, res)
            }
            let imgAddress = path.join(__dirname, `../${req.file.destination}/` + req.file.filename + '.' + req.file.mimetype.split('/')[1])
            let imgUrl = req.protocol + '://'+ req.headers.host + '/blogAdmin/file/down?downId=' + req.file.filename
            fs.writeFile(imgAddress, data, (err) => {
              if (err) {
                return utils.severErr(err, res)
              }
              const newSource = new Source({
                sourceId: req.file.filename,
                name: req.file.originalname,
                type: req.file.mimetype,
                url: imgUrl
              })
              newSource.save(function (err, source){
                if(err){
                  return utils.severErr(err, res)
                }
                utils.responseClient(res, RES_CODE.reqSuccess, "上传成功", source)
                // 删除二进制文件
                fs.unlink(path.join(__dirname, '../static/img/' + req.file.filename), function(unErr){
                  // console.log(unErr)
                })
              })
            })
          })
        }else{
          utils.responseClient(res, RES_CODE.dataFail, "获取文件失败")
        }
      }
    3、文件下载,通过文件id到数据库获取资源,然后拼接下载路径返回
      res.download(path.join(__dirname, imgUrl), function(err){
        if (err) {
          return utils.severErr(err, res)
        }
      })

说明

  • 默认超级管理员,账户:test,密码:123456

  • 开发环境使用 nodeman,一旦报错,程序断开,生产环境使用 pm2,把 node 设置为进程,不会因报错而断开服务

Build Setup ( 建立安装 )

数据库mongodb安装,mongodb按教程安装下载,然后配置:

1、下载mongodb在D:\mongodb位置(自定义),data文件夹下新建db文件夹

2、在D:\mongodb\bin中执行.\mongod --dbpath D:\mongodb\data\db,查看是否安装成功

3、配置文件安装服务,mongod -config " D:\mongodb\bin\mongod.cfg" -install -serviceName "MongoDB"

4、在D:\mongodb\bin中执行./mongo或配置系统变量使用mongo来创建超级用户:
use admin
db.createUser({user:"admin",pwd:"123456",roles:["root"]})

5、新建数据库:
use blogNode
db.createUser({user:"admin",pwd:"123456",roles:[{role:"dbOwner",db:"blogNode"}]})
(dbOwner:该数据库的所有者,具有该数据库的全部权限)

6、在mongod.cfg中配置需要权限认证,重启服务
security:
  authorization: enabled
  
7、安装navicat for mongodb 可视化数据库,导入恢复mongodb的数据  

8、全局安装npm install -g nodemon来监听重启

9、安装依赖,npm install

10、启动服务,npm run dev,默认端口3000

项目地址:

前台展示:https://gitee.com/sdj_work/blog-page(Vue/Nuxt/uni-app)

管理后台:https://gitee.com/sdj_work/blog-admin(Vue/React)

后端Node:https://gitee.com/sdj_work/blog-node(Express/Koa)

博客地址:https://sdjBlog.cn/

项目系列文章:

Vue+Nuxt 博客展示

Vue+uniapp 博客展示

Vue+ElementUI 后台博客管理

node + koa + mongodb 博客接口开发

node + express + mongodb 博客接口开发