商业模式

B2C

管理员【增删改,使用系统后台】和普通用户【查,使用系统前台】

B2B2C模式

京东【普通用户可以买自营也可以买普通商家】

 

在线教育平台

使用B2C商业模式,

系统后台包括"讲师管理","课程分类管理","课程管理","统计分析","订单管理","banner管理","权限管理"模块

系统前台包括"首页数据显示","讲师列表详情","课程列表详情【包括视频在线播放】","注册登录","微信扫描登录","微信扫描支付"功能

 

涉及技术

后端:springBoot、SpringCloud、MyBatisPlus、SpringSecurity、redis、Maven、easyExcel、jwt、OAuth2

前端:vue、element-ui、框架【TODO】、axios、node.js

其他技术:

阿里云oss、阿里云视频点播服务、阿里云短信服务、

微信支付和登录、docker、git、Jenkins

 

前后端分离开发

前端负责数据显示,用到html、css、js、jq

后端返回数据或者操作数据、结构为controller、service、mapper,java中开发接口指的就是开发上述3个结构的过程

前后端的联系是前端发送ajax请求调用后端接口将json数据返回给前端的过程

 

代码细节看文档,文档后端前端代码步骤很详细,这篇文档只讲开发要点

 

后台系统

讲师管理模块开发

  1. 创建数据库表

    • 表名edu_teacher,文件edu_teacher.sql

    • 数据库表设计规约,

      • 核心库名与应用名一致,

      • 表名字段名必须使用小写字母或数字,进制数字开头,表名不使用复数名词

      • 表命名用"业务名_表的作用"

      • 表必备三字段:"id"【类型bigint unsigned,单表时自增;分库分表集群部署是id为varchar,非自增,业务中使用分布式id生成器】、"gmt_create"【类型为datetime类型,记录创建时间】、"gmt_modified"【同前,记录更新时间】

      • 单表行数超500万行或单表容量超2GB才进行分库分表,预计三年后数据量达不到这个水平,建表时不靠分库分表

      • 表达是与否概念字段使用is_xxx格式命名,数据类型是unsigned tinyint【1表示是,0表示否】

      • 非负数字段必须为unsigned

      • 小数类型为decimal,进制使用float和double,这俩存储时存在精度损失问题,如果数据范围超限,整数和小数分开存储

      • 存储字符串长度几乎相等时,用char

  2. 创建项目结构

    其中父工程创建springboot工程,子模块和子子模块都创建maven工程

    • 创建父工程【管理依赖版本以及存放公共依赖 】

      • 子模块1

        • 子子模块1

        • 子子模块2

      • 子模块2

      • ...

    • 总的模块目录

    • 创建流程

      • 太多了,看文档【主要流程是创子模块;引入依赖;配置mp相关的spring配置项,注意乐观锁、逻辑删除相关功能没有涉及,还设置了返回json数据的时间格式为东八区;用mp代码生成器生成实体类和所有的目录结构实体类、mapper、controller、service;写控制器方法;创建启动类;创建配置类配置mapper扫描和其他;使用设定的模块端口号8001启动项目】

        • application.properties的配置项

           

      • 特点:

        • 父工程和子模块中都不写代码

        • 子子模块service_edu用mp提供的代码生成器生成相关代码

          • 生成器代码

          • 作用是生成实体类并创建好后端结构目录

            • 接口中的内容需要自己写,mapper接口自动实现了BaseMapper接口,无需自己建mapper

 

  1. 讲师逻辑删除功能

    • 控制器写删除方法【controller调用service的removeById,即调用mapper的deleteById】,请求路径需要传递讲师id,用@PathVariable注解读取请求路径的路径变量

    • 配置逻辑删除插件LogicSqlInjector,在表示逻辑删除字段上添加@TableLogic注解

    • 用swagger进行接口测试

      • swagger用于生成在线接口文档、方便接口测试、描述、调用、可视化Restful风格的web服务,是一个规范完整的框架,具有及时性、规范性、一致性、可测性的特点

      • 配置swagger2

        • 创建子模块common、在common下创建service_base子子模块,创建配置类向IoC容器中注入Docket组件,docket组件的类型是SWAGGER_2

        • 在service模块下引入service_base模块

          注意此时配置类swagger的包名为com.atlisheng.servicebase,而service模块下组件扫描的包默认是com.atlisheng.eduservice,两个包名不一样,swagger无法扫描到,需要用@ComponentScan将包扫描范围增大到com.atlisheng

          扩大组件扫描范围

        • 访问swagger的网址,固定为http://localhost:8001/swagger-ui.html

        • Swagger定义接口说明和参数说明的注解

          • @Api

            • 定义在类上,description属性会展示在swagger默认显示控制器类名的位置

          • @ApiOperation

            • 定义在方法上,value属性会展示在方法的说明上

          • @ApiParam

            • 定义在控制器方法的参数前,会展示在参数的说明上【其中required属性表示该参数是必须传参的】

 

  1. 统一返回数据格式

    • 统一返回数据格式,将响应封装成json返回,能够使前端(IOS、ANDROID、Web)对数据操作更轻松

    • 返回数据格式一般包括响应信息、状态码、处理信息、返回数据,本项目返回数据格式如下:

      • 列表返回数据【注意列表数据的key用items】

      • 分页数据【key用total,rows】

      • 没有返回数据

      • 响应失败

      • 从而可以定义统一结果

      用HashMap自动将键值对转成json对象来满足不同情景响应数据的需求

    • 创建统一结果返回类

      • 创建响应状态码接口【在common_utils下创建ResponseCode接口存放常量响应状态码、创建ResponseData作为统一响应结果类】

      • 创建响应结果类

      • 在service模块中添加统一返回结果的依赖

         

  2. 讲师查询结果分页

    • 配置类中配置分页插件

    • 分页查询方法

       

  3. 条件查询

    根据讲师的名字,头衔和入驻时间gmt_create查询讲师记录,实现多条件组合查询带分页

    • 在entity的vo包下创建条件查询对象TeacherQueryFactor,把条件值封装到对象,将对象传递到接口中

    • 条件查询带分页的控制器方法

  4. 实现gmt_Create和gmt_Modified字段的自动填充功能

    • 在实体类中添加自动填充注解@TableField

    • 编写自定义源对象处理器MyMetaObjectHandler并用@Component注解纳入Spring容器管理

     

  5. 讲师添加到列表功能

    • 控制器方法,@RequestBody注解接收post提交的参数封装成EduTeacher对象并调用service的save(entity)方法实现数据存入,版本号和逻辑删除默认值是由数据库设置的

     

  6. 讲师信息更新功能

    • 第一步在控制器方法中根据讲师id查询讲师信息

    • 第二步在另一个控制器方法中路径传递讲师id,@RequestBody封装讲师信息,使用put提交方式

     

  7. 统一异常处理

    • 在common_base中创建统一异常处理类GlobalExceptionHandler.java,作用是捕捉所有异常打印异常堆栈信息然后响应ResponseData.responseErrorCall().message("服务器异常,请联系管理员")

      这上面的@ControllerAdvice注解是干什么的

    • 发生@ExceptionHandler(Exception.class)这种指定的异常会去执行该方法

      还有特定异常处理和自定义异常处理见文档,核心还是捕捉不同类型的异常执行不同的方法

    • 特定异常处理

      这种异常的优先级高于全局异常处理,发生对应异常会优先去调用特定异常的处理方法

    • 自定义异常处理

      就是自己自定义异常,然后用特定异常处理进行处理

      • 第一步:创建自定义异常类继承RuntimeException,确定异常属性,状态码和异常信息

      • 第二步:对自定义异常进行特定异常处理

        自定义异常是在try语句块发生异常捕捉后在catch语句块中手动抛出的

      • 第三步:自定义异常使用举例

       

     

  8. 日志

    • 日志记录器的行为级别OFF、 FATAL、 ERROR、 WARN、 INFO、 DEBUG、 ALL ,默认情况下SpringBoot在控制台打印的日志级别为INFO及以上的日志级别

    • 通过Spring的配置文件可以设置在控制台打印的日志级别,如

      这种方式只能将日志打印在控制台上

    • 使用logback日志工具能将日志输出到控制台也能输出到文件

      常见日志工具有log4j,logback

      • 使用logback需要删除application.properties的日志配置,包括mybatis的日志配置

      • 配置logback日志

        • idea安装日志插件:grep-console

          这个插件是一个彩色日志插件,但是我没装以前日志也是彩色的,感觉装没装没影响

        • 在resource中创建logback-spring.xml,并复制拷贝下列内容

          其中就规定了日志文件的输出地址

    • 将程序运行异常的信息输出到文件中

      • 第一步:在统一异常处理类上添加@Slf4j注解

      • 第二步:使用异常输出语句log.error(e.getMessage),错误信息就会输出到error.log中

      • 默认异常信息写入文件只写一行信息,想要写的更详细可以参考文档ExceptionUtil.java工具类 ,可以将详细信息输出到日志中

     

 

前端框架搭建

VS code前端框架搭建

搭建项目前端页面环境

选取模板vue-admin-template框架进行前端页面环境搭建

  1. 模板安装

    • 使用171kb的小压缩包作为前端框架,将其解压放到工作区中

    • npm init初始化该项目,npm install安装所有依赖,由于nodejs版本过高也会导致依赖下载失败,这里又改成了v14,overStackflow上说14的版本兼容性更好

    • npm run dev启动项目即可通过浏览器访问该项目,访问地址:http://localhost:9528

  2. vue-admin-template前端框架环境说明

    该模板主要基于vue和element-ui两种技术实现的,这两个文件都能在node_module中找到

    • index.html和main.js是前端框架入口,在index.html中有一个id为app的div,在main.js中new了一个vue对象,其中的el就是index.html中的app【@一般是指src的根目录即就是src】

    • build目录放的是项目进行构建和进行编译的脚本文件,就像java编译class文件的一些工具文件

    • config目录,里面放着项目最基本的设置,如index.js,里面就放着port:9528,host:'localhost',都可以进行更改;index.js中有个useEslint:true,把这个值改成false,ESLint是vscode的插件,可以帮助自动整理代码格式并做代码检查,不建议装,检查太严格,多了一个空格或者换行都算错,这不好;另外两个文件分别对应开发环境或者生产环境去分别执行,启动时npm run dev就是开发环境启动,文件中的BASE_API规定了浏览器要访问接口的默认位置,后面要改成本地8001服务器端口

    • node_modules是下载好的依赖

    • src主要的代码都在src中

      重要的有api、router和views,改就主要改访问地址和这三个地方,其他地方基本不怎么动;开发流程是写接口,写路由、在页面中调用方法并用element-ui进行显示

      • api中定义了需要调用的方法

      • assets目录主要放一些静态资源,js文件,css文件,一些项目中的图片

      • components主要放一些当前框架没有的额外的组件

      • icons放的是项目中用的各种图标

      • router表示项目中用到的路由部分

      • store中主要放的是项目中用到的脚本文件,没啥用

      • styles中放的是一些样式文件

      • utils中放的是项目中用到的一些工具类,如权限、请求等

      • views目录中放的是项目中具体的页面,这里面用的页面都是vue的后缀名

    • static没啥用,主要都是一些不咋使用的静态资源

  3. 解决登录问题

    • 登录默认地址是https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin/user/login

      • 由config/dev.ens.js的BASE_API+src/api/login.js中的login方法的url两个拼接而成,login方法的request来自src/utils/request文件,该文件又包含axios文件,在这个文件中对ajax请求进行了封装,axios文件中的service常量规定了baseURL为配置文件dev.env的BASE_API

         

      • 将BASE_API路径设置为本机服务器所在的地址http://localhost:8001

      • 用户登录时调用啦login和info两个方法。login方法负责登录操作,info方法登录后获取用户信息

        • 其中login方法返回token值,info方法返回roles、name、avatar[头像],在服务器中写出对应的接口,注意前端要求服务端返回的数据一般都有commit方法,第二个参数就是要返回的值【token实际是用户名】

    • 后端登录接口编写

      • 在前端对响应的状态码是否等于两万进行了判断,如果不等于20000就会抛错,所以后端的状态码不要随便瞎定义

      • login方法返回token,info方法返回roles、name、avatar

    • 根据后端接口信息更改前端请求信息

      • 在src/api/login.js中的request对象中修改请求路径和请求方式与登录接口对应

    • 解决跨域问题,由于前端页面的端口号和服务器的端口号不是同一个,所以请求存在跨域问题,提示Access-Control-Allow-Origin,

      • 通过在后端接口的controller上添加@CrossOrigin注解来允许跨域访问

      • 也可以使用nginx网关解决【后面说】

    • 此时登录即可进入

      • 每个相同的请求会发送两次,是浏览器的机制,第一次是测试请求是否能成功连通接口【预检】,请求方式是options,并不会返回数据;第二次是真正的访问服务器获取响应数据

实现讲师列表功能

二次开发,一般都是在现有项目基础上开发新的功能

 

添加讲师头像上传功能

  1. 使用阿里云oss存储服务

    • 网站阿里云,冲几毛钱,搜索阿里云oss,产品分类的云计算中也可以找到,点击管理控制台,创建并管理bucket,勾选低频访问和公共读,文件管理中能看到文件信息,能手动上传文件,也可以用java代码操作阿里云oss

    • Java代码操作阿里云oss

      • 创建阿里云oss许可证【阿里云颁发的id和密钥:bucket管理的access key】

      • 创建service_oss模块,引入阿里云oss依赖aliyun-sdk-oss和时间工具依赖joda-time

      • 对阿里云oss服务在application.properties中进行配置

      • 创建启动类并排除数据源自动加载配置功能

      • 创建常量类读取配置文件内容

      • 创建controller和service,controller去调用service中的文件上传方法,service中使用阿里云简单文件上传模板编写文件上传方法

      • swagger测试

        • 两个问题:

          • 问题一,同名文件会造成阿里云oss同名文件内容覆盖,解决办法是获取文件原始名称,给每个文件拼接一个uuid避免同名

          • 问题二,对文件进行分类管理,根据年月日期分类或者根据用户名进行分类,办法是在文件名中用XX/XX/1.jpg表示文件目录【这里使用开始和oss一起引入的joda的DateTime的toString方法来将系统时间格式改成带斜杠的时间格式】

    • nginx反向代理服务器

      先用nginx做请求转发,后续用网关代替

      • nginx的功能

        • 请求转发:浏览器发送一个请求给nginx,nginx根据路径匹配把请求发送给另一个具体的服务器,nginx单独占用一个端口

        • 负载均衡:负载均衡将请求平均分摊到多个服务器中,负载均衡的策略有轮询【服务器按顺序一个一个依次对请求进行处理】、请求时间【】、权重、哈希等

        • 动静分离

      • 前端页面的端口号BASE_API为8001、实际图片上传是8002端口、访问的后台管理系统是8001端口,如何使用nginx实现图片上传访问8002端口

        • 让前端页面访问nginx,使用nginx分配访问的服务器

        • nginx启动,直接解压windows安装包,在解压目录中通过DOS命令窗口使用nginx.exe启动nginx,注意关闭CMD窗口nginx是不会停止运行的,改了配置nginx需要重启,此时不能通过关闭cmd窗口来关闭DOS窗口,nginx停止命令:nginx.exe -s stop;也可以通过nginx的nginx.exe -s reload直接进行重启

        • 在nginx解压包下的confg目录下的nginx.conf文件中对nginx进行配置

          • 配置nginx实现请求转发的功能

            • 配置文件中的worker相关是用作多路复用的

            • nginx的默认端口是80端口、最好改成81,避免不必要的端口冲突

            • 也可以设置nginx端口为9001,在nginx中配置当请求路径含eduoss就进入8002端口,地址中有eduservice就进入8001端口,添加配置的方法是在server中添加,listen表示nginx对外的监听端口设置为9001,server_name是主机名称,location是匹配路径,后面跟~ /请求路径,proxy_pass是匹配转发服务器的地址【这个81和9001有啥区别?】,改完以后重启nginx

              注意用作区分的eduservice和eduoss在两中请求路径中不能相互包含

            • 在前端dev.env.js中将前端请求端口改成本机的nginx监听端口9001,注意启动前端页面前一定要启动nginx

      • 在添加讲师页面提供讲师头像上传功能

        • 使用element-ui创建头像上传组件,为了效果更美观,自己找一个vue-element-admin-master框架中的功能,在体积更大的框架压缩包中找出ImageCropper组件和PanThumb组件并复制到当前的前端文件夹的component目录下

        • 添加讲师页面使用这两个组件,用import进行导入,并在export default中的components对两个组件进行声明

        • 使用element-ui导入头像上传组件【具体不懂,学element-ui再说,还对涉及变量进行了初始化,对close方法和cropSuccess方法进行了重写】

        • 修改上传接口的方法【把请求发送给对应的后端接口,后端接口把图片上传到阿里云oss,并把图片访问地址存入数据库】,注意前端框架会自动把文件名基础名改成file.png,以防止文件名出现中文的情况,不上传头像可以把头像设置成默认的头像,即图像的地址给前端的avatar属性,有修改再变更,注意这个默认avatar如何设置成可以在添加头像页面显示的头像

        • 头像上传的bug,第一次上传成功后再次点击头像上传,第一次头像上传成功后再次点击头像上传会显示上传成功无法再次上传,解决办法是把上传组件的imagecropperKey自动加1,原因不清楚,有机会学习该框架再说

          • 还有个问题,头像上传后即使不注册讲师,阿里云上仍然会保存数据,如何让保存教师记录的时候再对头像数据进行云存储

 

