Nodejs--Express框架使用
目录
一.概念
二.项目目录结构
三.app.js
四.项目需要的中间件
五.Mysql连接
六.日志配置
七.实体模型配置
八.统一结果封装
九.app.js的详细配置
十.自定义登录拦截器
十一.route路由配置
十二.controller处理
十二:静态页面:
十三:总结
一.概念
Express是一个使用Node.js编写的灵活且最受欢迎的Web应用程序框架。它提供了一组用于构建Web应用程序和API的工具和功能,使开发者能够轻松地处理路由、请求和响应、中间件、模板引擎等。
以下是一些Express框架的特点和用法:
-
快速搭建Web应用程序:Express提供了一个简洁的API,使您能够快速搭建一个完整的Web应用程序。通过使用Express,您可以轻松地定义路由、处理HTTP请求和响应、设置和使用中间件等。
-
简化的路由功能:Express提供了一种简单而强大的方式来定义和处理路由。您可以使用Express的路由功能来定义不同URL路径的处理函数,并将它们链接在一起以构建完整的Web应用程序。
-
中间件支持:Express使用中间件来处理请求和响应。中间件是一个函数,它可以在请求到达目标处理函数之前或之后执行,并对请求或响应进行处理。Express提供了许多内置的中间件,也支持使用第三方中间件。
-
数据库集成:Express与多个数据库提供商(如MongoDB、MySQL等)兼容,并提供了方便的数据库集成。您可以使用适当的数据库驱动程序和中间件来连接和操作数据库。
-
模板引擎支持:Express支持多种模板引擎,如EJS、Pug等。模板引擎允许您在服务器端生成HTML,并将动态数据注入到模板中。
总的来说,Express是一个简单而强大的Web应用程序框架,它使开发者能够快速构建可靠的Web应用程序和API。无论是构建小型项目还是大型应用程序,Express都是一个理想的选择。
二.项目目录结构
说明:
1.app.js : 该文件是启动服务器的主文件,类似于Springboot中的Appliction,
2.routes/ :该目录进行了路由的相关配置
3.public/ :该目录是静态文件目录,页面放在该目录下
4.config/ :该目录放我们项目的一些配置文件
5.models/ : 该目录放实体类模型
6.controller/ :该目录类似java的controller,mvc模式
7.interceptor/ :该目录放一些拦截器的相关配置
8.logs/ :该目录放日志文件
9.package.json :该文件是我们项目的说明和依赖,类似于pom.xml
三.app.js
express基于nodejs标准库中的http模块构建,下面我们看一下app.js文件
我们先看一下最核心最简单的配置:
const express = require('express')
//构建应用对象
const app = express()
app.get('/',(req,res)=>{
res.send("hello,world")
})
//启动服务器
app.listen(8081,()=>{
console.log("访问地址: http://localhost:8081");
})
当我们访问8081这个端口时,就能返回hello,world,
四.项目需要的中间件
我们的项目需要满足一些基本的要求,例如:连接mysql,会话管理,静态资源解析,等,下面我们看看我们需要安装哪些中间件:
{
"name": "untitled",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"axios": "^1.6.2",
"body-parser": "^1.20.2",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-session": "^1.17.3",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"pug": "2.0.0-beta11",
"sequelize": "^6.35.0",
"session-memory-store": "^1.0.0",
"static": "^2.0.0",
"winston": "^3.11.0"
}
}
我们来解释一下:
1.axios : 这个不用多说了,就是发请求的
2.body-parser: 这个是用来解析请求体的,
3.cookie-parser: 这个是用来解析cookie获取的
4.cors: 这个是用来配置跨域的
5.debug: express自带的
6.express-session: 负责session管理
7.sequelize: 这个是orm框架,类似于mybatis
8.session-memory-store: 这个是用来将session保存到内存中
9.static: 这个是负责解析静态资源目录的
10.winston: 这个是日志框架,类似于log4j,slf4j等
五.Mysql连接
在config/目录下创建db.js文件
const Sequelize = require('sequelize')
const sequelize = new Sequelize('mybatis','root','123456',{
host: 'localhost',
dialect: 'mysql',
timezone: '+08:00', // 这里是东八区,默认为0时区
// 使用连接池
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000,
},
})
module.exports = sequelize;
创建sequelize对象,前三个参数分别是:数据库名称,用户名,密码, 然后是地址,类型,
时区,以及连接池配置
最后不要忘记吧对象导出
六.日志配置
在config/目录下创建log.js文件
const winston = require('winston')
const path = require('path');
//上一级目录
const dirname = path.dirname(__dirname)
// 设置日志文件输出位置
const logFilePath = path.join(dirname, 'logs', 'logs.txt');
const util = require('util');
// 创建日志记录器
const logger = winston.createLogger({
level: 'info', // 设置日志级别(可选)
//format: winston.format.json(), // 设置日志格式(可选)
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss' // 设置时间格式
}),
winston.format.printf(info => {
// 检查是否传递了user参数
if (info.message && info.user) {
return `${info.timestamp} ${info.level}: ${info.message} ${info}`;
}
return `${info.timestamp} ${info.level}: ${info.message}`;
})
),
transports: [
new winston.transports.Console(), // 将日志输出到控制台
new winston.transports.File({ filename: logFilePath }) // 将日志输出到文件
]
});
function logInfo(...args){
logger.info(getStr(args))
}
function logError(...args){
logger.error(getStr(args))
}
function getStr(...args){
var str = ''
for(let i=0;i<args.length;i++){
var s = util.inspect(args[i])
str += s
}
return str;
}
module.exports = {logInfo,logError};
我这里使用了util模块的inspect()方法,他可以吧任意类型的值转化为字符串
七.实体模型配置
我们先看一下数据库:
tb_user表:
tb_student表:
然后我们分别在models/目录下创建User.js和Student.js文件
const {Model,DataTypes} = require('sequelize')
const sequelize = require('../config/db')
/**
* 创建user模型,指定表为tb_user
*/
const User = sequelize.define('User',{
id:{
type:DataTypes.INTEGER,
primaryKey:true,
autoIncrement:true
} ,
username: DataTypes.STRING,
password: DataTypes.STRING
},{
tableName:"tb_user",
timestamps: false // 关闭自动插入 timestamps 字段
})
module.exports = User;
const {Model,DataTypes} = require('sequelize')
const sequelize = require('../config/db')
/**
* 创建user模型,指定表为tb_user
*/
const Student = sequelize.define('Student',{
id:{
type:DataTypes.INTEGER,
primaryKey:true,
autoIncrement:true
} ,
stuid:DataTypes.INTEGER,
stuname:DataTypes.STRING,
sex:DataTypes.STRING,
myClass:DataTypes.STRING,
address:DataTypes.STRING,
tel:DataTypes.STRING,
sorce:DataTypes.INTEGER
},{
tableName:"tb_student",
timestamps: false // 关闭自动插入 timestamps 字段
})
module.exports = Student;
说明一下:
每个字段可以规定为mysql的那种类型,type:指定类型,primaryKey:指定主键,autoIncremnt:指定自增等...后面我会在出一期详细介绍sequlize框架的文章
八.统一结果封装
像java中那样封装结果:code,msg,data三个:
在models/目录下创建Result.js文件
const httpStatus = require('./HttpStatus')
class Result {
constructor(code, msg, data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 默认的成功返回方法
*/
static ok(httpStatus,data) {
return new Result(httpStatus.code,httpStatus.msg,data)
}
/**
* 默认的失败返回方法
* @returns {Result}
* @param httpStatus
*/
static error(httpStatus) {
return new Result(httpStatus.code,httpStatus.msg,null);
}
}
module.exports = Result
然后在创建HttpStatus.js文件
const HttpStatus = {
OK: { code: 200, msg: "success" },
ERROR: { code: 500, msg: "failure" }
};
module.exports = HttpStatus;
这是要给字面量对象,我们可以使用它来模拟java中的枚举类型
九.app.js的详细配置
const express = require('express')
const bodyParser = require('body-parser')
//导入用户路由
const userRoutes = require('./routes/user')
const stuRoutes = require('./routes/student')
//导入会话管理
const session = require('express-session')
const MemoryStore = require('session-memory-store')(session);
//导入拦截器
const {checkRequest} = require('./intercepter/intercepter')
//导入cookie解析器
const cookieParser = require('cookie-parser');
//导入跨域配置
const cors = require('cors')
//导入static静态资源模块
const static = require('static')
//构建应用对象
const app = express()
//设置跨域
app.use(cors())
//启用static模块
app.use(express.static('public'))
//解析请求体
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extend: true }))
//设置会话中间件
app.use(session({
sotre: new MemoryStore(), //使用内存管理会话
secret: 'ikun123',
resave: false,
saveUninitialized: true,
cookie: { secure: true,maxAge: 1800000 } //30分钟
}))
//使用cookie解析器
app.use(cookieParser())
//使用拦截器
app.use(checkRequest)
//使用路由
app.use('/user',userRoutes)
app.use('/stu',stuRoutes)
//启动服务器
app.listen(8080,()=>{
console.log("访问地址: http://localhost:8080");
})
我们通过以上配置可以基本实现一些主要的功能
十.自定义登录拦截器
我们可以写一个类似springmvc那样的拦截器,来判断是否登录
在interceptor/目录下创建intercerptor.js文件:
// AuthMiddleware.js
const whitelist = ['/user/login','/user/register'];
//const blacklist = ['/user/list','/user/logout','/user/add'];
//导入会话管理
const session = require('express-session')
const MemoryStore = require('session-memory-store')(session);
const checkRequest = (req, res, next) => {
const url = req.originalUrl;
if (whitelist.includes(url)) {
next();
}else{
//如果不是白名单,那么就需要验证了
console.log("黑名单拦截...");
// console.log(req.cookies.sessionID);
//黑名单
if(req.cookies.sessionID != null){
const cookieSessionID = req.cookies.sessionID
const acticeSessions = req.sessionStore.sessions;
// console.log(acticeSessions);
for(let sessionID in acticeSessions){
// console.log(sessionID);
//比较二者值
if(sessionID === cookieSessionID){
//相等,放行,跟新session时间
acticeSessions[sessionID].validity = 30*60
console.log("放行...");
return next()
}
}
//遍历完没有找到seesionID,说明回话过期
return res.status(401).json({message:'会话过期!,请重新登录'})
}else{
return res.status(401).json({message:'未登录!'})
}
}
};
module.exports = {
checkRequest
};
这里我们先创建了一个白名单,来默认放行不需要拦截的路径,然后其他的路径需要拦截,
我们可以看到它传入三个参数:req,res.next,这个next它是express框架内置的一个用来负责函数执行顺序的,也就是自带的,我们可以在任何函数中使用它,当放行之后,我们使用next()方法来继续执行下一个函数,
我们先尝试获取请求头中的cookie中的sessionID,如果有,我们拿着这个sessionID和我们服务端保存的session集合中遍历,对比是否有相等的,如果有,就放行,没有,返回会话过期提示
为什么是遍历呢?因为我们的服务端保存了很多用户的session,不可能是单个session,所以要遍历对比
十一.route路由配置
我们在route/目录下分别创建user.js和student.js,
const express = require('express')
const UserController = require('../controller/UserController')
const router = express.Router()
//GET /user/login
router.post('/login',UserController.login)
//GET /user/list --获取用户列表
router.get('/list',UserController.list)
//POST /user --添加用户
router.post('/add',UserController.addUser)
//GET /user/test 测试路径
router.get('/test',UserController.test)
//GET /user/logout 退出登录
router.post('/logout',UserController.logout)
module.exports = router
const express = require('express')
const StuController = require('../controller/StuController')
const router = express.Router()
router.get('/list',StuController.list)
router.get('/byid/:id',StuController.searchByid)
module.exports = router
类似于vue中的路由配置,
十二.controller处理
我们在controller/目录下分别创建UserController.js和StutController.js
const User = require('../models/User')
const {logInfo,logError} = require('../config/log')
//事务需要用
const Sequelize = require('../config/db')
const Result = require("../models/Result");
const HttpStatus = require("../models/HttpStatus");
//导入类型约束
module.exports = {
//登录
async login(req,res){
try {
//避免重复登录
const sinId = req.cookies.sessionID
if(sinId !=null){
//获取所有sessions
const acticeSessions = req.sessionStore.sessions;
//遍历
for(let sID in acticeSessions){
if(sID === sinId){
//说明有sessionID,返回重复登录错误
return res.status(200).json({message:'重复登录!'})
}
}
}
//都没有说明可以继续登录了
const {username,password} = req.body;
//假设
if(username === 'lisi' && password === '123'){
//服务端设置session
req.session.username = username;
//设置cookie
res.cookie('sessionID', req.sessionID, { httpOnly: true, maxAge: 3600000 });
// res.cookie('username', username, {
// expires: new Date(Date.now() + 1800000), // 设置 cookie 过期时间
// httpOnly: true, // 禁止客户端 javascript 访问 cookie
// secure: false, // 如果使用 HTTPS,请设置为 true
// });
//返回响应
logInfo('用户登录:',{username})
//res.status(200).json({message:'登录成功!'})
res.status(200).json(Result.ok(HttpStatus.OK,username))
}else{
res.status(401).json({messge:'用户名或者密码错误'})
}
} catch (err) {
logError('登录错误:',{err})
res.status(500).json({msg:'服务器错误'+err})
}
},
//查询所有用户
async list(req,res){
try {
const users = await User.findAll()
return res.json(users)
} catch (error) {
res.status(500).json({msg:'服务器错误'})
}
},
/**
* 添加用户
*/
async addUser(req,res){
const { username,password } = req.body;
try {
await Sequelize.transaction(async (t)=>{
await User.create({username,password},{transaction:t})
//记录日志
logInfo('添加用户:',username,password)
res.status(200).json({msg:'添加成功'})
}).then(()=>{
console.log("事务已经提交");
})
} catch (err) {
logError('添加用户失败',{err})
res.status(500).json({msg:'服务器错误'})
}
},
/**
* //退出登录
*/
async logout(req,rese){
console.log("退出登录。。。。");
try {
//将session销毁
var sessionID = req.cookies.sessionID
//获取所有sessions
const acticeSessions = req.sessionStore.sessions;
//遍历
for(let sID in acticeSessions){
if(sID == sessionID){
//销毁session
sessionStore.destroy(sID, (err) => {
if (err) {
console.error('Failed to destroy session:', err);
res.status(500).json({message:err})
} else {
console.log('Session destroyed successfully.');
//销毁cookie
req.clearCookie('sessionID')
res.status(200).json({message:'退出成功'})
}
});
}
}
} catch (error) {
res.status(500).json({msg:'服务器错误'})
}
},
//测试
async test(req,res){
try {
console.log("测试....");
res.json({test:"测试"})
} catch (error) {
res.status(500).json({msg:'服务器错误'})
}
}
}
读者自行看上面的代码和注释思考登录和注销的流程
注意:我们在添加用户时开启了事务,当成功添加后提交事务
StuController.js
const Student = require('../models/Student')
const {logInfo,logError} = require('../config/log')
//事务需要用
const Sequelize = require('../config/db')
module.exports = {
//查询所有学生
async list(req,res) {
try {
const stuList = await Student.findAll()
// console.log(stuList);
res.status(200).json(stuList)
} catch (error) {
res.status(500).json({msg: '服务器错误'})
}
},
//根据id查询学生
async searchByid(req,res){
const id = req.params.id
console.log(id)
console.log("hhhh")
try{
const stu = await Student.findOne({
where:{id:id}
})
if(stu){
res.status(200).json(stu)
}else {
res.status(200).json({message:"无此用户"})
}
}catch (error){
res.status(500).json({msg: '服务器错误'})
}
}
}
十二:静态页面:
下面看一下我们的静态页面:
public/目录下的index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<div id="app">
<div>账号:<input type="text" v-model="username"></div>
<div>密码:<input type="text" v-model="password"></div>
<button @click="login">登录</button>
</div>
<script src="js/vue.js"></script>
<script src="js/axios.js"></script>
<script>
new Vue({
el:"#app",
data:{
return(){
username:""
password:""
}
},
methods:{
//登录
login(){
axios.post('http://localhost:8080/user/login',
{
"username":this.username,
"password":this.password
}).then(res=>{
if(res.data.code===200){
alert("登录成功")
location.href = './html/home.html'
}
})
}
}
})
</script>
<body>
</body>
</html>
public/html/下的home.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统</title>
<link href="../css/home.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
<h1>学生管理</h1>
<input type="text" v-model="id"><button @click="search">搜索</button>
<table>
<tr>
<th>学号</th>
<th>姓名</th>
<th>性别</th>
<th>班级</th>
<th>地址</th>
<th>电话</th>
<th>分数</th>
<th>操作</th>
</tr>
<tr v-for="(item,index) in students" :key="index" class="stu">
<th>{{ item.stuid }}</th>
<th>{{item.stuname}}</th>
<th>{{item.sex}}</th>
<th>{{item.myClass}}</th>
<th>{{item.address}}</th>
<th>{{item.tel}}</th>
<th>{{item.sorce}}</th>
<th><a href="javascript:;" class="edit">编辑</a>
<a href="javascript:;" class="del">删除</a></th>
</tr>
</table>
<div>hello</div>
</div>
<script src="../js/axios.js"></script>
<script src="../js/vue.js"></script>
<script src="../js/config.js"></script>
<script>
new Vue({
el:"#app",
data(){
return{
students:[],
id:''
}
},
created(){
this.getStuList()
},
methods:{
//查询所有学生
getStuList(){
axios.get(url + '/stu/list').then(res=>{
this.students = res.data;
})
},
//根据id查询学生
search(){
axios.get(url+`/stu/byid/${this.id}`).then(res=>{
this.students = res.data
})
}
},
watch:{
students:function (newVal,oldVal){
this.students =
}
}
})
</script>
</body>
</html>
下面我们运行一下项目:
可以看到我们项目成功启动了!
我们先访问一下home.html中的/user/list看看如果不登录会咋样:
可以看到为登录,下面我们先登录,
这里返回的格式两个不一样因为我写的不完善,没有每一个都是三个那种Result格式的,请见谅!
可以看到,登录成功了,下面我们在访问一下/user/list接口:
可以看到成功查询到了学生列表!,
下面我们来新增一个学生:
可以看到添加成功了!
看一下控制台输出:
Executing (072a3295-6d06-424e-915d-8aa19e702c03): INSERT INTO `tb_user` (`id`,`username`,`password`) VALUES (DEFAULT,?,?);
2023-11-19 21:46:16 info: [ '添加用户:', 'jjh', '123' ]
Executing (072a3295-6d06-424e-915d-8aa19e702c03): COMMIT;
事务已经提交
看到事务已经提交!
再看一下日志:
2023-11-19 21:44:01 info: [ '用户登录:', { username: 'lisi' } ]
2023-11-19 21:46:16 info: [ '添加用户:', 'jjh', '123' ]
可以看到日志已经成功记录了!
下面我们看一下前端页面:
可以看到成功登录!
如果我们继续访问该接口:
可以看到重复登录!!!
登录成功后页面会跳转到home.html:
可以看到学生成功的查询出来了!
十三:总结
好了,以上就是nodejs的express框架的基本使用了,读者可以在此基础上实现根据id查询用户,分页查询,批量增加删除用户等功能,我们下期再见!