# SIK-blog **Repository Path**: beganing/sik-blog ## Basic Information - **Project Name**: SIK-blog - **Description**: SIK个人博客项目! - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-10-18 - **Last Updated**: 2022-12-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 一.需求分析 ## 1.主业务流程 ![image-operationFlow](./images/image-operationFlow.png) ## 2.角色功能分析 ![image-roleFunctionAnalysis](./images/image-roleFunctionAnalysis.png) ## 3.原型图 ### 3.1 前台 #### 1)首页 封面+文章列表页 ![原型图-前台首页](./images/原型图-前台首页.svg) #### 2)文章详情页 ![原型图-文章详情页](./images/原型图-文章详情页.svg) ### 3.2 后台 ![原型图-后台](./images/原型图-后台.png) ## 4.需求点 ### 4.1 前台 #### 1)首页 > 导航栏 - 默认进入首页页面 - 点击`搜索`、`留言`可弹框或进入对应功能页面 - 点击`登录`,可进入用户登录页面 > 博文信息 - 默认加载最新发布的几篇文章 - 按时间倒序显示博文 - 点击文章,进入文章详情页面,浏览量增加 > 右侧边栏 - 展示平台信息,包括文章量、分类数 - 日历 > 底部 - 页面右下角按钮,点击回到顶部 #### 2)文章详情页 > 导航栏 - 同首页一致 > 头部 - 展示文章标题、发表时间、更新时间、阅读量、评论数等 > 主体 - 展示文章详细内容 > 右侧边栏 - 展示最新的文章 > 底部 - 查看此文章的所有评论 - 可发布对此文章的评论 ### 4.2 后台 #### 1)登录页面 - 输入`用户名`和`密码`后进行验证 - 点击`登录`即进入个人页面 - 点击`退出登录`即可返回登录页面 #### 2)首页 - e-charts 饼图:分类名称和分类文章数量统计图 - e-charts 柱状图:时间和文章数量统计图 #### 3)用户管理 - 点击`编辑`按钮,可以切换`用户/管理员`的身份、 - 可以切换用户状态(是否限制用户登录) #### 4)文章管理 > 文章列表 - 显示文章列表 - 点击新增文章按钮即可新增文章 - 点击删除选中文章按钮即可删除被选定的文章 - 点击编辑按钮即可编辑文章 - 点击删除按钮即可删除文章 - 选择分类可以获取分类下的文章 > 新增文章 - 新增文章 - 上传图片 - 文章内容也可以加入网络图片 #### 5)分类管理 - 显示分类列表 - 点击添加分类按钮即可添加分类 - 点击编辑按钮即可修改分类 - 点击删除按钮即可删除分类 - 输入关键字,可实现模糊搜索 - 选中多个数据,可实现批量删除 #### 6)留言管理 - 显示留言列表 - 点击删除留言即可删除留言 # 二.技术分析 ## 1.技术选型 平台 - 操作系统: windows 10 - 开发平台: VSCode - 测试平台: Chrome/FireFox - 文档平台: 语雀 框架 - 前端 Vue 2.7 - 后端 Express 技术栈 - 前端 H5+css+stylus+vue2+axios+ElementUI+Echarts - 后端 express+mysql+JWT(token)+multer ## 2.数据库设计 ### 1.创建数据库 > 创建 db_blog 数据库 > blog_user 表 | 字段名 | 类型 | 属性 | 备注 | | ----------- | --------- | ----------------------------------------- | -------------------------- | | id | int | 主键,自增,非空,无符号 | 用户 id | | username | varchar | 非空, 默认值'' | 用户名 | | password | varchar | 非空, 默认值'' | 用户密码 | | nickname | varchar | 非空, 默认值'' | 用户昵称 | | avatar | text | 默认 null | 用户头像 | | role | varchar | 非空, 默认值 '用户' | 用户角色 | | ip | varchar | 非空, 默认值'' | 登录 ip | | address | varchar | 非空, 默认值'' | 登录地址 | | createdTime | timestamp | 默认'CURRENT_TIMESTAMP' | 创建时间 | | loginTime | timestamp | 默认'CURRENT_TIMESTAMP'根据当前时间戳更新 | 登录时间 | | state | int | 非空, 默认值为 0 | 状态,0 表示正常,1 表示禁用 | > blog_article 文章表 | 字段名 | 类型 | 属性 | 备注 | | ------------------- | --------- | ----------------------------------------- | ---------------- | | id | int | 主键,自增,非空,无符号 | 文章 ID | | article_title | varchar | 非空, 默认值'' | 文章标题 | | article_content | longtext | 非空, 默认值'' | 文章正文 | | article_createdTime | timestamp | 默认'CURRENT_TIMESTAMP' | 文章创建时间 | | article_updatedTime | timestamp | 默认'CURRENT_TIMESTAMP'根据当前时间戳更新 | 文章更新时间 | | article_deleteTime | timestamp | 默认 null | 文章删除时间 | | article_views | bigint | 默认值'0' | 文章浏览量 | | category_id | int | 设为外键,与 blog_sort 表中的主键相连 | 文章分类 ID | | article_pic | text | 非空, | 文章封面图片路径 | | article_praise | int | 默认值'0' | 文章点赞量 | > blog_sort 分类表 | 字段名 | 类型 | 属性 | 备注 | | ---------------- | --------- | ----------------------------------------- | -------------------------- | | id | int | 主键,自增,非空,无符号 | 分类 ID | | name | varchar | 非空, 默认值'' | 分类名称 | | sort_createdTime | timestamp | 默认'CURRENT_TIMESTAMP' | 分类创建时间 | | sort_updatedTime | timestamp | 默认'CURRENT_TIMESTAMP'根据当前时间戳更新 | 分类更新时间 | | sort_deletedTime | timestamp | 默认 null | 分类删除时间 | | sort_views | int | 默认为 0 | 该分类所有文章的浏览量总和 | | sort_articles | int | 默认为 0 | 该分类包含的文章数量 | > blog_comment 留言表 | 字段名 | 类型 | 属性 | 备注 | | --------------- | --------- | ----------------------------------------- | ------------ | | id | int | 主键,自增,非空,无符号 | 留言 id | | comment | text | 默认 null | 留言内容 | | com_article_id | int | 设为外键,与 blog_article 表中的主键相连 | 文章留言 id | | com_user_id | int | 设为外键,与 blog_user 表中的主键相连 | 用户留言 id | | com_createdTime | timestamp | 默认'CURRENT_TIMESTAMP' | 留言创建时间 | | com_updatedTime | timestamp | 默认'CURRENT_TIMESTAMP'根据当前时间戳更新 | 留言更新时间 | ## 3. 接口设计 ### 1) 接口说明 - 接口基准地址`baseURL`:[http://127.0.0.1:3000](http://127.0.0.1:3000) - 服务端已开启`cors`跨域支持 - 使用 code 标识状态(**0:成功.1:失败**) - 使用 msg 代表响应信息 - 使用 result 代表响应结果(数据) - 数据返回格式:JSON 对象 ### 2) 用户模块 **/users** | 请求路径 | 请求方式 | 参数 | 响应成功 | 备注 | | ---------------------- | -------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | | /users/login | post | `{ username, password, ip, address, }` | `{ code: 0, msg: "登录成功", result: { id, username, password:'', tokenStr } }` | 登录接口 | | /users/reguser | post | `{ username, password, nickname, }` | `{ code: 0, msg: "注册成功", result: {} }` | 注册接口 | | /users/updatePwd/:id | put | `{ id, oldPwd, newPwd, }` | `{ code: 0, msg: "更新密码成功", result: {} }` | 修改密码 | | /users/avatar/:id | get | `{ id,//用户id }` | `{ code: 0, msg: "获取成功", result: { id, username, avatar, } }` | 获取头像 | | /users/uploadPic/:id | put | `{ id, formData, // 表单对象 }` | `{ code: 0, msg: "更新头像成功", result: { id, username, avatar, } }` | 修改头像 | | /users/getUsers | get | 无 | `{ code: 0, msg: "获取成功", result: { id, //用户id username, //用户名 nickname,//用户昵称 avater,//头像 role,//用户角色 ip, //登录ip address,//登录地址 createdTime,//创建时间 loginTime//登录时间 } }` | 获取所有用户 | | /users/updateUsers/:id | put | `{ id, state, role, }` | `{ code: 0, msg: "修改信息成功", result: {} }` | 修改用户信息 | | /users/deleted/:id | delete | `{ id, }` | `{ code: 0, msg: "删除用户成功", result: {}, }` | 删除用户 | ### 3) 留言模块 **/comments** | 请求路径 | 请求方式 | 参数 | 响应成功 | 备注 | | -------------------- | -------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | /comments/ | get | `{ article_id }` | `{ code: 0, msg: "获取留言成功", result: { id, //用户id nickname, //用户昵称 avatar,//用户头像 state// 用户状态 com_createdTime, com_ip, com_address, com_createdTime } }` | 获取留言 | | /comments/add/:id | post | `{ article_id, user_id }` | `{ code: 0, msg: "添加留言成功", result: {}, }` | 添加留言 | | /comments/delete/:id | delete | `{ id,//留言id }` | `{ code: 0, msg: "删除留言成功", result: {}, }` | 删除留言 | ### 4) 文章模块 **/article** | 请求路径 | 请求方式 | 参数 | 响应成功 | 备注 | | ------------------------- | -------- | ---- | ----------------------------------------------------------------------------------- | ---------------- | | /article/getArticle | get | 无 | `{ code: 0, msg: "获取文章列表成功", result: { total, rows } }` | 获取所有文章信息 | | /article/getSingleArt/:id | get | id | `{ code: 0, msg: '获取指定文章成功', result: { ...rows[0] } }` | 获取单个文章信息 | | /article/getRecommend/:id | get | id | `{ code: 0, msg: '获取文章成功', result: { result } }` | 获取相关推荐 | | /article/getHotArticle | get | 无 | `{ code: 0, msg: '获取文章成功', result: { result } }` | 获取最热文章 | | /article/uploadPic/:id | post | id | `{ code: 0, msg: '修改文章成功', result: { result[0] }, }` | 添加文章图片 | | /article/add | post | | `{ code: 0, msg: '{ code: 0, msg: '添加文章成功', result: { result1[0] },` | 添加文章 | | /article/update/:id | put | id | `{ code: 0, msg: '修改文章成功', result: { "" }` | 修改文章 | | /article/deleted | delete | 无 | `{ code: 0, msg: '删除文章成功', result: { "" }` | 删除文章 | ### 5) 分类模块 **/admin/category** | 请求路径 | 请求方式 | 参数 | 响应成功 | 备注 | | ------------------- | -------- | ----------------- | ----------------------------------------------------------------------------------------------- | ------------ | | /admin/category | get | 无 | `{ code: 0, message: "获取所有分类成功", result: { data, }, }` | 获取全部分类 | | /admin/category/:id | get | { id } | `{ code: 0, message: "获取单个分类成功", result: data, }` | 获取单个分类 | | /admin/category | post | { sort_name } | `{ code: 0, message: "添加分类成功", result: { id: insertId, sort_name, }, }` | 添加分类 | | /admin/category/:id | put | { id, sort_name } | `{ code: 0, message: "修改分类成功", result: { id, }, }` | 修改分类 | | /admin/category/:id | delete | { id } | `{ code: 0, message: "删除分类成功", result: { id, }, }` | 删除分类 | # 三.WBS 团队三人,纵向分配任务,让每位成员都可以体验前后端完整流程 [项目进度表](https://zhuanguser.yuque.com/yuk9kl/or172b/rlmz38?view=doc_embed) # 四.项目总结 ## 1.前台 > 问题:在文章详情页点击推荐文章,虽然跳转到了该文章详情,但是显示的还是上一个文章,刷新即可正常 由于 vue `router-view`会缓存,第二次进入到文章详情,不会走 mounted 生命周期 解决: **第一种**: 使用生命周期函数 :`activated` ```javascript activated() { this.getArticleList() }, ``` **第二种**: 给 router-view 添加唯一`key` > 增加一个不同:key 值,这样 vue 就会识别这是不同的了 ```javascript computed: { key() { return this.$route.path + Math.random(); } }, ``` ## 2.中后台 ### 2.1 token 的使用 ![JWT验证流程](./images/JWT验证流程.png) > JWT(英文全称:JSON Web Token)主要用于跨域认证解决方案。包含三部分如下: - Header(头部)、Payload(有效荷载)、Signature(签名) (三个部分) - Payload 部分才是真正的用户信息,是用户信息经过加密之后生成的字符串 - Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。 客户端收到服务器返回的 JWT 之后,通常会将它储存在 `localStorage `或 `sessionStorage中`。 此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中。 - axios 响应拦截器中成功则存 token,返回 401 则清除`localStorage`中的 token,并定位到登录页面 - axios 请求拦截器中,拿到`localStorage`中的 token,挂载到请求头的 Authorization 字段,带到后端 ### 2.2 Dialog 对话框内,回车、数据回显引起的表单校验问题 > 背景 - 将`添加`和`编辑`功能在同一个`Dialog`对话框显示 - 给`Dialog`对话框内的`input`输入框添加了回车事件,实现回车添加或修改 - 监听对话框的`close`事件,关闭对话框时重置表单`this.$refs.addFormRef.resetFields()` > 现象 1. 打开`新增`对话框,输入内容,按下`回车`,添加成功 2. 接着打开刚才新增内容对应的的`编辑`对话框,数据回显成功,但是输入框下提示“请输入分类名” ![image-分类error](./images/image-分类error.png) > 原因 ![image-form表单](./images/image-form表单.png) 可以看出,`resetFields`方法只对添加有效,但对于添加和编辑同一个表单就不行了 > 解决方法 此时,我们还需要在**对话框打开时**,对表单的校验结果进行移除 在打开修改对话框对应的方法 handleChange 中,添加如下代码: ```vue this.$nextTick(() => { this.$refs.addFormRef.clearValidate(); }); ``` > 为什么不可以直接写`this.$refs.addFormRef.clearValidate()`? Vue 官方对 ref 的重要说明: - ref 本身是作为渲染结果被创建的,在初始渲染的时候还不能访问——因为此时还不存在 - $refs 也不是响应式的,因此不应该在模板中做数据绑定 所以通过`nextTick(()=>{})`,等到页面完全渲染完毕之后执行延迟回调 ### 2.3 搜索功能异常 > 现象 - 只能在第一页输入搜索关键字才可以搜索到,其余页面不可以 - 第一页搜索完成之后,删除搜索关键字,此时页面渲染不正常,页面内容与页数不匹配 > 原因(不太理解) - 当前页面`page`变量,绑定多个`prop` > 解决方法 修改分页器的属性,当前页面`current-page`,加上`.sync` `:current-page.sync="page"` ```vue ``` # 五.心得体会 ## 1. 项目实施前 完善的需求分析十分必要(特别是接口的设计和数据库设计),既是提前准备的过程,也是理清思路的过程,从而更好的把握项目的易、难点,有利于对整个项目的进度以及分工的安排,事半功倍 ## 2. 实施过程中 1. 遇到较难的功能,可以适当放放,先完成有思路的功能,以免耽误进度 2. 当在调试时出现 bug,但又没有飘红或报错时,多半是逻辑的问题,首先要找到可能出现的位置,通过'打断点','在控制台打印','查看 network'等相结合,从而找清错误的位置 3. 当发现对之前的知识遗忘了的时候,千万不能懒,不要怕耽误项目的进度,一定要及时翻笔记,及时巩固(不会就要看) ## 3. 合作 项目的完成是否成功,组员之间的合作十分重要,当遇到问题时及时沟通,相互帮助 # 六.项目展示 ## 1.前台 ![项目展示-前台首页](./images/项目展示-前台首页.png) ![项目展示-前台文章列表页](./images/项目展示-前台文章列表页.png) ![项目展示-前台文章详情页](./images/项目展示-前台文章详情页.png) ![项目展示-前台文章详情页评论](./images/项目展示-前台文章详情页评论.png) ## 2.后台 ![项目展示-后台首页](./images/项目展示-后台首页.png) ![项目展示-后台文章管理页面](./images/项目展示-后台文章管理页面.png) ![项目展示-后台文章分类管理页面](./images/项目展示-后台文章分类管理页面.png) ![项目展示-后台用户管理页面](./images/项目展示-后台用户管理页面.png) ![项目展示-后台评论管理页面](./images/项目展示-后台评论管理页面.png)