1.微服务异常处理
1. 异常信息枚举类的错误代码规范
ContentErrorCode编写过程:
1.创建一个枚举类并实现接口Errorcode
2.对Errorcode的方法进行实现
3.需要在改枚举类中声明出两个属性
code、desc
4.构建出该枚举类的构造方法
需要将定义全参的构造方法
5.声明枚举的信息
E_xxxxxx(6位错误代码)
前两位:服务标识
中间两位:模块标识
后两位:异常标识
PS:在getcode和getdesc的方法返回值中将code和desc传出
枚举类是一个Java类的多例形式:构建改对象的常量信息
底层会new出这个类对象,声明常量—一个枚举类相当于一个Java对象
new ContentErrorCode(12001,”保存课程基本信息失败”)
内容管理服务前两位错误服务标识为:12(在 {@link ErrorCode} 中定义)
模块错误代码定义如下:
2.异常信息类的结构
3.自定义异常类BusinessException的声明特点
这里可以传任意实现ErrorCode接口的类或对象====多态
设计模式:适配者模式,所体现的形式就是多态
- 定义异常信息类 ErrorCode 作为属性。
- 自定义异常继承 RuntimeException ,这样代码中不会被污染(不需要try-catch)。
4.全局异常处理流程
对于异常主要分两大类处理:
- 可预知的异常由程序员在代码中主动抛出,全局异常处理类统一捕获可预知异常主要是指的业务异常信息,在代码中手动抛出定义的业务异常信息,,程序员在抛出时会指定错误代码及错误信息。
- 不可预知的异常(运行时异常)由统一捕获Exception类型的异常。不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。