添加课程分类功能

  1. 使用EasyExcel读取excel内容添加数据,把课程分类编辑在excel表格中,在表格中数据通过技术手段如EasyExcel读取到数据库表格中

  2. EasyExcel是JAVA解析Excel表格的工具,由阿里巴巴提供;早期处理excel由apache的工具poi和jxl,都存在内存消耗严重的问题,EasyExcel的原理是从磁盘上一行行读取数据,逐行进行解析,而其他方式大多都是一次性读取数据加载到内存中,效率很低,以下对Easyexcel进行演示

    • eaxyExcel需要引入对应的依赖,同时由于EasyExcel对poi进行了封装,同时还需要引入poi的依赖,注意EasyExcel和poi的版本有对应,版本不正确可能会出现问题,2.1.1版本的EasyExcel对应poi的3.1.7的依赖

    • 写的操作比较简单,读的操作比较繁杂

      • 建立与表格对应的实体类,属性要对应表的不同列,加上@Data注解,注意对属性需要添加@ExcelProperty注解并设置属性与表头名称的对应关系

      • 创建对excel表格的写操作

        • 第一步确定并定义对应表格文件位置的字符串,注意这个文件会自动创建

        • 第二步调用easyexcel的write方法实现写操作,传参参数文件的路径名称,第二个是对应表格实体类的class文件

        • sheet中的参数是对应表格的sheet名

        • dowrite方法传入一个list集合,list集合中每个元素的对象是对应表格的实体类

    • 读操作需要用index对实体类的属性用@ExcelProperty属性指明对应的序号【可以直接加在读取数据类的同一个注解中,属性名是index】,创建实现AnalysisEventListener接口的监听器分别重写读取表头的invokeHeadMap方法,读取表格内容的invoke方法【读取数据会自动封装到data中】,读取完毕后的doAfterAllAnalysed方法【目前用不上】,然后对监听器进行调用

      【注意必须对实体类创建无参数构造方法,否则读取表格内容会报异常】

       

  3. 使用Easyexcel实现从上传的excel表格中导入一级目录和二级目录,并判断数据库中是否存在对应的课程目录,由于这里的数据量比较小,且easyexcel每次只处理一条记录,优化可以考虑把查询课程添加到缓存中或者使用ThreadLocal实现数据库连接池【这个不太会,因为本身数据源是使用的mp】

    【创建edu_subject表,使用mp的代码生成器生成框架结构】

    【写controller中文件上传解析的方法】

    【写eduSubjectServiceImpl中对应的解析方法】

    【写对应表格的实体类】

    【写对应读取数据对数据判断是否重复以及存储到数据库的监听器】

     

 

课程分类功能

  1. 树形结构显示

    • 用表来存储数据库结构,一级分类的pid为0,二级分类的记录的pid为一级分类的id,三级分类的pid为二级分类的id,注意一级分类的pid是人为设置的,二三级的pid是由mp自动生成的

    • 创建数据库表edu_subject,使用mp的代码生成器生成框架的基本结构,在controller中添加上传文件读取表格内容存储数据库的saveSubject方法,创建对应表格的实体类,在service中编写对应的Easyexcel读取表格的方法,编写监听器实现具体的数据处理逻辑,包括判断一级分类、二级分类是否是否在数据库中重复,以及使用传入的学科service类存入数据库的操作

  2. 课程分类的前端实现

    • 在index.js中完成路由对应页面的设置,在views中添加subject目录,该目录下创建list.vue和save.vue,把路由指向对应的页面

    • 对课程列表页面和添加课程分类页面进行实现

      • 使用element-ui组件实现页面上传效果,在script的methods中对点击按钮上传文件到接口的submitUpload方法,上传成功的fileUploadSuccess方法,上传失败的fileUploadError方法进行实现;并对组件涉及的变量进行定义初始化,注意文件上传一般都不是ajax提交,一般都是普通提交

        注意路由跳转的代码this.$router.push({path:'/subject/list'})路径借鉴路由中index.js中的写法,/subject是大路由,/list是对应的小路由

    • 课程分类树形列表显示功能

      • 前端页面:直接借用模板的树形列表代码并分析,主要两个功能,一个是对课程信息进行检索的功能,一个是自动对data2数据遍历以树形结构显示的功能

         

      • 后端接口:创建接口返回课程信息并封装成前端模板要求的data2的格式供前端树形结构自动遍历

        • 参考树形结构的属性,创建一级分类和二级分类两个实体类,用list集合作为一级分类的属性表示一级分类下有多个二级分类

        • 使用swagger测试没毛病

      • 前后端整合

        • 编写subject.js定义课程列表查询接口

        • 课程列表显示页面修改

          data2定义成空数据,准备接收后端返回数据,引入查询接口,在methods中定义查询所有课程方法getAllSubjectList,在created方法在页面加载时就对查询所有课程的方法进行调用,修改defaultProps的label为title,filterNode方法中的label也改成title,children也改成对应后端的属性名

          【前端效果】

      • 将检索功能修改为不区分大小写

      • 设置添加课程分类成功后路由跳转到课程列表页面

         

添加课程

讲师下拉列表

  1. element-ui表单中选择下拉列表的样式

    el-select

    el-option标签:其中label属性就是显示在下拉列表的内容,value是数据提交的内容,label标签用v-for遍历出所有讲师,下拉列表最终提交的数据value是讲师的id

    定义所有讲师查询接口

    将查询讲师接口数据赋值给teacherList

课程分类下拉列表

  1. 基本逻辑:第一次进入页面显示所有一级分类,选择某个一级分类后用change事件根据选中的id遍历一级分类数据将对应的children赋值给二级分类并显示对应一级分类中对应的二级分类,一级分类是edu_subject表中二级分类的subjectParentId,二级分类是subjectId

    注意在显示所有课程分类的列表中对应的subject.js中已经定义了查询课程分类的接口并且封装好了数据,可以直接拿来用

    二级联动下拉列表element-ui样式

    二级联动下拉列表参数处理

课程封面上传

  1. 图片上传还是调用讲师头像上传的接口

  2. 课程封面上传前端element-ui组件

  3. 封面上传前后的图片格式大小校验和上传后的地址处理

课程简介整合文本编辑器

注意数据库中用于存储富文本编辑内容的内省是text类型,加粗效果是用strong标签标记的,图片内容作了base64编码,将编码内容直接存储在数据库中,注意text类型是有大小限制的,不能上传太大的图片【注意一下有没有longtext类型】

  1. 富文本编辑器

    • Tinymce可视化编辑器

    • 整合步骤

      • 将富文本编辑器组件的components和static文件夹的内容复制到项目的components和static目录下

      • 在build/webpack.dev.conf.js中添加配置

        作用是使html页面中可以使用这里定义的BASE_URL变量

      • 找到index.html,在文件中引入两个JS脚本文件

      • 在views具体页面中从组件包引入Tinymce,并在export default中声明组件Tinymce

      • 使用tinymce标签就可以直接使用富文本编辑器了

      • 给富文本编辑器添加如下样式调整上传图片按钮的高度

 

课程大纲管理

课程大纲列表功能

  1. 实现课程大纲展示列表功能

    还是将章节小节信息封装成二级目录的形式

    • 后端接口

      创建对应二级目录的两个实体类,章节类和小节类,在章节中用list集合封装小节【表示一对多】,使用单独的controller即EduChapterController来控制章节的对应功能,但是要注意章节表中的课程id确保着章节和课程的对应关系,查询课程列表的时候要作为条件进行传入

      • 创建封装章节、小节的二级查询实体类

      • 在控制器方法中定义查询课程大纲列表的控制器方法,使用@PathVariable注解封装查询课程id,确定返回结果的类型为章节的list集合【前端能展示所有的数据,直接返回所有章节的list集合,每个章节中都有各自小节的list集合】

      • 在课程service中实现对应的查询封装过程

        • 根据课程id查询所有的章节

        • 根据课程id查询课程所有的小节,小节表中的chapterId对应上级章节目录,小节表中的courseId字段对应小节的上级章节目录,courseId用于查询所有的小节,chapterId用于所有小节的章节封装

        • 遍历查询到的所有章节信息进行封装,遍历所有查询到的小节封装成list集合封装到对应chapterId的章节的小节属性

    • 前端页面

      • 在chapter.js中定义章节查询接口

      • 在chapter.vue中编写前端列表组件,并调用后端接口获取课程数据,课程id在第一步编辑课程基本信息时已经添加到路由末端,可以通过this.$route.params.id获取,太丑,后面改一下

         

  2. 点击上一步修改课程基本信息

    点击上一步要实现数据的回显以供修改

    在数据回显页面点击修改内容并保存,会修改数据库的内容

    • 后端接口

      • 根据课程id查询课程基本信息

        在course的控制器方法中调用service的getCourseInfo方法通过课程id获取课程信息

        【web层】

        【业务层】

      • 修改课程信息接口

        【web层】

        【业务层】

    • 前端实现

      • 在api的course中定义后端接口

      • 在chapter页面,向前跳转的路由添加id

      • 在info页面调用接口方法实现数据回显,如果路由有id就调用回显接口,如果路由没有id就直接准备调用填写课程基本信息接口

      • 在update方法中调用修改数据库信息接口修改数据库信息,在点击下一步按钮绑定有id就调用更新接口,没有id就调用保存课程信息接口

        测试:填写课程基本信息的下拉列表,二级联动下拉列表、点击下一步的数据库保存功能;点击上一步的数据库回显功能;再次点击添加课程的内容清空功能,特别是富文本编辑器的内容清空功能;修改课程基本信息后的更新功能

章节增删改

  1. 添加章节的功能

    • 功能需求:有一个添加章节的按钮,点击该按钮会弹出窗口,在弹出窗口中添加课程,点击保存进行添加,弹出窗口可以选择element-ui的对话框

    • 前端实现

      【定义对应后端的接口】

      【添加章节对话框element-ui组件代码】

      【弹窗的保存功能】

      【修改章节信息的实现】

      在章节列表中添加编辑和删除按钮,编辑按钮绑定事件,打开弹框,调用接口通过chapterId调用接口获取章节id,将返回数据赋值给chapter并显示在添加对话框中,chapter.id是初始化章节列表时带过来的,点击事件可以直接获取对应的章节的id,此时弹框的事件要进行判断对应的弹框是修改状态还是添加状态,文档是判定id是否存在判定的,因为回显会绑定章节对象的id,没有id就做添加工作,有id就做修改操作,修改成功后的操作和添加是一样的

      【编辑和修改按钮】 注意:p标签的样式图层会浮动在span的上面,导致span的按钮无法被点击,也就没有办法触发单击事件,这时候的解决办法是通过样式设置p和span标签的图层位置为相对,将span图层的优先级z-index设置为1,让span图层置于所有图层的最上方,直接注释掉float也是可以的,但是这样会导致页面布局混乱,细节看关于float属性导致button按钮无法点击问题的解决思路_明天天明~的博客-CSDN博客

      【单击编辑的绑定事件】

      【单击确定的绑定事件】

      【删除章节的前端实现】

      需求是先提示是否确认删除,点击确定后没有小节会直接删除

      存在问题,删除按钮不会主动失去焦点,使用JavaScript手动失去焦点不起作用

       

    • 后端实现

      在章节前端控制器中开发章节的添加,查询、修改,删除接口

      添加和修改传参章节对象,查询和删除传参章节id

      删除章节要特别注意,如果章节下面没有小节可以直接删除,但是章节下面有小节常见的开发处理方式有两种,第一种方式删除章节时将章节中的所有小节全部删除;第二种方式是章节下面有小节,不允许删除章节【目前采用的方式是章节中有小节就不允许删掉章节:逻辑是根据章节id查小节,能查出小节就不删,查不出就直接删章节,不需要查出小节具体的对象,只需要判断是否有小节,可以使用service的count方法查出对应条件的记录数;baseMapper的deleteById传参id可以删除对应记录】

      【前端控制器方法】

      【删除的逻辑】

       

小节增删改

需求:在章节标签上有一个添加小节的按钮,点击按钮弹出对话框添加小节信息,小节信息除了id和title外还有课程id、章节id、以及视频的id,这个id需要阿里云视频点播返回,暂时先标记成可以为空,以及视频名称同样如此,讲到再说

删除小节时需要完善,需要将视频一起进行删除

查询接口暂时不用写,因为已经在课程章节信息中查过了,当时在chapterservice中注入的videoService

  1. 后端接口实现

    【小节前端控制器增删改接口】

  2. 前端实现

    【前端定义后端对应接口】

    【添加小节按钮】

    【添加小节对话框组件】

    【编辑和修改小节按钮】

    【相关处理函数】

    实现了小节的添加、修改和删除功能,以及小节数据的回显功能,组件和方法涉及的变量都在data中进行了定义

     

 

 

课程信息确认

添加课程信息展示和确认发布

编写SQL语句的方式完成,涉及多表操作。展示内容包括课程封面、课程名称、价格、课程分类、课程简介、课程讲师;涉及到4张表,可以创建一个vo类查四次表封装数据,但是不建议,建议手写sql实现;涉及到多表连接,常用方式为内连接和外连接

内连接:查两张表有关联得数据,没有关联查不出来,关联是指有个字段为两张表共有且有对应关系【外键】

左外连接:左边的表作为主表,其中的数据全部查,右边的表作为副表,只查关联数据

右外连接:右边表作为主表查所有,左边的表只查关联部分

常用的是内连接和左外连接,由于本课程中可能没有简介或者部分信息,用内连接可能查不出来需要的数据,选择左外连接,课程都查出来,其他的只查关联数据

