一、功能
用户登录 用户注册 用户在线挂号 用户在线签到、签退查看当前就诊等待人数 用户对挂号信息修改、删除
二、核心思想
基于MVC实现 其中view采用ejs模板引擎进行渲染、和后台的交互基于express框架中的Router完成
2.1 数据存储
数据存储在本地的json文件中
具体存储
- user.json 用户登录注册数据存储
- students.json 用户挂号数据存储列表
- visits.json 用户在线签到签退列表
- coming.json 展示就诊等待人数列表
2.2 页面跳转、用户请求以及返回响应
基于express中的Router完成
2.3 就诊人数展示
基于echart完成
三、具体实现
3.1 项目创建
yarn init -y
yarn add express
// 创建index.js编写代码
3.2 index.js 基础写法
const express = require("express")
// 获取服务器的实例(对象)
const app = express()
app.use((req, res, next) => {
console.log("222", Date.now())
// 发送数据给用户 在界面展示
res.send("<h1>222</h1>")
// render传递数据给某页面
res.render('跳转的页面', {'传递的数据'})
// 重定向到某页面
res.redirect('/a/b')
next()
})
app.listen(3000, () => {
console.log("服务器已经启动~")
})
补充:req和res有什么方法?
- req->request得到请求数据。对于get,req.query post req.body,
- res的方法:res.send、res.redirect、res.render、res.status().end()、res.json
- req是请求,从客户端发来的请求;res是响应,从服务器返回的响应
3.3 各功能拆分
3.3.1 view界面|ejs模板引擎
我们使用ejs模板引擎时,也需要在index中导入
const path = require("path");
app.set("view engine", "ejs");
// 这里的views即编写用户界面
app.set("views", path.resolve(__dirname, "views"));
// 这里的public是存放公用的 用户可见的内容 例如图片
app.use(express.static(path.resolve(__dirname, "public")));
app.use(express.urlencoded({ extended: true }));
总结:
“E” 代表什么?可以表示 “可嵌入(Embedded)”,也可以是“高效(Effective)”、“优雅(Elegant)”或者是“简单(Easy)”。EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。EJS 没有如何组织内容的教条;也没有再造一套迭代和控制流语法;有的只是普通的 JavaScript 代码而已。
-
<%'脚本' 标签,用于流程控制,无输出。 -
<%_删除其前面的空格符 -
<%=输出数据到模板(输出是转义 HTML 标签) -
<%-输出非转义的数据到模板 -
<%#注释标签,不执行、不输出内容 -
<%%输出字符串 '<%' -
%>一般结束标签 -
-%>删除紧随其后的换行符 -
_%>将结束标签后面的空格符删除 -
语法简单:EJS 支持直接在标签内书写简单、直白的 JavaScript 代码。只需让 JavaScript 输出你所需要的 HTML ,完成工作很轻松!
-
易于调试:调试 EJS 错误(error)很容易:所有错误都是普通的 JavaScript 异常,并且还能输出异常发生的位置。
-
快速开发:无需浪费时间钻研那些所谓“优雅”的神秘语法,也不用研究数据究竟如何能够被正确处理。
-
易于调试:调试 EJS 错误(error)很容易:所有错误都是普通的 JavaScript 异常,并且还能输出异常发生的位置。
3.3.2 后台服务
各功能拆分之后需要在index中导入并注册 用于使用
// 组件中内容
const express = require("express")
// 创建router对象
const router = express.Router()
router.get("/list", (req, res) => {
res.send("hello 我是商品的hello路由")
})
// 将router暴露到模块外
module.exports = router
// index中的导入 和注册
const userRouter = require("./routes/users");
app.use("/users", userRouter);
// 这样设置之后 和users相关的完整路径变为http://localhost:3000/users//xxx
// xxx是在router中设置 即第一个参数
// eg:router.get('/xxx',(req,res)....)
总结:利用express时,index类似于一个将各种能组件汇聚在一起供项目使用的js文件 各组件将功能分割书写操作。在完成代码时,需要注意:
- 在子组件中,写完代码需要将其暴露在外侧
modeule.exports = router - 在父组件中,要将子组件导入并且注册使用 这里采用的
require并使用use进行注册 - 这里使用的模板导入导出方式是CommonJS 是一种基于服务端的模块化方式
- require和import的区别 - 知乎 (zhihu.com)
- (4条消息) 前端面试——CommonJS模块和ES6模块的区别?_commonjs 同步_焦妮敲代码的博客-CSDN博客
- 还有一种模块化导入导出的方式 我们以在vue中的使用为例
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/',
name: '/',
component: () => import('../layout'),
redirect: '/users',
children: [
{
path: 'users',
name: 'users',
component: () => import('@/views/users/index.vue')
},
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
3.3.3 完成用户请求和响应
我们利用express中的Router得到用户的请求、返回响应、完成页面之间的跳转。在routes文件夹中,按照用户注册登录、用户在线挂号|修改挂号信息|取消挂号信息、用户到达医院后签到、签退分成三个js文件:user.js patient.js visit.js。利用get、post、use来完成一系列操作。
3.3.4 router.get
router.get('/delete',(req,res,next)=>{})
let {user, pwd} = req.query;\
get请求解析:get 请求的参数 通过 req.query 获取,执行query()方法的时候可以传递
第一个参数是查询语句
第二个参数可以是数组或者对象;
第三个参数中是错误err,成功后result的结果;
3.3.5 router.post
const { username, password } = req.body;
3.3.6 router.use
(4条消息) nodejs理解Express中router.use app.use 拦截器 next方法 结合实例新手向_node.js router.use_优雅的王德奥的博客-CSDN博客
3.3.7 其他的一个HTTP请求
3.4 如何实现和本地json数据的联动
在子组件的js文件夹中,导入本地的json数据
const express = require("express");
const fs = require("fs/promises");
const path = require("path");
let USER_ARR = require("../data/user.json");
并且最后利用use拦截器,将数据重新写入到json文件中
router.use((req, res) => {
fs.writeFile(
path.resolve(__dirname, "../data/user.json"),
// 使用 JSON.stringify() 方法将 JavaScript 对象转换为字符串。
JSON.stringify(USER_ARR)
)
// 处理成功返回
.then(() => {
res.redirect("/students/list");
console.log("用户名和密码一致");
})
// 捕获异常
.catch(() => {
res.send("操作失败!");
});
});
四、在ejs中的一些请求
最简单的请求即表单请求。
<form action="/users/login" method="post">
<div>
<input type="submit" value="登录" />
</div>
<div>
<a href="/users/to-register">注册</a>
</div>
</form>
- action中写的是在路由中定义的路由地址
- method包括get和post
a标签也可以实现一些简单的请求,例如删除、修改 跳转到某页面或并进行某操作 是基本的get请求
<a href="/students/to-update?id=<%=stu.id%>">xxx</a>
五、cookie
5.1 基本设置
/*
需要安装中间件来使得express可以解析cookie
1 安装cookie-parser
2 引入 const cookieParser = require("cookie-parser");
3 设置为中间件 router.use(cookieParser())
*/
router.get("/get-cookie", (req, res) => {
const { username, password } = req.body;
// 给客户端发一个cookie
res.cookie("username", "admin");
res.send("cookie已经发送");
});
router.get("/hello", (req, res) => {
// req.cookies 用来读取客户端发回的cookie
console.log(req.cookies);
res.send("这是hello路由");
});
- cookie是有有效期的 默认情况下cookie的有效期就是一次会话(session)会话就是打开到关闭浏览器的过程
- 设置cookie的浏览器 第三个参数 options(配置对象)
res.cookie("name", "sunwukong", {
// expires:new Date(2022, 11, 7) // 月份表示0-11 通常不使用这个
maxAge: 1000 * 60 * 60 * 24 * 30 // 通常使用maxAge 单位是ms
})
- 删除cookie 可以设置一个新的同名cookie来覆盖原来的cookie
app.get("/delete-cookie", (req, res) => {
// cookie一旦发送给浏览器我们就不能在修改了
// 但是我们可以通过发送新的同名cookie来替换旧cookie,从而达到修改的目的
res.cookie("name", "", {
maxAge: 0
})
res.send("删除Cookie")
})
5.2 cookie的不足
- cookie是由服务器创建,浏览器保存.每次浏览器访问服务器时都需要将cookie发回,这就导致我们不能在cookie存放较多的数据,并且cookie是直接存储在客户端,容易被篡改盗用
- 注意:我们在使用cookie一定不会在cookie存储敏感数据
- 所以为了Cookie的不足,我们希望可以这样:
- 将用户的数据统一存储在服务器中,每一个用户的数据都有一个对应的id。我们只需通过cookie将id发送给浏览器,浏览器只需每次访问时将id发回,即可读取到服务器中存储的数据。这个技术我们称之为
session(会话)
六、session
6.1 简单介绍
- session是服务器中的一个对象,这个对象用来存储用户的数据
- 每一个session对象都有一个唯一的id,id会通过cookie的形式发送给客户端
- 客户端每次访问时只需将存储有id的cookie发回即可获取它在服务器中存储的数据
- 在express可以通过express-session组件来实现session功能
使用步骤:
① 安装
yarn add express-session
② 引入
const session = require("express-session")
③ 设置为中间件
app.use(session({
secret:"hello", // 必须要填的内容
}))
req.session.username = username;
- 用户请求来了服务器先去检查客户端有没有connectid这个cookie,有这个cookie(证明他是会员)就把她对应的session对象放在request里,向下进行
- 没有这个cookie(证明他不是会员,会自动给他办一张卡),把这个新生成的对象放到request中,并且会返回到服务器中存储,下次来了就有了
6.2 基本使用·以登录后查看主页面为例
- 登陆成功之后,将用户信息存储在session之中。
req.session.isusername = username; - 登录成功之后,利用
res.redirect('/students/list');重定向至这个页面 - 所以,我们要在这个页面中判断是否有这个sessionid
- 又因为,我们在重定向之后的页面中所有的操作都要登录后完成,因此我们选择在最开始设置一个use拦截器,权限验证成功后才可以向下操作
router.use((req, res, next) => {
if (req.session.isusername) {
next()
} else {
res.redirect("/")
}
})
6.3 session的失效
session是服务器中的一个对象,这个对象用来存储用户的信息。每一个session都会有唯一的id,session创建后,id会以cookie的形式发送给浏览器。浏览器收到以后,每次访问都会将id发回,服务器中就可以根据id找到对应的session。
session包括两个部分
- sessionid(存储在cookie中,发送给浏览器)
- session对象
session失效
- 浏览器的cookie没了(默认情况下,cookie的有效期是一次会话)
- 服务器中的session对象没了(重启服务器)
- express-session默认是将session存储到内存中,所以服务器一旦重启session就会自动重置,所以我们使用时通常会对session进行一个持久化的操作(写到文件或数据库(正式开发时 要写入到数据库)中)
- 手动删除 使其失效 (即登出)
router.get("/logout", (req, res) => {
req.session.destroy(() => {
res.redirect("/")
})
})
6.4 将session存储到本地文件中
使用步骤:
① 安装
yarn add session-file-store
② 引入
const FileStore = require("session-file-store")(session)
③ 设置为中间件
app.use(
session({
store: new FileStore({
path: "./sessions",
secret: "哈哈",
// 有效时间 默认单位为s
ttl: 3600,
// 默认情况下,filestore会每隔一小时清楚一次session 单位是秒
reapInterval: 10
}),
secret: "dazhaxie",
cookie: {
maxAge: 1000 * 3600
}
})
);
- 设置长登录要保证cookie的有效期和session的有效期是一致的
七、bug解决|安全问题
7.1 登录时 需要登录两次
// 这里仅仅是将isusername加到了内存里 而没有将值写到文件里
req.session.isusername = username;
需要手动存以下
// 参数传一个回调函数,回调函数是存储成功之后的下一步操作
req.session.save(() => {
next();
});
7.2 csrf攻击
- 跨站请求伪造http://localhost:3000/students/delete?id=3
- 现在大部分的浏览器的都不会在跨域的情况下自动发送cookie,这个设计就是为了避免csrf的攻击 如何解决?
- 使用referer头来检查请求的来源
- 使用验证码
- 尽量使用post请求(结合token)
- token(令牌)可以在创建表单时随机生成一个令牌,然后将令牌存储到session中,并通过模板发送给用户,用户提交表单时,必须将token发回,才可以进行后续操作,(可以使用uuid来生成token)
token的使用
客户端给服务器发请求,服务器生成token返回给客户端,客户端拿到token(res.token)存起来(localStorage.setItem),存起来以后再发给服务器,服务器解这个token,然后根据解出来的token来识别用户身份
可以利用jsonwebtoken来对数据加密