项目1

203 阅读10分钟

一、功能

用户登录 用户注册 用户在线挂号 用户在线签到、签退查看当前就诊等待人数 用户对挂号信息修改、删除

二、核心思想

基于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文件 各组件将功能分割书写操作。在完成代码时,需要注意:

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请求

image.png

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里,向下进行

image.png

  • 没有这个cookie(证明他不是会员,会自动给他办一张卡),把这个新生成的对象放到request中,并且会返回到服务器中存储,下次来了就有了

6.2 基本使用·以登录后查看主页面为例

  1. 登陆成功之后,将用户信息存储在session之中。req.session.isusername = username;
  2. 登录成功之后,利用res.redirect('/students/list');重定向至这个页面
  3. 所以,我们要在这个页面中判断是否有这个sessionid
  4. 又因为,我们在重定向之后的页面中所有的操作都要登录后完成,因此我们选择在最开始设置一个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包括两个部分

  1. sessionid(存储在cookie中,发送给浏览器)
  2. 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攻击

  1. 使用referer头来检查请求的来源
  2. 使用验证码
  3. 尽量使用post请求(结合token)
  • token(令牌)可以在创建表单时随机生成一个令牌,然后将令牌存储到session中,并通过模板发送给用户,用户提交表单时,必须将token发回,才可以进行后续操作,(可以使用uuid来生成token)

token的使用

客户端给服务器发请求,服务器生成token返回给客户端,客户端拿到token(res.token)存起来(localStorage.setItem),存起来以后再发给服务器,服务器解这个token,然后根据解出来的token来识别用户身份 可以利用jsonwebtoken来对数据加密 image.png image.png image.png image.png