查询的数据包括课程id、课程标题、课程价格、课程时长、课程简介、课程讲师名字、课程一级分类、课程二级分类

  1. 使用左外连接查询需要的信息的SQL语句

    这个sql可以通过减连接次数提升查询效率,在复习sql以后优化,这个sql写在mapper.xml中,且只能通过service中自动注入的对应的baseMapper才能调用,不能通过service调用

    取的别名需要和属性名一致

    xml中的SQL写对了但是找不到对应的方法,显示BindingException,方法not found;这是maven的文件加载机制造成的,maven只会在java目录下去加载java后缀的文件,不会去加载xml后缀和其他后缀的文件,解决方式:

    • 方式1:将xml文件夹整个复制到对应的mapper包下

    • 方式2:将xml文件夹放在resources目录下

    • 方式3:通过配置项进行引入【常用的就是第三种】

      • pom.xml中做配置【在pom.xml中规定java目录的xml文件也进行打包】

      • 项目application.properties中做配置【配置mp的mapper-locations属性为mapper.xml文件的路径正则表达式】

    【在对应的eduCourseMapper.java中定义出对应的方法】

    由动态代理机制自动态实现

    【定义封装查询数据的对象】

    【编写控制器方法】

    【在service中对getPublishConfirmCourseInfo进行实现】

    【存在mapper.xml无法被maven默认加载机制扫描的问题,解决办法是在模块的pom.xml加上build标签把需要打包的.xml类型文件包含进来,然后在application.properties】

    【pom.xml配置】

    【在application.properties中对属性进行配置】

    【前端页面展示】

    前端组件,从chapter中跳转的路由得到课程id,在页面构建的时候就调用接口获取对应的可成确认信息

    发布逻辑是点击发布课程,弹框提示是否发布课程,然后调用接口改状态,跳转课程列表页面显示所有课程列表,课程列表根据normal字段显示已经发布的课程

    点击发布以后根据课程id去修改数据库中course的status属性为normal来标记课程的发布状态,draft为未发布状态

     

 

阿里云视频点播服务

  1. 阿里云视频点播服务

    • 产品--企业应用--视频点播

    • 开通服务,按流量进行收费,最多花个几毛钱

    • 视频点播VoD:集音视频采集【视频音频录制】、编辑【视频剪辑】、上传【视频存储,本质上是oss存储,用视频点播进行管理】、自动转码【视频分辨率,这个功能要收费】、媒体资源管理【视频资源的分类、增删改】、分发加速【让视频少出现正在缓冲的提示,播放更顺畅,该功能要收费】于一体的一站式音视频点播解决方案【就是视频相关的功能阿里云视频点播都做好了】

    • 和对象存储一样,实际生产都是对资源用java代码对阿里云视频点播进行管理,用代码上传,播放和删除视频

  2. 阿里云视频点播管理控制台的使用

    • 媒资库的音/视频可以上传视频,视频会自动生成id和视频资源地址

    • 本质上还是oss进行存储,视频点播不负责存储,可以对视频进行分类管理,即放在不同的文件夹

    • 转码模版组:视频转成高清、超清、或者加密

      • 视频参数讲的太水【hls视频格式是一种加密方式,加密后即使拿到视频地址也不能播放,只有自己知道怎么播放】

  3. 用java代码实现视频的阿里云点播视频上传、删除和播放

    • 文档:视频点播--文档&SDK--服务端API、服务端SDK、上传SDK【上传、删除和播放在这里面】

    • 服务端:后端接口

      • 服务端API和服务端SDK是指在java在后端接口中的操作

      • API:阿里云提供一个url地址,只需要调用该固定地址并提供需要的参数【类似于url后问号拼接参数的方式】,就能实现相应的功能,用httpclient技术可以不通过浏览器调用api地址、安卓和ios不使用浏览器而经常使用httpclient技术

      • SDK:通过SDK【软件开发工具包来调用api,也强烈推荐使用这种方式来调用api】,对api的调用方式进行了封装,直接调用其中的类或者接口中的方法来实现功能

    • 客户端:浏览器、安卓、ios

    • 使用javaSDK的流程

      • 安装SDK,即引入依赖

        所有的版本号都在顶级父工程中做了约束

      • 初始化,创建DefaultAcsClient对象,设置几个参数值【点播服务接入区域regionId=“cn-shanghai”,这个值不能改,因为目前的服务都部署在上海,传参点播服务接入区域、accessKeyId、accessKeySecret【这俩是oss对象存储的id和密钥】获取DefaultAcsClient对象并作为初始化的返回值】

      • 通过得到视频地址对视频进行播放【根据视频id获取,id是存视频返回存在数据库中的】

        加密视频获取的地址无法直接播放、实际生产视频是要加密的,避免视频白嫖,数据库中不存储视频地址,存储每个视频的唯一id值,根据视频id可以同时获取视频地址和视频凭证,拿着凭证相当于许可证,既可以播放加密视频,也可以播放非加密视频,id对应数据库的video_source_id

        注意手动阿里云视频点播需要启用存储管理,在存储管理中点击启用就可以手动上传视频了

        播放加密视频必须要域名,没有域名播放不了,非加密视频阿里云改成不需要域名也可以播放

      • 获取视频播放凭证【根据视频id获取】

      • 上传视频到阿里云视频点播服务

        aliyun-java-vod-upload-1.4.9.jar没有正式开源,maven中下载不到,需要手动将jar包添加到本地仓库中

        从阿里云下载对应jar包,在含有该jar包的目录下使用maven命令进行安装mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar

        下载对应的sdk下除了包含jar包的lib目录外,在sample目录下的VODUploadDemo.java中还有视频上传的7种代码示例,比文档讲的更详细,这里使用其中的文件流上传,注释内容暂时都用不着

        手动添加这个jar包在第一集引入父工程依赖报错时就手动查资料解决了,并且进行了手动引入,这里并没有报错而且对应的jar包已经存在了

         

 

完善小节添加上传视频功能

  1. 后端实现

    • 第一步:引入VOD依赖

    • 第二步:创建application.properties设置必要的参数

    • 第三步:创建启动类【没有涉及数据源,直接排除掉数据源的自动配置,避免报错】

    • 第四步:创建后端接口【使用流式上传接口】

      【controller】

      【service】

      【绑定配置数据,有更好的绑定方式,复习了springboot再改进】

  2. 前端实现

    • 前端视频上传组件

    • 涉及方法定义

    • 添加参数定义

  3. 配置nginx地址转发规则并设置nginx的上传大小限制修改

    配置完nginx后nginx重启nginx -s reload,这个命令偶尔会不好使,用stop和nginx停启最保险,注意配置文件保存后才能生效

    • 配置nginx地址转发规则

    • 配置nginx上传文件大小,否则上传时会有 413 (Request Entity Too Large) 异常,打开nginx主配置文件nginx.conf,找到http{},添加

  4. 添加视频文件原始名称

    • 前端的file.name就可以直接得到视频文件原始名称,不需要后端进行处理返回

  5. 点击删除视频的同时把阿里云视频点播上的视频也删掉【还有bug,不能直接直观看到每个小节下的视频信息】

    小节的视频回显功能,必须要把fileList设置成数组,否则框架会不认的,将fileList赋值为如下形式,就能回显数据库中视频的名字

    • 对添加的视频不满意,点击视频文件后面的叉号会直接删除阿里云视频的功能实现

    • 后端service删除阿里云视频点播的实现

    • 前端删除方法

      有个很严重的bug,用户上传视频到完成这段时间,保存小节信息的按钮处于可以点击的状态,没有时间点来判断视频开始上传和结束,会导致还没获取到videoId就对小节数据更新,导致视频成功上传但是数据库中找不到视频信息,解决办法可以在视频上传成功后单独执行一次小节数据更新【这个解决办法不行,上传过程直接关闭窗口会直接导致上传成功的后续代码不会执行而报执行异常,暂时找不到好的解决办法】

       

  6. 用springCloud实现删除小节删除视频、删除课程删除视频

 

课程列表

课程列表显示

需求:课程列表上方有课程查询框,课程列表像讲师列表一样,有三个选项,分别是编辑课程基本信息、编辑课程大纲、删除课程,课程列表具有分页展示效果,编辑课程信息跳转课程信息编辑info页面、编辑大纲信息跳转chapter页面

  1. 后端实现

    【vo类封装查询条件】

    【后端控制器多条件分页查询方法】

  2. 前端实现

    【前端接口定义】

    【前端页面组件】

需要对课程列表进行优化,手动写sql语句解析查询条件多表连接查询并显示课程简介和讲师姓名

课程删除

课程删除户把视频小节、章节、描述信息和课程本身都删除掉

外键:一对多多的哪一方创建字段作为外键指向一的哪一方的主键,开发中不建议把外键生命出来,原因:1.外键必须要保持数据一致性,如果外键存在那么对应主键的记录是删不掉的,有外键要按顺序先删小节、章节、描述,再删课程

有很多操作都应该加事务的,但是都没有加

  1. 后端接口

    【控制器方法:根据课程id删除课程】

    【service的实现】

    根据课程id删除课程小节【还要删除对应视频文件、后面再讲】

    根据课程id删除课程描述,具体封装很简单,用queryWrapper封装一下就行,没什么好说的

    根据课程id删除课程描述

    根据课程id删除课程本身

    需要把小节、章节、描述的service注入进课程service

     

 

###

微服务架构

Nacos服务注册中心

使用feign对服务进行调用

父工程,子模块。子模块下有多个子子模块,每个子子模块的占用端口又各不相同,

  1. SpringCloud

    • 不是一种一种技术、是一系列框架的集合,使用这些框架就能实现微服务架构,利用Spring Boot的开发便利性简化了分布式系统基础设施的开发,选取成熟经得起考验的服务框架如服务发现、服务注册、配置中心、消息总线、负载均衡、 熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

    • 使用springCloud需要依赖于springboot技术,springboot实质上就是快速构建spring的脚手架工具,springCloud的开发代号【都是地铁站名】必须和springBoot的版本号严格对应

      • springCloud有些小版本:

        有稳定版本优先用稳定版本,没有GA的情况下用SR版本,SR版本没有用M版本,不要用快照版

        • SNAPSHOT: 快照版本,是不稳定的,随时可能被修改

        • M: MileStone, M1表示第1个里程碑版本【实现预定目标的版本】,一般同时标注PRE,表示预览版版。

        • SR: Service Release, SR1表示第1个正式版本,一般同时标注GA: (GenerallyAvailable),表示稳定版 本。

    • Spring Cloud相关基础服务组件

      • 服务发现——Netflix Eureka (Nacos)

        Eureka出现了一些瓶颈,换成了nacos。服务发现就是注册中心

      • 服务调用——Netflix Feign

      • 熔断器——Netflix Hystrix

      • 服务网关——Spring Cloud GateWay

      • 分布式配置——Spring Cloud Config (Nacos)

      • 消息总线 —— Spring Cloud Bus (Nacos)

       

       

  2. Nacos

    edu模块对vod模块中的方法进行调用实现对小节视频的删除功能,区分于引入项目依赖和通过HTTP API传递数据,即服务间的相互调用

    Nacos 是阿里巴巴构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。 Nacos 能快速实现动态服务发现、服务配置、服务元数据及流量管理。 快捷构建、交付和管理微服务平台。Nacos同时相当于服务发现和分布式配置,除此外还可以实现消息总线

    早期springCloud使用的Eureka,后来遇到性能瓶颈,更新代价高,就被替换了

    zookeeper也是常见的注册中心,GO是Consul

    注意如果服务中添加了Nacos依赖但是没有配置nacos地址,服务就无法启动起来

    • 在删除小节的edu服务中的方法中调用vod服务中删除视频的方法

      • 第一步:将edu和vod两个服务在注册中心进行注册

        • 注册中心:类比于房产中介,将房产在注册中心做登记,再想租户介绍

    • Nacos的执行流程

      • Nacos由三个部分构成、Nacos注册中心、消费者【调用方法】、生产者【提供方法】

        • 被调用的服务的都是生产者,调用服务就是消费者,两个组件都要在注册中心进行注册,注册的基本逻辑是使用ip和端口号进行注册

        • 消费者在注册中心中得到消费者的ip和端口号,使用这俩对生产者进行调用

    • 安装nacos

      • 使用的是nacos1.1.4版本,不要选用beta版本,beta版本是公测版本,就是让大家帮他测试,里面有问题再完善,下载windows版本,下载地址:https://github.com/alibaba/nacos/releases

      • 运行就是解压压缩包,运行windows版本下的startup.cmd文件即可,最好使用命令startup -m standalone设置成standalone模式【单击版本启动,表示非集群的方式启动】

      • nacos的访问:使用http://localhost:8848/nacos直接进行访问,默认端口就是8848,默认用户名密码都是nacos,进入后服务管理下的服务列表就会显示当前已经注册了的服务

    • 使用nacos对服务进行注册

      Nacos注册中心会显示服务在配置文件中的name属性配置的服务名称,注意服务名称可以用短横杠,但是不要使用下划线

      • 在service模块中引入nacos客户端的pom依赖,因为service中的模块都需要进行注册,所以直接在service的pom文件引入

      • 在要进行注册的服务的application.properties文件中配置Nacos地址对应的属性server-addr【即ip地址和端口号,不用加http】

      • 在微服务启动类上添加@EnableDiscoveryClient注解,添加Nacos客户端注解,表示注册该springBoot应用

    • 对服务进行调用

      • 对服务进行调用需要使用Feign【由Netflix网飞开发,springCloud很多组件都由该公司开发】、Feign是一个声明式、模板化的HTTP客户端、可以快捷优雅的调用HTTP API,Spring Cloud Feign对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。整合了Spring Cloud Ribbon和Spring Cloud Hystrix, 除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。能够帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量

    • 使用Feign对服务进行调用

      • 第一步:引入Feign的依赖openFeign

      • 第二步:在调用端edu中创建client包和XXXClient接口写服务调用代码

        @FeignClient注解用于指定从哪个服务中调用功能 ,名称【value属性】与被调用的服务名保持一致。【配置文件中配置的服务名】 @GetMapping注解用于对被调用的微服务进行地址映射【类似于前端定义接口的地址写法】 @PathVariable注解一定要指定参数名称,否则出错【也是value属性】 @Component注解防止,在其他位置注入CodClient时idea报错

      • 第三步:在调用端的启动类上加上@EnableFeignClients注解

      • 第四步:在调用端对应的方法中对服务中的方法进行调用

        • 向方法所在类注入对应的XXXClient接口,通过该对象调用对应的方法即可,底层细节相当于是向被调用端发起请求远程调用对应的方法

  3. 使用微服务架构的实现删除课程删除多个视频

    • 删除多个视频可以给request传入多个id【需要传递用逗号分割的多个id的字符串】,可以以list集合的方式传入一个数组,StringUtils.join(list.toArray,","),是apache.common.lang包下的方法,返回值是String,相当于将list集合转成数组然后遍历用“,”分割拼接成字符串;Java8中的方法String.join(",",list)也能直接达到同样的效果

    • 第一步:用list集合封装前端传入的多个id

      【controller层】

      确定一下@RequestParam("videoIdList")的用法

      【service层】

    • 第二步:给VOD的对应api的delete请求的request传参多个id,删除方法和删除单个的方法是一样的,id可以由服务调用方查数据库查出来,queryWrapper中查询指定字段的selecrt(“字段名”),查出来还是会封装成对应对象的list集合,而不是对应字段的list集合【注意视频id可能为null,要加非空判断,空的记录没必要放入list集合】

    • 第三步:在VodClient接口中定义vod服务中删除多个视频的方法,注意也要list集合非空判断,如果list集合为空list.size()就不用调用对应的删除视频方法了

      已测试,删除功能完全没问题

 

