# 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