# wiki
**Repository Path**: Page-Source/wiki
## Basic Information
- **Project Name**: wiki
- **Description**: Spring Boot + Vue3 前后端分离 实战 wiki 知识库系统
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2023-12-11
- **Last Updated**: 2023-12-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SpringBoot知识体系+Vue3 实战WIKI知识库系统
## 相关软件已传到QQ群文件中
* jdk-8u221-windows-x64.exe
* ideaIU-2019.2.3.exe
* Git-2.23.0-64-bit.exe
* mysql-installer-community-5.7.27.0.msi
* jdk-8u261-linux-x64.tar.gz
## 源码下载
* 关于慕课网GIT说明:https://www.imooc.com/help/detail/111
* 使用下面的命令将源码从远程仓库拉取到本地,需要本地提前安装好git
```
git clone https://git.imooc.com/coding-474/jiawawiki.git
会配置ssh的,可以用ssh:
git clone ssh://git@git.imooc.com:80/coding-474/jiawawiki.git
```
* 数据库初始化脚本已传到QQ群中
## 项目初始化
* 需要本地安装好idea, nodejs,jdk1.8, mysql8.0/5.7, navicat(数据库可视化工具)
* 将下载好的源码,用idea打开
* 刷新maven依赖
* 安装vue cli,参照课程4-3
* 初始化web模块
```
cd web
npm install
```
* 新建数据库参照课程3-2,数据库配置在application.properties
* 数据库初始脚本从QQ群文件中下载
## 项目启动
* 启动服务端:WikiApplication
* 启动前端网站:web\package.json
## 页面访问
* 网站地址: http://localhost:8080
初始用户名密码:test/test
``` @ResponseBody``` 用来返回字符串或JSON对象,使用此注解,表示接口支持所有请求接口(GET、POST、PUT、DELETE)
```@GetMapping``` 只接受GET方法,类似还有```@PostMapping``` 、```@PutMapping```、```DeleteMapping```
```@RequestMapping```用法:```@RequestMapping(value = "/hello", method = RequestMethod.GET)```
```@RestController``` 一般用来返回字符串
```@Controller``` 一般用来返回页面
`` @Primary``注解,当有多个实现类是,首先注入此类
```Alt``` + ```Enter``` 自动导包
```Alt``` + ```Insert``` 键,打开Generate面板,包含getter and setter、constructor、toString等方法
如果Applicatiob和Controller类不在同一个包内,须要添加``` @ComponentScan("Cotroller包全限定路径")```
自动删除无用的引用file-->settings-->搜索 ```auto``` 在Auto Import中勾选```Optimize import on the fly```
```bootstrap``` 配置,单个SpringBoot不会读取bootstrap配置,要SpringCloud架构下的SpringBoot应用才会读,一般用于动态配置,线上可以实时修改实时生效的配置,一般配合nacos使用。
+ 从数据库中获得汉字为乱码,在链接数据库的数据库源改为
``` xml
```
- 突然飘红,出现红色的波浪下划线,错误提示:cannot access com.xx…xx.class
+ 1、重启大法,解决问题。
+ 2、如果重启不行,就清一下Idea缓存(根本原因),强制清除idea缓存方法:
```File > Invalidate Caches /Restart```即可清理缓存
- 如果数据库是5.7版本,使用8.0.22版本连接驱动,会导致sql中的汉字乱码
### 集成热部暑
1. 引入依赖,使用SpringBoot内置的依赖,不需要添加``version` 版本号`
```properties
org.springframework.boot
spring-boot-devtools
```
2. file-->settings-->搜索 compiler,再在右侧勾选 Build project automatically
3. 按两次```Shift``` 键,切换至``Action`` (或者在help--> find Action...),搜索registr(注意:不是``register``,也不是选 ``Registration Actions```),勾选`` compiler.automake.allow.when.app.running ``
注意:只有保存配置文件(application.properties 等)`Ctrl`+ `S`, 才会触发热部署, 或者点击“小锤子” 图标编译;或者稍微改一下service层代码
+ 当复制一个类,在复制的类里按 ``Ctrl`` + ``R`` 进行替换(注意勾选 match case 区分大小写)
+ 复制一个类,如果在左边复制是复制一个新类,在右边复制是复制一个类名
- new 类名 按下``Ctrl`` + ``Alt`` + ``V`` 自动生成一个新类
- 后端接口很多,为了前端能够统一处理逻辑(登录校验、权限校验),需要统一后端的返回值
- 选中一个类,按``` Shift``` + ```F6``` 进行类重命名
- toString 一般用来打日志用
- 两种for循环模板:``fori`` 、 ``iter``
- 在cotroller层不要出现domain实体ebook,用ebookResp(或ebookVo)代替
- ```Ctrl``` + ```Y``` 删掉当前行
- 有时idea提示程序包不存在,实际是有的。Maven可能出现编译缓存。maven—>clean即可.如果觉㧹代码没有错,但编译报错,就clean一下
> + 测试结果跟代码不符
> + 代码没错却编译出错
> + 引入了jar包却没反应
- 养成习惯:改动再小,也要测试通过
## 第4章 vue + vue cli 项目搭建
+ Vuex 保存全局变量
+ main.ts是初始(配置)文件
+ Vue CLI 初始执行main.ts,将内容页App.vue渲染到index.html,完成页面显示
``` json
"dependencice":{
"vue": "^30.0.0",
"vue-router": "^4.0.0-.",
"vux": "^4.0.0-0"
}
```
`` ^`` 表示如果有小版本,将自动更新。```package-lock.json``` 文件进行版本锁定
+ 安装ant-design-vue 一定要进入web里,注意避免直接在项目里。否则加载不出页面,并且会报错
+ 使用`````` 标签来填充路由内容
## 第五章 前后端交互整合
+ ```setup()``` Vue3新增的初始化方法
+ “No 'Access-control-All-Origin' header is ... 出现里跨域问题。跨域可以这样理解,来自一个IP端口的页面(Vue项目),需要访问另一个IP断口的资源(Spring Boot请求接口),会产生跨域问题。
+ ``Ctrl`` + ``Shift`` +``F`` 全局搜索
+ 在调用电子书接口之前会先发一个OPTIONS请求。
+ 修改了配置类或pom.xml,建议重启应用,不要热部署。
#### 5-3 vue3数据绑定显示列表数据
+ 初始化逻辑都写到onMounted(生命周期函数)方法离,setup就放一些参数定义,方法定义setup执行的时候界面还没渲染好,这个时候如果去操作界面元素会报错。多利用生命周期钩子函数
+ 在response里面有一个data,这个data对应的就是后端返回的CommonResp的数据结构
+ 所谓的响应式的数据就是说js里面动态修改的值
+ vue3 新增了ref,用来定义响应式数据,要用这个ref,需要import进来,html代码要拿到响应式变量,需要在setup最后return,使用{{xxx}}来获取变量
``` vu
setup(){
console.log("setup");
const ebooks = ref();
onMounted(() => {
axios.get("http://localhost:8880/ebook/list?name=Spring").then((response) => {
console.log(response);
const data = response.data;
ebooks.value = data.content;
console.log(response);
})
})
return {
ebooks
}
}
```
+ reactive里面一般放一个对
``` vue
setup(){
console.log("setup");
const ebooks1 =reactive({books: []});
onMounted(() => {
axios.get("http://localhost:8880/ebook/list?name=Spring").then((response) => {
console.log(response);
const data = response.data;
ebooks.value = data.content;
ebooks1.books = data.content;
console.log(response);
})
})
return {
ebooks2: toRef(ebooks1,"books")
}
}
```
> 上述的两种方法都可以用,但是我们在项目中,尽量统一,要么全用ref,要么全用reactive
### 5-4 电子书列表界面展示
```vue
const pagination = {
onChange: (page: number) => {
console.log(page);
},
pageSize: 3,
};
```
+ (page: number) 因为它是一个参数,就是onChange后边那段是一个回调函数,是一个function。在参数连边加number的话。我们需要把它用括号括起来。
+ 一次性将图标库导入,而不需要每个页面导入
+ 使用图标库,需要自行安装 ```npm install --save @ant-design/icons-vue```
全局使用图标:
1. 切换到main.js
2. 添加```import * as Icons from '@ant-design/icons-vue'```
3. 在下边继续添加
```ty
const icons: any = Icons;
for (const i in icons) {
app.component(i, icons[i]);
}
```
4. 改造代码:
``` ty
createApp(App).use(store).use(router).use(Antd).mount('#app');
```
为:
``` ty
const app = createApp(App);
app.use(store).use(router).use(Antd).mount('#app');
```
```ty
createApp(App).use(store).use(router).use(Antd).mount('#app');
```
+ style 标签下的scoped:表示这里的样式只在当前组件(Antdv的组件,例.ant-avatar)起作用
``` html
```
### 5-5 Vue CLI多环境配置
+ 增加开发和生产配置文件
+ 修改编译和启动支持多环境
+ 修改axios请求地址支持多环境
自定义参数:VUE_APP_XXX必需以VUE_APP_开头,自定义文件放在web目录下
```
NODE_ENV=production
VUE_APP_SERVER=http://127.0.0.1:8080
```
### 5-9 SpringAOP的使用
三个通知:前置、后置、环绕。
## 第6章 电子书管理功能开发
### 6-1 章节介绍
- 增加电子书管理页面:页面、路由、菜单
- 电子书列表展示:表格组件、查询表单、后端列表查询接口
- 前后端分页处理:后端分页插件、前端分页组件、参数传递
- 电子书编辑:模态框组件、表单组件、后端保存接口
- 电子书新增:界面复用编辑功能、后端复用保存接口、雪花算法
- 电子书删除:确认组件、后端删除接口
- 参数校验:集成SpringBoot Validation
### 6-2 增加电子书管理页面
```Shift``` + ```F6``` 重命名
### 6-3 电子书表格展示
- slots:自定义渲染
- title:表头渲染
- customRender:值渲染
``` vu
{
dataIndex: 'name',
key: 'name',
slots: { title: 'customTitle', customRender: 'name' },
}
```
### 6-4 使用PageHelper实现后端分页
``` java
PageHelper.startPage(1,3);
// 语句只对第一个select起作用,如果后边有两个select,第二个不执行
```
```java
PageInfo pageInfo = new PageInfo<>(elookList);
LOG.info("总行数:" + pageInfor.getTotal());
LOG.info("总页数:" + pageInfor.getPages());
```
- 分页四个元素:
- 页码:(pageNume:1)
- 数量:(pageSize:3)
- 总行数:pageInfor.getTotal()
- 总页数:pageInfor.getPages()
后两个,建议返回total,前端还可以自己去计算总页数;但是如果返回总页数,前端不能确切的知道总条数。
### 6-5 封装分页请求参数和返回参数
- ``Alt `` + ``Insert`` 键,生成get set方法
- Lombok 有反射,万一写的性能不好,多用一层框架,就多一层风险
- 不确定的数据要用泛型
### 6-6 前后端分页功能整合
- 要用响应式变量必须加上.value,
``` vue
onMounted() => {
handleQuery({
page:1,
size:pagination.value.pageSize
})
}
```
真正传到后端的是上述的page、size,这个必须和后端的PageReq的两个参数要一致才会将前端的参数自动映射到EbookReq req这个类里来;
- 表格分页组件内置了一些属性:current、 、pageSize等
- 前边介绍过前端分页,它本来是成功的,就算后端不写分页,前端功能本来就是正常的,它是一个前端分页(逻辑分页),所以一定要确认一下,后端也做了分页
- 看日志非常重要
- 因为handleQuery可能被很多地方调用到,它传过来的参数可能各式各样,都不太一样。所以我们一般会展开来,如下,不管params里面有多少参数,这里只会用到这两个参数。
``` vue
/**
* 数据查询
**/
const handleQuery = (params: any) => {
loding.value = true;
axios.get("/ebook/list",{
params: {
page: params.page,
page: params.size
}
}).then((response) => {
loading.value = false;
const data = response.data;
ebooks.value = data.content.list;
}
```
另一种方式:
``` vue
const handleQuery = (params: any) => {
loding.value = true;
axios.get("/ebook/list?page=" + params.page + "&size=" + params.size).then...
}
```
### 6-6 制作电子书表单
- ``Ctrl`` + ``D`` 复制当前行并粘贴
- ```edit``` 方法无参数,可用下边两种方法表示:
``` vue
编辑
```
``` vue
编辑
```
- ```edit``` 方法有参数表示方法
``` vue
编辑
```
### 6-8完成电子书编辑功能
- ``Alt`` + ``Shift`` + 上/下:可将当前行上下移动
- ``` @RequestBody``` 注解对应的是json方式和(POST)提交,就像ebook用的是content-type:application/json 需要用@RequestBody才能接收到。同样是POST提交,以form方式提交(Content-Type: application/x-www-form-urlencoded)就不需要任何注解。
``` http
POST http://localhost:8880/ebook/save
Content-Type: application/json
{}
```
``` java
public CommonResp save(@RequestBody EbookSaveReq ebookSaveReq) {}
```
- EbookSaveReq类和EbookQueryResp类内容相同,这种设计方法会使类变得很多,但是比较灵活,后面加参数校验时可以体现出来。在6-11中
- 如果定义的类带有泛型,new时也要带有泛型,否则会new失败且不会报错
``` java
public class CommonResp {} //声明类
CommonResp resp = new CommonResp<>(); //new对象
```
### 6-9 雪花算法与新增功能
- 雪花算法:时间戳,数据中心,机器中心,序列号
- @Resource是jdk自带的,@Autoeired是Spring自带的
- id几种算法:自增、uuid、雪花算法
- Integer类默认值是null,
- 要在实体Ebook类中把数据库中 not null 值 赋值set为 0 不用 default 0
因为mbyatis生成的代码是全字段的,所以insert 或 update都是带全字段的
如果insert SQL里写了字段,且给的值是null,这种情况就会存null,default 就失效了
- 雪花算法得到的ID较长,传到前端后,精度丢失,修改不了数据
解决方法:增加统一配置类JacksonConfig.java,将Long类型转成String,再传给前端
### 6-10 增加删除电子书功能
- 重要的业务操作,如删除、审批等,一定要有确认动作
### 6-11 集成Validation做参数核验
- SpringBoot很多功能都是通过注解来完成
- 在git记录上按```Ctrl``` + ```D``` 对比修改内容
- 6-8提到的好处:在request里添加一个注解,不会去影响response。虽然两个类里面属性相同,但是我们对请求类可以增加一些特殊的校验,而返回的类,就没有这些注解了。
- 选中代码,按``Alt`` + ``Shift`` + 上 代码上移
### 6-12 电子书管理功能优化
``` vue
setup(){
const param = ref(); //定义一个响应式变量
param.value = {}; //初始给它一个空变量,否则会报错
}
```
- js对象复制:将json对象转为json字符串,再转回json对象
### 7-2分类表设计与代码生成
- 代码生成器生成的四个类(Category、CategoryExample、CategoryMapper.java、CategoryMapper.xml)不要动,这样我们后面有新的扩展,会增加一些新的字段的话,可以重新去生成。
### 7-3完成分类基本增删改查功能
``` Ctrl``` + ```Y``` 删除选中的内容
### 7-4分类表格显示优化
- 加上``:`` 表示变量,不加表示属性
``` vue
```
- 树形数据展示,查看Ant Design Vue—>Table表格—>树形数据展示
### 7-5 分类编辑功能优化
1. ``` vue
{{c.name}}
```
- 如果是在属性里边用变量,我们直接用变量就可以了;
- 如果是在HTML,就是属性外面,在节点中间要去使用这个变量的话,应该用两个大括号
- ``Ctrl`` + ``Shift`` + ``Z`` :代码重做(就是回到Ctrl+Z回退之前)
### 7-6 电子书增加分类管理功能
- 点击编辑保存之后,列表数据遍了,再次点击编辑之后,返回的内容还是前边儿没有编辑之前的内容(即Action列的变量没有变),是因为“Action”这一列按钮对应的渲染没有变
### 7-8 点击二级分类菜单显示电子书
```const``` = constant 不变的、永恒的
```let``` 表示变量
---
## 第八章 文档管理功能开发
---
### 8-1 本章介绍
+ 重点学习无限极树的管理功能设计&富文本编辑框的使用
+ 文档表设计与代码生成
+ 按照分类管理的代码,复制出一套文档树管理
+ 关于无限极树的增删改查功能开发
+ 文档内容保存与显示:富文本框的使用
+ 首页点击某个电子书时,跳转文档页面,显示文档树
+ 点击某个文档时,加载文档内容
### 8-2 文档表设计与代码生成
### 8-3 完成文档表基本增删改查功能
### 8-4 使用树形选择组件选择父节点
``` vue
/**
* 将某节点及其子孙节点全部置为disabled
*/
const setDisable = (treeSelectData: any, id: any) => {
// console.log(treeSelectData, id);
// 遍历数组,即遍历某一层节点
for (let i = 0; i < treeSelectData.length; i++) {
const node = treeSelectData[i];
if (node.id === id) {
// 如果当前节点就是目标节点
console.log("disabled", node);
// 将目标节点设置为disabled
node.disabled = true;
// 遍历所有子节点,将所有子节点全部都加上disabled
const children = node.children;
if (Tool.isNotEmpty(children)) {
for (let j = 0; j < children.length; j++) {
setDisable(children, children[j].id)
}
}
} else {
// 如果当前节点不是目标节点,则到其子节点再找找看。
const children = node.children;
if (Tool.isNotEmpty(children)) {
setDisable(children, id);
}
}
}
};
/**
* 编辑
*/
const edit = (record: any) => {
modalVisible.value = true;
doc.value = Tool.copy(record);
// 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开
treeSelectData.value = Tool.copy(level1.value);
setDisable(treeSelectData.value, record.id);
// 为选择树添加一个"无"
treeSelectData.value.unshift({id: 0, name: '无'});
};
/**
* 新增
*/
const add = () => {
modalVisible.value = true;
doc.value = {};
treeSelectData.value = Tool.copy(level1.value) || [];
// 为选择树添加一个"无"
treeSelectData.value.unshift({id: 0, name: '无'});
};
```
### 8-5 Vue页面参数传递完成新增文档功能
``` vue
```
### 8-6 增加删除文档功能
tips:
+ 程序设计小技巧,将复杂的算法放到前端来做,减少服务器压力
+ 先设计方案,再做技术调研,验证方案可行性,最后才开始开发
+ 有些业务很复杂时,把一个大SQL拆成多次按主键操作,反而性能更高
### 8-7 集成富文本插件wangeditor
``` 安装
npm iwangeditor@4.6.3 --save
```
``` 使用
import E from "wangeditor"
const editor = new E("#div1")
editor.create()
```
+ 初始的时候,没有modal这块标签(即文档表单),放入到sutUp中渲染不出来;根本就没有这样一个元素,所以初始的时候我们写`` #ID`` 去获取这个选择器的时候,选择不到任何元素根本原因:html上没有content元素,只要html有content元素,即使不可见,也能被渲染出来
+ 加入到onMount中仍旧显示不出来,考虑到modal初始是隐藏的,会不会是因为隐藏而找不到content?
+ 添加到add()和edit()中也不显示;考虑到加载modal是很耗时的,在modal还没加载出来的时候editor.reate()就已经执行了,所以需要异步执行;
+ 需要异步执行时,要想到setTimeout
``` vue
setTimeout(function(){
editor.create();
},100);
```
### 8-8 文档内容表设计与代码生成
### 8-9 文档管理页面布局修改
``` vue
// 将富文本的层级调低
editor.config.zIndex = 0;
```
``` vue
...
const level1 = ref(); // 初始化的时候为null
level1.value = []; // 初始化一个空数组,否则当为null时,上边的level1.length就会出错了
```
### 8-10 文档内容的保存与显示
doc.value.content = editor.txt.html();报`` Property 'content' does not exist on type '{}'.`` 错误,是因为在定义的时候定义错了定义成了 ``` const doc= ref({});``` 这样是写了一个空对象;可以初始给它复制一个空对象,如:
``` vue
const doc = ref();
doc.value = {};
```
这样就可以了
### 8-11 文档内容的显示
### 8-12 文档页面功能开发
- 新加的样式,不要影响页面其它内容,给目标区域加个特定的样式名字,如课程中用的
class=“wangeditor”
- 使用!important提高样式的优先级
- style里加了scoped属性,表示里面的属性只在当前页面有用,不加scoped的话,则是全局
起作用
---
## 第九章 用户管理&单点登录
---
### 9-1 本章介绍
- 用户管理,用户登录,登录校验(界面+接口)
- 用户管理功能
- 用户表设计与持久层代码生成
- 基本的增删改查
- 用户名不能重复
- 关于密码的两层加密处理:加密传输+加密存储
- 重置密码
- 登录功能
- 前端登录界面
- 后端登录接口
- 登录成功后的处理
- 退出登录
### 9-2 用户表设计与持久层代码生成
### 9-3 完成用户表基本增删改查功能
### 9-4 用户名重复交验与自定义异常
使用!!绕过类型检验
``` vue
:disabled="user.id" // 会报Expected Boolean, got Number with ...
```
修改成
``` vue
:disabled="!!user.id"
```
``` java
// 更新用户,是用户名不可修改
user.setLoginName(null); // 设置接收用户名为空,防止被人绕过前段
userMapper.updateByPremaryKeySelective(user);// 只修改有改动的值
```
### 9-5 关于密码的两层加密处理
tips:如果要用到第三方的js里边的一些变量,需要在使用的地方定义一下,告诉它这些变量是存在的。
``` vue
declare let hexMd5: any;
```
### 9-6 增加重置密码功能
v-if和i-show都可以用来显示或者隐藏某个元素;v-show只是不显示,适用于动态变化;而v-if会不加载(删掉),初始的时候就判断好是隐藏还是显示。
### 9-7 单点登录token与JWT介绍
单点登录系统有可能只是提供接口,也可能包括页面
要使用JWT需要引入额外的依赖包
- token + redis:token是无意义的(通用的做法)
- JWT:token是有意义的,加密的,包含业务信息,一般是用户信息,可以被解出来(很多也是把token放到redis里边)
### 9-8 登录功能开发
``@NotNull`` : 会校验null
`` NotEmpty`` : 会校验null 和“”
### 9-9 登录成功处理并集成vuex-1
使用Redis,需要先引入redisTemplate
``` java
redisTemplate.opsForValue().set(token, userLoginResp, 3600*24, TimeUnit.SECONDS)
```
token随着用户登录信息返回给前端
把一个类放入到rides里边,需要序列化(implements Serializable )或者直接转化成字符串`` JSONObject.toJSONString(userLoginResp) ``;DUBBO是RPC框架,就用到了序列化技术,比如:从A应用的类传到B应用去执行
@Autowired自动注入注解
实际上调用Impl的具体实现
但是当一个接口的方法,对应多个实现的时候,怎么区分到底注入哪一个呢
答案是@Qualifier注解和@Resource注解
@Qualifier注解的用处:当一个接口有多个实现的时候,为了指名具体调用哪个类的实现
@Resource注解:可以通过 byName命名 和 byType类型的方式注入, 默认先按 byName的方式进行匹配,如果匹配不到,再按 byType的方式进行匹配。 可以为 @Service和@Resource 添加 name 这个属性来区分不同的实现
### 9-10 登录成功处理并集成VUEX-2
vuex :全局响应式变量
vuex封装sessionStorage,各页面/组件只知道vuex,不需要知道sessionStorage
``` vue
index.ts
declare let SessionStorage: any;
const USER = "USER"
const store = createStore({
state:{
user: SessionStorage.get(USER) || {} // 避免空指针异常
},
// 同步
mutations: {
setUser(state, user){ // user为传进来的值
state.user = user
}
}
// 异步
actions: {
}
modules:{
}
});
export default store;
```
``` vue
store.commit("setUser",user.value); // 会自动调用上边的mutations方法
```
- *computed*用来监控自己定义的变量,如果一个响应式变量是要根据某个变量的变化而计算得来,可以使用computed
### 9-11 增加退出登录功能
- 目标:将token置为失效
- 后端增加退出登录接口,退出后,清除redis用户信息
- 前端增加退出登录按钮,退出后,清除前端用户信息
### 9-12 后端接口增加登录校验
- 未登录时,管理菜单要隐藏
- 对路由做判断,防止用户通过手敲url访问管理页面
### 9-13 前端接口增加登录校验
- 后端校验:必须,防止别人绕过界面直接调用接口
- 前端校验:非必要,可减少服务器压力
### 9-14 用户密码初始化
## 第十章 阅读量和点赞量
### 10-1 章节介绍
- 更新阅读数
- 更新点赞数
- 更新电子书的文档数、阅读数、点赞数
- 有文档被点赞时,前端可以收到通知
- SpringBoot异步化、WebSocket、RocketMQ
### 10-2 文档阅读数更新
### 10-3 文档点赞功能开发
### 10-4 电子书信息更新方案调研
更新方式:
- 实时更新
- 优点:准确型高
- 缺点:改动的地方多
- 定时批量更新
- 优点: 改动的地方少
- 缺点:数据实时性差
### 10-5 SpringBoot定时任务实例
定时框架Quartz
### 10-6 完成电子书信息定时更新功能
### 10-7 日志流水号的使用
logback 增加自定义参数
可以给定时任务添加线程池
在线程开始的地方,给LOG_ID赋值
### 10-8 WebSocket使用实例
- 功能:网站通知,点赞时,前端收到通知
- 定时轮训&被动通知
``` java
@Component
@ServerEndpoint("/ws/{token}")
publi class WebSocketServer{
// 注意上边的注解,练习时由于忘了写注解导致链接WebSocket出错
}
```
### 10-9 完成点赞通知功能
SpringBoot异步化使用
两个功能代码写在一条线上,会互相影响,可以使用异步线程让两个功能走两条线
### 10-10 使用异步化解耦点赞通知功能
易犯错误:
- 认为启动没问题,注解也加了,异步化就完成了,异步化注解`` @Asyn``要加在方法上;
- 认为功能调试没问题,异步化就完成了(通过日志号的线程号查看是否为同一个)
- 要想一步话成功,`` @Asyn`` 注解的异步方法,要写到另一个类里边去,不能跟调用的地方在同一个类里边 很多新手认为加上注解,异步化就成功了,测试起来也没有发现什么异常
WebSocketServer.java 单一职责原则
WsService类要能被注入到其他类,前提是一定要能被扫描到该类,记得添加@Service注解
同时对两张表有增删改的操作,就要考虑加事务,否则会造成数据不准确。当然也有不加事务的场景,不能一概而论
同一个类里A调用B,B加事务注解不生效
### 10-11 使用MQ解耦点赞通知功能
异步有可能方法内部耗时较长,容易出错
当线程的数量超过线程池的最大线程数量(容量),后边的请求就会变成同步,业务量很大的话,就会有可能让线程越来越多,一直到把我们整个服务器塞满,就会影响到原有的业务。所以就引入了MQ,和Redis一样,MQ是一个中间件,需要单独安装。常用的有RocketMQ、Kafka、RabbitMQ等
- 安装RocketMQ,配置环境变量ROCKETMQ_HOME 跟地址(路径,不要到bin路径),启动`` mqnamesrv.cmd``(相当于注册中心,管理mqbroker)
- 再开启一个窗口: mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
- topic 就是主体,不同的业务使用不同的主体(比如:客户端<发送方>可以往一个主题里面发送一个消息,服务端<接收方/消费方>可以去监听这个主题) 加上后边的参数,SpringBoot 可发送任意的topic到RocketMQ,否则需要在RocketMQ里先创建好Topic,为了生产环境的安全,一般是在RocketMQ先创建好Topic,使用阿里云的一个Topic
- 引入依赖,配置地址
- 在使用的地方注入RocketMQTemplate 类
- 消费端一般会写到另一个专门监听MQ的应用,案例中,只有一个应用,就写到了一起
- 消费端注解:``` @RocketMQMessageListener(consumerGroup = "default", topic = "VOTE_TOPIC")``` ,记得添加@Service注解,使能够被扫描到
- 通常情况下:发送方跟接收方是两个不同的应用,也有项目放在同一个应用,自发自收
- 上边启动的两个窗口叫做服务端,客服端分为两个:一个是发送方,另一个是消费方
流程:点赞—>发送MQ—>消费MQ—>推送WS
本章内容都被注释掉了,只是学习,本应用可以用异步进行处理,没必要引入MQ
## 第十一章 知识库功能开发
### 11-1 章节介绍
统计数据收集与Echarts报表
- 确认报表统计方案
- 统计维度
- 数据收集
- 数据展示
- 复杂SQL的编写、Echarts报表的使用
### 11-2 报表统计方案的探讨
- 统计维度:
- 统计数值:总阅读数、总点赞数、今日阅读数、今日点赞数、今日预计阅读数、今日预计阅读增长
- 统计报表:30天阅读/点赞趋势图、文档阅读量排名(热门文章)、文章点赞量排名
- 业务表统计: 所有报表数据都是从业务表直接获取
- 优点: 实时性好(数据准确)
- 缺点:对业务表性能有影响
- 中间表统计:定时将业务表数据汇总到中间表,报表数据从中间表获取
- 优点: 性能好、可实现多功能统计
- 缺点: 工作量大、步骤多容易出错
### 11-3 电子书快照表设计
概念: 快照
### 11-4 电子书快照收集脚本脚本编写
- 从业务表收集数据的SQL尽量简单,不要影响业务表性能
- 快照分成两部分:
- 总量:总阅读数、总点赞数
- 增量:今日阅读数、今日点赞数
凌晨1点,把所有的电子书都insert一条快照、后边每小时都是update后四个字段。
字段添加唯一键(unique)不影响java代码
### 11-6 完成电子书快照功能
mybatis默认一次只能执行一个sql , 我们可以通过修改链接,增加一个配置项,让它可以执行多个sql。即在application.properties中的数据库地址中添加
``` properties
spring.datasource.url=jdbc:mysql://ip/datatable?...&allowMultiQueries=true
```
### 11-7 首页统计数值功能开发
- 统计数值:总阅读数、总点赞数、今日阅读数、今日点赞数、今日预计阅读数、今日预计阅读增长
- 后端获取统计数值接口开发
- 前端统计数值组件展示
- 今天预计 = 今天到目前的阅读量 / (当前时间点占一天的百分比)
### 11-8 Echarts的集成与使用示例
### 11-9 30天趋势图功能开发
``` java
@JsonFormat(pattern="MM-dd", timezone = "GMT+8")
private Date date;
```
### 11-10 网站优化
加载完index.html后,vue相关的内容还没初始化,所以界面会显示...
这块内容。加载完vue后,vue会将这块替换成页面内容。
## 第十二章 项目部署
### 12-1 章节介绍
- 购买RDS配置生产数据库
- 购买ECS,安装JDK、nginx
- SpringBoot项目发布,多环境配置
- Vue项目发布
- 域名配置
- ESC镜像
### 12-2 RDS购买与配置
ECS和RDS要在同一地域,这样可以用内网链接,否则只能用外网链接
### 12-3 ECS购买预配置
### 12-4 配置IDEA链接ECS
### 12-5 JDK的安装与配置
### 12-6 后端Java项目发布
- 多环境的启动
- 创建配置文件
- 编辑configuration 在VM options:中 -Dspring.profiles.actice=prod
- 启动成功,过滤profile这个单词,多环境常见
- 修改打包完成后的名字:在pom的标签下添加${artifactId}
- nohub可以让要执行的指令后台运行
- deploy.sh 文件内的~,表示的是当前用户的目录
- 启动deploy.sh会报错,是因为windows下的换行和linux的换行符不一致,需要在linux下vim deploy.sh 设置,按Esc 输入: set ff=unix 回车,这样就转换了换行符了,这样再保存,就不会再报错,执行:sh deploy.sh
- 查看日志:tail -100f trace.log
- 如果日志较多,可以查看日志的跟踪号: grep "跟踪号" trace.log即可过滤出需要的信息
### 12-7 nginx安装与配置
- 安装命令:yum install nginx
- 安装完成之后,修改/etc/nginx/nginx.conf的user改为root,改成使用root用户来启动nginx
- 使用命令行 service nginx start 启动nginx服务
- 查看是否启动成功,使用命令行:curl http://IP地址
### 12-8 前端Vue项目发布
- 点击npm的build-prod生成静态文件在dist路径下,然后把静态文件上传到服务器:在root目录创建web目录 把文件放到web里(地址在web.conf有映射)
- 编辑web.conf文件,其中`` try_files $uri $uri/ /index.html;`` 解决页面刷新后空白的问题;server_name 修改成自己的ip
- 放入到/etc/nginx/conf.d文件夹下,能引入的原因是因为在nginx.conf配置文件有引入该文件夹`` include /etc/nginx/conf.d/*.conf``
- 执行 nginx -s reload
### 12-9 域名准备
### 12-10 nginx配置域名
mysql 8版本 中数据导入mysql5版本
1.通过文本编辑器搜索“ utf8mb4_0900_ai_ci” 批量替换成 “ utf8_general_ci ”
2.将“ utf8mb4 ” 批量替换成 " utf8 "
3.保存后,再次进行导入sql文件即可。