Hystrix熔断器

  1. SpringCloud执行过程中组件的调用流程

    消费者:edu、生产者:vod

    • Feign-->Hystrix-->Ribbon-->Http Client

      • Feign:

    • 第一步:定义接口化请求调用:编写接口【如VodClient】,指定调用服务的名字和调用方法的路径,抽象方法

    • 第二步:服务开始调用,执行Feign组件,找到服务的名字和方法地址,根据服务名字和地址对方法进行调用

    • 第三步:Hystrix:断路器、熔断器;调用方法的过程去检验对应服务是否能调用,能调用继续执行,调用不了服务挂掉了就执行熔断机制,目的是保护系统

    • 第四步:Ribbon,做负载均衡,把请求均衡分担到多个服务器中

    • 第五步:Http Client,真正根据服务和方法路径真正去调用对应的服务,做真实的http通信请求

  2. Hystrix

    供分布式系统使用,提供延迟和容错功能,保证分布式系统错误情况下的弹性

    分布式:把项目的不同服务部署在不同的服务器上,不同的服务加在一起构成完整的项目就构成了分布式系统

    应用场景是系统中某些服务不稳定,使用这些服务的用户线程可能会发生阻塞、如果没有隔离机制,整个系统可能会挂掉

    熔断机制【有几种方式】:

    • 服务器宕机情况下的处理:某个服务的服务器宕机,当该服务再被其他服务调用时Hystrix不会再调用宕机的服务器,hystrix执行fallback把对应服务器从系统中踢出去

    • 响应过慢情况下的处理:服务调用请求本身有等待返回结果的时间,超时就认为请求失败;被调用者有时存在服务器没有宕机,但是相应时间很慢的情况,熔断器可以设置当遇到请求很慢的情况下允许调用者在响应很慢的情况下的延迟等待

  3. 在项目中整合springCloud Hystrix熔断器

    • 引入Hystrix依赖

    • 在配置文件中添加Hystrix配置【开启熔断机制【默认是false】和设置hystrix超时时间,默认时间是1000ms,可以自定义设置,该时间内的响应都不会提示超时】

    • 编写一个远程调用接口的实现类【vodClient的实现类】,在远程调用失败后会自动执行实现类中的方法,比如抛出错误信息;同时需要再VodClient的@FeignClient注解的fallback属性上指定实现类的.class类型对象,实现类也需要加@Component注解

 

 

前台系统

项目前台系统搭建

NUXT

对比于vue-admin-template框架,前台用这个框架,这是一个服务端渲染技术

SEO:网站中出现关键词的数量更多在页面展示的次序更靠前,由于ajax是异步请求,在搜索引擎爬虫抓取工具扫描完网站关键词之前异步请求的响应还没有展示出来,导致搜索的网站排序和关键字的匹配度降低【即异步请求的ajax不利于SEO】

NUXT服务端渲染技术在服务端将以上问题解决,客户端只做数据的显示,不进行其他处理;客户端发送请求给服务器,服务器中包含了tomcat和多出一个Nodejs,tomcat得到数据然后被tomcat处理封装,然后发给客户端进行展示;NUXT是nodejs的一个框架,在服务端对数据进行渲染然后将数据返回给客户端

NUXT框架的安装运行

  1. 获取NEXT框架的压缩文件starter-template-master,解压将template的内容复制到前台目录中,将后台系统的.eslintrc.js配置文件复制到前台目录根路径下

    【这不行的,课件里面写的有问题,直接拷贝NUST框架template中的.eslintrc.js,eslint的检查规则很严格,有没有空行空格,id属性在class属性前面都会检查,不对就过不了编译,这里有错误改代码就酸爽了,后台系统是禁用eslint格式检查所以编译没报错,前台教程是直接用NUXT框架中的eslint配置文件,改了以后就没有报错了】

  2. 修改package.json的name、description、author

  3. 修改nuxt.config.js

    这里的设置最后会显示在页面标题栏和meta数据中

  4. 在根目录使用npm install安装依赖,在根目录下使用npm run dex测试运行

    出现占用多少内存的提示就表示应用启动成功

     

NUXT目录结构

NUXT框架本身只是基于VUE,并没有基于element-ui;而后台管理系统的vue-admin是同时基于VUE和Element-ui实现的

 

 

 

首页

轮播图【幻灯片】

也叫banner,使用一个幻灯片插件vue-awesome-swiper,使用npm install vue-awesome-swiper@3.1.3

  1. 安装幻灯片插件

  2. 配置幻灯片插件

    在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js,内容是

    在 nuxt.config.js 文件中配置插件 将 plugins 和 css节点 复制到 module.exports节点下

  3. 复制项目中使用的静态资源到asset目录

    资料中页面原型下的asset目录复制到nuxt项目的asset目录下,包括css样式,项目中用到的图片,js等内容;实际生产中这些静态资源都是由美工制作好的

  4. 从课件复制代码到default.vue文件下

    文件中代码有头信息和尾信息,中间的nuxt是引入其他的页面

  5. 从课件复制首页面到pages中的index.vue页面

    后续将这个页面的数据更改为静态页面的效果

  6. 整合幻灯片

    幻灯片放在index页面中,目前幻灯片只有手动切换功能,没有自动切换功能

    复制幻灯片代码到index.vue,复制幻灯片的切换组件到index.vue,幻灯片的原文件在photo的banner中

 

首页数据banner显示

幻灯片或者轮播图,新建banner微服务cms【content management system】,注意如果在mp的mapper.xml文件中写sql需要在pom.xml中配置builder设置xml可以被打包

  1. 后端构建

    1. 创建cms项目

    2. 配置application.properties

    3. 创建轮播图对应的数据库表,使用mp的代码生成器生成后端框架代码

  2. 编写后端轮播图操作接口,

    • controller设置成后台和前台使用的控制器,不使用默认设置

    • 后台banner控制器方法

      • banner不带条件分页查询

      • 增加banner

      • 根据修改banner

      • 根据id删除banner

      • 根据id查询banner的方法

    • 前台banner控制器方法

      • 查询所有banner【幻灯片显示数据不需要分页,这个自己封装一个方法,为了后续加redis方便】

  3. 用户前台轮播图数据的展示操作

    • NUXT框架本身没有带axios组件,需要使用命令npm install axios@0.19.2先下载axios组件

    • 参考后台管理系统utils/request.js对ajax请求进行封装,axios的baseURL要写成nginx的地址

      由于之前的框架对axios进行了封装,返回的是response.data;所以只需要写一个response.data.XXX就能获取数据,这里需要两个data才能获取数据

      【轮播图显示组件】

      同理搞出首页热门课程和热门讲师数据的遍历

      1. 根据课程的浏览量进行查询,前台显示前8个热门课程,可以查询按浏览量排序的前八个

      2. Sql语句:根据id进行降序排列,显示排序之后的前八条记录【可以根据浏览量排序,不要根据视频讲的id排序】

        • 核心一个orderByDesc和一个last方法拼接sql,由于课程和讲师都在EDU模块中,在edu模块写代码,在cms模块中进行调用

    • :key是对数据遍历过程中每个组件的key标识,常用id进行标识,alt属性有两种情况,第一种情况是将鼠标移至图片上显示alt中的信息,第二种情况是src地址的图片没有了就会显示alt的内容,两种情况需要看是哪一种浏览器,点击图片跳转超链接,连接地址是banner属性的linkURL

  4. 实现后台管理员对轮播图的操作

 

 

 

NUXT中的路由操作

路由:类似与菜单,可以跳转页面

  1. 固定路由

    • 路由路径是固定的,不发生变化的,

      该项目固定路由的位置在default.vue中

     

  2. 动态路由

    • 路由路径是动态变化的,路由后面比如跟一个/id属性,这个属性值是动态变化的,比如课程详情

      NUXT的动态路由是以下划线开头的vue文件,参数名为下划线后边的文件名【实际不是必要,只是一种规范】,如course/id是参数名,pages/course/_id.vue就是对应的页面详情

  3. 整合课程列表、课程详情、讲师列表、讲师详情

    • 整合课程列表

      pages/course/index.vue

      pages/course/_id.vue

      pages/teacher/index.vue

      pages/teacher/_id.vue

       

Redis缓存首页数据

能够提升数据查询的效率

  1. redis回顾

    • 基于key-value的方式进行存储,读和写的速度可观【内存读取速度快】,支持多种数据结构【看笔记】

    • 支持持久化【数据可以存入硬盘】

    • 支持过期时间【设置数据的过期时间】和事务

    • 支持消息订阅

    • 做内存和缓存数据库,一般把高频访问不频繁修改且不重要【如钱】的数据放入redis

      面试题:redis集群搭建

      redis和memcache的区别:memcache不支持持久化

  2. SpringBoot整合redis

    • 第一步

    在common包下整合redis依赖starter-data-redis和commons-pool2【这个是redis的连接池】

    • 第二步

      在service_base模块下创建RedisConfig,即redis配置类,里面两个插件,一个做缓存,一个做缓存管理,写法都是固定的

      redisTemplate做redis缓存操作

      CacheManager主要做一些类型转换,数据过期时间等

    • 第三步

      基于SpringBoot缓存注解使用redis进行数据的redis缓存,这个用RedisConfig中的redisTemplate插件也能做到

      SpringBoot三种常用缓存注解:【注解可以加在service中对应的方法上】

      • @Cacheable【一般用于查询方法,对方法返回结果进行缓存,下次请求如果缓存中有就查询缓存,如果缓存没有就执行方法并把结果存入缓存】

        • key、value属性字面意思,key和value都是自定义的,共同构成数据的名字,这里key用的首页数据,value用的bannerList,key在双引号间还要就加单引号,否则可能会有问题,都显示绿色则没问题

        • 这个注解还可以加在控制器方法上,一样没有毛病

      • @CachePut【该注解标注方法每次执行都会查数据库,并将数据存入数据库,其他方法可以直接从缓存中拿该数据,一般用在新增记录方法上】,这个key和value中间加两个冒号共同构成redis中的key。value在前,key在后

        • key,value属性

      • @CacheEvict【该注解标注的方法会清空对应指定的缓存,一般用在更新和删除的方法上】

        • allEntries属性设置为true,方法执行完会立即清空对应value名的缓存

    • 第四步

      改造首页banner接口,把数据加入redis缓存

      在方法上添加@Cacheable注解并设置key和value即可

      控制器方法上加也没问题,而且是远程调用的

      SERVICE中加也没毛病

       

    • 第五步

      启动redis

      • 虚拟机装redis并启动redis

      • xshell连接虚拟机,找到redis.conf配置文件,在该目录下用./redis-server /etc/redis.confg启动redis服务【这个命令需要在redis的默认安装目录/usr/local/bin目录使用,redis需要c语言编译环境,本机6.0编译可能有问题,用6.2没问题】,使用./redis-cli对redis进行本地链接,出现端口号就是启动成功了

        • redis必记命令

          • keys *【查询所有key】

          • get key【通过key获取值】

      • 用windows访问linux上的redis服务需要

        • 关闭linux防火墙或者开放redis对应的端口

        • 修改redis配置文件

          注意修改完redis配置文件需要重启,重启命令ps -ef | grep redis,查看redis命令进程;kill -9 3259杀进程,然后启动命令启动,把redis的密码和端口改掉

          • 注释掉bind 127.0.0.1【如果不注释掉这句话只能通过本地访问,windows是访问不了的】

          • 如果IDEA报错redis是protected-mode,需要修改配置文件protected-mode yesprotected-mode no【保护模式不允许远程访问】

    • 第六步:在service-cms模块配置文件添加redis配置

       

       

       

登录注册功能

单一服务器模式登录,所有的程序部署在一台tomcat中,使用session存储用户登录成功后的数据,session中可以获取用户数据说明已经登录,一台服务器这种方式很适合,session.setAttribute("key",value),session.getAttribute("key");

  • session默认过期时间:默认是30分钟不做任何操作

分布式服务器集群部署分摊访问压力,扩展方便

单点登录:SSO【single sign on】模式在任何一个服务模块登录后,其他所有模块登录后都不需要登录,可以直接进行访问,比如百度在贴吧登录后,在图片、文库等都不需要再次登录,可以直接访问,分布式必用登录方式

单点登录的三种常见方式:

第二种和第三种用的最多,有时选择使用,有时混合使用

  • session的广播机制实现【session复制,单个服务器模块登录后session存入用户信息,然后将session对象复制到各个模块中;致命缺点:项目中模块太多,session复制很耗时间、空间,极其浪费算力和存储空间;是一种互联网早期的机制】

  • cookie+redis实现

    • cookie是客户端技术,存在浏览器中,每次请求都会带cookie;redis读取速度快,基于k-v做存储;

    • 实现过程:在项目任何一个模块做登录,登录后将数据放入cookie和redis,在redis中的value放用户数据,在key中放入生成的唯一值【一般是用户ip或者用户的id或者uuid】,将redis中生成的key放入cookie中

    • 每次访问携带cookie,在服务中获取cookie,拿着cookie到redis根据key查询,查询到有数据就是已经登录,再严谨一点,拿着数据到数据库进行验证,验证成功就是已登录,不成功就是未登录

  • token实现

    • token是按一定规则生成字符串,token也叫令牌,字符串可以包含用户信息,这种字符串叫自包含令牌,如ip#username#职位#头像#...,将该字符串做一个base64编码,然后做一个加密

    • 实现过程:

      • 第一步:单点登录后生成一个包含用户信息特定规则的字符串,将字符串通过cookie或者地址栏返回

      • 第二步:每次访问模块,地址栏带该字符串,访问模块对地址栏中的字符串解码获取用户信息,可以获取到就是已登录,获取不到就是未登录

后端接口

  1. 注册接口

    • 整合jwt【json web token】

      • JWT:是一种通用的自包含令牌,规定好了生成字符串的规则,里面可以包含用户信息,JWT的规则比较完善,用的比较广

        token是按一定规则生成的字符串,包含用户信息,但是规则每个公司都不一定,一般采用通用的,如JWT

        JWT生成的字符串包含3个部分,用'.'进行划分

        • 第一部分:JWT头信息【编码方式alg:'HS256';token类型'typ':'JWT'】,一般是json对象,只是经过base64编码后变成字符串

        • 第二部分:有效载荷,JWT主体内容部分,一个json对象,七个默认字段

          除了默认字段外,还可以自定义私有字段,用户名,是否管理员等等,但是注意默认情况下JWT是未加密的,不要放隐私保密信息,防止信息泄露,用户信息就可以放在这部分

          • iss:发行人

          • exp:到期时间

          • sub:主题

          • aud:用户

          • nbf:在此之前不可用

          • iat:发布时间

          • jti: JWT ID用于标识该JWT

        • 第三部分:签名哈希

          • 自定义一个密码,该密码仅保存在服务器中且不向用户公开,使用HS256算法按以下公式计算签名哈希【HS256算法是加密算法】

            通过签名哈希可以验证该字符串是否由我方服务器生成,可以作为一种防伪标志,claims就是有效载荷

            HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(claims),secret)

        三个部分JWT头、有效载荷、签名哈希和"."共同组成整个JWT对象【注意头和有效载荷都是base64编码】

      • jwt的优缺点:

        • 减少服务器请求数据库的次数、可包含用户头像、id、昵称等信息且存储在客户端、减少查库和服务器内存消耗

        • 默认不加密,不加密情况下无法存储私密数据、但是可以对原始令牌进行加密

        • 最大的缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效

        • 为了减少敏感信息盗用和窃取,第一令牌有效期不能设置过长、第二重要操作前还是要对身份进行验证、第三不建议使用HTTP协议进行传输、建议使用加密的HTTPS协议进行传输

      • 整合流程

        在common模块引入主要为了其他模块都能用到

        • 在common模块引入JWT依赖jjwt

        • 在common模块创建JWT工具类:直接复制代码

    • 阿里云短信服务

      • 创建service_msm模块

      • 搭建SpringBoot应用基本结构

      • 开通阿里云短信服务--进入管理控制台--国内短信--申请签名管理和模板管理【模板管理是发送短信的模板,点击添加模板、模板名字要有实际意义,申请说明也要有实际意义,等待审核;签名管理是为了发送信息,只有签名通过才能发送验证码,模板通过模板code进行使用】

        注意阿里云发送验证码的码值还是由自家服务器生成的,把验证码传递给阿里云进行发送,验证码用自定义随机数工具类RandomUtil生成,这个工具类直接复制

      • 编写发送阿里云短信服务的service方法

        【controller方法】

        通过设置redis中手机号对应的验证码的有效时间来实现五分钟验证码有效,超过五分钟就重新发短信

        【service方法】

  2. 登录接口

    • 登录用户信息验证

      • 密码不能明文,存储密码是加密的,验证采用的办法是输入密码加密与数据库密码进行对比,实际中用MD5加密,特点是只能加密,不能解密,工具类为MD5.java,其中的encrypt方法就是加密,MD5在java.security包下就有

    • 核心:

      • 第一步:建表edu_user,使用mp代码生成器生成后端框架,创建启动类,设置Spring的配置文件,设置服务端口号、服务名、配置测试mp执行效率的环境、数据源、mp日志、设置json的时间格式和时区、配置mp的逻辑删除定义、mapper.xml的路径、Hystrix熔断机制和超时时间、redis相关配置、

      • 第二步:创建登录和注册的vo类、封装用户信息的bo类

      • 第三步:在common模块下添加MD5Util类生成MD5加密密文并将密文转成32位

        MD5加密大致流程:字符串转成字节数组,字节数组补足64的倍数个字节,补足规则:原字节数组后一位补16进制80,最后八个字节补足原字节数组的bit总数并展示为小端序,在此基础上将总字节数补0x00至64字节的倍数,将处理后的字节数组按64个字节一组分成若干整数组,设置A、B、C、D四个4字节16进制数,分别赋值给a、b、c、d;对a、b、c、d按照MD5算法的规则循环计算每一组数据后得到a、b、c、d,这四个数与A、B、C、D相加每个数中的字节按小端序的顺序排列输出一个16字节数,即md.digest()中的运算规则,分别用b >>> 4 & 0xf和b & 0xf将对应的一个字节的两个16进制取到并查表转换成对应的16进制字符,将得到的字符数组转换成字符串即MD5不加盐算法的最终实现

        MD5算法原理没有深究

      • 第四步:后端接口

        【controller层】

        【service层】

      • 第五步:注入mp逻辑删除组件ISqlInjector,并在逻辑删除字段上标注@TableLogic注解

         

