# 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文件即可。