后台接口学习-管理员登录页

71 阅读7分钟

项目准备

image.png 各层的作用如下:

  • 表示层:主要对用户的请求接收,以及数据的返回,为客户端提供应用程序的访问。
  • 业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。
  • 数据访问层:主要看数据层里面有没有包含逻辑处理,实际上它的各个函数主要完成各个对数据文件的操作。而不必管其他操作。
  1. 安装脚手架

    npm i -g express-generator
    
  2. 查看express版本

    express --version
    
  3. 创建项目名称

    express mysite-express
    
  4. 安装依赖

    npm i
    
  5. 运行

    npm start 
    
  6. 设置提示

    // /bin/www
    function onListening() {
    	...
      console.log("服务器端已启动,监听3001端口...");
    }
    
  7. 增加目录

image.png

  1. 删除view目录,并在app.js中删除有关view部分

image.png

  1. 安装nodemon
  npm i -g nodemon

10. 运行

 nodemon npm start

11. 创建数据库

  1. 安装sequelize,dotenv
  npm install --save sequelize msyql2
  npm i dotenv

13. 在根目录下创建 .env

   DB_NAME=mysite
   DB_USER=root
   DB_PASS=123456
   DB_HOST=localhost

14. 建立数据库连接 /dao/dbConnect.js

   // 该文件负责连接数据库
   const {Sequelize} = require('sequelize');

   // 创建数据库连接
   const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
     host: process.env.DB_HOST,
     dialect:'mysql',
     logging: false
   });
   // (async function(){
   //   try {
   //     await sequelize.authenticate();
   //     console.log('Connection has been established successfully.');
   //   } catch (error) {
   //     console.error('Unable to connect to the database:', error);
   //   }
   // }())

   // 向外暴露连接实例
   module.exports = sequelize;

   // app.js
   // 引入路由
   var indexRouter = require('./routes/index');
   var usersRouter = require('./routes/users');

   // 默认读取项目根目录下的 .env 环境变量文件
   require("dotenv").config()
   // 引入数据库连接
   require('./dao/dbConnect');
  1. 新建工具文件夹 utils ,创建error.js
 // 自定义错误
 // 当错误发生的时候,我们捕获到发生的错误,然后抛出我们自定义的错误

 /**
  * 业务处理错误基类
  */
 class ServiceError extends Error {
   /**
    * @param {*} message 错误消息
    * @param {*} code 错误状态码
    */
 constructor(message, code) {
     super(message);
     this.code = code;
   }

   // 方法
   toResponseJSON(){

   }
 }

 // 文件上传错误
 exports.UploadError = class extends ServiceError {
   constructor(message) {
     super(message, 413);
   }
 };
 // 禁止访问错误
 exports.ForbiddenError = class extends ServiceError {
   constructor(message) {
     super(message, 401);
   }
 };
 // 验证错误
 exports.ValidationError = class extends ServiceError {
   constructor(message) {
     super(message, 406);
   }
 };
 // 无资源错误
 exports.NotFoundError = class extends ServiceError {
   constructor() {
     super("not found", 406);
   }
 };
 // 未知错误
 exports.UnknownError = class extends ServiceError {
   constructor(message) {
     super("server internal error", 500);
   }
 };

 module.exports.ServiceError = ServiceError;

管理员模块登录功能

  1. 在dao下创建model目录,存放数据模型 adminService.js

    const sequelize = require("../dbConnect");
    const { DataTypes } = require("sequelize");
    
    // 定义数据模型
    const Admin = sequelize.define("admin", {
      // 配置表的列
      loginId: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      name:{
        type: DataTypes.STRING,
        allowNull: false,
      },
      loginPwd: {
        type: DataTypes.STRING,
        allowNull: false,
      }
    }, {
      freezeTableName: true, // 禁止修改表名
      createdAt:false,
      updatedAt:false,
      paranoid:true, // 从此以后,该表的数据不会真正的删除,而是会添加一个deletedAt的字段,记录删除时间
    });
    
    module.exports = Admin;
    
  2. 安装md5,哈希加密

    npm i md5
    
  3. 创建/dao/db.js

    // 该文件负责对数据库进行一个初始化操作
    const sequelize = require("./dbConnect"); // 数据库连接实例
    const adminModel = require("./model/adminModel"); // 数据模型
    const  md5 = require("md5");
    
    (async function(){
      // 将数据模型和表进行同步
      await sequelize.sync({
        alter:true
      })
    
      // 同步完成之后,有一些表是需要一些初始化数据的数据
      // 需要先查询这张表有没有内容,没有内容才初始化数据
      const adminCount = await adminModel.count()
      if(!adminCount){
        // 进入此 if,说明该表没有数据,我们进行一个初始化
        await adminModel.create({
          loginId:"admin",
          name:"超级管理员",
          loginPwd:md5("123456")
        })
        console.log("初始化管理员数据完毕");
        
      }
      console.log("数据库数据已经准备完毕");
      
    })()
    
    // app.js
    // 引入数据库连接
    require('./dao/db');
    