前端实现

  1. 安装element-ui和vue-qriously,安装后需要像轮播图一样在plugins目录下进行引用

    • vue-qriously是为了后续下载微信支付码使用的

  2. 整合注册页面

    • assets/css/sign.css是注册相关的样式

    • 在layout目录下创建注册布局页面sign.vue,

    • 在pages目录下创建注册页面register.vue,default中注册、登录的超链接路径与该注册页面的名字要一致且只取"/页面名字",复制页面内容

      倒计时效果基于js中的定时器方法setInterval("js代码",1000),每隔一秒让参数中的js代码执行一次,只需要设置每秒让显示参数自减1即可实现倒计时效果

      SpringBoot应用改了模块名字以后一定要检查配置文件是否在类路径下被打包,否则会出现绑定数据错误导致项目起不来和其他问题,用了redis的服务运行时redis一定要启动起来,否则运行会出问题

      有很多bug,比如后端返回号码已经被注册,也会显示注册成功;验证码不正确也会显示注册成功,没注册上也会显示注册成功,验证码不是立即验证,而是点击注册再验证,逻辑应该改成验证码不正确无法注册,手机的验证也要在输入手机后立即验证,避免失败后重新输入,拉低用户体验;一分钟的验证码只是防刷短信的,只要重发了验证码,redis中的数据就更新了,这里的bug大部分都修好了,解决办法是提交注册前校验

    • 整合步骤

      • 定义前端接口

      • 创建登录页面

      • 定义数据和其中的方法

  3. 整合登录页面

    • 登录前端页面

    • 整合微信扫描登录

      • 这是腾讯提供的一个流程

      • OAuth2

    • 整合步骤

      • 第一步:调用接口返回用户的token

      • 第二步:将返回的token字符串放在cookie中【必须安装js-cookie才能用cookie】

      • 第三步:写一个前端拦截器,判断cookie中有没有token字符串,如果有将token字符串放入请求头中

      • 第四步:根据token值调用接口获取用户信息,以便展示在首页面,将用户信息放入cookie,请求接口前会先调用前端拦截器

      • 第五步:在首页从cookie获取用户信息,在首页面对用户信息进行展示,cookie.set是设置值,cookie.get是获取值,同样需要引入js-cookie

      • 第六步:通过用户信息对象或者其属性是否有值判断是否在注册登录位置显示用户信息

      • 第七步:退出直接把cookie中的数据清空就回到最初的状态了,cookie的清空是向同名cookie中设置一个空串,cookie中的数据就消失了,清空后回到首页面

      • 第八步:定义前端请求接口:

        太多了,按照文档更改组件参数,主要是前端页面组件的校验规则,cookie设置值和取值的视线,请求拦截器的token放入header供后端获取,cookie数据对应的值取空串就能清空cookie数据,登出就是清空cookie数据,必须安装js-cookie才能对cookie放值和取值操作,用一个标志参数让注册表单无法多次请求

        这里主要优化了手机号码注册前验证,短信验证码注册前验证,密码二次确认的功能

        注意配置文件中用value引入的字符串不需要加"",加了会让数据库中对应的字符串也出现"",如头像连接,会导致前端 无法识别连接

        存在问题,以弹窗的形式提示成功校验信息,点击注册会全部进行一次校验,仍然会弹出所有弹窗,不好看,怎么进行处理

        【短信验证码校验】

        注意提示框不要写在then函数的外面,否则一定会发生数据还没返回赋值就进行了判断并提示信息的情况

        【电话号码校验】

        【密码二次验证】

         

  4. 微信扫码登录

    自动注册、自动登录

    • OAuth2

      • 针对特定问题的一种解决方案,能解决一些特定问题,主要解决两个问题

        • 解决开放系统间的授权

          线上的打印服务无法访问用户在线上的第三方图片存储服务,图片存储服务只能由用户提供身份证明主动去操作,打印服务要访问特定用户的照片需要用户对其进行授权,Otuth2解决的第一个问题,如何授权让第三方服务去访问用户资源

          授权方式:

          1. 方式1:用户名密码复制【适用于同一个公司内部多个系统,不适用于不授信的第三方应用】

            • 客户应用复制资源拥有者的用户密码,并将其传递给受保护的资源

          2. 方式2:通用开发者key【适用于合作商或者授信的不同业务部门】

            • 万能钥匙,两个客户应用商量好了开发某种认证,使两个应用可以互相访问【缺点是两个应用之间实力对等才能促进这种方式的达成】

          3. 方式3:办法令牌

            • 用户资源提供商向第三方提供一个令牌【字符串】,令牌的内容包含给谁进行颁发,有效时间,通过网络给别的服务颁发令牌、令牌管理、颁发、吊销需要统一的协议,在此基础上形成了OAuth2解决方案,类似于token的感觉

        • 分布式系统的访问问题

          指的就是单点登录,生成token,请求携带token【OAuth2令牌机制:按照一定的规则生成字符串,字符串包含用户信息,OAuth2没有规定token生成规则,如何生成是用户自己决定的,可以用JWT,也可以自定义】

          单个服务登录成功,封装用户信息,按照一定规则生成token字符串,token响应给应用并通过路径或者cookie进行传递给各个服务,服务获取字符串,判断token中是否有用户对应信息来判断用户登录状态

          犯了事去美国是一种解决方案,怎么去是技术细节【该技术细节类比于如何生成token】

      • Oauth2是一种解决方案,不是一种协议,只支持HTTP协议,没有定义技术细节,没有定义token生成方式,没有定义加密方式,仅仅是一种用于REST/APIs的代理授权框架,仅用于授权代理,通过这种方式解耦认证和授权,解耦资源服务器和授权服务器,基于令牌的方式,在不暴露用户密码的情况下让应用获取对用户数据的有限访问权限,让应用代表用户去访问用户数据,是标准安全框架,适用于服务端WEBApp、浏览器单页SPA、无线原生APP、服务器对服务器之间,HTTP/JSON友好,易于请求传递token

        • 缺点是框架太宽泛,各种实现、互操作性和兼容性太差,和OAuth1不兼容,各种安全场景需要去定制

        • 一般来说授获取访问令牌【Acess Token】是客户应用【一般是Web或者无线应用】在用户许可下在单独的授权服务器【AS】中完成的,拿着令牌去访问资源服务器【RS】,访问令牌具有作用域【由资源拥有者额外指定的有限权限】

    • 腾迅相关的准备工作

      • 到网站https://open.weixin.qq.com 注册用户,完善开发者资质认证【300元,1到2天审批】,创建网站名字提交审核【一般七个工作日审批,名字不要太个性,名字通俗平庸,这个名字用于用户扫码后的跳转】,需要域名地址【扫码后的请求跳转,包括服务器地址】

      • 老师提供了,在微信登录的wx id。txt中,不能用了,测试用看评论前几条有

      • 微信登录的基本流程

      • 在edu_user模块中配置微信id、密钥、域名地址,创建类读取配置文件内容;这几个值暂时使用固定的,实际生产中都是公司申请好的

      • 生成微信扫描的二维码

        • 微信文档中写的

          • 访问固定地址传固定参数就能生成微信二维码

            "https://open.weixin.qq.com/connect/qrconnect" + "?appid=%s" + "&redirect_uri=%s" + "&response_type=code" + "&scope=snsapi_login" + "&state=%s" + "#wechat_redirect";

            appid是应用唯一标识,就是微信申请的应用id,在配置文件配好了

            redirect_uri是自己使用urlEncode编码方式对重定向连接进行处理,注意只需要对请求重定向地址编码,此前和后续参数不用编码,并用传递给微信编码后的重定向地址

            response_type是填code,这个是固定的

            scope是应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用对应snsapi_login

            以上四个参数是必须的

            可选参数state是state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻 击),建议第三方带上该参数,可设置为简单的随机数加session进行校验【就是给该地址传递什么state值就返回什么state值,防止跨站请求伪造攻击】

            实际非固定的就两个appid和redirect_uri

          • 直接拼接连接字符串参数很容易出错,实际开发中常用%s来占位,用String.format来传递参数,第一个参数为拼接字符串,后续参数对应要拼接的变量或者字符串常量

          • 编码用到URLEncoder.encode方法

          • 微信二维码用到了重定向不要加@RestController,@RestController中的@ReponseBody是返回对象,直接用@Conroller直接重定向,只是重定向,不需要返回地址

          【微信二维码生成】

          • 扫描二维码后的跳转流程

            • 扫描后会去跳转配置的域名wx.open.redirect_url,即编码前的redirectUrl=http://localhost:8160/api/ucenter/wx/callback,//这个扫描是跳转到尚硅谷的服务器,然后转发回来的地址,方便测试用,只需要把本地的地址改成返回的连接形式就能端口8160和路径api/ucenter/wx/callback与尚硅谷服务器返回结果一致就能回到本地接口,实际生产中不需要中间服务器,因为会跳转到对应公司的服务器地址接口上

            • 返回了code和state两个参数,直接用形参就能接收

              • code类似于手机验证码,随机唯一值

              • state自定义的,防止网站攻击设置的【防止csrf攻击(跨站请求伪造攻击),设置为ngrok的前置域名】

            • 拿着获取到的code,携带code发送请求到固定地址,传参网站id和secret;获取到access_token访问凭证和openid【微信昵称可以相同,但是都有唯一标识的openid,相当于主键】

            • 拿着access_token和openid再去请求微信固定地址,最终获取微信扫码人的信息,比如头像、昵称等等【多个步骤多次验证得到数据】

              完整流程:用户要登录一个系统,用户是第一方,系统是第二方;用微信登录,微信就是第三方;用户请求第三方应用,第二方向第三方发起OAuth2授权登录,微信请求用户确认,用户确认后微信重定向到第二方带上临时授权票据【code相当于手机验证码】,第二方通过code和第二方注册的应用信息请求微信获取授权令牌access_key和用户id,第二方应用拿着access_key和用户id去请求用户数据

              用到的技术点:

              1. 用httpClient去使用程序在服务端发送请求,可以自己写个工具类去操作httpClient发送请求,还有类似的服务okhttp

                返回的响应体是json格式的字符串,包含以下信息,主要使用的就是access_token和微信id

                • access_token

                • expires_in:凭证有效时间

                • refresh_token

                • open_id:微信id

                • scope:作用范围

                • unionid:作用单元

              2. json转换工具,常用的有fastjson和gson【这次用gson】

                • 还有jackson,@RestController返回json数据用的就是jackson

            • httpClient发送get请求的工具类方法

               

          • 返回的微信用户信息格式

            一般用到的就是openid、nickname、sex、headimgurl

            • openid

            • nickname

            • sex:男性是1,女性是0

            • language

            • city

            • country

            • headimgurl:这个就是微信头像链接,这个返回的双斜杠\\会用\/转义,存数据库的时候需要去掉

            • privilige

            • unionid

            验证数据库中是否有该微信id,没有取出信息然后存入数据库,

            并用用户id和昵称生成token字符串,通过路径传递到首页面,不能放在cookie中,因为分布式项目中cookie不能跨域传递

          • 前端获取地址栏的token,加入cookie中,方便页面初始化时前端请求拦截器将cookie中的token放入请求头中去

            注意以?token=""的形式传递的地址栏参数不能通过this.$route.params.id的方式获取参数值,这种取值方式仅限于参数矩阵的方式 路径带参数名称的参数可以通过this.$route.query.参数名取到

            进入default页面,会存在token不能放入cookie的情况,原因很不理解,而且检验了两次地址栏有没有token参数的情况

     

讲师列表

分页查询讲师功能

固定每页显示八个讲师

  1. 后端分页查询讲师接口

     

  2. 前端调用接口获得数据

    异步调用这能调用一次完成初始化,最后获取当前页数据需要调用其他方法

  1. 讲师列表没有数据提示信息

  2. 讲师页面

  3. 分页组件

    写一个方法做分页切换,之前的分页数据只有当前页,盲猜还要再次请求接口,就是定义一个方法传参当前选择页码和每页记录数再调用一次分页查询接口

    【组件】

     

 

讲师详情

  1. 后端接口【一个接口查询讲师基本信息和相关课程】

    • 根据讲师id查询讲师基本信息

    • 根据讲师id查询讲师所讲课程

      【查关联课程的service层】

       

  2. 前端页面

    NUXT框架获取动态路由params.id的id要和页面_id保持一样

    给多个变量定义赋值用逗号隔开

     

 

 

 

课程列表

一级分类下有二级分类,可以筛选课程列表,即带条件分页查询,可以自主选择关注度、最新、价格做排序

  1. 后端接口

    【Vo对象封装查询条件】

    【多条件分页查询课程列表控制器方法】

    【service方法】

     

  2. 前端实现

     

课程详情

点击课程详情界面跳到新页面,显示课程介绍,并显示课程章节信息,显示讲师头像和简介,需要写SQL查出来,并且在章节信息中整合阿里云的播放器,把视频播放功能整合进去

编写SQL查询课程基本信息、课程分类、课程描述、所属讲师

调用之前的方法根据课程id查询章节和小节

写SQL要注意build目录是否包含mapper的打包目录,配置文件设置mapper.xml的路径【精确到后缀】

注意课程详情中课程介绍内容有标签,是富文本编辑器自动生成的,如果直接输出到浏览器不会翻译标签,标签会被原样输出,vue中的v-html属性可以把富文本编辑器中的标签翻译成输入富文本编辑器的效果,和v-if、v-for一样的使用方法

  1. 前端代码

  2. 后端接口

    【controller】

    【service】

    【mapper.xml】

    注意:mapper.xml中写的sql需要再mapper.java中声明对应的方法,就是可以通过mapper调用,会在mapper中动态生成方法,注意该方法必须在service中进行调用才能生效

     

 

整合阿里云播放器视频播放功能

视频播放有两种方式,一种是地址播放,一种是凭证播放;推荐用凭证播放,用地址播放加密后的视频是无法播放的,因为视频存在阿里云上,视频需要使用阿里云播放器才能播放,别的播放器理论上是播放不了的

  1. 无加密视频的阿里云播放器整合

  2. 有加密的视频播放器整合

