项目准备
各层的作用如下:
- 表示层:主要对用户的请求接收,以及数据的返回,为客户端提供应用程序的访问。
- 业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。
- 数据访问层:主要看数据层里面有没有包含逻辑处理,实际上它的各个函数主要完成各个对数据文件的操作。而不必管其他操作。
-
安装脚手架
npm i -g express-generator -
查看express版本
express --version -
创建项目名称
express mysite-express -
安装依赖
npm i -
运行
npm start -
设置提示
// /bin/www function onListening() { ... console.log("服务器端已启动,监听3001端口..."); } -
增加目录
- 删除view目录,并在app.js中删除有关view部分
- 安装nodemon
npm i -g nodemon
10. 运行
nodemon npm start
11. 创建数据库
- 安装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');
- 新建工具文件夹 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;
管理员模块登录功能
-
在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; -
安装md5,哈希加密
npm i md5 -
创建/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');
三层架构
-
/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的生成和验证-管理员模块
-
安装生成token的包
npm i jsonwebtoken -
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; -
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 } } -
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)) } -
error.js
// 方法 // 格式化的返回错误信息 toResponseJSON(){ return formatResponse(this.code,this.message,null) } -
对传过来的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()) } });
更新管理员信息
-
捕获异步错误
npm i express-async-errors // app.js // 默认读取项目根目录下的 .env 环境变量文件 require("dotenv").config() require("express-async-errors") -
adminDao.js
// 更新管理员 module.exports.updateAdminDao = async function(newAccountInfo){ return await adminModel.update(newAccountInfo,{ where:{ loginId:newAccountInfo.loginId } }) } -
admin.js
// 修改 router.put('/',async function(req, res, next) { res.send(await updateAdminService(req.body)) }) module.exports = router; -
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("旧密码不正确")
}
}
验证码
-
安装包
npm i svg-captcha npm i express-session -
新建路由文件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; -
在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); -
captchaService.js
const svgCaptcha = require('svg-captcha') module.exports.getCaptchaService = async function () { return svgCaptcha.create({ size: 4, // 验证码长度 ignoreChars: '0Oo1iI', // 验证码字符中排除 0Oo1iI color: true, // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 noise: 6, // 干扰线条的数量 }) } -
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("验证码错误")
}
...
}