三层架构

  1. /routes/admin.js

    var express = require('express');
    var router = express.Router();
    
    const {loginService} = require('../service/adminService')
    
    /* GET home page. */
    router.post('/login',async function(req, res, next) {
      // 首先应该有一个验证码的验证
      // console.log(req.body);
      
      // 假设上面的验证码已经通过了
      const result =await loginService(req.body)
      console.log("result>>",result);
      
    });
    
    module.exports = router;
    

    /service/adminService.js

    // admin 模块的业务逻辑层
    const md5 = require('md5');
    const {loginDao} = require('../dao/adminDao')
    
    module.exports.loginService  = async function(loginInfo){
      // console.log(loginInfo);
      loginInfo.loginPwd = md5(loginInfo.loginPwd); // 对密码进行加密
      // 接下来进行数据的严恒,也就是查询该条数据在数据库中有没有
      let data = await loginDao(loginInfo);
      if(data && data.dataValues){
        // 添加token
    
      }
      return {data}
    }
    

    /dao**/adminDao.js**

    // 这一层负责和数据库打交道
    const adminModel = require('./model/adminModel')
    
    // 登录
    module.exports.loginDao = async function(loginInfo){
      // console.log(loginInfo);
      // 接下来就需要连接数据库进行一个查询
      return await adminModel.findOne({
        where:{
          loginId:loginInfo.loginId,
          loginPwd:loginInfo.loginPwd
        } 
      })
    
      
    }
    

    /app.js

    ...
    // 引入数据库连接
    require('./dao/db');
    // 引入路由
    var adminRouter = require('./routes/admin');
    // var usersRouter = require('./routes/users');
    
    ...
    // 使用路由中间件
    app.use('/api/admin', adminRouter);**
    

token的生成和验证-管理员模块

  1. 安装生成token的包

     npm i jsonwebtoken
    
  2. admin.js

    var express = require('express');
    var router = express.Router();
    var {formatResponse,analysisToken} = require('../utils/tool')
    
    const {loginService} = require('../service/adminService')
    
    // 登录
    router.post('/login',async function(req, res, next) {
      // 首先应该有一个验证码的验证
      // console.log(req.body);
      
      // 假设上面的验证码已经通过了
      const result =await loginService(req.body)
      // console.log("result>>",result);
      if(result.token){
        res.setHeader("authorization",result.token)
      }
      res.send(formatResponse(0,"",result.data))
      
    });
    
    // 恢复登录状态
    router.get('/whoami',async function(req, res, next) {
      // 1. 从客户端的请求拿到token,解析token,还原成有用的信息
      const token = analysisToken(req.get("Authorization"))
      console.log(token);
      
        //2. 返回给客户端
        res.send(formatResponse(0,"",{
          "loginId":token.loginId,
          "name":token.name,
          "id":token.id,
        }))
      
    })
    
    module.exports = router;
    
  3. adminService.js

    // admin 模块的业务逻辑层
    const md5 = require('md5');
    const { loginDao } = require('../dao/adminDao')
    const jwt = require('jsonwebtoken');
    
    module.exports.loginService = async function (loginInfo) {
      // console.log(loginInfo);
      loginInfo.loginPwd = md5(loginInfo.loginPwd); // 对密码进行加密
      // 接下来进行数据的严恒,也就是查询该条数据在数据库中有没有
      let data = await loginDao(loginInfo);
      if (data && data.dataValues) {
        // 添加token
        data = {
          id: data.dataValues.id,
          loginId: data.dataValues.loginId,
          name: data.dataValues.name,
        }
        // data = data.dataValues
        var loginPeriod = null
        if (loginInfo.remember) {
          // 如果用户勾选了登录7天,那么remember 里面是有值的,将这个值赋值给period
          loginPeriod = parseInt(loginInfo.remember)
        } else {
          //否则的话,默认时长为1天
          loginPeriod = 1
        }
        // 生成token
        const token = jwt.sign(data, md5(process.env.JWT_SECRET), { expiresIn: 60 * 60 * 24 * loginPeriod });
        console.log("token>>",token);
        return {
          token,
          data
        }
    
      }
      return { data }
    }
    
  4. utils/tool.js

    const jwt = require('jsonwebtoken')
    const md5 = require('md5')
    // 格式化要响应的数据
    module.exports.formatResponse = function(code,msg,data){
      return {
        "code":code,
        "msg":msg,
        "data":data ,
        // "status":"OK"
      }
    }
    
    module.exports.analysisToken = function(token){
      return jwt.verify(token.split(" ")[1],md5(process.env.JWT_SECRET))
    }
    
  5. error.js

      // 方法
      // 格式化的返回错误信息
      toResponseJSON(){
        return formatResponse(this.code,this.message,null)
      }
    
  6. 对传过来的token进行验证

    npm i express-jwt
    
    // 在app.js中引入
    const { expressjwt: expressJWT } = require("express-jwt");
    ...
    // 配置验证 token 中间件
    app.use(expressJWT({
      secret:md5(process.env.JWT_SECRET),
      algorithms:["HS256"], // 新版本的 expressJWT 必须要求指定算法
    }).unless({
      // 需要排除的token验证的路由
      path:[{"url":'/api/admin/login',methods:['POST']}]
    }))
    ...
    // 错误处理,一旦发生了错误,就会到这来
    app.use(function(err, req, res, next) {
      console.log("err.name>>",err.name);
      console.log("err.message>>",err.message);
      if(err.name ==='UnauthorizedError'){
        // 说明是token验证错误,接下来抛出我们自定义的错误
        res.send(new ForbiddenError("未登录,或者登录已经过期").toResponseJSON())
      }
      
    });
    