后端接口

根据视频id获取视频播放凭证

  1. 【通过视频id获取播放凭证】

 

前端页面

谷粒学院是点击小节,会弹框然后播放视频

51cto会弹出新页面,在该页面做视频播放

这里采取51cto的方式,在新页面做视频播放

用异步的请求方式页面渲染创建好以后数据不一定有,要在mouted方法中当页面数据获取渲染之后再对mounted进行调用

点击超链接打开新页面是在超链接中添加target属性并设定值为"_blank"

视频播放器中插入广告,开通vip、视频列表、弹幕、倍速播放、字幕等组件在阿里云播放器中的功能展示都有相关的示例等,有直接的代码可以拷贝

【layout】

【阿里云播放器组件】

 

课程评论功能

需求:以下需求一般是项目经理负责

课程下方有一个输入评论的框,有回复两个字,在评论框输入评论后回车评论可以被展示

评论展示为 人名 评论内容

最下方是评论分页条

评论表包括课程id,讲师id,用户id,头像、昵称【可以优化多表查询连接查询,这个评论量一大占用空间很大】

评论之前要提示先登录,不支持匿名评论

控制器方法:

  • 分页查询课程评论

  • 添加评论【要添加的数据包括课程评论内容[由用户输入]、课程和讲师id[由课程详情页面可以查到]、用户信息可以由当前登录用户的token获取用户id,根据用户id查询用户信息】

    在service中实现定义添加评论的内容,在user中实现获取用户信息的功能

  1. 前端构建

    【对应的方法】

  2. 后端实现

    前端控制器,没有区分服务层,这不是一个好习惯

    【服务调用】

    【服务调用熔断执行】

     

 

 

微信支付功能

如果课程免费,可以直接观看,如果课程不免费,点击立即购买跳转订单生成页面,订单页面注意物品只能选择购买一个,点击去支付跳转支付流程,微信支付完成后原来点击购买的按钮变成了立即观看

点击立即购买,生成一个订单,向订单中添加一条记录,为了方便支付测试,把价格都设置成0.01,支付了100没地方退款

手机扫完二维码就会在表pay_log中生成一个支付日志记录【交易状态,微信后台可能存在延迟,判断支付是否成功的标志,必须等待成功再做其他事情】

涉及接口:

  • 生成订单接口【远程调用edu服务获取课程信息和讲师信息,远程调用user服务从token获取用户id获取用户信息添加到订单表】

  • 根据订单号查询订单信息的接口

  • 生成微信支付二维码的接口

  • 查询订单微信支付状态是否成功的接口,因为微信支付存在延迟

流程:

  • 点击立即购买--创建订单,生成返回订单号-->前端跳转商品去支付页面,调用后端查询课程信息接口将课程信息查询出来-->点击去支付调用后端生成微信支付二维码接口跳转支付页面-->支付成功前端隔3秒调用后端接口查询一次订单状态,查询支付成功后生成支付记录并修改订单的支付状态为1跳转立刻学习页面-->根据课程id和用户id判断课程用户是否已经购买,已经购买或者免费的课程用v-if和v-else实现选项切换

微信支付二维码生成

  • 这个和登录注册的二维码不同,哪个是直接输入地址就能返回二维码;这个支付二维码需要使用vue的组件进行下载

  • 但是还是要注册微信开发者,只支持企业用户

    测试信息如下,生产中公司已经准备好了,主要使用微信id、商户号和商户key

扫描二维码支付以后需要

  • 查询是否支付成功,第一要在支付记录表中添加一条记录,第二要将订单表中的记录的status字段改成1,并在订单支付状态验证方法中远程调用将课程的购买数量加1

bug:

  • 支付以后还没返回结果但是把浏览器页面关闭了,后面创建支付记录的方法无法执行了,为什么微信支付成功后不直接发一次请求告诉服务器成功了

  • 多次点击购买会生成多条订单记录,可能被攻击

  • 小节列表的状态是否免费定义的是是否可以试看课程,不免费表示不能试看,不能跳转视频播放页面,是否有bug直接拷贝id到播放器地址就能观看,如果有bug在获取视频凭证的时候就要判断在未支付情况下且不免费的情况下不能获取视频凭证,返回请购买课程的提示信息

  • 没登录点击购买应该跳转登录页面

  • 购买以后还要把课程的购买数加1

  • 此外QQ登录注册和支付宝支付功能没有实现

  1. 后端接口

    太多了,懒得写,流程看文档,注意以下几点

    • 服务调用最好不要用统一返回格式封装vo和bo类,存在数据传输过程类型统一转换成Map类型,导致数据类型如BigDecimal失真变成Double,又转换成decimal可能导致数据出错,直接把服务之间调用的vo类和bo类抽象成公共类,服务调用直接返回对应数据类型即可

    • 多学习一下实际支付的代码和处理流程,这个支付判断很大程度依赖支付过程前端页面的定时器任务,如果页面丢失,设备没电,支付结果就会丢失,这是很严重的bug,实际生产是怎么写的,学习一下

    • 涉及到的工具类标记一下

      【发送微信支付请求的工具类】

      【生成订单号的工具类】

      实际没用这个工具类生成订单号,实际使用的是mp的IdWorker.getIdStr()方法生成的订单号

    • 该模块和edu模块存在多次相互调用,留意一下,此外还有mp逻辑删除插件也需要引入,这点课堂没说

    • 购买数量加1的服务调用也在订单支付确认中实现了,课程没有涉及

    • 一定要留意支付二维码的下载前端插件和element-ui一起下载过了

 

 

  1. 前端页面

    • 项目源码中的静态资源文件覆盖项目前端的assets文件,因为之前没有添加支付相关的静态资源

    • 前端页面主要在course/id,order目录下、pay目录下

    • 比较重要的就是定时任务验证支付状态、异步调用的变量获取值较慢,可能存在页面渲染完还没获取到数据,此时涉及到页面渲染的数据不要放在异步调用中获取,避免出现错误

     

后台完善

 

统计分析模块

需求

以一个数据为例,其他都是类似的,把统计出来的注册人数,使用图表的形式进行展示【柱状图、折线图、饼状图等】

生成统计数据

创建表存储统计数据,字段包括统计日期、注册人数、登录人数、每日视频播放时、没惹你新增课程数、创建日期、更新时间

如:查某天有多注册人数,对应差当前生成了多少条记录,注意创建时间中带时分秒,如何忽略时分秒,可以使用like,但是不建议这么做,like一般用在昵称,名字等,日期不建议用like;mysql中有一个函数Date,可以把带时分秒的日期转换成不带时分秒的日期部分【确认一下用了函数是否不能使用索引】

实现的数据库基础就是把用户表查询出的注册人数存储到统计分析数据表中,手写sql,多个参数要给方法中的参数用@Param注解起名,在mapper.xml中使用该名字

当天的人数还可以统计到redis中,一天结束时设置定时任务进行处理,把数据放入缓存,有三个标准: 1.数据量不大 2.访问频率高 3.数据更改频率低

图表显示统计数据:用图表插件进行显示

流程:统计数据模块调用user模块中的接口统计注册人数,添加到统计表中,统计模块去调用服务的原因一般是统计的数据库和用户服务的数据库不是同一个数据库,统计数据库一般也不能直接访问用户数据库,所以一般通过服务调用去获取数据来生成统计数据

每次生成当天统计数据或者生成某天统计数据先查是否有对应数据,有就删除后添加,没有就直接添加,可以优化,非当天数据直接查,因为有定时任务查,查不到再生成

 

  1. 定时任务

    纳入Spring容器管理,@Scheduled(cron = "0/5 * * * * ?")表示定时任务方法,corn规定定时规则,看着和linux的有点像,这个corn表达式可以在线生成Scheduled(cron = "0/5 * * * * ?")

    流程:

    • 在定时任务启动类上添加@EnableScheduling注解

    • 创建定时任务类,类交给Spring容器管理,用@Scheduled注解标注定时方法,cron属性为对应corn表达式【也称七子表达式、七域表达式【年、周、月、日、小时、分、秒】】,表达式可以借助网站http://cron.qqe2.com/ 生成,SpringBoot的七子表达式默认只支持六位,年默认是当前年,写上第七位运行会报错

    【日期工具类】

     

 

图表数据统计【echarts】

图表显示需要使用的ECharts组件,ECharts是百度的一个项目,后来百度把Echart捐给apache,用于前端图表展示,提供了常规的折线图、 柱状图、 散点图、 饼图、 K线图,用于统计的盒形图,用于地理数据可视化的地图、 热力图、 线图,用于关系数据可视化的关系图、 treemap、 旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图, 仪表盘,并且支持图与图之间的混搭。 官方网站: https://echarts.baidu.com/ ,官网有文档,介绍用法;实例介绍图标类型,点击具体的图可以直接获得图的代码,k线图都有

Echarts本身是一个js文件,需要对该文件进行引入,可以在官网或者github下载,也可以通过npm install echarts --save下载--->下载后通过script标签对js文件进行引入-->在body中为Echarts准备一个具备高宽的DOM容器【div】,通过echarts.init方法初始化一个echarts实例并通过setOption(option)方法生成图表,图表在option中进行定义,通过后端返回的数据赋值给data就能实现数据在途中的展示,

因为data数据是一个json数组格式,后端响应数据给前端也要响应list格式的数据,被自动转换成json数组,注意可以在map中放入list集合,解析成json对象中的json数组

vue中在response中对数据赋值,在response同一个方法的接下来的方法中使用了该赋值,该方法需要放在response的括号中,否则取不到该数据,

Echarts很牛逼,x轴不够长会自动隐藏部分x坐标值,但是会保留数据点

【前端页面】

【后端接口--只写service】

 

 

canal数据同步工具

  1. 应用场景

    • 分库:一般建数据库,统计数据专门存在统计数据库中,用户信息专门存放在用户数据库中,

      • 早期是通过对应的服务去调用相应的数据库,缺点是服务间耦合度高,远程调用效率比较低

      • 目前可以采取实时同步数据库表的方式进行实现,把会员表同步到统计数据库中,就可以实现本地统计,效率更高、耦合更低,canal就是实现数据库同步的工具【由阿里纯java开发,基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持MySQL 】【把远程库中的内容同步到本地库中】

    • 实现过程,把用户数据库中的表创建一份相同表结构的数据到统计数据库中,用户数据库变化,统计数据库中的表会相应的同步变化,然后直接在本地做数据统计工作

  2. 准备工作

    • 准备Linux系统安装MySQL数据库,创建数据库和数据库表;

    • 准备windows系统MySQL,创建数据库和数据库表【名称和表结构要和linux中的相同】;

    • 在linux中使用cannal数据同步工具

      • 第一步:修改linux中即远程Mysql数据库的配置【mysql高级中的读写分离集群配置】

        • canal基于mysql的binlog技术,要开启mysql的binlog写入功能

          • 开启mysql,使用命令show variables like 'log_bin';检查binlog功能是否开启【OFF表示未开启,on表示已开启】

          • 如果没有开启,按以下配置开启binlog功能

        • 在mysql中添加一下相关用户和权限

          mysql本身有一个user表,第一个用户是root、localhost;表示该用户只能通过本地访问,远程访问不到;添加一条用户,把localhost改成%表示可以远程访问,用户改成canal,即开放canal的远程访问权限;用以下语句在linux中添加一下相关用户和权限

          连接完用数据库工具远程连接一下linux数据库

      • 第二步:下载安装linux的Canal服务

        • github下载地址:https://github.com/alibaba/canal/releases ,本项目用的1.1.4.tar.gz

        • 把压缩文件上传到linux中,可以直接上传到/usr/local/canal目录,也可以上传到opt/canal目录下使用cp canal.deployer-1.1.4.tar.gz /usr/local/canal将压缩包拷贝到/usr/local/canal目录下

        • tar zxvf canal.deployer-1.1.4.tar.gz解压压缩文件,解压就能用,可以直接考虑在usr/local/canal目录下解压压缩包

        • vi conf/example/instance.properties修改canal配置文件instance.properties

          修改成自己的数据库信息

        • 进入bin目录下sh bin/startup.sh启动【canal的bin目录下有启动脚本startup.sh】

          • 关闭有stop.sh脚本

          • grep canal查看canal的进程

      • 第三步:打开对应的防火墙端口【开放安全组】

      • 第四步:安装jdk

       

       

  3. 项目整合canal

    • 创建新模块,在模块中引入canal相关的依赖

    • 在数据库中引入对数据源的相关配置,设置服务名和端口号,以及开发环境

    • 创建canal工具类

      真拉,还要自己写工具类

    • 创建启动类

    • 注意事项

      • 这里的canal貌似是利用ip和端口远程访问远程库上面的canal,而且不需要用户名和密码,且固定端口号是11111,那为什么看不到linux系统上canal对应的端口号,

      • 操作的本地库是在本地服务上进行规定的,必须确保数据库名和表名与远程库的一致性【经过测试,数据库不同名也可以,只要表名和表结构相同即可】

 

 

 

GateWay网关

SpringCloud组件,可以实现nginx的相关功能

nginx的作用就是网关的作用,请求先到网关,又网关根据请求统一分配服务器地址【请求转发功能,还可以做负载均衡,权限控制,提供统一的路由方式、基于Filter链提供网关基本的安全、埋点、监控、限流等功能】【nginx又专门的端口】

gateway功能更强大,可以做权限控制【限制访问ip、控制器跨域也可以放在网关中实现】

gatway操作一般要继承nacos进行操作,要通过服务注册中心用网关访问系统中的服务

早期网关用的是网飞的Zuul,后来被功能更强的gateway替代了

客户端直接请求微服务的缺点:

  • 客户端多次请求服务,增加客户端的复杂性

  • 跨域问题再一定场景下很复杂

  • 每个服务都要独立认证

  • 重构复杂【如微服务的拆分和合并】

  • 微服务可能使用了防火墙或者对浏览器不友好的协议,直接访问会有困难

    这些问题都可以使用API网关解决,API 网关是介于客户端和服务器端之间的中间层,所有的外部请 求都会先经过 API 网关这一层,安全、性能、监控可以交由 API 网关来做

网关能辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息。

网关的核心是Filter以及Filter Chain(Filter责任链)

    • 用户通过客户端进行访问-->进入网关首先来到GateWay Handler Mapping【即匹配路径和服务的地址的映射器】-->然后到GateWay Web Handler【处理器将请求分配指定的过滤器链然后发送到指定的服务执行业务逻辑】-->然后到GateWay的一系列过滤器【做权限、跨域等处理】-->真正的服务器

    • 涉及概念

      • 路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配

      • 断言: Java8中的断言函数。 Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。 Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于httprequest中的任何信息,比如请求头和参数等。 【简单来说就是路径的端口的匹配规则】

      • 过滤器:Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理

  1. 网关整合流程

    • 引入相关依赖

    • 创建application.properties

      设置完成edu服务可以直接通过网关的端口号8222进行访问

      【yaml】

       

    • 创建启动类

      需要在服务器中进行注册,要加上@EnableDiscoveryClient注解实现服务注册

  2. GateWay实现负载均衡

    • GateWay自动封装了负载均衡功能,比如两台服务器部署了edu服务,客户端发送请求到网关,网关通过请求路径和nacos注册中心发现了edu服务集群,会自动将请求平均分配到集群的服务器上,如果服务器性能不一样呢?

    • 负载均衡策略【dubbo会讲这个东西】

      • 轮询:第一个第二个第一个第二个...

      • 权重:哪个服务器的权重高先访问

      • 最少请求时间:谁的响应时间最短就先去访问哪个服务器

  3. api网关的各种配置和工具类

    包名可以随便起,但是类名必须保持一致

    • config.CorsConfig

      解决跨域问题,注意网关和控制器中的跨域二者只能出现一个,否则会出问题,可以理解为第一次网关跨域成功,第二次跨域由跨回去了,前端的端口也必须改成Gateway的端口,不要再使用nginx的端口

      前端后台改dev.env.js;前台改request.js;把端口号改成gateway的端口号

    • filter.AuthGlobalFilter

      限定哪些请求可以访问,哪些请求不能访问,请求失败会输出什么结果

    • handler.ErrorHandlerConfig

      覆盖默认的异常处理

    • handler.JsonExceptionHandler

       

 

