# node-project0314 **Repository Path**: newsegmentfault/node-project0314 ## Basic Information - **Project Name**: node-project0314 - **Description**: 工程化项目...... - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-06-22 - **Last Updated**: 2022-08-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### web项目的分类 前端项目:前端的项目(直接和用户接触),例如说:页面展示类项目、移动端展示类、小程序、 后端项目:后端的项目(直接和数据接触),例如说:java、c、go、python、nodejs... 前台项目:面向与C端,只让全国用户使用 后台管理项目:面向与B端,让运营人员使用 ### 学习什么? 把工程化所有相关的知识串一下 ### 目录结构 backend 后端项目,nodejs去写接口 frontend 前端项目,渲染页面 node静态 静态资源,直接拿页面使用 README.DOC 关于项目的文档 ### 先做路由 路由: 一种映射关系(规则) 后端路由: 一个路径对应的一个处理函数 前端路由: 一个路径对应的一个界面(注意可以是页面,也可以不是页面) 前端路由又分为两种: hash 和 history hash: http://crm.atguigu.com/#/exam/add 路径当中带有#号的就是hash路由 history: https://juejin.cn/events/shenzhen 路径中没有带#的就是history路由 > 拓展: 切换页面路径发生改变,页面内容改变,但是没有刷新,叫SPA > single page application -> SPA ### sme-router(不作为重点) 1. 下载 2. 引入 3. 创建实例 `let router = new SMERouter('app');` 4. 配置路由规则 ` router.route('/login', (req, res) => { res.render('xx') }) ` ### 将hash改成historey模式 1. 创建路由实例的时候,添加第二个参数`let router = new SMERouter('app', 'html5'); ` 2. 对webpack进行处理 ```js devServer: { port: 8080, // 顿口号 open: true, // 自动打开浏览器 compress: true, // 启动gzip压缩 liveReload: true, // 启动自动更新 historyApiFallback: true// 增加: 当找不到对应信息时,会将其定位到index.html }, ``` ### 渲染ejs模板 1. 安装loader 2. 在webpack配置ejs-loader > 拓展: 使用了一下html-loader,说明了使用ejs和html的差异,选择ejs是因为可以在ejs中传数据 此时页面又内容,没有样式 ### 使用adminLTE模板样式 1. 在 public 下放上 adminLET文件夹 2. 在 public/index.html 中引入所有的css和js(注意js的顺序) --------------------------------------------------------------- ### 对图标不显示的处理-打包文件过大警告处理 1. 把webpack中的html-loader干掉 2. wwebpack中配置一下允许大小即可 ```js mode: 'productions', ... performance: { hints: 'warning', // 枚举 false关闭 maxEntrypointSize: 100000000, // 最大入口文件大小 maxAssetSize: 100000000, // 最大资源文件大小 assetFilter: function (assetFilename) { //只给出js文件的性能提示 return assetFilename.endsWith('.js'); } }, ``` ### 二级路由 二级路由 在一级路由的基础上对应的路径和渲染的模板 步骤: 2步 1. 设置当前路由的子路由(通过next方法设置),给ejs模板中传入这个子路由对象,在ejs模板中拿到这个子路由,在需要渲染的位置设置这个子路由 ```js router.route('/adv', (req, res, next) => { // res.render(advView()); // next 当前的路由渲染完毕后,传递给下一个路由,让下一个路由接着渲染 next( advView({ subRoute: res.subRoute() }) ) }) ``` ```html
<%= data.subRoute %>
``` 2. 定义子路由 ```js router.route('/adv/adminList', (req, res) => { res.render('管理员列表') }) router.route('/adv/advList', (req, res) => { res.render('广告列表') }) ``` 此时发现样式不好使?因为设置完二级路由的时候,请求依赖的静态资源路径发生改变(SMERouter给我们改了) 我们自己修复了,将index.html中依赖的静态资源修改成以根路径引入即可 ### 点击侧边栏按钮二级路由跳转 点击侧边栏跳转逻辑两种 1. 获取到按钮,给按钮绑定点击事件跳转 document.querySelectorAll('.nav-link')[1].onclick = function () { router.go() } 2. 在模板标签种 在ejs中,拿不到router,如何解决,帮到window上 3. 侧边栏按钮高亮显示 在ejs模板中传入 url ,标签上判断路径,进行侧边栏按钮高亮显示 ### 定义接口(写在接口文档中) 创建用户的接口 ### 后端:搭建服务(由于没网,放一放) express创建app服务 ### 书写后端路由 app.post('/register', () => {}) > 注意: 这里是post请求,需要使用 body-parser 来解析数据 > app.use(bodyParser.json()) ### 前端:发送网络请求 1. 在`/adv/adminList`路由中获取弹框保存按钮 绑定点击事件 获取到页面弹框input输入的值 发送ajax请求 2. 安装axios引入,在发送请求按钮事件的回调中发送请求 > 注意: axios是异步的,使用await/async ### 发送请求: 发现跨域, 如何解决? 配置webpack-dev-server代理即可 ```js devServer: { // 看门狗(代理) proxy: { // "/api" 是标识,请求只要带这个标识,就认识 "/api": { target: "http://127.0.0.1:80", // 代理的目标路劲,转发的地址 pathRewrite: {"^/api" : ""}, // 路劲重写 } } } ``` 此时后端就可以接收到前端发送的数据了 ### 将用户添加到数据库 - 数据库初始化 ```js const mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:27017/project0314', (err) => { if (err) console.log('连接数据库失败') console.log('连接数据库成功') }); // 在路由中写 // 创建文档结构 let adminSchema = new mongoose.Schema({ adminName: String, passWord: String, regTime: { type: String, default: Date.now().toString() }, loginTime: { type: String, default: "" }, }) // 创建模型 let adminModel = mongoose.model('adminlists', adminSchema); try { // 插入数据 let result = await adminModel.create({ ...req.body, }) console.log('创建用户成功', result) // 返回调用接口的信息 res.send({ code: 10000, message: "ok" }) } catch (error) { console.log('创建用户失败', error) res.send({ code: 10001, message: error }) } ``` ### 前端:界面上接收到返回的数据,关闭弹框 ### 渲染页面 1. 定义接口(前后端都遵循) 2. 写后端api 创建路由 使用 adminModel 去数据库查询数据 返回给前端 3. 前端写请求(axios),在渲染二级路由的时候 在渲染adminList之前发请求,获取到数据,放到ejs模板中去渲染 4. 当创建完用户的时候,需要再次请求获取所有用户列表数据 ### 添加管理员成功后再次添加失败解决-事件委派 原因: 重新渲染列表,按钮被重新渲染了,点击事件就没有了 ### 删除功能 1. 解决如何获取到点击的就是删除的按钮吧 2. 发送ajax请求:发现没有接口 3. 定义删除接口 4. 书写后端接口 5. 调用测试前后端能否跑通 -------------------------------------------------------- ### 前端代码优化 - 路由优化 把所有路由的回调函数抽出来,放到一起去管理 ### 后端代码优化 - 路由优化 把所有路由的回调函数抽出来,放到一起去管理 ### 前端:为了方便维护,把所有调用接口函数进,抽离出来行统一管理 ### 只要页面数据变化,页面顶部就加一个蓝色进度条 - nprogress 包 只要发请求就有蓝色进度条 axios的二次封装 ********* 1. 创建请求示例 2. 配置基础路径和超时时间 3. 配置拦截器 在请求拦截器中,执行 `NProgress.start()` 在请求失败、响应成功、响应失败的时候`NProgress.done()` 4. 抛出示例 然后在`api/index.js`中所有的请求函数都使用request实例 > 还可以优化的项 > 1. 响应拦截器`return response.data`托一层数据 > 2. 可以对错误的统一处理 ### 后端:创建用户时,用户已存在 在存入用户之前 要判断当前用户名是否已经存在 在创建用户的接口中,插入数据之前要查询数据库,查看是否已经存在该用户 ### 登录逻辑 在登录界面输入用户名密码,点击登录按钮,发送ajax请求,到后端服务器 后端服务器拿着前端传过来的用户名和密码,去数据库中比对 对比成功的话,返回数据 注意 >>> 返回一个token(令牌)<就是一个字符串>,返回给前端,只要有这个token就代表登录成功 对比失败的话,不能让登录 后端需要做什么事情? 1. 接收前端发过来的用户名密码 2. 去数据库对比 3. 对比成功,创建 token(令牌) 4. 返回给前端 再之后,每次发送请求的时候,都需要携带 token(令牌),一般情况下,在请求头当中使用的键为 token/authorization: xxxxxxx ### jwt-token技术(Json Web Token) - `jwt-simple` 了解: cookie -> cookie-session -> token -> jwtToken 使用的包 - `jwt-simple ` ### 登录步骤 1. 接口文档 2. 后端 3. 前端 ### 之后所有的操作都需要进行校验 1. 每次发送请求都应该携带token,在请求头携带 在请求拦截器里面,去给所有的请求携带请求头token ```js request.interceptors.request.use((config) => { NProgress.start(); // 进度条开始 // 携带token请求头 let token = localStorage.getItem('TOKEN_KEY'); if (token) { config.headers.token = token; } return config }, (err) => { NProgress.done(); return Promise.reject(err); }) ``` 2. 服务端需要进行校验,所有的请求都需要进行校验 都需要解析token,如何做? 中间件去做 注意: 真的是所有的请求都需要校验吗? login 这个路由不需要,除了 login 以外都需要校验 中间件 -> `/middleware/isLogin.js` 到这一步,我们的登录做了2/3 需要做登录状态校验的有两个地方: 1. 所有的请求需要校验(直接就是后端校验,返回给前端数据,告诉登录状态) 2. 只要路由跳转,就需要校验(是前端需要大量的逻辑处理,需要调用后端接口) ### 只要路由跳转,就发请求去校验一次 这里我们使用的是SME-Router,它没有全局的拦截路由跳转的地方, 所以我们曲线救国,发现除了登录外所有的路由跳转都过 `/adv` 在`/adv`中进行发送请求校验 定义判断登录状态接口 ### 登录这块还有最后一个内容 ### 当用户点击退出登录的时候,逻辑如何处理? 前端去删除掉token即可,注意:这种不科学? 如果我就前端不删token呢,后端还能让继续访问吗? ---------------------------------------------------------- ### 回顾前一天 #### 代码优化(模块化) 前端: 1. 把所有的调用接口的函数抽离出来,放到一地方统一管理`src/api` 2. 把所有的前端路由的回调函数抽离出来,放到一个地方统一管理 后端: 把所有的后端路由抽离出来,放到一个地方统一管理 #### axios的二次封装(NProgress引入) *** 主要使用的是axios的拦截器,所有的请求都需要走这个拦截器 1. 统一去添加一些参数和配置项,例如说: 1.1 baseURL的设置和超时时间的设置 1.2 请求头统一添加参数 2. 统一的对错误的处理(要知道,要有想法) #### localStroage 和 sessionStroage - 浏览器的本地存储 localStroage: 永久存储 sessionStroage: 临时存储,只要关闭浏览器就清空 特点: 最大存5M,只能存字符串 #### 登录逻辑 为什么写的这么复杂? 了解: cookie -> cookie-session -> token -> jwtToken 因为HTTP请求是一个无状态请求,需要在每次发请求的时候校验当前用户的身份 前端: 在请求头当中携带一个令牌来识别身份,既然要给所有的请求都携带 所有请求都过拦截器,在拦截器当中去设置 1. 在页面上输入用户名密码,发请求携带上用户名密码,到后端 2. 前端只要接收到token就代表登录成功 3. 在之后每次请求都在请求头携带token(拦截器当中设置) 4. 如果发请求验证token失败直接跳转登录页面 后端: 所有的请求过来,都需要校验身份,所以需要一个统一拦截的地方,中间件 要做的事情: 1. 后端接收到请求之后,去数据库中验证当前用户是否真的存在 不存在,直接返回对应错误信息`用户名密码错误` 存在,生成token给前端返回回去 2. 当每次请求过来都要验证当前用户,在中间件中验证 验证成功,继续之后的业务逻辑 验证失败,直接返回`用户未登录` ### 退出登录(前端) 核心要点: 1. 清空token 2. 跳转至登录页面 步骤: 1. 获取退出登录按钮 2. 绑定点击事件 3. 在事件回调中 清空token 跳转至登录页面 ### 遗留问题 前端去删除掉token即可 如果我就前端不删token呢,后端还能让继续访问吗? 这样做前端没问题,但是做的不够,后端并没有对这个token做失效处理 后端要做的事情(了解): 在后端没法办处理token的失效,只要token发过来,没有过期,后端就能解析出用户名 解决思路: 拿个东西给记住,当退出登录的时候,把后端记的这个东西删掉就行 那么此时校验登录,就是两个条件, 1. 就是token没有过期 2. 你记录的那个东西没有删除,是存在的(这里我们采用数据库存登录状态) 记录登录状态步骤: 1. 先给数据库加一个字段, loginStatus 0代表退出 1代表登录 2. 创建用户的时候,默认给的0,为未登录 3. 要在登录的时候修改这个字段为 1 状态,证明是登录的 4. 在退出登录的时候,要修改状态为 0 前端需要发送请求 后端要接收请求修改状态 需要定接口 ``` 请求方式: get 请求地址: /logout 参数: 无 因为请求头里面有token,可以校验身份 ``` ### 文件上传 ```js // application/x-www-form-urlencoded form表单提交 // application/json 传递json数据 // multipart/form-data 文件上传 ``` 前端使用`FormData`这个类,步骤: 1. 收集表单内容(包括文本输入内容,和文件),直接收集这个form元素 2. 使用 let myForm = new FormData(documnet.form) 把收集的这个form元素直接扔进去 3. 发送请求,参数直接是 myForm `axios.post('/xxx', myForm)` 后端使用`formidable`包,步骤 1. 安装引入`formidable`包 2. 在接收请求的路由中,创建一个解析form表单的实例 ```js // 创建一个实例,参数是一个配置对象 const form = formidable({ multiples: true, // 服务器文件存储的路劲 uploadDir: path.resolve(__dirname, './upload'), // 保持扩展名(后缀名) keepExtensions: true, }); ``` 3. 使用创建出来的实例来解析文件上传 ```js form.parse(req, (err, fields, files) => { if (err) { // 失败处理 return; } console.log('字段 -> ', fields); console.log('文件 -> ', files); console.log('文件名 ->', files.adv.newFilename); // 系统内容 let network = os.networkInterfaces(); // http:// 192.168.12.87 :8000 /upload/175612b519925528751f1c900.jpg let str = `http://${ network['以太网'][1].address }:8000/upload/${ files.adv.newFilename }`; res.send({ code: 10000, data: { imgUrl: str }, msg: '上传成功' }) }); ``` 4. 配置服务器可以访问静态资源,让图片可以访问即可 ```js app.use("/upload", express.static(path.join(__dirname, "./upload"))) // 静态资源 限制路径 ``` ### 添加广告(之前需要把文件上传吃透) 1. 定义接口: 2. 写后端逻辑 写路由 安装引入`formidable` 在路由中创建解析对象 `from` 使用 `form.parse(req, (err, fields, files) => {})` > 注意: 配置解析文件的路径,这个文件夹一定要存在,否则存不上图片 1. 去advList.ejs把[添加广告]按钮添加上点击弹框 获取按钮,输入内容, 创建 `FormData` 实例对象 `myform` 调用接口的时候传入这个参数 `createAdv(myform)` ### 获取广告列表 1. 定接口 2. 写后端 1. 去数据库查询 2. 需要把 图片的url重新拼接以下(使用的是 `os.networkInterfaces()`,注意: 记得打印一下这个东西,因为网络环境会变) 3. 把数据返回回去 3. 写前端 书写api 进入advList页面先发请求,获取去数据 整理组装数据(广告类型、创建时间、更新时间) 塞到ejs模板中渲染去 ### 广告列表分页 分页需要知道: 当前页 page 1 每页条数 limit 2