更新管理员信息

  1. 捕获异步错误

     npm i express-async-errors
     
     // app.js
     // 默认读取项目根目录下的 .env 环境变量文件
    require("dotenv").config()
    require("express-async-errors")
    
  2. adminDao.js

    // 更新管理员
    module.exports.updateAdminDao = async function(newAccountInfo){
      return await adminModel.update(newAccountInfo,{
        where:{
          loginId:newAccountInfo.loginId
        }
      })
    }
    
  3. admin.js

    // 修改
    router.put('/',async function(req, res, next) {
      res.send(await updateAdminService(req.body))
    })
    
    module.exports = router;
    
  4. adminService.js

// 更新
module.exports.updateAdminService = async function (accountInfo) {
  // 1. 根据传入的账号信息查询对应的用户(注意使用旧密码)
  const adminInfo = await loginDao({
    loginId: accountInfo.loginId,
    loginPwd: md5(accountInfo.oldLoginPwd), // 对密码进行加密
  })
  //2. 分为两种情况,有用户信息和没有用户信息
  if(adminInfo && adminInfo.dataValues){
    // 说明密码正确,开始修改
    // 其实就是组装新的对象,然后进行更新即可
    const newPassword = md5(accountInfo.loginPwd) // 对新密码进行加密
     await updateAdminDao({
      name: accountInfo.name,
      loginId: accountInfo.loginId,
      loginPwd: newPassword,
    })
    // console.log("result>>",result);
    return formatResponse(0,"",{
      "loginId":accountInfo.loginId,
      "name":accountInfo.name,
      "id":adminInfo.dataValues.id, // 注意这里要使用原来的id,而不是新生成的id
    })
    
  }else{
    // 密码输入不正确
    // 抛出自定义错误
    throw new ValidationError("旧密码不正确")
  }
}

验证码

  1. 安装包

    npm i svg-captcha
    npm i express-session
    
  2. 新建路由文件captcha.js

    var express = require('express');
    var router = express.Router();
    const {getCaptchaService} = require('../service/captchaService')
    
    // 登录
    router.get('/',async function(req, res, next) {
      // 生成一个验证码
      const captcha = await getCaptchaService()
      req.session.captcha = captcha.text;
      // 设置响应头
      res.setHeader('Content-Type', 'image/svg+xml');
      // 响应验证码图片
      res.send(captcha.data);
    });
    
    module.exports = router;
    
  3. 在app.js中引入路由

    const session = require('express-session'); 
    ...
    var captchaRouter = require('./routes/captcha');
    ...
    // 创建服务器实例
    var app = express();
    
    app.use(session({
      secret: md5(process.env.SESSION_SECRET), // 用于加密的字符串(密钥)
      resave: true, // 强制保存 session 即使它并没有变化
      saveUninitialized: true, // 强制将未初始化的 session 存储
    }))
    ...
    app.use(expressJWT({
    	...
      path:[
        {"url":'/api/admin/login',methods:['POST']},
        {"url":'/res/captcha',methods:['GET']},
      ]
    }))
    
    app.use('/res/captcha', captchaRouter);
    
  4. captchaService.js

    const svgCaptcha = require('svg-captcha')
    
    module.exports.getCaptchaService = async function () {
      return svgCaptcha.create({
        size: 4, // 验证码长度
        ignoreChars: '0Oo1iI', // 验证码字符中排除 0Oo1iI
        color: true, // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有
        noise: 6, // 干扰线条的数量
      })
    }
    
  5. admin.js

router.post('/login',async function(req, res, next) {
  // 首先应该有一个验证码的验证
  // console.log(req.body);
  if(req.body.captcha.toLowerCase() !== req.session.captcha.toLowerCase() ){
    //如果进入此if, 说明是有问题的,用户输入的验证码不正确
    throw new ValidationError("验证码错误")
  }
  ...
 }