权限管理模块

需求

管理员只能管理部分模块,如只做讲师管理,或者只做课程管理等等;整体是为用户分配角色,通过角色去操作有操作权限的菜单;为角色分配菜单,为用户分配角色

用户管理:

  • 用户增删改查

  • 用户的角色分配

角色管理:

  • 角色的增删改查

  • 角色权限管理

    • 系统管理员:可以操作所有菜单

    • 讲师管理员:只能操作讲师

菜单管理:

  • 菜单列表:路由存入数据库,从数据库中读取并做显示,把数据封装成路由的数据格式

  • 菜单添加、修改功能

  • 菜单删除功能:删除菜单的同时删除子菜单,递归删除

  1. 权限管理表的关系

    acl开头的表,至少五张表才能把功能做完善

    • acl_permission:菜单表,树形结构存储菜单信息

    • acl_role:角色表,用户管理员,测试

    • acl_user:用户表,

    • acl_role_permission:角色和菜单的关系表

    • acl_user_role:用户和角色的关系表

    菜单表和角色表是多对多的关系:一个菜单可以供多个角色访问,一个角色也可以访问多个菜单

    角色表和用户表也是多对多的关系:一个角色可以由多个用户担任,一个用户也可以担任多个角色

    • 一对多的处理方法是在多的表建一个外键,和1形成对照关系

    • 多对赌的处理方法是在多对多的表之间创建一个中间表,在中间表建立两个表的多对多关系

      • 就是将两个表对应有关系的记录的id存入中间表的同一条记录,比如marry既可以担任讲师管理员又可以担任课程管理员,就将marry和讲师管理员单独生成1条记录,marry和课程管理员又单独生成一条疾苦

 

权限管理典型接口

  1. 查询所有菜单

    dfs查询所有菜单,封装成无限层树形结构,注意在swagger中禁用了admin请求路径,必须注释掉或者修改路径才能使用swagger访问acl下的接口

  2. 递归删除菜单

  3. 添加角色权限

  4. 根据角色id查询出所有菜单并将角色对应菜单的isSelected属性设置为true

 

权限管理系统整合SpringSecurity

Spring Security 基于 Spring 框架,提供了一套Web 应用安全性的解决方案。一般来说, Web 应用的安全性包括用户认证(Authentication)【用户登录时输入账号密码查询数据库验证登录】和用户授权(Authorization)【根据用户角色授权用户的操作权限】 两个部分

SpringSecurity本质上就是过滤器,对请求的路径进行过滤

  • 如果项目基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。【SpringSecurity封装了从session中取数据的过程,如session.getAttribute()】

  • 如果项目基于token,则是解析出token,然后获取用户信息和用户权限信息将当前请求加入到Spring-security管理的权限信息中去

  1. 实现思路

    • 如果系统的模块众多,每个模块都需要就行授权与认证,

      • 所以选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,

      • 用户登录后,以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,

      • 每次调用api接口都默认将token携带到header请求头中, Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问

    • 流程

      • 用户登录,输入用户名和密码进行验证同时查询登录用户权限列表

      • 将用户名作为key,用户权限列表作为value存入redis

      • 生成jwttoken,将token传递到前端,放入cookie,cookie在发起请求时通过请求拦截器放入请求头

      • 每次发起请求,SpringSecurity都是获取token,解析token获取用户名,根据用户名从redis中获取用户权限,SpringSecurity判断当前请求是否有访问权限,即访问权限是SpringSecurity进行判断的

 

整合权限管理模块到edu-acl服务

  1. 整合流程

    • common下创建SpringSecurity模块

    • 引入相关依赖

  2. 代码结构

    代码结构基本是固定的,直接照猫画虎整合,以后在学习SpringSecurity的相关内容

    很多工作都被SpringSecurity封装了,用户需要决定token如何加密,redis中存储什么信息,打印什么信息;细节得看SpringSecurity的课程,这里讲的很浅显

    • 搭建SpringSecurity模块

      • 一个核心配置类

      • 两个用户授权和认证的实体类

      • 两个授权和认证过滤器

      • 四个工具类:包括密码处理器【密码加密】、退出处理器、token操作工具类、未授权统一处理类【没有权限返回什么值】

    • 在权限管理中整合SpringSecurity模块

      • 在service_acl中引入SpringSecurity模块的依赖

      • 在service_acl模块中创建UserDetailsService的实现类【这个类的作用是查询登录和用户权限的类,实现了在security模块中SpringSecurity的UserDetailsService接口】

         

整合前端权限管理部分

系统包括SpringSecurity的代码中也设置了admin是超级管理员,拥有所有权限,在数据库角色表中也做了设定,项目初始时用admin进行登录,加用户,加角色,加权限

  1. 在后台管理系统重的node_modules中把element-ui删掉,再复制过去

    使用课上提供的element-ui依赖会导致菜单管理列表出问题,这里使用以下代码更换element-ui版本,换完以后确实好了

    执行下面的代码,更换其版本。先卸载:npm uninstall element-ui -S 再安装 npm install element-ui -S

  2. 替换后台管理系统代码

    拷贝18天中的前端代码,拷贝的都是没写过的页面,问题不大

    • 替换api中的login.js并拷贝acl文件【login.js中的api改成了security下的文件】,之前的login是edu中做的临时登录

    • 路由改成查数据库并显示的新路由写法

    • store目录加了一些js文件

    • utils

    • main.js和permission.js文件需要替换

    • views中添加一些页面

  3. npm install --save vuex-persistedstate安装vuex-persistedstate,该依赖是为了后台系统做登录持久化存储数据使用

  4. 修改文件两个地方

    • router的index.js的页面跳转地址改成自己项目中的地址【path和import属性】

    • 修改数据库菜单表路径和页面地址【从讲师管理开始更改菜单相关的信息以符合前端页面,path不需要加@/views,已经被封装好了】

  5. 确认后台系统的地址是网关的地址

  6. 后台系统登录后运行流程

    • 首先进入到SpringSecurity的TokenLoginFilter【登录过滤器】的attemptAuthentication方法,得到用户输入的用户名和密码,然后执行acl模块下的UserDetailsServiceImpl中的loadUsername方法,用用户名查询用户信息和用户权限,封装用户权限到SecurityUser类并返回用户权限,查询到用户信息后会进入DefaultPasswordEncoder的matches方法比对密码, 密码比对成功后进入下面的successfulAuthentication方法,然后执行登录成功的方法,在该方法中根据用户名生成token字符串,然后把用户名和用户权限放入redis中,通过responseData的data属性返回token;往后的操作都是SpringSecurity做到的,从redis中根据用户名获取用户的权限,然后给用户赋值权限做到的,登录一次后,每次请求都走上诉过程并到TokenAuthentixationFilter【授权过滤器中】类中的getAuthentication方法获取请求头中的token,获取用户名,从redis中获取用户权限并赋值给用户

      【用户权限数据】

    • 没有对应后台权限的用户的后台系统是没有相应路由菜单的

    • 有很严重的bug

      bug1,对角色修改权限时会导致角色已经有的权限再次在数据库中被添加,导致使用该角色登录时无法查看菜单栏【我确实出现了修改看数据有没有回显的情况,也确实出现了菜单无法显示的情况,还有登录后会直接访问退出登录的页面,导致用户没有相应权限前端页面会直接无法响应,其实就是被SpringSecurity拦截了】

 

 

 

 

Nacos分布式配置中心

服务器集群部署,如果一个服务有多态服务器,配置文件发生变更,所有相同服务的配置文件都需要进行修改,很麻烦

SpringCloud提供配置中新SpringCloudConfig,但是不好用,后来替换成了nacos

  1. 配置中心SpringCloudConfig

    • Spring Cloud Config 为分布式系统的外部配置提供了服务端和客户端的支持方案。在配置的服务端您可以在所有环境中为应用程序管理外部属性的中心位置。客户端和服务端概念上的Spring Environment和 PropertySource 抽象保持同步, 它们非常适合Spring应用程序,但是可以与任何语言中运行的应用程序一起使用。

      • 通俗的讲,一个服务集群式部署,当涉及到的数据库地址发生变化,所有的配置文件都需要改,还可能涉及改错忘了改的问题

      • 整个专门作为配置中心的服务,专门存放服务的配置文件,让各个服务都去读取配置中心的对应文件,修改配置文件只需要改配置中心中的文件【nacos也可以有集群】

      • Spring Cloud Config 包含了Client和Server两个部分, server提供配置文件的存储、以接口的形式将配置文件的内容提供出去, client通过接口获取数据、并依据此数据初始化自己的应用。 Springcloud使用git或svn【也是一个版本控制工具】存放配置文件,默认情况下使用git。

      • Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, SpringCloud Config。 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。

        • 实现该功能需要以下几项依赖

        • nacos配置中心的功能

          • 调整系统运行时更改配置文件是有效手段。如果微服务架构中没有使用统一配置中心时,所存在的问题: - 配置文件分散在各个项目里,不方便维护 - 配置内容安全与权限 - 更新配置后,项目需要重启

          • nacos配置中心:系统配置的集中管理(编辑、存储、分发)、动态更新不重启、回滚配置(变更管理、 历史版本管理、变更审计)等所有与配置相关的活动。

  2. nacos配置中心配置流程

    • SpringBoot配置文件加载顺序

      • 优先加载bootstrap.properties文件,该文件配置的数据一般是系统级别的配置,这些参数一般是不会变动的。

        bootstrap.properties 用于应用程序上下文的引导阶段。bootstrap.properties 由父Spring ApplicationContext加载。父ApplicationContext 被加载到使用 application.properties 的之前

      • 然后加载application.properties,用来定义应用级别的。

    • nacos添加配置

      • Data ID的命名格式:${prefix}-${spring.profiles.active}.${file-extension}

        • ${prefix}就是bootstrap文件中配置的服务名

        • ${spring.profile.active}是bootstrap文件中配置的spring.profiles.active ,可以为dev、prod、test

          • 当spring.profiles.active属性没有配置时-${spring.profile.active}省略不写

          • 注意当bootstrap加了spring.profiles.active=dev,原来的配置文件就不能用了,会自动取nacos上匹配完整命名格式的文件即${prefix}-dev.${file-extension}

        • ${file-extension}是配置内容的数据格式,nacos支持properties 和 yaml 类型。

      • 配置流程

        • 点击配置列表,点击添加,根据命名规范命好名,把原配置文件拷贝进来,把原来配置文件改成bootstrap.properties,

          配置列表添加好了名字就不能改了

          【bootstrap配置】

    • 在项目中读取nacos配置中心的文件

      • 注释掉原来的Application.properties配置,但是可以有该文件,创建bootstrap.properties,在该文件中配置配置中心地址【ip:端口】,服务名字【该配置会统一影响配置中心的dataId,spring.profile.active=dev可以不写】

      • 在调用服务中引入nacos-config依赖

        高版本SpringBoot还需要额外引入bootstrap

        可以通过查看端口号来判断那个配置文件被采纳了

       

  3. Nacos名称空间切换环境

    • 在配置列表上有个public,这就是一个名称空间,可以简单理解为一个包

    • 项目中的几种环境

      不同环境的配置可能不同,比如开发就用自己的计算机,测试会上测试环境,有各种的压力测试,测试没问题才会上线运行,涉及到很多环境的配置;生产环境是用户真正使用的环境

      这些环境在nacos中用命名空间统一描述,nacos中的默认命名空间为public

      • dev:开发环境

      • test:测试环境

      • prod:生产环境

    • 创建其他三种命名空间

      • 在命名空间新建其他三种命名空间

        创建好以后配置列表会显示刚创建的几个命名空间

      • 在命名空间中可以点击加号创建配置文件,也可以使用克隆的方式创建配置文件,选中配置文件点击克隆后弹窗选择要克隆到的命名空间,然后点击开始克隆,克隆不会导致原始文件消失

      • 需要使用自定义命名空间的配置文件需要再bootstrap中配置属性spring.cloud.nacos.config.namespace=命名空间的nacespace值【表示根据名称空间做切换】

     

  4. Nacos配置中心加载多个配置文件

    端口号可以写在一个文件中,数据库配置可以写在一个单独文件中,redis也可以写在一个单独的配置文件中,通过Nacos读取多个配置文件中的内容

    • 新添port.properties和redis.properties到dev命名空间

    • 在bootstrap中对上述两个配置文件进行追加,其他不要动,配置文件的值一变化,后端就会有反应,如改了端口,不需要重新启动

       

       

 

项目提交到Git

码云:公有仓库不限制人数,私有仓库免费限制五人

  1. 创建一个项目仓库

  2. 管理中仓库名字可以改,管理中成员管理可以添加项目管理人员

  3. 本地代码提交到git

    • 电脑上必须安装git,idea上必须配置git【即把path设置成git.exe的路径】

    • 第二步:在idea上点击VCS创建本地仓库,选择本地仓库的位置,一般选择当前项目为本地仓库,也可以选择别的地方作为本地仓库,此时项目中的文件都变成红色的

    • 第三步:右键项目选择git进行add,添加代码到本地库,没有创建本地库右键项目是不会有git选项的

    • 第四步:在idea上点击git下的repository下的remote设置远程仓库的地址并点击ok

    • 第五步:本地库内容提交到远程库点击git然后commit【提交信息必须填写,不填会提交失败】

  4. git远程库的密码更改以后可以到windows凭据中修改本地存储的远程库的密码

  5. 让本地代码和远程库断开连接

    • setting中VC移除项目目录【相当于从本地库移除项目】

    • 找到项目仓库文件夹,删掉.git文件

 

Jenkins持续化部署工具

用maven把项目编译打包,然后部署到windows或者linux系统上,用java命令运行起来,服务就可以进行访问,这个过程需要手动打包,手动部署和运行

