node + MongoDb + Robo 3T

128 阅读10分钟

开始

node

MongoDB - 数据库

Robot 3T - 可视化工具

yarn add -g nodemon

Express 应用程序生成器

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。 你可以通过 npx (包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。

npx express-generator

对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并使用:

npm install -g express-generator

-h 参T数可以列出所有可用的命令行参数:

express -h
​
  Usage: express [options] [dir]
​
  Options:
​
    -h, --help          输出使用方法
        --version       输出版本号
    -e, --ejs           添加对 ejs 模板引擎的支持
        --hbs           添加对 handlebars 模板引擎的支持
        --pug           添加对 pug 模板引擎的支持
    -H, --hogan         添加对 hogan.js 模板引擎的支持
        --no-view       创建不带视图引擎的项目
    -v, --view <engine> 添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
    -c, --css <engine>  添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
        --git           添加 .gitignore
    -f, --force         强制在非空目录下创建

例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎(view engine):

方式1:express myapp
方式2:express --view=pug myapp
express --view=pug myapp

   create : myapp
   create : myapp/package.json
   create : myapp/app.js
   create : myapp/public
   create : myapp/public/javascripts
   create : myapp/public/images
   create : myapp/routes
   create : myapp/routes/index.js
   create : myapp/routes/users.js
   create : myapp/public/stylesheets
   create : myapp/public/stylesheets/style.css
   create : myapp/views
   create : myapp/views/index.pug
   create : myapp/views/layout.pug
   create : myapp/views/error.pug
   create : myapp/bin
   create : myapp/bin/www

然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。 注意:一定要安装nodemon模块,否则在修改代码后,需要手动重启服务器才能看到效果。

  "scripts": {
    "dev": "nodemon ./bin/www"
  },

通过生成器创建的应用一般都有如下目录结构:

.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug

启动MongoDB数据库服务器

mongod --dbpath=用于指定数据库文件的存储/启动路径

mongod是MongoDB数据库的守护进程,用于启动和管理数据库服务器。 --dbpath=是一个命令行选项,用于指定数据库文件的存储路径。你需要在等号后面提供一个有效的路径,MongoDB将在该路径下创建并存储数据库文件。 注意:你需要确保指定的路径是存在且可写的。如果路径不存在,MongoDB将无法启动;如果路径不可写,MongoDB将无法创建或写入数据库文件。

Mongoose库来连接到MongoDB数据库

yarn add mongoose

指令:npm i mongoose "mongoose" 是一个 Node.js 库。并进行数据操作 1,连接到 MongoDB 数据库:使用 "mongoose.connect()" 方法,你可以建立与 MongoDB 数据库的连接。 2,定义模式和模型:使用 "mongoose.Schema" 和 "mongoose.model()" 方法,你可以定义数据的结构和模型。 3,执行查询和操作:使用 "Model.find()"、"Model.create()"、"Model.update()" 等方法,你可以执行数据库查询和操作。 4,实现数据验证和中间件:使用 "Schema" 的预定义和自定义验证规则,以及 "pre" 和 "post" 中间件钩子函数,你可以处理数据验证和操作事件。 5,支持事务处理:"mongoose" 提供了事务处理的支持,可以确保一系列数据库操作的原子性和一致性。

连接步骤: 1,新建文件夹db.config,并新建文件db.config.js用于存储连接数据库的代码; 2.将该文件引入到入口文件 bin > www,require("../db.config/db.config")

// 引入mongoose模块并赋值给变量mongoose
const mongoose = require("mongoose");
​
// 连接数据库
// mongoose.connect()方法用于连接到MongoDB数据库
// 参数1:数据库的连接字符串,指定了数据库的地址和名称
// mongodb://:这是MongoDB连接URL的协议部分,指示使用MongoDB协议进行连接。
// 127.0.0.1:这是MongoDB服务器的IP地址或主机名。在这个例子中,127.0.0.1代表本地主机,也可以使用其他IP地址或主机名。
// 27017:这是MongoDB服务器的端口号。默认情况下,MongoDB使用27017作为默认端口。
// company-system:这是要连接的数据库的名称。在这个例子中,数据库名称为company-system。
// 参数2:配置对象,用于设置连接选项
// useNewUrlParser:启用新的URL解析器
// useUnifiedTopology:使用新的服务器发现和监视引擎
mongoose.connect("mongodb://127.0.0.1:27017/company-system", {
  useNewUrlParser: true,
  useUnifiedTopology: true
}).then(() => {
  console.log("数据库连接成功!");
}).catch(error => {
  console.error("连接失败:", error.message);
});

mongoose.Schema()方法创建数据的结构模型

创建步骤: 1.新建文件夹models> 新建文件(见名之意)News.js 2.导出到services文件夹下使用,services文件夹用于调用模型做增删改查等操作 3.数据结构写好后,进入可视化工具使用 F5 刷新,就能看见数据表了

UserModel.js

const mongoose = require('mongoose')
const Schema = mongoose.Schema// user模型===>users集合
const UserType={
    username:String,//用户名
    password:String,//密码
    avatar:String,//头像
    role:Number//管理员1,编辑2
} 
const UserModel= mongoose.model("user",new Schema(UserType))
// "user":表示这个集合的名称,它将映射到MongoDB中的users集合
// new Schema(UserType):表示使用之前定义的UserType作为模型的结构
module.exports = UserModel  

routers定义接口

操作步骤: 1.在routers文件夹下创建我的接口,可以新建文件夹(admin)进行统一管理项目接口; 2.新建文件NewsRouter.js,书写接口;

// 3.引入app.js文件,进行挂载使用
const NewsRouter = require("./routes/admin/NewsRouter");
// use一定要放到靠下面一点,否则获取req.body是空值
app.use(NewsRouter)

3.此文件只用作定义接口地址,其余操作交由 controllers 处理

var express = require('express')
const NewsController = require('../../controllers/admin/NewsController')
// multer是一个用于处理文件上传的中间件
const multer = require('multer')
const upload = multer({
  // dest:指定了上传文件的存储地址
  dest: 'public/avatarupload',
})
var NewsRouter = express.Router()
​
/* post users listing. */
NewsRouter.post('/adminapi/users/login', NewsController.login)
// 涉及文件上传,普通 post 不行,需要加上 multer 中间件
NewsRouter.post('/adminapi/users/add',upload.single('file'),NewsController.add)
UserRouter.post("/adminapi/users/updateOne", upload.single("file"), NewsController.updateOne);
UserRouter.get("/adminapi/users/list",  NewsController.getListById);
UserRouter.get("/adminapi/users/list/:id",  NewsController.getListById);
UserRouter.delete("/adminapi/users/list/:id",  NewsController.deleteById);
module.exports = NewsRouter

controllers处理接口

操作步骤: 1.在controllers文件夹下创建处理接口的 js 文件,可以新建文件夹(admin)进行统一管理项目接口; 2.新建文件NewsController.js,处理接口;

login - 查

const NewsService = require("../../services/admin/NewsService");
const JWT = require("../../utils/JWT");
​
const NewsController = {
  login: async (req, res) => {
    console.log("请求体的内容", req.body);
    try {
      const userInfo = await NewsService.login(req.body);
      if (userInfo.length > 0) {
        const token = JWT.generate(
          {
            _id: userInfo[0]._id,
            username: userInfo[0].username,
          },
          "1d"
        );
        res.header("Authorization", token);
        res.status(200).send({
          message: "恭喜你, 登录成功了 ! ^_^ ",
          code: 1,
          data: {
            msg: "这是你目前登录的账户的信息, 喏, 给你 -_- !",
            username: userInfo[0].username, //用户名
            avatar: userInfo[0].avatar, //头像
            role: userInfo[0].role, // 管理员1,编辑2
            UserKey: userInfo[0]._id, ///用户id
          },
          tips: "登录有效期是 24小时, 你的登录状态会在 24小时后自动注销",
          token: token,
        });
      } else {
        res.status(204).send({
          code: -1,
          error: "用户名密码不匹配",
        });
      }
    } catch (error) {
      console.error(error);
      res.status(500).send({
        code: -1,
        error: "服务器错误,登录失败!",
      });
    }
  },
};
​
module.exports = NewsController;

add - 增

const NewsService = require('../../services/admin/NewsService');
const fs = require('fs');
const path = require('path');
​
const AVATAR_UPLOAD_PATH = '/avatarupload/';
​
const NewsController = {
  add: async (req, res) => {
    try {
      const { username, password, role } = req.body;
      const avatar = req.file ? `${AVATAR_UPLOAD_PATH}${req.file.filename}` : '';
​
      const existingUsers = await NewsService.removeDuplicate({ username });
      if (existingUsers.length > 0) {
        if (avatar) {
          const tempPath = path.join(process.cwd(), '/public' + avatar);
          if (fs.existsSync(tempPath)) {
            fs.unlinkSync(tempPath);
            console.log(`${tempPath} 文件已成功删除`);
          }
        }
        res.status(204).send({
          code: 0,
          message: '添加失败,该账户名已存在!',
        });
      } else {
        await NewsService.add({ username, password, role: Number(role), avatar });
        res.status(200).send({
          code: 1,
          message: '恭喜你, 添加成功了 ! ^_^ ',
        });
      }
    } catch (error) {
      console.error(error);
      res.status(500).send({
        code: -1,
        message: '服务器错误,添加失败!',
      });
    }
  },
};module.exports = NewsController;

updateOne - 改

const NewsService = require('../../services/admin/NewsService')
const fs = require('fs')
const path = require('path')
const NewsController = {
  updateOne: async (req, res) => {
    const { username } = req.body
    const token = req.headers['authorization'].split(' ')[1]
    const payload = JWT.verify(token)
    const avatar = req.file ? `/avatarupload/${req.file.filename}` : ''
    
    // 删除旧文件
    if (avatar) {
      await deleteOldAvatar(payload._id)
    }
​
    // 更新数据库中的数据
    await NewsService.upload({
      _id: payload._id,
      username,
      avatar,
    })
​
    // 返回结果
    if (avatar) {
      res.status(200).send({
        code: 1,
        message: '恭喜你, 更新成功了 ! ^_^ ',
        data: {
          username,
          avatar, // 更新后的文件一并返回
        },
      })
    } else {
      res.status(200).send({
        code: 1,
        message: '恭喜你, 更新成功了 ! ^_^ ',
        data: {
          username,
        },
      })
    }
  },
}
​
// 删除旧文件的函数
async function deleteOldAvatar(userId) {
  let temp = await NewsService.find({ _id: userId })
  if (temp[0].avatar) {
    let tempPath = path.join(process.cwd(), '/public' + temp[0].avatar)
    fs.unlink(tempPath, err => {
      if (err) {
        console.error(err)
        return
      }
      console.log(`${tempPath}文件已成功删除`)
    })
  }
}
​
module.exports = NewsController

deleteById- 删

const NewsService = require('../../services/admin/NewsService');
const fs = require("fs");
const path = require("path");
const NewsController = {
  deleteById: async (req, res) => {
    // 删除用户信息
    await NewsService.deleteById({ _id: req.params.id });
​
    // 同时需要删除相应的用户文件信息(头像文件)
    let user = await UserService.findById(req.params.id);
    if (user[0].avatar) {
      let avatarPath = path.join(process.cwd(), '/public' + user.avatar);
      fs.unlink(avatarPath, err => {
        if (err) {
          console.error(err);
          return;
        }
        console.log(`${avatarPath} 文件已成功删除`);
      });
    }
​
    res.status(200).send({
      code: 1,
      message: '恭喜你,删除成功了!^_^',
    });
  },
};module.exports = NewsController;

getListById

const NewsService = require('../../services/admin/NewsService');
​
const NewsController = {
  getListById: async (req, res) => {
    try {
      // 通过路由参数获取 id
      const result = await NewsService.getListById({ _id: req.params.id });
      if (result) {
        res.status(200).send({
          code: 1,
          message: "恭喜你,获取列表成功了!^_^",
          data: result,
        });
      } else {
        res.status(404).send({
          code: -1,
          message: "未找到相应数据",
        });
      }
    } catch (err) {
      console.error(err);
      res.status(500).send({
        code: -1,
        message: "获取列表时出现错误",
      });
    }
  },
};
​
module.exports = NewsController;

services查询模块

操作步骤: 1.在services文件夹下创建处理接口的 js 文件,可以新建文件夹(admin)进行统一管理项目接口; 2.新建文件NewsService.js,处理接口;

login

// NewsServices.js
const UserModel = require('../../models/UserModel')
const NewsService = {
  login: async ({ username, password }) => {
    return UserModel.find({ username, password })
  },
}
module.exports = NewsService

add

const UserModel = require('../../models/UserModel')
const NewsService = {
  add: async ({ username, password, role, avatar }) => {
    return UserModel.create({ username, password, role, avatar })
  },
}
module.exports = NewsService

updateOne

const UserModel = require('../../models/UserModel')
const NewsService = {
    updateOne: async ({ _id, username, avatar }) => {
        // 判断 avatar 是否为真,更新头像就是真,不更新就是假;真:就需要更新头像;假:不需要更新头像
        if (avatar) {
            return UserModel.updateOne({ _id }, { username, avatar });
        } else {
            return UserModel.updateOne({ _id }, { username });
        }
    },
}
module.exports = NewsService

deleteById

const UserModel = require('../../models/UserModel')
const NewsService = {
    deleteById: async ({ _id }) => {
        return UserModel.deleteOne({ _id });
    },
}
module.exports = NewsService

getListById

const UserModel = require('../../models/UserModel')
const NewsService = {
    getListById: async ({ _id }) => {
        // 两种情况:一种是带 id 查询;一种是全部查询
        return _id
            ? UserModel.find({ _id }, ["username", "password", "role", "introduction"])
            : UserModel.find({}, ["username", "role", "avatar"]);
    },
}
module.exports = NewsService

utils

jsonwebtoken

// yarn add jsonwebtoken
// Expected version ">=16.20.1"
// node : Expected version ">=16.20.1".
const jsonwebtoken = require("jsonwebtoken");
const secret = "key"; // 设置签名的密钥
const JWT = {
    // value:是你要传递的数据 exprires:设置过期时间
    generate(value, exprires) {
        // 生成 token
        return jsonwebtoken.sign(value, secret, { expiresIn: exprires });
    },
    verify(token) {
        // 验证 token
        // try{}catch(e){} 捕获 防止超时验证报错 
        try{
            return jsonwebtoken.verify(token, secret);
        }catch(e){
            return false;
        }
    },
};
// 测试
// const token = JWT.generate({ name: "king" }, "10s");
// console.log(JWT.verify(token));
// 模拟错误发生
// setTimeout(() => {
//     // 超时验证 token 
//     console.log(JWT.verify(token));
// },11000)// 导出到 suercontroller 文件
module.exports = JWT;

appjs的中间件

// ⬆ 上面的接口不受影响
app.use((req, res, next) => {
  // 中间件
  if (req.url === "/adminapi/users/login") {
    // 登录接口不需要验证token,直接放行
    next();
  } else {
    const token = req.headers["authorization"] ? req.headers["authorization"].split(" ")[1] : null; // 取出 token
    if (token) {
      try {
        const payload = JWT.verify(token); // 验证 token 是否失效
        if (payload) {
          // 重新生成 token 并返回给前端
          const newToken = JWT.generate({ _id: payload._id, username: payload.username }, '1d');
          res.header("Authorization", newToken);
          next();
        } else {
          // token 过期,返回给前端 401 错误
          res.status(401).send({ error: 401, message: "token已过期" });
        }
      } catch (err) {
        console.error(err);
        res.status(500).send({ error: 500, message: "服务器内部错误" });
      }
    } else {
      // 没有提供 token,返回给前端 401 错误
      res.status(401).send({ error: 401, message: "未提供有效的 token" });
    }
  }
});
// ↓ 下面的接口会受中间件的影响

第三方中间件

文件上传

yarn add multer
// Expected version ">=16.20.1"
// multer是一个用于处理文件上传的中间件,在Node.js中处理上传的文件
const multer = require('multer')
// 创建upload对象,该对象是multer中间件的实例。通过传递一个配置对象给multer,我们可以定义文件上传的行为和设置
const upload = multer({
  // dest:指定了上传文件的存储地址
  dest: 'public/avatarupload',
})
NewsRouter.post(
  '/adminapi/users/add',
  upload.single('file'),
  NewsController.add
)
// upload.single('file')的作用是使用multer中间件来处理上传的文件,指定了上传文件需要将文件数据放在名为file的字段中。这样可以让服务器端从请求中提取出上传的文件,并进行相应的处理;
// 前段需要将文件数据放在名为 file 的字段里

fs

介绍:fs是node的核心模块之一,提供文件操作功能;

// fs.unlink(path, callback) 通常用于处理删除操作的结果或错误
// 参数1:path:要删除的文件的路径
// 参数2:callback:回调函数,可选参数;在删除文件完成后执行的回调函数
fs.unlink(tempPath, err => {
  if (err) {
    console.error(err)
    return
  }
  console.log(`${tempPath}文件已成功删除`)
})

path

介绍:path模块是一个内置模块,用于处理文件路径和目录路径;

let tempPath = path.join(process.cwd(), '/public' + temp[0].avatar)
// path.join():将两个路径拼接成一个完整的路径
// process.cwd():获取 Node.js 进程当前的工作目录,通常是运行 node 命令时所在的目录。
// '/public' + temp[0].avatar:将 '/public' 字符串与 temp[0].avatar 变量相加,生成一个新的字符串,用于表示文件路径