做新闻类网站,专业企专业企业网站设计,个人设计作品集,专业的做网站的《学成在线》项目实操笔记系列【上】#xff0c;跟视频的每一P对应#xff0c;全系列12万字#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳#xff0c;参考这篇#xff0c;相信会带给你极大启发。同时也欢迎大家提问与讨论#xff0c;我会尽力帮大家解…《学成在线》项目实操笔记系列【上】跟视频的每一P对应全系列12万字涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳参考这篇相信会带给你极大启发。同时也欢迎大家提问与讨论我会尽力帮大家解答。
一、前期准备
1.1 项目介绍 P2
To C面向个人B2B2C第1个B是指商品或服务的供应商第2个B是指从事电子商务的企业C是消费者。例子腾讯课堂第1个B是腾讯公司第2个B是入驻授课的企业C是用户学习课程
本项目含有3个端用户端机构端运营端 1.2 说自己项目 P3
从以下几个方面进行项目介绍
1.项目的背景包括是自研还是外包什么业务服务的客户群是谁谁去运营等问题。
2.项目的业务流程核心的业务流程
3.项目的功能模块核心模块一定要说
4.项目的技术架构
5.个人工作职责说得详细一些
6.个人负责模块的详细说明包括模块设计用到的技术技术的实现方案等找最熟悉的模块进行说明。
项目基本介绍是公司自研的专门针对成人职业技能教育的网络课堂系统网站提供了成人职业技能培训的相关课程如软件开发培训。基于B2B2C的业务模式。培训机构可以在平台入驻、发布课程我们公司作为运营方由专门的人员对发布的课程进行审核审核通过后课程才可以发布成功课程包括免费和收费两种形式对于免费课程用户可以直接选课学习对于收费课程要在选课后支付成功才可以继续学习。
本项目包括3个端用户端、机构端、运营端。
核心模块内容管理、媒资管理、课程搜索、订单支付、选课管理、认证授权等。
本项目采用前后端分离架构后端采用SpringBoot、SpringCloud技术栈开发数据库使用了Mysql还使用了Redis、消息队列、分布式文件系统、Elasticsearch等中间件系统要清楚这些中间件在系统中是如何使用的在哪里使用的。
划分的微服务包括内容管理服务、媒资管理服务、搜索服务、订单支付服务、学习中心服务、系统管理服务、认证授权服务、网关服务、注册中心服务、配置中心服务等。
我在这个项目中负责了内容管理、媒资管理、订单支付模块的设计与开发。
个人负责模块的详细说明内容管理模块是对平台上的课程进行管理。设计了课程基本信息表、课程营销表、课程计划、课程师资表。培训机构要发布一门课程需要填写课程基本信息、课程营销信息、课程计划信息、课程师资信息填写完毕后要提交审核由运营人员进行课程信息的审核整个审核过程是程序自动审核加入人工确认的方式通常24小时完成审核。课程审核通过即可发布课程课程的相关信息会聚合到课程发布表中这里不仅要将课程信息写到课程发布表还要将课程信息写到索引库、分布式文件系统中所以这里存在分布式事务的问题项目使用本地消息表加任务调度的方式去解决这里的分布式事务保证数据的最终一致性。
1.3 技术架构 P5
业务解决了什么问题为用户提供了什么样的服务。 技术栈 1.4 环境配置 P6
配置环境版本如下 IDEA基本配置如下 Maven配置
先把maven解压然后把maven仓库解压。 在maven的setting.xml文件中进行配置 mirroridalimaven/idnamealiyun maven/nameurlhttp://maven.aliyun.com/nexus/content/groups/public/urlblockedcentral/blocked
/mirror
在IDEA中进行配置
虚拟机配置
解压虚拟机 双击虚拟机 点击虚拟网络编辑器 把VMnet8的子网地址改为192.168.101.0 虚拟机的用户名root密码centos
虚拟机ip192.168.101.65 启动docker
systemctl start docker
运行所有的软件
sh /data/soft/restart.sh 查询docker容器docker ps
docker ps 数据库配置
我是先用Navicat连接上虚拟机中的mysql Git配置
下载完Git然后配置到IDEA上 搭建Gogs
Gogs是一个轻量级的远程仓库是在虚拟机里面的本项目使用Gogs作为Git远程仓库。进入Gogs
http://192.168.101.65:10880 账号gogs密码gogs
关联远程仓库
我这里项目已经创建好了我想要关联远程仓库就不按照视频的方法进行。
点击VCS-Create Git Repositoty选择当前项目的文件夹作为仓库全选当前项目中所有文件输入文字然后点击commit。就可以把项目上传到本地仓库。 然后点击向上的按钮点击Define remote会跳出一个弹窗弹窗的地址填写下面网页中的地址 我直接使用现成的仓库复制下面的HTTP地址粘贴到上面弹窗中然后会有输入账号密码的弹窗填gogs的账号密码 然后就会出现项目内容 如果忘记gogs密码
可以在用户设置处更改密码也可以在管理面板用户管理编辑处让管理员指定密码 如何还是提示密码错误可以在IDEA中把文件名改掉改成不存在的会提示输入密码也可以选不保存忘记密码 配置.gitignore文件
把下面文件夹中的内容复制到IDEA的.gitignore文件中。 选中.gitignore先commit然后push到远程仓库。 关于分支
老师每一天的授课内容都会创建一个分支。如果想看看第1天的代码就可以切换到第1天的分支然后该分支只会显示第1天的代码。
分支在Git中相当于一个独立的工作流每个分支都可以有不同的提交历史和代码改动。
可以通过在Git界面右键分支点击Checkout来切换分支。 1.5 创建工程 P7
父工程职责把所有依赖的版本确定下来模块的聚合作用。
基础工程基础的代码。所有的微服务依赖于基础工程。
首先创建名为xuecheng-plus-project的工程把src文件删掉然后创建xuecheng-plus-parent的模块。 项目结构大概如下然后把第1章里的pom.xml代码复制到父工程的pom.xml文件中 首先用properties标签把所有依赖的版本确定然后dependencyManagement标签来引入对应的依赖。 下面创建xuecheng-plus-base模块这个模块和parent模块是并列关系。base模块里除了src和pom.xml文件外其它东西删掉包括一些启动类和配置项都要删除只留下基本结构。 所有模块都是直接或间接继承父模块所以要把父工程的坐标下面红框里复制然后粘贴到base模块的parent标签下 然后把下面文件中base模块pom.xml中的dependencies标签下的内容单独复制粘贴替换base模块的pom.xml中的dependencies下的内容。 1.6 Git面试 P8
可以在Git面板看到所有commit提交到本地仓库的版本。小铅笔所在的就是当前的分支。 面试题1Git代码冲突怎么处理 冲突的原因本地文件的版本浴目标分支中文件的版本不一致时当存在同一行的内容不同时在进行合并时会出现冲突。
场景多个分支向主分支合并时A同事和B同事开发过程中对同一文件的同一行内容进行修改。同一个分支下pull或push操作时。
在IDEA里commit是提交到本地仓库是本地机上的一个目录。push是把本地仓库提交到远程仓库。
可以通过图形界面修改 通过代码行修改方式如下 然后要add将文件添加到暂存区commit将文件提交最后push提交到远程仓库。
面试题2你是在哪个分支开发
我们不是直接在主分支开发由技术经理创建独立的开发分支我们是在独立的开发分支中进行开发最后由技术经理将开发分支合并到主分支技术经理对代码进行审查最终合并到主分支。
1.7 Maven面试 P9
面试题1Maven指令的作用
mvn clean 清除target目录中的生成结果
mvn compile 编译源代码生成target目录然后会有classes文件 mvn test 执行单元测试
mvn package 打包打成的jar包会放在target目录
mvn install 打包并把打好的包上传到本地仓库
mvn deploy 打包并把打好的包上传到远程仓库
面试题2Maven依赖版本冲突怎么处理
maven的依赖版本冲突一般是由于间接依赖导致一个jar包有多个不同的版本。比如A依赖了B的1.0版本C依赖了B的2.0版本项目依赖A和C从而间接依赖了B的1.0和2.0版本此时B有两个版本引入到了项目中可能会出现ClassNotFoundException和NoSuchMethodError等错误。
处理版本冲突可以使用以下方法
1.使用exclusions排除依赖
比如我们只依赖B的1.0版本此时可以在依赖C时排除对B的依赖。 2.使用dependencyManagement锁定版本号
通常在父工程对依赖的版本同一管理
比如我们只依赖B的1.0版本此时可以在父工程中限定B的版本为1.0
1.8 数据库环境 P10
数据库用的是虚拟docker容器里的数据库。
数据库用户名root密码mysql。
输入下面启动运行
systemctl start docker
sh /data/soft/restart.sh我是在Navicat中对虚拟机中MySQL的连接下新建了一个数据库 右键表然后点击运行sql文件选择下面的xcplus_content.sql这个文件打开最后可以看到表都加载到了数据库中 1.9 存储引擎及区别 P11
1.InnoDBInnoDB用于事务处理具有ACID事务支持等特性如果要执行大量insert和update操作应该选择这个支持事务。使用的锁颗粒度默认为行级锁可以支持更高的并发也可以支持表锁。支持外键约束外键约束降低了表的查询速度增加了表之间的耦合度。
2.MyISAM管理非事务表提供高速存储和检索以及全文搜索能力不提供事务支持。只支持表级锁。不支持外键。
3.memory数据存储在内存中。
1.10 MySQL建表注意 P12
注意选择存储引擎如果要支持事务需要选择InnoDB。
日期类型如果要记录时分秒选择datetime只记录年月日使用date固定长度字符选择char不固定长度字符varcharvarchar比char节省空间但速度没有char快对于内容介绍类的长广文本字段使用text或longtext类型如果存储图片等二进制数据使用blob或longblob类型对金额字段使用DECIMAL。
如果要存储text、blob字段建议单独建一张表使用外键关联。
尽量不要定义外键保证表的独立性可以存在外键意义的字段。
注意字段的约束如非空、唯一、主键等。
1.11 (内容管理)创建工程 P15 这一节具体可以参考day01/资料中的第2章讲义。 在xuecheng-plus-content下面创建xuecheng-plus-content-api、xuecheng-plus-content-model和xuecheng-plus-content-service这三个模块。 新建的目录结构如下 模块之间依赖关系如下 xuecheng-plus-content-model依赖xuecheng-plus-base。
xuecheng-plus-content-service依赖xuecheng-plus-model。
xuecheng-plus-content-api依赖xuecheng-plus-content-service。因为看图api依赖service也依赖model但因为service依赖model所以api如果依赖了service变相也依赖了model。
xuecheng-plus-content-model的pom.xml文件
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentartifactIdxuecheng-plus-content/artifactIdgroupIdcom.xuecheng/groupIdversion0.0.1-SNAPSHOT/version/parentartifactIdxuecheng-plus-content-model/artifactIddependenciesdependencygroupIdcom.xuecheng/groupIdartifactIdxuecheng-plus-base/artifactIdversion0.0.1-SNAPSHOT/version/dependency/dependencies/projectxuecheng-plus-content-service的pom.xml文件
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentartifactIdxuecheng-plus-content/artifactIdgroupIdcom.xuecheng/groupIdversion0.0.1-SNAPSHOT/version/parentartifactIdxuecheng-plus-content-service/artifactIddependenciesdependencygroupIdcom.xuecheng/groupIdartifactIdxuecheng-plus-content-model/artifactIdversion0.0.1-SNAPSHOT/version/dependency/dependencies
/projectxuecheng-plus-content-api的pom.xml文件
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentartifactIdxuecheng-plus-content/artifactIdgroupIdcom.xuecheng/groupIdversion0.0.1-SNAPSHOT/version/parentartifactIdxuecheng-plus-content-api/artifactIddependenciesdependencygroupIdcom.xuecheng/groupIdartifactIdxuecheng-plus-content-service/artifactIdversion0.0.1-SNAPSHOT/version/dependency/dependencies
/project二、课程模块开发
2.1 课程查询 需求分析 P16
操作流程一步一步要怎么操作就是业务流程然后弄清在操作流程中需要哪些数据。
2.2 课程查询 生成PO类 P17
具体的搭建步骤可以看资料中下面这个文件里的内容 course_base文件是课程基本信息文件。
表结构如下。 首先把xuecheng-plus-generator这个代码生成器解压出来然后放到xuecheng-plus-project这个总工程下面。 此时该模块还是灰色的右键pom.xml然后点击Add as Maven Project可以使其变成maven工程。 修改generator下的ContentCodeGenerator类 修改下面数据的连接配置像我的表叫content可以去掉前面xc402 先把java下面的content删掉然后运行ContentCodeGenerator的main方法
main方法执行完后可以看到生成了content包model下面有po类。把po下面的所有po类复制粘贴到xuecheng-plus-content-model下的po包中 发现有一些报错主要的原因是缺乏了依赖。 可以加入如下的依赖到model模块的pom.xml文件中
!--存在mybatisplus注解添加相关注解保证不报错--dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-annotation/artifactIdversion${mybatis-plus-boot-starter.version}/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-core/artifactIdversion${mybatis-plus-boot-starter.version}/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId
/dependency现在就不会报错了 2.3 课程查询 设计分析 P18
2.4 课程查询 接口定义 P19
该节内容可以参考下面的文档进行配置所以我只罗列关键步骤。 第1步定义分页查询模型类。在xuecheng-plus-base下面的src\main\java\com\xuecheng\base\model下面创建PageParams类写入代码。 第2步定义条件模型类。在xuecheng-plus-content/xuecheng-plus-content-model下面src/main/java/com/xuecheng/content/model下创建dto包创建QueryCourseParamsDto类 第3步定义响应模型类。
PageResult分为2部分1部分是数据另1部分是分页信息。 第4步把依赖放到xuecheng-plus-content-api的pom.xml中
详细请见文档 我个人导入的时候cloud的基础环境包有点问题然后我修改了parent里面的代码
第5步在xuecheng-plus-content-api的com/xuecheng下面创建content/api然后创建一个CourseBaseInfoController类
RestController
public class CourseBaseInfoController {RequestMapping(/course/list)public PageResultCourseBase list(PageParams pageParams,RequestBody QueryCourseParamsDto queryCourseParamsDto){return null;}
}
第6步在xuecheng-plus-content-api的com/xuecheng下面创建ContentApplication启动类注意这个类一定是要在xucheng下面不要放到content下。
SpringBootApplication
public class ContentApplication {public static void main(String[] args){SpringApplication.run(ContentApplication.class,args);}
}
log4j2-dev.xml在下面的位置粘贴到下面位置 在xuecheng-plus-content的xuecheng-plus-content-api下面的resources下创建bootstrap.yml文件然后写入如下代码注意把url中端口后的数据库名称改成自己的 点击启动类中下面按钮启动项目 重点请求localhost:63040/content/course/list出现的是下面的情况政策现象 请求的数据和接口不匹配加上pageNo和pageSize仍旧不行。 localhost:63040/content/course/list?pageNo1pageSize30
是因为第二个参数要接受json数据转化为Java对象而现在没有这个json数据。 RequestBody这个注解对参数的要求为true因此不行。修改方式如下给RequestBody加一个requiredfalse。 现在再请求就没有任何问题了 控制台也显示成功 前端和controller层间用vo传递数据controller和service层间用DTO传输数据service和dao层间用po传输数据。 如果有多个前端比如手机、PC传入的参数个数不同就需要有VO避免让负责手机前端的工程师误认为有5个参数比如VO1对应手机是3个参数VO2对应PC是5个参数。如果没有多个前端就只有1个前端那就只需要用DTO即可不需要VO。 2.5 课程查询 swagger P20
swagger可以在线生成接口文档。
首先加入swagger依赖之前已加完然后要在配置文件bootstrap.yml中进行配置
swagger:title: 学成在线内容管理系统description: 内容系统管理系统对课程相关信息进行管理base-package: com.xuecheng.contentenabled: trueversion: 1.0.0
在启动类上加EnableSwagger2Doc注解
然后重启项目
在浏览器中输入localhost:63040/content/swagger-ui.html然后可以看到如下的界面 在类上加Api注解在方法上加ApiOperation接口把RequestMapping改成PostMapping
Api(value课程信息管理接口,tags课程信息管理接口)
RestController
public class CourseBaseInfoController {ApiOperation(课程查询接口)PostMapping(/course/list)public PageResultCourseBase list(PageParams pageParams,RequestBody(requiredfalse) QueryCourseParamsDto queryCourseParamsDto){return null;}
}
效果如下 ApiModelProperty可以加在属性上用来给属性备注名称。 swagger里还可以进行接口测试 但发现日期不太好看 可以直接从下面这个文件中取出LocalDateTimeConfig这个工具类 然后放到如下的位置 重启项目重新测试
2.6 SpringBoot常用注解 P21
ResponseBody 将数据以json的格式响应给前端侧重返回定义在类上
RequestBody 将json数据转化为java对象侧重接收定义在方法上
PathVariable 接收请求路径中占位符的值 Autowired是基于类型的注入。
Resource基于名称注入。
2.7 项目开发流程 P22
1.产品人员设计产品原型。
2.讨论需求。
3.分模块设计接口。
4.出接口文档。
5.将接口文档给到前端人员前后端分离开发。
6.开发完毕进行测试。
7.测试完毕发布项目由运维人员进行部署安装。
2.8 课程查询 DAO接口 P23
前后端分离开发先定义controller层的接口生成接口文档然后再前后端一起开发。
要从底层开始写从持久层开始写。
在xuecheng-plus-content-service下面的com/xuecheng下面创建content包在content下创建mapper包。
然后把xuecheng-plus-generator下的com/xuecheng/content/mapper下的所有文件拷贝到上面service的mapper包下。 然后要进行单元测试首先把xuecheng-plus-content-service的依赖补全把如下这些依赖复制粘贴到service 在service的test下创建resources 把api模块定义的2个配置文件拷贝到service模块 配置文件只需要像下面这样 在service的test/java下创建com/xuecheng包然后把api的启动类拷贝到com/xuecheng下删掉生成接口文档的注解 分页插件会自动加limit语句
分页插件的原理分页参数会放到ThreadLocal中mybatis plus有一个拦截器可以拦截执行的sql根据数据库类型添加对应的分页语句重写sql在末尾拼加limit。
在xuecheng-plus-content-service的java/com/xuecheng/content下创建一个config包然后创建MybatisPlusConfig类把代码都写入进去 在service模块的test/java/com/xuecheng/content下创建CourseBaseMapperTests写入如下内容 如果左侧为绿√代表测试通过也可以打个断点看看 CourseBaseMapperTests代码如下
SpringBootTest
public class CourseBaseMapperTests {AutowiredCourseBaseMapper courseBaseMapper;Testpublic void testCourseBaseMapper(){CourseBase courseBase courseBaseMapper.selectById(18);Assertions.assertNotNull(courseBase);//详细进行分页查询的单元测试//查询条件QueryCourseParamsDto courseParamsDto new QueryCourseParamsDto();courseParamsDto.setCourseName(java); //课程名称查询条件//拼装查询条件LambdaQueryWrapperCourseBase queryWrapper new LambdaQueryWrapper();//根据名称模糊查询.在sql中拼接course_base.name like %值%queryWrapper.like(StringUtils.isNotEmpty(courseParamsDto.getCourseName()),CourseBase::getName,courseParamsDto.getCourseName());//根据课程审核状态查询 course_base.audit_status ?queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,courseParamsDto.getAuditStatus());//分页参数对象PageParams pageParams new PageParams();pageParams.setPageNo(1L);pageParams.setPageSize(2L);//创建page分页参数对象参数当前页码每页记录数。PageCourseBase page new Page(pageParams.getPageNo(), pageParams.getPageSize());//开始进行分页查询PageCourseBase pageResult courseBaseMapper.selectPage(page, queryWrapper);//数据列表ListCourseBase items pageResult.getRecords();//总记录数long total pageResult.getTotal();//ListT items,long counts,long page,long pageSizePageResultCourseBase courseBasePageResult new PageResultCourseBase(items,total,pageParams.getPageNo(),pageParams.getPageSize());System.out.println(courseBasePageResult);}
}
在末尾打上断点进行断点调试 看一下是否有LIMIT语句 看一下结果数据是否完整 如果觉得数据有问题可以把SQL语句复制到Navicat中然后把参数逐一替换问号进行执行、 2.9 数据字典表 P24
下拉框的文字来源于数据字典表方便前端进行修改替换展示。 数据字典由code编码和文字组成通过编码来指代文字。
在Navicat中创建一个新的数据库xc_systemuff8mb4_general_ci。然后执行.sql文件全称是xcplus_system.sql。 虚拟机里面的Mysql已经有了这个数据库名字叫作xcplus-system所以我们不用创建只需要知道流程。
2.10 (课程查询)service P25
在xuecheng-plus-base的xuecheng-plus-content-service下的src/main/java/com/xuecheng/content下创建service包在service包下创建CourseBaseInfoService接口写入如下代码
//课程信息管理接口
public interface CourseBaseInfoService {/*** 课程分页查询* param pageParams 分页查询参数* param queryCourseParamsDto 查询条件* return 查询结果*/public PageResultCourseBase queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto);
}
再在service包下创建impl包在impl包下创建实现类CourseBaseInfoServiceImpl写入如下代码注意一定要把return null改过了
Slf4j
Service
public class CourseBaseInfoServiceImpl implements CourseBaseInfoService {AutowiredCourseBaseMapper courseBaseMapper;Overridepublic PageResultCourseBase queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto courseParamsDto) {//拼装查询条件LambdaQueryWrapperCourseBase queryWrapper new LambdaQueryWrapper();//根据名称模糊查询.在sql中拼接course_base.name like %值%queryWrapper.like(StringUtils.isNotEmpty(courseParamsDto.getCourseName()),CourseBase::getName,courseParamsDto.getCourseName());//根据课程审核状态查询 course_base.audit_status ?queryWrapper.eq(StringUtils.isNotEmpty(courseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,courseParamsDto.getAuditStatus());//创建page分页参数对象参数当前页码每页记录数。PageCourseBase page new Page(pageParams.getPageNo(), pageParams.getPageSize());//开始进行分页查询PageCourseBase pageResult courseBaseMapper.selectPage(page, queryWrapper);//数据列表ListCourseBase items pageResult.getRecords();//总记录数long total pageResult.getTotal();//ListT items,long counts,long page,long pageSizePageResultCourseBase courseBasePageResult new PageResultCourseBase(items,total,pageParams.getPageNo(),pageParams.getPageSize());return courseBasePageResult;}
} 然后要进行单元测试复制service模块test下原有的CourseBaseMapperTests粘贴到自己的路径下然后改名为CourseBaseInfoServiceTests写入如下代码
SpringBootTest
public class CourseBaseInfoServiceTests {AutowiredCourseBaseInfoService courseBaseInfoService;Testpublic void testCourseBaseInfoService(){//查询条件QueryCourseParamsDto courseParamsDto new QueryCourseParamsDto();courseParamsDto.setCourseName(java); //课程名称查询条件courseParamsDto.setAuditStatus(202004);//分页参数对象PageParams pageParams new PageParams();pageParams.setPageNo(1L);pageParams.setPageSize(2L);PageResultCourseBase courseBasePageResult courseBaseInfoService.queryCourseBaseList(pageParams, courseParamsDto);System.out.println(courseBasePageResult);}
}看一下控制台输出没太大问题 2.11 (课程查询)接口测试 P26
完善xuecheng-plus-content-api下的CourseBaseInfoController的代码
Api(value课程信息管理接口,tags课程信息管理接口)
RestController
public class CourseBaseInfoController {AutowiredCourseBaseInfoService courseBaseInfoService;ApiOperation(课程查询接口)PostMapping(/course/list)public PageResultCourseBase list(PageParams pageParams,RequestBody(requiredfalse) QueryCourseParamsDto queryCourseParamsDto){PageResultCourseBase courseBasePageResult courseBaseInfoService.queryCourseBaseList(pageParams, queryCourseParamsDto);return courseBasePageResult;}
}
用swagger来测试没有问题 点击请求这里的地球然后点击如下生成一个Http的请求。 把swagger上面的参数转移到下面
###
POST http://localhost:63040/content/course/list?pageNo1pageSize2
Content-Type: application/json{auditStatus: 202004,courseName: java,publishStatus:
}
同样可以查询出结果 现在可以把这些查询的文件统一放到一个包下方便管理。
在xuecheng-plus-project下面创建一个api-test包在包下创建一个文件名为xc-content-api.http用来存放请求测试的语句然后再创建一个http-client.env.json文件用来配置相应的环境变量。 效果如下 2.12 部署前端和管理服务 P27
安装完node和npm看看版本我用的是《苍穹外卖》配置的版本经测试可以表明项目可以向下兼容 用IDEA打开project-xczx2-portal-vue-ts右键package.json点击show npm scripts 右键serve然后点击Edit serve Settings。然后配置好Node和nmp的版本。最后Run serve 4
成功后点击链接访问即可 打开开发者工具all报错这是系统管理服务请求的是63110端口。 打开第2天的资料把xuecheng-plus-system放到project里把pom.xml转化为maven工程把api模块的配置文件中的数据库修改为我们自己的数据库。 运行Api模块下的SystemApplication启动类。 出现的是跨域问题 2.13 跨域三种方案 P28
判断是否跨域请求是基于浏览器的同源策略同源策略是浏览器的一种安全机制从一个地址请求另一个地址如果协议、主机、端口三者全部一致则不属于跨域否则有一个不一致就是跨域。
从http://localhost:8601到http://localhost:8602因为端口不同所以是跨域
从http://192.168.101.10:8601到http://192.168.101.11:8601由于主机不同是跨域
从http://192.168.101.10:8601到https://192.168.101.10:8601由于协议不用是跨域
方法1JSONP
方法2添加响应头
Access-Control-Allow-Origin: http://localhost:8601
服务器收到请求判断这个Origin是否允许跨域如果允许则在响应头中说明允许该来源的跨域请求。
Access-Control-Allow-Origin: *
方法3通过nginx代理跨域
由于服务器之间没有跨域浏览器可以通过nginx去访问。 Nginx和浏览器之间不是跨域的协议、服务器、端口都一样通过Nginx代理去访问服务器。
2.14 定义cors过滤器 P29
在xuecheng-plus-system的xuecheng-plus-content-api下的config包下创建一个GlobalCorsConfig类写入如下代码
Configurationpublic class GlobalCorsConfig {Beanpublic CorsFilter corsFilter() {CorsConfiguration config new CorsConfiguration();//允许白名单域名进行跨域调用config.addAllowedOrigin(*);//允许跨越发送cookieconfig.setAllowCredentials(true);//放行全部原始头信息config.addAllowedHeader(*);//允许所有请求方法跨域调用config.addAllowedMethod(*);UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration(/**, config);return new CorsFilter(source);}}现在解决了跨域问题前端页面能正常显示。 2.15 课程查询前后端联调 P30
前端.env是重要文件修改了要重新启动项目。
当前内容管理模块打开VUE_APP_SERVER_API_URLhttp://localhost:63040
引入网关后会打开VUE_APP_SERVER_API_URLhttp://localhost:63010
前后端联调的过程。
测试输入java看是否出现课程。点击下拉框看是否有文字。 2.16 (分类查询)接口定义 P31
下表是树形结构是三级结构。 在xuecheng-plus-content的xuecheng-plus-content-model下的dto中定义一个新类CourseCategoryTreeDto写入如下代码
Data
public class CourseCategoryTreeDto extends CourseCategory implements java.io.Serializable{ListCourseCategoryTreeDto childrenTreeNodes;
}
在xuecheng-plus-content的xuecheng-plus-content-api/src/main/java/com/xuecheng/content/api下新增一个类CourseCategoryController写入如下代码
RestController
public class CourseCategoryController {GetMapping(/course-category/tree-nodes)public ListCourseCategoryTreeDto queryTreeNodes(){return null;}
}2.17 (分类查询)树型查询 P32
方法1表的自连接。
select
one.id one_id,
one.label one_label,
two.id two_id,
two.label two_label
from course_category one
inner join course_category two
on two.parentid one.id
where one.parentid 1
and one.is_show 1
and two.is_show 1
order by one.orderby,two.orderby 方法2递归
向下递归由根节点找子节点。
with recursive t1 as (select * from course_category where id1union all select t2.* from course_category t2 inner join t1 on t1.idt2.parentid
)
select * from t1
order by t1.id 向上递归由子节点找根节点。
with recursive t1 as (select * from course_category where id1-1-1union all select t2.* from course_category t2 inner join t1 on t1.parentid t2.id
)
select * from t1
order by t1.id2.18 (分类查询)开发测试 P33
在xuecheng-plus-content的xuecheng-plus-content-service下的mapper中增加CourseCategoryMapper中的代码
public interface CourseCategoryMapper extends BaseMapperCourseCategory {//使用递归查询分类public ListCourseCategoryTreeDto selectTreeNodes(String id);
}
在xuecheng-plus-content的xuecheng-plus-content-service下的mapper中增加CourseCategoryMapper.xml中的代码
select idselectTreeNodes parameterTypestring resultTypecom.xuecheng.content.model.dto.CourseCategoryTreeDtowith recursive t1 as (select * from course_category where id#{id}union allselect t2.* from course_category t2 inner join t1 on t1.id t2.parentid)select * from t1order by t1.id
/select
在xuecheng-plus-content的xuecheng-plus-content-service的test下的java/com/xuecheng/content下把CourseBaseMapperTests拷贝一份起名CourseCategoryMapperTests
写入如下代码
SpringBootTest
public class CourseCategoryMapperTests {AutowiredCourseCategoryMapper courseCategoryMapper;Testpublic void testCourseBaseMapper(){ListCourseCategoryTreeDto courseCategoryTreeDtos courseCategoryMapper.selectTreeNodes(1);System.out.println(courseCategoryTreeDtos);}
}在xuecheng-plus-content的xuecheng-plus-content-service下的service下创建CourseCategoryService接口写入如下代码
public interface CourseCategoryService {public ListCourseCategoryTreeDto queryTreeNodes(String id);
}
在xuecheng-plus-content的xuecheng-plus-content-service下的service下创建CourseCategoryServiceImpl类写入如下代码
Slf4j
Service
public class CourseCategoryServiceImpl implements CourseCategoryService {AutowiredCourseCategoryMapper courseCategoryMapper;Overridepublic ListCourseCategoryTreeDto queryTreeNodes(String id) {//调用mapper递归查询出分类信息ListCourseCategoryTreeDto courseCategoryTreeDtos courseCategoryMapper.selectTreeNodes(id);//找到每个节点的子节点最终封装成ListCourseCategoryTreeDto//先将list转成map,key就是结点的id,value就是CourseCategoryTreeDto对象目的是为了方便从map获取结点。filter(item-!id.equals(item.getId()))把根节点拍出MapString, CourseCategoryTreeDto mapTemp courseCategoryTreeDtos.stream().filter(item-!id.equals(item.getId())).collect(Collectors.toMap(key - key.getId(), value - value));//定义一个list作为最终返回的listListCourseCategoryTreeDto courseCategoryList new ArrayList();//从头遍历ListCourseCategoryTreeDto一边遍历一边找子节点放在父节点的childrenTreeNodescourseCategoryTreeDtos.stream().filter(item-!id.equals(item.getId())).forEach(item-{if(item.getParentid().equals(id)){courseCategoryList.add(item);}//找到节点的父节点CourseCategoryTreeDto courseCategoryTreeDto mapTemp.get(item.getParentid());if(courseCategoryTreeDto!null) {if (courseCategoryTreeDto.getChildrenTreeNodes() null) {//如果该父节点的ChildrenTreeNodes属性为空要new一个集合因为要向该集合中放它的子节点courseCategoryTreeDto.setChildrenTreeNodes(new ArrayListCourseCategoryTreeDto());}//到每个节点的子节点放在父节点的childrenTreeNodes属性中courseCategoryTreeDto.getChildrenTreeNodes().add(item);}});return courseCategoryList;}
}
在xuecheng-plus-content的xuecheng-plus-content-service下的test下创建CourseCategoryServiceTests测试类写入如下代码
SpringBootTest
public class CourseCategoryServiceTests {AutowiredCourseCategoryService courseCategoryService;Testpublic void testCourseBaseInfoService(){ListCourseCategoryTreeDto courseCategoryTreeDtos courseCategoryService.queryTreeNodes(1);System.out.println(courseCategoryTreeDtos);}
}
单元测试的效果如下 在xuecheng-plus-content-api的CourseCategoryController下完善代码如下
RestController
public class CourseCategoryController {AutowiredCourseCategoryService courseCategoryService;GetMapping(/course-category/tree-nodes)public ListCourseCategoryTreeDto queryTreeNodes(){return courseCategoryService.queryTreeNodes(1);}
}
先把xuecheng-plus-content的xuecheng-plus-content-api下的ContentApplication启动。 然后在api-test包下的xc-content-api.http中添加如下代码
### 查询课程分类
GET {{content_host}}/content/course-category/tree-nodes
可以看到正常输出结果 在前后端联调的时候可以看到课程分类有了结果 2.19 (新增课程)接口定义 P34
2.20 (新增课程)接口开发 P35 把下面2个Dto类复制到xuecheng-plus-content的xuecheng-plus-content-api的dto下面 在xuecheng-plus-content的xuecheng-plus-content-api下的CourseBaseInfoController中写入如下代码
ApiOperation(新增课程)PostMapping(/content/course)public CourseBaseInfoDto createCourseBase(RequestBody AddCourseDto addCourseDto){return null;}
在xuecheng-plus-content的xuecheng-plus-content-service的service下的CourseBaseInfoService中添加接口
public CourseBaseInfoDto createCourseBase(Long companyId,AddCourseDto addCourseDto); 在xuecheng-plus-content的xuecheng-plus-content-service的service/impl下的CourseBaseInfoServiceImpl中添加代码
Autowiredprivate CourseMarketMapper courseMarketMapper;Autowiredprivate CourseCategoryMapper courseCategoryMapper;TransactionalOverridepublic CourseBaseInfoDto createCourseBase(Long companyId, AddCourseDto dto) {//参数的合法性校验if (StringUtils.isBlank(dto.getName())) {throw new RuntimeException(课程名称为空);}if (StringUtils.isBlank(dto.getMt())) {throw new RuntimeException(课程分类为空);}if (StringUtils.isBlank(dto.getSt())) {throw new RuntimeException(课程分类为空);}if (StringUtils.isBlank(dto.getGrade())) {throw new RuntimeException(课程等级为空);}if (StringUtils.isBlank(dto.getTeachmode())) {throw new RuntimeException(教育模式为空);}if (StringUtils.isBlank(dto.getUsers())) {throw new RuntimeException(适应人群为空);}if (StringUtils.isBlank(dto.getCharge())) {throw new RuntimeException(收费规则为空);}//向课程基本信息表course_base写入数据CourseBase courseBaseNew new CourseBase();//将传入的页面的参数放到courseBase对象中BeanUtils.copyProperties(dto,courseBaseNew); //只要属性名相同就可以拷贝courseBaseNew.setCompanyId(companyId);courseBaseNew.setCreateDate(LocalDateTime.now());//审核状态默认为未提交courseBaseNew.setAuditStatus(202002);//发布状态为未发布courseBaseNew.setStatus(203001);//插入数据库int insert courseBaseMapper.insert(courseBaseNew);if(insert0){throw new RuntimeException(添加课程失败);}//向课程营销表course_market写入数据CourseMarket courseMarketNew new CourseMarket();//将页面输入的数据拷贝到courseMarketNewBeanUtils.copyProperties(dto,courseMarketNew);//课程的idLong courseId courseBaseNew.getId();courseMarketNew.setId(courseId);//保存营销信息saveCourseMarket(courseMarketNew);//从数据库查询课程的详细信息包括两部分CourseBaseInfoDto courseBaseInfo getCourseBaseInfo(courseId);return courseBaseInfo;}//查询课程信息public CourseBaseInfoDto getCourseBaseInfo(long courseId){//从课程基本信息表查询CourseBase courseBase courseBaseMapper.selectById(courseId);if(courseBasenull){return null;}//从课程营销表查询CourseMarket courseMarket courseMarketMapper.selectById(courseId);//组装在一起CourseBaseInfoDto courseBaseInfoDto new CourseBaseInfoDto();BeanUtils.copyProperties(courseBase,courseBaseInfoDto);BeanUtils.copyProperties(courseMarket,courseBaseInfoDto);//通过courseCategoryMapper查询分类信息将分类名称放在courseBaseInfoDto对象return courseBaseInfoDto;}//单独写一个方法保存营销信息逻辑存在则更新不存在则添加private int saveCourseMarket(CourseMarket courseMarketNew){//参数的合法性校验String charge courseMarketNew.getCharge();if(StringUtils.isEmpty(charge)){throw new RuntimeException(收费规则为空);}//如果课程收费价格没有填写也需要抛出异常if(charge.equals(201001)){if(courseMarketNew.getPrice()null || courseMarketNew.getPrice().floatValue()0){throw new RuntimeException(课程的价格不能为空并且必须大于0);}}//从数据库查询营销信息存在则更新不存在则添加Long id courseMarketNew.getId();CourseMarket courseMarket courseMarketMapper.selectById(id);if(courseMarketnull){//插入数据库int insert courseMarketMapper.insert(courseMarketNew);return insert;}else{//将courseMarketNew拷贝到courseMarketBeanUtils.copyProperties(courseMarketNew,courseMarket);courseMarket.setId(courseMarket.getId());//更新int i courseMarketMapper.updateById(courseMarket);return i;}}2.21 (新增课程)接口测试 P36
首先是完善controller的接口。在xuecheng-plus-content的xuecheng-plus-content-api下的CourseBaseInfoController中完善createCourseBase方法
ApiOperation(新增课程)
PostMapping(/course)
public CourseBaseInfoDto createCourseBase(RequestBody AddCourseDto addCourseDto){//获取到用户所述机构的idLong companyId 1232141425L;CourseBaseInfoDto courseBase courseBaseInfoService.createCourseBase(companyId, addCourseDto);return courseBase;
}
然后节约时间跳过单元测试直接倒api-test下的xc-content-api.http中进行接口测试
### 新增课程
POST {{content_host}}/content/course
Content-Type: application/json
{charge: 201001,description: adsd,grade: 204001,mt: 1-1,name: java网络编程高级,originalPrice: 100,phone: 13333333,pic: fdsf,price: 10,qq: 22333,st: 1-1-1,tags: sdsdwe,teachmode: 20002,users: 初级人员,validDays: 365,wechat: 223344
} 在controller和service的实现类上打上断点主要是看看断点调试时接受的数据是否完整。
放行之后数据效果如下 前端可以直接看到之前新增的课程 数据库中也有如下数据没问题 前端还暂时无法提交。
2.22 Mybatis相关问题 P37
1.Mybatis分页插件的实现原理
首先分页参数会被放到ThreadLocal中拦截执行的sql语句根据数据库的类型比如是Mysql就会在末尾添加LIMIT添加对应的分页语句重写sql。
计算出total总记录数pageNum当前是第几页pageSize每页的大小。是否为首页是否为尾页总页数等。
2.树型表的标记字段是什么如何查询MySQL树型表
树型表的标记字段是parentid即父结点的id。
3.查询一个树型表的方法
当层级固定时可以用表的自连接进行查询。
如果想灵活查询每个层级可以使用mysql递归方法使用with RECURSIVE实现。
4.Result Type和Result Map的区别
Result Type当查询到的SQL字段的名字和Result Type中模型类型的属性名字对应上的可以由MyBatis自动完成映射。
Result Map当查询到的SQL字段的名字和Result Type中模型类型的属性名字对应不上时需要通过Result Map手动完成映射。
5. #{}和${}的区别
#{}是标记一个占位符可以防止sql注入。
${}用于在动态sql中拼接字符串可能导致sql注入。
2.23 自定义异常类型 P38
如果前端名称为空会报错500。但仅在控制台输出前端没展示。
如果写很多try-catch代码会造成代码冗余。
由增强类来捕获异常原理是AOP面向切面编程。
用ControllerAdvice注解来控制器增强用异常处理注解ExceptionHandler。
在xuecheng-plus-base的base包下创建exception包。
在exception包下创建RestErrorResponse类和前端约定返回的异常信息模型
/*** 错误响应参数包装*/
public class RestErrorResponse implements Serializable {private String errMessage;public RestErrorResponse(String errMessage){this.errMessage errMessage;}public String getErrMessage() {return errMessage;}public void setErrMessage(String errMessage) {this.errMessage errMessage;}
}在exception包下创建XueChengPlusException类
/*** description 学成在线项目异常类* author Mr.M* date 2022/9/6 11:29* version 1.0*/
public class XueChengPlusException extends RuntimeException {private String errMessage;public XueChengPlusException() {super();}public XueChengPlusException(String errMessage) {super(errMessage);this.errMessage errMessage;}public String getErrMessage() {return errMessage;}public static void cast(CommonError commonError){throw new XueChengPlusException(commonError.getErrMessage());}public static void cast(String errMessage){throw new XueChengPlusException(errMessage);}}在exception包下创建CommonError类
/*** description 通用错误信息* author Mr.M* date 2022/9/6 11:29* version 1.0*/
public enum CommonError {UNKOWN_ERROR(执行过程异常请重试。),PARAMS_ERROR(非法参数),OBJECT_NULL(对象为空),QUERY_NULL(查询结果为空),REQUEST_NULL(请求参数为空);private String errMessage;public String getErrMessage() {return errMessage;}private CommonError( String errMessage) {this.errMessage errMessage;}}2.24 异常处理开发测试 P39 现在想测试课程名称为空是否会触发XueChengPlusException异常处理。
首先在service模块的service包的impl包下的CourseBaseInfoServiceImpl类中修改下面的代码
//参数的合法性校验
if (StringUtils.isBlank(dto.getName())) {//throw new RuntimeException(课程名称为空);XueChengPlusException.cast(课程名称为空);
} 在xuecheng-plus-base的exception下的GlobalExceptionHandler类下的如下位置打上断点 最后在api-test包下的xc-content-api.http下面让name参数为空。 在如下位置添加自定义的异常处理语句。 把现价改为负数会触发异常。 2.25 系统异常处理 P40
处理自定义异常程序在编写代码时根据校验结果主动抛出自定义异常类对象抛出异常时指定详细的异常信息异常处理器捕获异常信息记录异常日志并相应给客户。
2.26 JSR303校验 P41
JSR303是一个校验框架。
使用方式只需要在模型类中通过注解指定校验规则比如NOTEmpty来指定不能为空Size来指定长度的下限和上限对不同的应用场景可以通过分组来切换校验规则使用groups在controller方法上开启校验Validated。
前端请求后端接口传输参数在controller和service中都需要校验。
controller校验请求参数的合法性包括必填项校验、数据格式校验是否符合一定的日期格式。
service校验业务规则的相关内容。
因为service是根据业务规则去校验所以不方便写成通用代码controller中则可以将校验的代码写成通用代码。
引入如下依赖 合法性校验的注解如下 如果想要使用需要激活 首先要把service层校验屏蔽掉我是直接删掉 然后记得要把启动类重启一下代码才能生效。
在xc-content-api.http中把name改为空走的是系统异常处理所以一律输出为位置错误执行过程异常请重试。 现在出现问题如果多个接口使用同一个模型类时对校验的需求不一样时会出现问题。
解决方法分组校验。
在xuecheng-plus-base的exception下面的ValidationGroups类中写入如下代码
//用于分组校验定义一些常用的组
public class ValidationGroups {public interface Insert{};public interface Update{};public interface Delete{};
}
在xuecheng-plus-content的xuecheng-plus-content-model的dto下面的类AddCourseDto替换如下代码
NotEmpty(message 新增课程名称不能为空,groups{ValidationGroups.Insert.class})
NotEmpty(message 修改课程名称不能为空,groups{ValidationGroups.Update.class})
ApiModelProperty(value 课程名称, required true)
private String name; Validated注解里面写上所分的组类 2.27 系统参数合法性校验 P42
提问对表单的数据是怎么校验的
回答JSR303校验规则。
如果javax.validation.constraints包下的校验规则满足不了需求怎么办
1.手写校验代码
2.自定义校验规则注解
2.28 (修改课程)接口开发 P44
在xuecheng-plus-content的xuecheng-plus-content-api的api包下的CourseBaseInfoController中写入如下代码
ApiOperation(根据课程id查询接口)
GetMapping(/course/{courseId})
public CourseBaseInfoDto getCourseBaseById(PathVariable Long courseId){CourseBaseInfoDto courseBaseInfo courseBaseInfoService.getCourseBaseInfo(courseId);return courseBaseInfo;
}
在xuecheng-plus-content的xuecheng-plus-content-service的service包下的CourseBaseInfoService中写入如下代码
//根据课程id查询课程信息
public CourseBaseInfoDto getCourseBaseInfo(Long courseId);
在xuecheng-plus-content的xuecheng-plus-content-service的service/impl包下的CourseBaseInfoServiceImpl中写入如下代码
//查询课程信息
public CourseBaseInfoDto getCourseBaseInfo(Long courseId){//从课程基本信息表查询CourseBase courseBase courseBaseMapper.selectById(courseId);if(courseBasenull){return null;}//从课程营销表查询CourseMarket courseMarket courseMarketMapper.selectById(courseId);//组装在一起CourseBaseInfoDto courseBaseInfoDto new CourseBaseInfoDto();BeanUtils.copyProperties(courseBase,courseBaseInfoDto);BeanUtils.copyProperties(courseMarket,courseBaseInfoDto);//通过courseCategoryMapper查询分类信息将分类名称放在courseBaseInfoDto对象return courseBaseInfoDto;
} 在api-test的xc-content-api.http中写入如下代码
### 课程查询
GET {{content_host}}/content/course/40
Content-Type: application/json
返回的结果如下 在xuecheng-plus-content的xuecheng-plus-content-model下面的dto包下面重新定义一个类EditCourseDto代码如下
因为修改比新增就多了一个id可以继承新增。
Data
ApiModel(valueEditCourseDto,description修改课程基本信息)
public class EditCourseDto extends AddCourseDto{ApiModelProperty(value课程id,requiredtrue)private Long Id;
}
在xuecheng-plus-content的xuecheng-plus-content-api的api包下的CourseBaseInfoController中写入如下代码
ApiOperation(修改课程)
PutMapping(/course)
public CourseBaseInfoDto modifyCourseBase(RequestBody Validated EditCourseDto editCourseDto){//获取到用户所属机构的idLong companyId 1232141425L;CourseBaseInfoDto courseBaseInfoDto courseBaseInfoService.updateCourseBase(companyId, editCourseDto);return courseBaseInfoDto;
}
在xuecheng-plus-content的xuecheng-plus-content-service的service包下的CourseBaseInfoService中写入如下代码
public CourseBaseInfoDto updateCourseBase(Long companyId,EditCourseDto editCourseDto);
在xuecheng-plus-content的xuecheng-plus-content-service的service/impl包下的CourseBaseInfoServiceImpl中写入如下代码缺少更新营销信息
Override
public CourseBaseInfoDto updateCourseBase(Long companyId, EditCourseDto editCourseDto) {//拿到课程idLong courseId editCourseDto.getId();//查询课程信息CourseBase courseBase courseBaseMapper.selectById(courseId);if(courseBase null){XueChengPlusException.cast(课程不存在);}//数据合法性校验//根据具体的业务逻辑去校验//本机构只能修改本机构的课程if(!companyId.equals(courseBase.getCompanyId())){XueChengPlusException.cast(本机构只能膝盖本机构的课程);}//封装数据BeanUtils.copyProperties(editCourseDto,courseBase);//修改时间courseBase.setChangeDate(LocalDateTime.now());//更新数据库int i courseBaseMapper.updateById(courseBase);if(i0){XueChengPlusException.cast(修改课程失败);}//查询课程信息CourseBaseInfoDto courseBaseInfo getCourseBaseInfo(courseId);return courseBaseInfo;
} 在api-test的xc-content-api.http中写入如下代码
### 课程查询
GET {{content_host}}/content/course/40
Content-Type: application/json
2.29 (修改课程)接口测试 P45
输入下面的网址打开前端界面
http://localhost:8601/#/ 点击编辑按钮尝试修改基本信息 修改完毕之后原先的信息会被更改。 返回的结果如下属于正常跳转到另外一个还没写的界面 2.30 (计划查询)接口定义 P46
在xuecheng-plus-content的xuecheng-plus-content-model下面的dto包下面定义一个新类TeachplanDto代码如下
Data
ToString
public class TeachplanDto extends Teachplan {//课程计划关联的媒资信息private TeachplanMedia teachplanMedia;//子结点private ListTeachplanDto teachPlanTreeNodes;
}
在xuecheng-plus-content的xuecheng-plus-content-api的api包下的TeachplanController中写入如下代码初步
//课程计划管理相关的接口
Api(value课程计划编辑接口,tags课程计划编辑接口)
RestController
public class TeachplanController {ApiOperation(查询课程计划树形结构)GetMapping(/teachplan/{courseId}/tree-nodes)public ListTeachplanDto getTreeNodes(PathVariable Long courseId){return null;}
}
2.31 (计划查询)sql语句 P47
在xuecheng-plus-content的xuecheng-plus-content-service的mapper下的TeachplanMapper中写入如下代码
public interface TeachplanMapper extends BaseMapperTeachplan {//课程计划查询public ListTeachplanDto selectTreeNodes(Long courseId);
}
在xuecheng-plus-content的xuecheng-plus-content-service的mapper下的TeachplanMapper.xml中写入如下代码
select idselectTreeNodes parameterTypelong resultMaptreeNodeResultMapselectone.id one_id,one.pname one_pname,one.parentid one_parentid,one.grade one_grade,one.media_type one_mediaType,one.start_time one_stratTime,one.end_time one_endTime,one.orderby one_orderby,one.course_id one_courseId,one.course_pub_id one_coursePubId,two.id two_id,two.pname two_pname,two.parentid two_parentid,two.grade two_grade,two.media_type two_mediaType,two.start_time two_stratTime,two.end_time two_endTime,two.orderby two_orderby,two.course_id two_courseId,two.course_pub_id two_coursePubId,m1.media_fileName mediaFilename,m1.id teachplanMeidaId,m1.media_id mediaIdfrom teachplan oneINNER JOIN teachplan two on one.id two.parentidLEFT JOIN teachplan_media m1 on m1.teachplan_id two.idwhere one.parentid 0 and one.course_id#{value}order by one.orderby,two.orderby
/select
2.32 (计划查询)接口开发 P48
在xuecheng-plus-content的xuecheng-plus-content-service的java的mapper下的TeachplanMapper下写入如下代码
public interface TeachplanMapper extends BaseMapperTeachplan {//课程计划查询public ListTeachplanDto selectTreeNodes(Long courseId);
}
在xuecheng-plus-content的xuecheng-plus-content-service的java的mapper下的TeachplanMapper.xml下写入如下代码
column是从数据库查到的字段property是目标类的属性也就是将从数据库查到的字段column映射到目标类的属性property上。
除了id字段用id标签其它都用result标签。
resultMap idtreeNodeResultMap typecom.xuecheng.content.model.dto.TeachplanDtoid columnone_id propertyid/result columnone_pname propertypname/result columnone_pname propertypname /result columnone_parentid propertyparentid /result columnone_grade propertygrade /result columnone_mediaType propertymediaType /result columnone_stratTime propertystratTime /result columnone_endTime propertyendTime /result columnone_orderby propertyorderby /result columnone_courseId propertycourseId /result columnone_coursePubId propertycoursePubId /!--映射子节点一对多映射ofType填的是list中对象类型--collection propertyteachPlanTreeNodes ofTypecom.xuecheng.content.model.dto.TeachplanDto!-- 二级数据映射 --id columntwo_id propertyid /result columntwo_pname propertypname /result columntwo_parentid propertyparentid /result columntwo_grade propertygrade /result columntwo_mediaType propertymediaType /result columntwo_stratTime propertystratTime /result columntwo_endTime propertyendTime /result columntwo_orderby propertyorderby /result columntwo_courseId propertycourseId /result columntwo_coursePubId propertycoursePubId /!--一对一映射--association propertyteachplanMedia javaTypecom.xuecheng.content.model.po.TeachplanMediaresult columnteachplanMeidaId propertyid /result columnmediaFilename propertymediaFilename /result columnmediaId propertymediaId /result columntwo_id propertyteachplanId /result columntwo_courseId propertycourseId /result columntwo_coursePubId propertycoursePubId //association/collection
/resultMap
在xuecheng-plus-content的xuecheng-plus-content-service的test的content下创建一个TeachplanMapperTests类写入如下代码
SpringBootTest
public class TeachplanMapperTests {AutowiredTeachplanMapper teachplanMapper;Testpublic void testSelectTreeNodes(){ListTeachplanDto teachplanDtos teachplanMapper.selectTreeNodes(117L);System.out.println(teachplanDtos);}
}
当测试数据为117L的时候会有2个大章节。 在xuecheng-plus-content的xuecheng-plus-content-service的service包下的TeachplanService
中写入如下代码
//课程计划管理相关接口
public interface TeachplanService {//根据课程id查询课程计划public ListTeachplanDto findTeachplanTree(Long courseId);
}
在xuecheng-plus-content的xuecheng-plus-content-service的service/impl包下的TeachplanService
中写入如下代码
Service
public class TeachplanServiceImpl implements TeachplanService {AutowiredTeachplanMapper teachplanMapper;Overridepublic ListTeachplanDto findTeachplanTree(Long courseId) {ListTeachplanDto teachplanTree teachplanMapper.selectTreeNodes(courseId);return teachplanTree;}
}
在xuecheng-plus-content的xuecheng-plus-content-api的api包下的TeachplanController中写入如下代码完善
//课程计划管理相关的接口
Api(value课程计划编辑接口,tags课程计划编辑接口)
RestController
public class TeachplanController {Autowiredprivate TeachplanService teachplanService;ApiOperation(查询课程计划树形结构)GetMapping(/teachplan/{courseId}/tree-nodes)public ListTeachplanDto getTreeNodes(PathVariable Long courseId){ListTeachplanDto teachplanTree teachplanService.findTeachplanTree(courseId);return teachplanTree;}
} 在api-test的xc-content-api.http中写入如下代码
### 课程计划查询
GET {{content_host}}/content/teachplan/117/tree-nodes
控制台输出的效果如下测试成功前后端联调测试没问题 2.33 (新增修改计划)接口定义 P49
点击“添加章”新增第一级课程计划。
点击“添加小节”向某个第一级课程计划下添加小节。
点击“章”、“节”的名称可以修改名称、选择是否免费。
同一个接口接收新增和修改两个业务请求以是否传递课程计划id 来判断是新增还是修改传递了课程计划id说明当前是要修改该课程计划否则是新增一个课程计划。
在xuecheng-plus-content的xuecheng-plus-content-model下的dto中新增一个类写入如下代码
Data
ToString
public class SaveTeachplanDto {private Long id;private String pname;private Long parentid;private Integer grade;private String mediaType;private Long courseId;private Long coursePubId;private String isPreview;
}在xuecheng-plus-conten的xuecheng-plus-content-api下的content/api下的TeachplanController中写入如下代码
ApiOperation(课程计划创建或修改)
PostMapping(/teachplan)
public void saveTeachplan(RequestBody SaveTeachplanDto teachplan){
}