处理流程解释:
1、在controller、service、dao中程序员抛出可预知异常类型,微服务框架抛出不可预知异常类型。
2、统一由异常捕获类捕获异常,并进行处理。
3、捕获到可预知异常则直接取出ErrorCode错误代码及错误信息,响应给用户。
4、捕获到微服务框架中不可预知异常,错误代码则统一为99999错误代码并响应给用户。
5、将错误代码及错误信息以Json格式响应给用户。
//NullPointerException——RuntimeException—-Exception
NullPointerException 向上匹配:项目中没有定义RuntimeException,再向上匹配找到项目中定义的Exception
全局异常处理的实现方式
从 Spring 3.0 - Spring 3.2 版本之间,对 Spring 架构和 SpringMVC 的Controller 的异常捕获提供了相应的异常处理。
- @ExceptionHandler Spring3.0提供的标识在方法上或类上的注解,用来表明方法的处理异常类型。
- 方法上声明捕获指定异常类型,表明方法的处理异常类型
- @ControllerAdvice Spring3.2提供的新注解,从名字上可以看出大体意思是控制器增强, 在项目中来增强SpringMVC中的Controller。通常和@ExceptionHandler 结合使用,来处理SpringMVC的异常信息。
- 通知类,SpringAop,面向切面编程,类上加上这个注解,可以在不影响Controller的基础上进行增强(执行前后)在类上
- @ResponseStatus Spring3.0提供的标识在方法上或类上的注解,用状态代码和应返回的原因标记方法或异常类。调用处理程序方法时,状态代码将应用于HTTP响应。
- 指定错误的响应状态码
2.课程计划的查询
1.内存中生成树形结构数据
树形结构查询生成:
1.sql语句+mybatis的封装(方式一)
sql要进行自关联查询:树形结构有几级就需要查询几次
mybatis的封装:resultMap来封装数据-标签 collection
适用场景:使用一张表数据量不大,并且表数据能不易更改
2.通过sql查询出一个数据的所有内容,然后在程序中通过代码来生成树形结构(方式二)
sql查询:将关联数据全部查询(包含树形结构中的所有内容-list)
程序要通过逻辑来实现树形结构的生成:遍历list来生成
适用场景:使用于表数据量很大,而且数据经常变更
对于数据库的自关联查询数据库表的数据量不大,且不易变化,如果自表三级关联的表数据经常变化且数据量大,那么自关联SQL查询效率会灰常的低,原因在于:
1.自关联的连接查询可以针对小表进行查询。
2.自连接查询会进行笛卡尔全集,(笛卡尔积:两张表行数的乘积)所以自连接查询出来的记录会是此表数据量的平方。
课程计划(课程大纲)数据经常性的变化且数据量大,不适合使用自关联 SQL 语句查询。本次我们将通过后端代码查询出课程下的课程计划(课程大纲)数据,并通过递归方法来构建出课程计划的树形结构数据。具体步骤如下:
1.根据课程 CourseBaseId 查询出相关的课程计划。
2.使用 MP 的映射文件,将查询出的集合数据进行封装到 TeachplanNode 扩展类中。
3.通过 Java 的递归方法将集合的树形结构生成。
2、课程计划查询接口定义
3、课程计划查询分析
/
业务分析:
1.判断关键数据
courseBaseId companyId
2.判断业务数据
课程基础信息
判断是否存在
判断是否是同一家机构
判断课程是否删除
3.根据课程id查询一门课程下的所有课程计划-LIst
4.通过Java的递归来生成课程计划的树形结构
.生成树形结构转为dto返回给前端
*/4.需求分析:
课程计划表结构
2.树形表结构说明
课程计划采用树形结构来表示一个课程下的整个课程章节信息。树形结构一共分为三级,一级的为根级,一个课程对应一条根级课程计划数据,其他两级有多条。树形结构关系仅靠 Id 和 parentId 来表示,三级数据都在一张表中,所有需要使用表的自关联来完成三级菜单数据显示。
课程计划关联关系
内存中生成树形结构数据
树形结构查询生成:
1.sql语句+mybatis的封装(方式一)
sql要进行自关联查询:树形结构有几级就需要查询几次
mybatis的封装:resultMap来封装数据-标签 collection
适用场景:使用一张表数据量不大,并且表数据能不易更改
2.通过sql查询出一个数据的所有内容,然后在程序中通过代码来生成树形结构(方式二)
sql查询:将关联数据全部查询(包含树形结构中的所有内容-list)
程序要通过逻辑来实现树形结构的生成:遍历list来生成
适用场景:使用于表数据量很大,而且数据经常变更
对于数据库的自关联查询数据库表的数据量不大,且不易变化,如果自表三级关联的表数据经常变化且数据量大,那么自关联SQL查询效率会灰常的低,原因在于:
1.自关联的连接查询可以针对小表进行查询。
2.自连接查询会进行笛卡尔全集,(笛卡尔积:两张表行数的乘积)所以自连接查询出来的记录会是此表数据量的平方。
课程计划(课程大纲)数据经常性的变化且数据量大,不适合使用自关联 SQL 语句查询。本次我们将通过后端代码查询出课程下的课程计划(课程大纲)数据,并通过递归方法来构建出课程计划的树形结构数据。具体步骤如下:
1.根据课程 CourseBaseId 查询出相关的课程计划。
2.使用 MP 的映射文件,将查询出的集合数据进行封装到 TeachplanNode 扩展类中。
3.通过 Java 的递归方法将集合的树形结构生成。3. 课程计划信息创建和修改
后端对于业务的分析:
1.添加和修改操作的区别
添加:没有teachplanid
修改:有teachplanid
2.添加2级和3级数据的区别
2级:没有parentid
3级:有parentid.
一个课程只有一个一级课程计划
(2)添加数据时:
1.课程计划的一级(根级)
●数据来源:课程基础信息(CourseBase)
●创建时机:在第一次添加二级数据时自动生成
●注意事项:一级父级(根级)父级 ID 值为 0
2.课程计划的二级和三级节点
●数据来源:教育机构人员在页面填写的数据
●创建时机:教育机构人员提交课程计划数据
●注意事项:添加时要校验父级菜单是否存在
○二级在添加时
■父级不存在(一级):自动创建并获得获得父级Id值
■父级存在(一级):获得父级Id值
○三级在添加时
■父级不存在(二级):抛出异常
■父级存在(二级):获得父级Id值
(3)修改数据时:
1.要判断要修改的课程计划是否有 ID 值
课程计划在进行修改时,必须要有 ID 值,有 ID 值说明已经在数据库中存在该数据。
2.判断关键数据是否存在
对关键的业务数据进行判断操作,如:课程计划名称、父级 Id 值等。
- 指定错误的响应状态码
课程计划创建和修改接口定义
业务分析:
主方法:
业务分析:
0.是否开启事务
1.判断业务操作
teachplandto中是否id值
如果有:修改
如果没有:添加
3.业务操作后返回结果内容
*添加方法业务分析
添加方法业务分析:
- 判断关键数据
- courseID 课程id
- pname 课程计划名称
- companyID 教学机构id
- parentId(后端业务维护即可,此值必须赋值)根据父级数据赋值
- grade等级(后端业务维护即可,此值必须赋值)根据父级数据赋值
- 判断业务数据
- 课程基础信息
- 判断是否存在
- 判断是否是同一家教学机构
- 判断是否删除
- 判断审核状态:未提交、审核未通过 ,才可以编辑课程计划
- 课程计划
- 判断是否存在
- 判断等级:教学机构只能操作二级和三级课程计划
- 获得添加课程计划的父级数据
- 添加课程计划中要依据父级数据进行赋值
- 添加课程计划的parentID = 父级.id
- 添加课程计划的grade =父级.grade+1
- 添加课程计划的orderby =父级的子级数据的个数+1 select count(*) from teachplan where parentid = ?
- 课程id
- 课程计划名称
- 父级id
- 是否免费
- 教学机构id
- 判断业务数据
- 课程基础信息
- 判断是否存在
- 判断是否是同一家教学机构
- 判断是否删除
- 判断审核状态:未提交、审核未通过
- 课程计划
- 判断是否存在
- 判断等级:教学机构只能操作二级和三级课程计划
- 将dto转为po并保存
为了数据的安全性,只让前端修改字段:
课程计划名称、是否免费、开始时间、结束时间
- 将数据库最新的数据返回给前端(dto)
package com.xuecheng.content.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.xuecheng.api.content.model.dto.CourseBaseDTO;import com.xuecheng.api.content.model.dto.TeachplanDTO;import com.xuecheng.common.domain.code.CommonErrorCode;import com.xuecheng.common.enums.content.CourseAuditEnum;import com.xuecheng.common.enums.content.CourseModeEnum;import com.xuecheng.common.enums.content.TeachPlanEnum;import com.xuecheng.common.exception.ExceptionCast;import com.xuecheng.common.util.StringUtil;import com.xuecheng.content.common.constant.ContentErrorCode;import com.xuecheng.content.convert.TeachplanConvert;import com.xuecheng.content.entity.Teachplan;import com.xuecheng.content.entity.TeachplanMedia;import com.xuecheng.content.entity.ex.TeachplanNode;import com.xuecheng.content.mapper.TeachplanMapper;import com.xuecheng.content.service.CourseBaseService;import com.xuecheng.content.service.TeachplanMediaService;import com.xuecheng.content.service.TeachplanService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.util.CollectionUtils;import org.springframework.util.ObjectUtils;import org.springframework.util.StringUtils;import java.time.LocalDateTime;import java.util.ArrayList;import java.util.List;/*** <p>* 课程计划 服务实现类* </p>** @author itcast*/@Slf4j@Servicepublic class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {@Autowiredprivate CourseBaseService courseBaseService;@Autowiredprivate TeachplanMediaService teachplanMediaService;/** 业务分析:* 0.是否开启事务* 1.判断业务操作* teachplandto中是否id值* 如果有:修改* 如果没有:添加* 3.业务操作后返回结果内容* */@Transactionalpublic TeachplanDTO createOrModifyTeachPlan(TeachplanDTO dto, Long companyId) {// 1.判断业务操作// teachplandto中是否id值Long teachPlanId = dto.getTeachPlanId();TeachplanDTO resultDTO = null;if (ObjectUtils.isEmpty(teachPlanId)) {// 如果没有:添加resultDTO = createTeachplan(dto,companyId);} else {// 如果有:修改resultDTO = modifyTeachplan(dto,companyId);}// 3.业务操作后返回结果内容return resultDTO;}/** 业务分析:* 1.判断关键数据* 课程id* 课程计划名称* 父级id* 是否免费* 教学机构id** 2.判断业务数据* 课程基础信息* 判断是否存在* 判断是否是同一家教学机构* 判断是否删除* 判断审核状态:未提交、审核未通过* 课程计划* 判断是否存在* 判断等级:教学机构只能操作二级和三级课程计划* 3.将dto转为po并保存* 为了数据的安全性,只让前端修改字段:* 课程计划名称、是否免费、开始时间、结束时间** 4.将数据库最新的数据返回给前端(dto)* */private TeachplanDTO modifyTeachplan(TeachplanDTO dto, Long companyId) {//1.判断关键数据// 课程id// 课程计划名称// 教学机构id// 父级id// 是否免费// 2.判断业务数据// 课程基础信息// 判断是否存在// 判断是否是同一家教学机构// 判断是否删除// 判断审核状态:未提交、审核未通过if (ObjectUtils.isEmpty(dto.getParentid())||StringUtil.isBlank(dto.getIsPreview())) {ExceptionCast.cast(CommonErrorCode.E_100101);}CourseBaseDTO courseBase = verifyTeachplanMsg(dto, companyId);// 课程计划// 判断是否存在// 判断等级:教学机构只能操作二级和三级课程计划Teachplan teachplan = this.getById(dto.getTeachPlanId());if (ObjectUtils.isEmpty(teachplan)) {ExceptionCast.cast(ContentErrorCode.E_120402);}Long parentid = teachplan.getParentid();if (TeachPlanEnum.FIRST_PARENTID_FLAG.equals(parentid)) {ExceptionCast.cast(ContentErrorCode.E_120417);}// 3.将课程计划信息保存// 为了数据的安全性,只让前端修改字段:// 课程计划名称、是否免费、开始时间、结束时间// update teachplan set pname = ? , is_preview = ? ,startime =? ,end=?,change_date = ? where id = ?// PS: LambdaUpdateWrapper使用了,mp的自动填充就会失效LambdaUpdateWrapper<Teachplan> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.set(Teachplan::getPname,dto.getPname());updateWrapper.set(Teachplan::getIsPreview,dto.getIsPreview());updateWrapper.set(Teachplan::getStartTime,dto.getStartTime());updateWrapper.set(Teachplan::getEndTime,dto.getEndTime());updateWrapper.set(Teachplan::getChangeDate, LocalDateTime.now());updateWrapper.eq(Teachplan::getId, dto.getTeachPlanId());boolean result = this.update(updateWrapper);if (!result) {ExceptionCast.cast(ContentErrorCode.E_120407);}// 4.将数据库最新的数据返回给前端(dto)Teachplan po = this.getById(dto.getTeachPlanId());TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(po);return resultDTO;}private CourseBaseDTO verifyTeachplanMsg(TeachplanDTO dto, Long companyId) {//1.判断关键数据if (ObjectUtils.isEmpty(companyId)||StringUtil.isBlank(dto.getPname())||ObjectUtils.isEmpty(dto.getCourseId())) {ExceptionCast.cast(CommonErrorCode.E_100101);}Long courseId = dto.getCourseId();// 2.判断业务数据return getCourseAndVerify(companyId, courseId);}/** 业务分析:* 1.判断关键数据* pname grade courseid* 2.判断业务数据* 课程基础信息* 判断是否存在* 判断是否是同一家机构* 判断是否删除* 判断审核状态(未提交、审核未通过)** 3.获得父级数据* 添加课程计划中要依据父级数据进行赋值* 添加课程计划的parentid = 父级数据id* 添加课程计划的grade = 父级数据grade+1* 添加课程计划的orderby = 父级数据所有子级数据的个数+1** 4.保存课程计划数据并判断保存后的结果** */private TeachplanDTO createTeachplan(TeachplanDTO dto, Long companyId) {//1.判断关键数据// pname grade courseidif (StringUtils.isEmpty(dto.getPname())||ObjectUtils.isEmpty(dto.getGrade())||ObjectUtils.isEmpty(dto.getCourseId())) {ExceptionCast.cast(CommonErrorCode.E_100101);}// 2.判断业务数据// 课程基础信息// 判断是否存在// 判断是否是同一家机构// 判断是否删除// 判断审核状态(未提交、审核未通过)CourseBaseDTO courseBase = getCourseAndVerify(companyId, dto.getCourseId());// 3.获得父级数据// 添加课程计划中要依据父级数据进行赋值// 添加课程计划的parentid = 父级数据id// 添加课程计划的grade = 父级数据grade+1// 添加课程计划的orderby = 父级数据所有子级数据的个数+1Teachplan parentNode = getParentNode(courseBase,dto);dto.setGrade(parentNode.getGrade()+1);dto.setParentid(parentNode.getId());// select count(*) as ncount from teachplan where parentId = ?LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Teachplan::getParentid,parentNode.getId() );int count = this.count(queryWrapper);dto.setOrderby(count+1);// 4.保存课程计划数据并判断保存后的结果Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);boolean result = this.save(teachplan);if (!result) {ExceptionCast.cast(ContentErrorCode.E_120407);}Teachplan po = this.getById(teachplan.getId());TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(po);return resultDTO;}/** 业务分析:* 1.判断添加的课程计划是几级数据* 前端只能操作:2级和3级* 区分:三级课程计划添加时需要携带parentid* 2.获得父级数据* 获得二级* 如果获得父级数据不存在:自动在后端创建* 如果获得父级数据存在:直接返回* 获得三级* 如果获得父级数据不存在:直接抛出异常* 如果获得父级数据存在:直接返回* */private Teachplan getParentNode(CourseBaseDTO courseBase, TeachplanDTO dto) {//1.判断添加的课程计划是几级数据// 前端只能操作:2级和3级// 区分:三级课程计划添加时需要携带parentid// 2.获得父级数据if (ObjectUtils.isEmpty(dto.getParentid())) {// 获得二级// 如果获得父级数据不存在:自动在后端创建// 如果获得父级数据存在:直接返回// select * from teachplan where courseid = ? and parentid = 0 and grade = 1LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);queryWrapper.eq(Teachplan::getCourseId, courseBase.getCourseBaseId());Teachplan rootNode = this.getOne(queryWrapper);if (ObjectUtils.isEmpty(rootNode)) {rootNode = new Teachplan();rootNode.setPname(courseBase.getName());rootNode.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));rootNode.setGrade(TeachPlanEnum.FIRST_LEVEL);// rootNode.setDescription(courseBase.getDescription());rootNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);rootNode.setCourseId(courseBase.getCourseBaseId());boolean result = this.save(rootNode);if (!result) {ExceptionCast.cast(ContentErrorCode.E_120407);}}return rootNode;} else {// 获得三级// 如果获得父级数据不存在:直接抛出异常// 如果获得父级数据存在:直接返回// select * from teachplan where parentid = ?LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Teachplan::getId, dto.getParentid());Teachplan parentNode = this.getOne(queryWrapper);if (ObjectUtils.isEmpty(parentNode)) {ExceptionCast.cast(ContentErrorCode.E_120408);}return parentNode;}}private CourseBaseDTO getCourseAndVerify(Long companyId, Long courseId) {// 课程基础信息// 判断是否存在// 判断是否同一家机构// 判断是否删除// 判断课程基础信息审核状态:未提交和审核未通过CourseBaseDTO courseBase = courseBaseService.getCourseById(courseId, companyId);String auditStatus = courseBase.getAuditStatus();if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)||CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)) {ExceptionCast.cast(ContentErrorCode.E_120015);}return courseBase;}}
测试注意 parentid
{"pname":"测试第一个小节","courseId": 47,"parentid" : 99}