用Jenkins可以写一个脚本就可以实现自动化打包部署运行,jenkins安装比较麻烦

  1. 手动打包部署运行过程

    • maven命令打包

      • 在项目目录中打开cmd窗口使用命令mvn clean package,需要安装maven且配置好环境变量,SpringBoot通过main方法运行,打成的是jar包

      • 打包完成后的jar包会自动放在项目中的target目录下,jar包的运行命令为java -jar XXX.jar【当前目录下】

      • 课程演示的是单体项目打包,并没有演示分布式项目的打包,这里根据博客内容成功实现各个子模块的打包,后续上服务器和部署也可以参考一下,博客连接:在线教育项目后端部署_谷粒学院打包部署-CSDN博客

  2. Jenkins安装

    • 安装Jenkins和前置工具

      • Java环境:linux系统上安装jdk,配置好环境

      • 安装maven环境

      • 安装Git

      • 安装Docker

        Docker是容器虚拟化技术,

        注意docker的使用和redis一样需要使用命令service docker start开启Docker服务

      • 安装Jenkins

        • Jenkins有很多安装方式,最方便的是使用war包进行安装,war包就是一个web项目,通过浏览器能进行访问,war包放在tomcat中能直接运行

          • jenkins的jar包不要用最新版的,最新版不支持java8;jar包也不要用太旧的,太旧有些插件不支持,使用Jenkins 2.346.1,按照课程的操作插件可以全部下载成功

        • 下载Jenkins的war包传到Linux中直接用命令nohup java -jar /usr/local/jenkins/jenkins.war >/usr/local/jenkins/jenkins.out &就能启动,访问http://ip:8080就能直接访问

          • java -jar /usr/local/jenkins/jenkins.war是命令的核心部分,war包目录要和命令一致

          • /usr/local/jenkins/jenkins.out是日志输出的目录和日志名称,日志输出到jenkins.out文件

          • nohup是命令前缀,表示后台静默启动,前台不会看见日志,日志会被输出到日志文件中

          • &是命令后缀,表示该进程是守护线程

  3. Jenkins配置

    • Jenkins访问会要求输入密码来解锁jenkins,目的是确保管理员安装了Jenkins,到指定文件中能找到密码

      • cat和tail都能查文件命令,使用命令cat /root/.jenkins/secrets/initialAdminPassword就能查看到密码

      • 复制密码输入后进入Jenkins

    • Jenkins安装插件

      • 注意此时点击安装插件会连接国外的网,点了就要重装【下载不下来甚至下载失败】,需要把镜像改成国内的,配置镜像也只能解锁Jenkins才能配置

      • 配置国内镜像

        先杀死Jenkins的进程,需要时间,多查看几次知道没有正在杀死提示

        • 以下是针对Jenkins版本有updates目录的情况下的更换镜像的方法

          • cd {你的Jenkins工作目录【就是显示密码的哪个目录/root/.jenkins】}/updates #进入更新配置位置,default.json就是配置下载地址的目录,

          • 使用命令sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json这是直接修改的配置文件把国外地址设置成国内镜像,如果前边Jenkins用sudo启动的话,那么这里的两个sed前均需要加上sudo【不是超级管理员需要加上sudo】,没有任何消息就说明成功了。

         

        • 以下是针对Jenkins没有updates目录,只有hudson.model.UpdateCenter.xml文件的情况下

          • 更改/root/.jenkins/hudson.model.UpdateCenter.xml文件配置为

        • 如果还是启动不起来,那就是Jenkins选择了ping Google看网络是否通畅,直接

          • 如果第一步没问题不需要执行以下操作,避免系统出现其他问题,经确认同时完成第一和第二步的情况下可以启动,但是因为第一步出错了,所以没有测试过只有第一步情况下能否成功

          • 使用vim /etc/host命令修改linux系统配置,在文件最后添加以下内容让Jenkins访问Google访问本地,

          • 改完以后重启Jenkins即可正常访问Jenkins并安装插件

          • 如果此时访问可能会出现Jenkins需要输入账户密码的情况,可以通过修改账户密码并进入界面,但是之前没有安装插件再次进去也无法安装插件,此时只能卸载干净jenkins并且重装再走一遍流程,推荐2.346.1版本,问题少

      • 使用命令nohup java -jar /usr/local/jenkins/jenkins.war >/usr/local/jenkins/jenkins.out &重启Jenkins,在浏览器点击安装推荐插件安装插件即可,大概有二十个必须插件 ;

    • jenkins卸载

      Jenkins的卸载卸载不干净会出现巨多bug,linux下的war包卸载流程为

      • rm -rf /root/.jenkins/ 删除之前的配置文件 这个一定要删除,不然重装会直接让输入密码,而且新旧配置文件会叠加在一起

      • 删除日志文件和之前上传的war

      • 删除日志文件和之前上传的war

    • Jenkins服务器修改账户密码

      • war包去vim /root/.jenkins/users/admin_8245939347250595393/config.xml文件修改以下内容

      • 修改后重启jenkins,此时用户名重置为admin,密码重置为111111

附录

  1. mysql5的url不需要带服务器时区,驱动中无需加cj;mysql8的url需要带时区且url中需要带时区,且springBoot需要2.1以上

    • serverTimezone=GMT%2B8作用是设置服务器时区为东八区,用于服务器处理时间相关的操作,如记录日志、生成时间戳等

     

  2. mybatis-plus怎么更改默认访问user表为访问t_user表

  3. springboot开启mybatis日志

    • 能在控制台显示sql语句和查询结果

  4. mybatis-plus操作

    • 查询所有

    • 添加一条记录

      • mp会自动生成19位id主键值,不需要手动设置

      • 主键策略,详细见谷粒学院文档mp简介,包扩

        • 默认主键策略是ID_WORKER、生成全局唯一ID

        • 自增主键【表要设置自增主键,且实体类对应字段需要配置@TableId(type = IdType.AUTO) 注解】

        • UUID生成唯一的随机值,但是排序不方便

        • 用redis生成主键,用redis原子操作incr和incrby实现,每天从0点开始的流水号也可以用“日期+当日自增长号”实现,优点灵活数字天然排序,对分页和需要排序的情况友好,缺点是没有redis需要引入redis,增加系统复杂度和编码量

        • mp自带策略是推特的雪花算法【snowflake:分布式ID生成算法,生成long类型的ID,使用41bit作毫秒数,10bit作机器ID,包括5位表示数据中心,五位表示机器码,12bit作为毫秒内的流水号,每毫秒可以产生4096个ID,1个永远为0的符号位?这不是和19位冲突】

          • 自增主键就是上述情况设置为AUTO、ID_WORKER生成19位值,为数字类型专用;ID_WORKER_STR生成19位值,为字符串类型专用、INPUT表示自定义主键值,通过自己注册自动填充插件进行填充,NONE表示跟随全局,约等于input、UUID表示生成随机唯一值

          • 不设置type值就会使用雪花算法自动生成,雪花算法生成的是64位主键

        • 注意ID_WORKER、UUID、ID_WORKER_STR自由当插入对象的ID为空时才会自动填充

    • 根据id更新数据

      • 需要设置user的id值,只会更新user中和表中相同id数据不同的字段

    • 自动填充

      • 一般数据表中会有记录创建时间和记录更新时间,正常情况下需要自己设置创建时间和修改时间,比较麻烦,而且创建时间还需要对数据库记录进行逻辑判断,很不方便;借助mp的源对象处理器接口MetaObjectHandler和@TableField注解实现自动填充功能,注解的fill属性为FieldFill.insert表示第一次插入时自动填充;fill属性为FieldFill.insert_update表示第一次插入和修改都自动填充,update表示仅当更新填充字段,default表示默认不进行处理

      • 第一步在需要自动填充的字段上加注解

      • 第二步编写自定义源对象处理器实现源对象处理器接口

        • 在insertfill方法中通过setFieldValByName方法指定添加记录时对应的填充字段,填充内容

        • 在updatefill方法中通过setFieldValByName方法指定更新记录时对应的填充字段,填充内容

    • mp简单查询

      • 根据id进行批量查询

        • 多个id组织成list

      • 用Map封装查询条件

        • map中存放的是不同的条件,最终效果是返回满足所有条件的交集的所有记录

        • 注意map中的key对应的是数据库中的字段名,user_id在实体类中对应userId,但在map中仍然填写字段名user_id

      • 使用mp自带分页插件分页查询

    • 根据id删除记录

      • 物理删除

      • 批量物理删除

      • mp实现逻辑删除[标记为删除]

        第一步:添加deleted字段:ALTER TABLE 'user' ADD COLUMN 'deleted' boolean

        第二步:配置逻辑删除插件ISqlInjector

        第三步:实体类的deleted字段上添加@TableLogic注解以及@TableField(fill=FieldFill.INSERT注解【?这需要设置,数据库表为什么不能设置默认值?讲师模块就没有设置】)

        第四步:插入时设置添加自动填充初始值

        第五步:在配置文件中添加逻辑删除deleted的值的含义

        注意:逻辑删除会导致物理删除的方法失效,转而执行更新语句将deleted值设置为1,查询操作也会忽略这些deleted被更新为1的字段

    • 条件查询

  5. 乐观锁

    • 解决丢失更新的问题,多人同时修改同一条记录,最后提交更新的数据把之前提交更新的数据全部覆盖了,导致之前的数据库操作丢失失效,解决方式可以采用串行的悲观锁,即拿到锁才能读;或者乐观锁方式,更新数据的版本与读取数据的版本一致才能提交事务,更新后数据版本加1

    • mp实现字段乐观锁

      • 在实体类中添加@Version注解并用自动填充功能设置版本的默认值为1

      • 配置乐观锁插件,即向IoC容器中注入OptimisticLockerInterceptor组件

        注意乐观锁只支持int、Integer、long、Long、Date、Timestamp、LocalDateTime类型数据,整形下新版本等于旧版本号+1,newVersion会回写到实体类对象的对应version属性中,更新版本号只支持updateById和update(entity,wrapper)两个方法

        这种方式必须要先查记录再更改记录对应的版本才会自加1,否则只有更新操作没有查询动作版本号是不会变化的

  6. 用指令向表中添加version字段

  7. list集合遍历的另一种写法

  8. Mp中的分页插件

    • 在MyBatis-plus配置类中注入PaginationInterceptor【页面拦截器】,作用与pageHelper类似

    • 编写分页代码

  9. mp的性能分析插件PerformanceInterceptor

    开发环境使用,线上不推荐,一共有三种环境,dev【开发】、test【测试】、prod【生产】用@Profile({"dev","test"})注解设置dev test环境开启

    • 输出每条SQL和其执行时间,超过指定时间停止SQL的执行,便于发现问题,执行超时会直接抛异常

    • 参数:maxTime:设置SQL执行的是最大时长,单位毫秒

      format:SQL是否格式化,默认为false

    第一步:向IoC容器中注入性能分析插件

    第二步:配置springBoot配置文件

  10. wapper条件构造抽象类,顶级父类

  1. java注释

    • TODO、FIXME、XXX

      • //TODO: 表示待实现的功能

      • //FIXME: 代码存在Bug,不能Run或运行结果不正确,需要修复

      • //XXX: 勉强可以工作,但是实现的方法不一定很好

  2. jsonView浏览器插件可以很好的看返回浏览器的json数据

  3. 403错误一般发生在跨域和路径写错的情况下

  4. el-table不要把所有字段的宽度限定死,所有字段都限定死,会导致表格右边出现白框

  5. 一定一定要注意,当修改了模块名称后以及包路径后,一定要将.idea中的workspace文件中的包路径和 service_sms.iml 文件中的包路径修改正确,否则后续代码的任何编译都不会更新到当前项目下,还是更新在老包路径下,且直到现在springBoot配置文件仍然不会自动打包,需要手动复制

  6. sms:short message service

  7. 留意RFC、IETF、Flickr、Dropbox

  8. @PathVariable注解無法將前台传入的String类型自动转换成Long类型,只能自动转换成long类型

  9. VUE的异步调用

    在页面加载完成后调用,且只会被调用一次,这是调用了一次前台讲师列表分页查询

  10. javascript:void(0);取消超链接的跳转行为,转而可以执行点击事件的方法

  11. 图片样式中的height和图层高度的height可以设置成一样就能填满图层

  12. 设置linux环境变量命令vim /etc/profile;shift+g切换至文本最后一行

  13. 安装Jenkins遇到的问题

    • 首次安装由于版本问题,修改镜像没有updates目录,搜索以后更改配置文件配置国内镜像源,结果报错无法登录,查了以后更改系统google地址,将jenkins尝试ping通google指向本机,登录成功后发现需要输入账号密码,以为系统出问题了;下载了一个有updates目录的war包,结果根目录的残留文件没有删除干净导致新旧目录叠加,直接导致新项目需要输入账户密码才能登录,实际并没有设置账户密码,又查资料,找到账户密码设置文件重置了账户密码,改完登录进去以后发现插件无法下载,经过查找资料找到完整卸载jenkins的方法,完整卸载以后重装以后恢复正常,但是部分插件还是无法下载,最后打动

 

优化

  1. 通过excel表格添加一级目录和二级目录过程中需要检查数据是否存在于数据库中,不必每条记录都去查数据库,使用HashMap对查询记录缓存,没有再去查询数据库并对记录进行缓存【查询到不存数据并对查询数据进行缓存,查不到则保存记录并对当前保存记录进行缓存】

  2. 在对课程一二级分类封装的过程中,不要对每个一级分类进行数据库查询,而采取一级分类一次查询,二级分类一次查询;用两层for循环嵌套对一二级信息进行封装,但是考虑到时间复杂度比较高,特别是含有三级分类的过程,采用HashMap缓存一级分类课程的id和二级分类列表,根据二级分类的父id一次遍历存入不同的列表,最后统一对HashMap遍历并赋值给一级分类的children属性

  3. web提交的数据涉及多个数据库表,创建一个vo类统一封装web提交信息,后续处理成对应数据库表的entity类并进行数据库操作

  4. 在课程信息内容编辑到一半时重新点击添加课程,执行初始化方法init()清空数据对象内容,富文本编辑器的内容并没有被清空,在社区查询之后了解到要手动使用代码来对编辑器的内容进行清空

    【社区链接:javascript - Tinymce content clear mceCleanup - Stack Overflow

  5. p标签的样式图层会浮动在span的上面,导致span的按钮无法被点击,也就没有办法触发单击事件,这时候的解决办法是通过样式设置p和span标签的图层位置为相对,将span图层的优先级z-index设置为1,让span图层置于所有图层的最上方,直接注释掉float也是可以的,但是这样会导致页面布局混乱,细节看关于float属性导致button按钮无法点击问题的解决思路_明天天明~的博客-CSDN博客

  6. //添加新章节或者修改新章节弹出对话框并将确认按钮重置,在处理完就重置会出现连点两次导致章节信息清空的bug

  7. 章节页面点击编辑后点击取消原文档没有做chapter的清除工作,只是简单的关闭了对话框,给取消绑定对话框处理完成后的收尾方法

  8. 需要对课程列表进行优化,手动写sql语句解析查询条件多表连接查询并显示课程简介和讲师姓名

  9. 课程分类列表没有修改功能

  10. 章节管理需要一个一个excel表格批量导入功能

  11. 编辑章节信息点击上一步到课程基本信息,此时再次点击添加课程添加新课程仍然会修改编辑章节信息的课程

    已经解决,常量的id单独设置为空串

  12. 有个很严重的bug,用户上传视频到完成这段时间,保存小节信息的按钮处于可以点击的状态,没有时间点来判断视频开始上传和结束,会导致还没获取到videoId就对小节数据更新,导致视频成功上传但是数据库中找不到视频信息,解决办法可以在视频上传成功后单独执行一次小节数据更新【这个解决办法不行,上传过程直接关闭窗口会直接导致上传成功的后续代码不会执行而报执行异常,暂时找不到好的解决办法】

  13. ESLint校验Vue项目最后一行有个空行校验,可以加个空行或者直接在.eslintrc.js取消最后空行校验'eol-last': 0

  14. @ApiParam可能会导致@PathVariable注解获取变量时不能获取变量名,而直接获取变量名,导致id获取不到,可以直接把@ApiParam删掉,也可以尝试把@PathVariable的value属性设置成和@ApiParam的name属性一致

     

 

问题

  1. 有一个小问题,点击同一个路由,界面内容不会清空,这个问题暂时没有提及,讲师列表界面还有这个问题

  2. 没有章节信息,仍然可以创建成功还可以在数据库存入数据,也需要优化进行判定,比如没有课程名称或讲师就不能添加,使用js中的事件对部分信息进行限制

  3. 留意一下,跳转到新页面时条件判断【即不满足跳转条件的时候怎么办】

 

 

 

面试准备

  1. 遇到问题,设置服务心跳时间间隔大于Eureka服务端心跳等待上限,无法实现负载均衡;Eureka服务器集群存在没有相互注册的情况,也无法实现负载均衡

  2. 兜底方法设置

    这是在SpringCloud里面发现的,套到兜底方法中去

    • controller中超时时间配置不生效原因: 关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了。 改变这个默认超时时间方法: hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 然后ribbon的超时时间也需加上 ribbon: ReadTimeout: 5000 ConnectTimeout: 5000

      配置文件这里的 timeoutInMilliseconds 并不是覆盖注解中的设置,而是两者取较低值, 同时也会算上 ribbon: ReadTimeout 的值,也就是三者取最低值。

    •  

项目功能

 

项目技术点

 

 

 

🗲