今日重点
增加删除修改公共操作:
1. 课程状态必须为 未提交 或者 审核失败即可操作
2. 必须要开启事务支持
3. 操作课程必须属于该操作机构
数据库表设计规范:
常用三字段必备 主键ID 创建时间 修改时间 isxxx(判断类型)
课程基本信息的创建:
直播 : 只有开始时间和结束时间
点播 : 发布作业等等
添加数据:
1.数据的必填项
前端把必填项标红
2.用户填写数据内容
用户填写数据
用户选填数据(该数据同步后端服务的常量最合适 不能让前端写死)
3.课程图片保存(文件服务器)
后期讲解。。。。。
4.课程收费模式
免费---没有价格
收费---必须填写价格
5.一个新增课程的必须要属于一个教学机构下(有主人)
数据: 机构的标示性数据
数据赋值: 后端赋值
6.传递数据的格式
前端对个新增或修改的格式为JSON ----> 在请求体中放置(@RequestBody接收)
新增信息中: 选题的数值都采取编号格式进行保存,使用中文UTF-8编码占存空间太大!
课程封面图片类型选为Varchar 保存图片的路径地址!
课程收费类型 专属于一个支付微服务模块绑定于营销服务业务!(course_market)
系统管理微服务:
属于基础服务不需要自己开发 公司架构师搭建好拿来直接用即可,不需要关心底层实现逻辑,能帮助开发微服务功能需求即可!
RequestBody 数据封装:
功能名称+VO ValueObject(数据传入封装类)通常是指前端传输过来的数据封装对象,VO中属性一般和前端所需的数据或表单中的数据一致!
Response 使用DTO进行封装,并且要添加俩个前端需要的营销参数!
VO在本项目中的定义 : 接受前端新增或者修改的数据内容
QO在本项目中的定义 : 接受前端条件对象封装查询数据的内容
PO在本项目中的定义 : 定义表结构数据内容
DTO在本项目中的定义 : 响应前端所需要的结果数据内容
Controller层 接受前端穿入参数VO实体类,将VO实体类转换为DTO给service进行操作使用! 返回值为service层处理过的JSON格式DTO实体类!
service 层只接受DTO的关键数据进行使用!将PTO转换为PO数据与DAO层进行交互,但是返回类型还是DTO!
DAO层 PO实体类 CourseBase属性
新增课程基础信息的接口实现:
1. (增删改)数据事务必须开启 @Trnsactional
2. 判断关键信息: 必填项必须穿入数据 如果没有拒绝添加!
前端页面 * 标识为必填项 接口文档的必须项 数据库表结构非空的字段(companyid name mt st grade trachmode auditstus )
课程信息是否为收费内容 如果标识为收费内容 必须有价格参数传入!
3. 特定数据内容不是由前端维护,都是由后端维护
id create_date change_date
status(数据库默认值为1 添加可不填写)
audit_status(新增状态为未提交,需要机构自行点击前端按钮进行提交审核)
4. 添加数据到数据库 (需要操作俩张表 course_base<先保存生成ID> course_market<使用base的ID进行保存>)
对数据库的响应结果做判断 (受影响行数)
5. 返回操作后的数据dto
course_nase
course_market
课程基础信息表设计 :
coursebase 前端数据 审核数据 数据库必要数据 教学机构的隔离数据ID
coursemaket courseid price charge
课程基础信息修改:
前置判断条件: 1.课程存在 2.要修改的课程必须属于该机构 3. 课程属于未提交或者审核未通过才可以!
接口定义:
PUT/content/course 根据body体修改数据
GET/content/{courseBaseId} 根据ID查询数据
修改业务分析:
第一步:
1. 判断关键数据,前端传来的数据,需要校验的内容
courseBaseId CompanyId
2. 判断业务数据,根据关键数据来操作的数据内容,在后端存储
判断课程信息是否存在
根据ID查询
判断是否是同一家机构
将数据库中的CourseBase和Companyid和前端传入的Companyid进行对比
判断课程是否删除
status: 1 (使用中) 0 (删除)
3. 将po数据转换未dto返回前端
courseBase courseBaseMarket
第二步:
1. 修改数据 需要开启事务!
2. 判断关键数据 dto中 直接调用添加时关键数据的判断 课程id不能为空
3. 判断业务数据 课程基础信息 判断是否存在 是否为同一家机构 判断是否删除 判断课程审核状态(未提交,未通过审核)
4. 一系列判断结束后,进行修改表数据!
修改课程基础信息 修改课程营销数据
收费课程直接覆盖源数据(charge,price)
5. 封装结果集返回给前端
coursebase courseMarket
什么样的数据可以逻辑删除
对于用户比较重要的数据资料,还有数据量比较大的数据 一般设置未逻辑删除!
存在实际数据意义的数据库表信息,可以进行逻辑删除,以免客户万一误操作删除重要信息,无法找回!
所以很有必要设置为逻辑删除,将表中某个数值字段按照规定设置为已被删除即可!
java Bean:
RequestBody 数据封装: 功能名称+VO ValueObject(数据传入封装类)通常是指前端传输过来的数据封装对象,VO中属性一般和前端所需的数据或表单中的数据一致!
Response 使用DTO进行封装,并且要添加俩个前端需要的营销参数!
VO在本项目中的定义 : 接受前端新增或者修改的数据内容
QO在本项目中的定义 : 接受前端条件对象封装查询数据的内容
PO在本项目中的定义 : 定义表结构数据内容
DTO在本项目中的定义 : 响应前端所需要的结果数据内容
Controller层 接受前端穿入参数VO实体类,将VO实体类转换为DTO给service进行操作使用! 返回值为service层处理过的JSON格式DTO实体类!
service 层只接受DTO的关键数据进行使用!将PTO转换为PO数据与DAO层进行交互,但是返回类型还是DTO!
DAO层 PO实体类 CourseBase属性
课程中的审核状态流程说明:
一共5种状态 已提交 未提交 审核未通过 审核中 已发布
新增数据默认设置为未提交审核
机构要想操作自己的课程基本信息内容,必须要保证操作的课程状态为:
课程未提交 课程审核未通过 其他一律不可以进行增删改操作!
学习目标
1熟悉新增课程需求
2能够完成新增课程基本信息的功能实现
3能够根据文档定义课程修改信息接口
4能够完成修改课程基本信息的功能实现
5能够根据文档定义删除课程基本信息接口
6能够完成删除课程基本信息的功能实现
1.课程基本信息创建
学成在线需要教育机构入住到本平台后,通过教育机构中的老师来录入课程的相关信息。信息录入后由平台来管理,针对数据录入的需求,我们下面要开发课程基本信息创建的功能实现。
1.1 信息创建接口业务需求
课程基本数据的创建需要用户在页面中将课程的基本信息填写并提交给后端微服务,微服务将数据保存到数据库表中。
课程基本信息添加流程:
内容管理的课程业务流程
1.1.1 接口业务需求
具体需要如下:
1.机构老师点击“新增课程”,选择添加课程类型:直播课程或录播课程。
2.选择完课程类型后,填写基本内容:课程名称、课程适用人群、课程介绍等信息。
3.课程中的课程分类信息、课程等级、教学模式系统数据,需要前端查询系统管理服务后端接口。
4.填写完毕后,保存课程基本信息,并标识一个机构下的数据。
操作界面展示如下:
课程添加按钮示例图
选择直播或录播课程
前端课程新增页面效果
上图解释:
注释①:课程分类—数据来资源系统管理服务中的课程分类信息
注释②:课程等级—数据来资源系统管理服务中的数据字典信息
注释③:课程类型—数据来资源系统管理服务中的数据字典信息
课程基本信息创建前后端调用过程:
前后端调用示例图
上图解释:
1.前端会调用系统管理微服务获得页面常量信息并在页面显示。
2.用户在页面中填写完数据后向内容管理微服务提交课程基本信息数据并保存。
需求结论:
1.课程基本信息创建,前端需要两个微服务来完成
2.前端调用 ‘系统管理微服务’ 完成页面中常量信息的页面显示
3.前端调用 ‘内容管理系统微服务’ 完成课程基本信息的保存
1.1.2 课程基础信息数据模型
下面我来分析下这张表的主要字段。
课程基本信息(course_base)表结构
●课程基本信息表分析
机构相关数据
课程基本信息是附属于一个教育架构下,学成在线主要是提供在线教学的平台。教学机构入驻后,在平台创建课程数据。
课程自身信息
对课程数据基本信息的描述,说明课程的教学模式、课程名称等。
课程数据操作数据
课程数据的操作会在相关字段进行记录,例如:课程数据的创建时间、课程数据的修改时间、课程数据的创建者等。
课程审核信息
教育结构创建出课程后,学成在线运营商需要对其进行数据审核,审核的操作也会进行记录,例如:审核人、审核时间等。
1.1.3 课程的状态说明
课程基础信息在内容管理中会有状态的显示,课程状态为 5 个状态,分别为:
1.未提交
2.已提交
3.审核通过
4.审核未通过
5.已发布
课程状态示意图
在添加对新增加的课程,应给予 “未提交” 的状态。
1.2 内容管理系统业务实现
下面将会实现课程基本信息保存功能,课程基本信息的保存是在内容管理系统服务,所以此接口的实现将在内容管理微服务中开发,接口信息依然定义在 Api 工程中。
1.2.1 信息创建接口定义
1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址
接口传入传出列表
上图解释:
两个属性源于课程营销信息,需要在 CourseBaseDTO中定义。
2.内容管理课程新增操作
前端传入的数据中包含课程基本信息和课程营销信息,所有在实现课程新增功能的同时,还需要对课程营销数据保存。
课程新增保存数据示意图
3. 课程数据封装类
在 ‘项目开发规范文档.md — 接口开发规范’ 文档声明,传入的参数使用 VO 实体类来封装传入的参数。 传出参数使用 DTO 已经声明,但根据参数列表,我们需要在 DTO 中将课程营销 DTO 作为属性。
保存和修改操作数据流转图
VO (Value Object)为值对象,通常是前端传输过来的数据封装对象。 VO 中的属性一般和前端所需的数据或表单中的数据一致。<br />VO在本项目中的定义:接受前端新增或修改的数据内容。<br />QO在本项目中的定义:接受前端查询条件数据内容。<br />PO在本项目中的定义:定义表结构数据内容。<br />DTO在本项目中的定义:返回前端所需要的结果数据内容。<br />●课程基本信息 VO 数据(数据传入封装类)
package com.xuecheng.api.content.model.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* <p></p>
*
* @Description:
*/
@Data
@ApiModel(value="CourseBaseVO", description="课程基本信息视图类,用于页面对课程基本信息添加和修改,操作的属性比较有限,不开放的属性不让操作")
public class CourseBaseVO {
@ApiModelProperty(value = "课程Id")
private Long courseBaseId;
@ApiModelProperty(value = "课程名称", required = true)
private String name;
@ApiModelProperty(value = "适用人群", required = true)
private String users;
@ApiModelProperty(value = "课程标签")
private String tags;
@ApiModelProperty(value = "大分类", required = true)
private String mt;
@ApiModelProperty(value = "小分类", required = true)
private String st;
@ApiModelProperty(value = "课程等级", required = true)
private String grade;
@ApiModelProperty(value = "教学模式(普通,录播,直播等)", required = true)
private String teachmode;
@ApiModelProperty(value = "课程介绍")
private String description;
@ApiModelProperty(value = "课程图片", required = true)
private String pic;
@ApiModelProperty(value = "收费规则,对应数据字典", required = true)
private String charge;
@ApiModelProperty(value = "价格")
private BigDecimal price;
}
课程基本信息 DTO 数据(数据传出封装类—项目已经创建)
@Data
@ApiModel(value="CourseBaseDTO", description="课程基本信息DTO")
public class CourseBaseDTO implements Serializable {
//其他代码省略
//添加营销信息的属性
@ApiModelProperty(value = "收费规则,对应数据字典", required = true)
private String charge;
@ApiModelProperty(value = "价格")
private BigDecimal price;
}
4. 使用代码生成器生成课程营销代码
● PO 数据 — CourseMarket
● DAO 持久层代码 — CourseMarketMapper和映射文件
● Service 业务层代码 — CourseMarketService接口和实现类
生成的代码后拷贝到 xc-content-service 基础包下。
5. 接口编写
在 xc-api 接口工程中的 ContentBaseApi 接口定义方法:
@Api(tags = "课程基本信息服务接口",description = "对课程基本信息业务操作")
public interface CourseBaseApi {
//其他代码省略
@ApiOperation(value = "保存课程基本信息")
@ApiImplicitParam(name = "courseBaseVO",
value = "课程基本视图信息", required = true,
dataType = "CourseBaseVO", paramType = "body")
CourseBaseDTO createCourseBase(CourseBaseVO courseBaseVO);
}
1.2.2 信息创建接口开发
在之前的代码基础上开发课程基本信息添加功能,已经对数据源、持久层框架的配置信息都已配置,下面我们直接开始在应用三层(controller、service、dao)开发。
1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
●接口
@Service
public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper, CourseBase> implements CourseBaseService {
//其他代码省略
/*
* 业务分析:
* 1.是否要开启事务(查询不需要开启,增删改时需要开启事务)
* 2.判断关键数据
* 关键数据:数据来源是前端
* 添加时的必要数据:
* 1.从数据库结构-非空字段
* 2.前端的必要数据(红色*)
* 3.接口文档Yapi
* 数据内容:companyId、name、mt、st、grade、teachmode、users、pic、charge、price(收费课程需要判断)
* 如果必要数据没有给,告诉前端数据必填:抛出异常(终止程序、传递错误信息)
* 3.将dto数据转为po数据
* po数据都是全都传过来的信息
* po中对于新增数据一些字段需要在业务层进行赋值
* auditStatus
* status(数据库会默认赋值)
* createdate(mp自动填充)
* changedate(mp自动填充)
* 4.保持数据-判断保持的结果
* PS:要先保持课程基础信息表,课程营销表要记录coursebaseid
* 课程基础信息表
* 课程营销表
* PS:如果保持两张表时,其中有一张表保存失败,需要让全部的数据进行回滚:抛出异常
* 5.返回数据库最新的数据DTO
* */
@Override
@Transactional
public CourseBaseDTO createCourseBase(CourseBaseDTO dto) {
// 2.判断关键数据
// 关键数据:数据来源是前端
// 添加时的必要数据:
// 1.从数据库结构-非空字段
// 2.前端的必要数据(红色*)
// 3.接口文档Yapi
// 数据内容:companyId、name、mt、st、grade、teachmode、users、pic、charge、price(收费课程需要判断)
// 如果必要数据没有给,告诉前端数据必填:抛出异常(终止程序、传递错误信息)
if (ObjectUtils.isEmpty(dto.getCompanyId())) {
// 业务异常:程序员在做业务判断时,数据有问题。
// 异常:
// 1.终止程序
// 2.传递错误信息
// 3.使用运行时异常来抛出
// 运行时异常不需在编译期间处理
throw new RuntimeException("公司id不能为空");
}
if (StringUtil.isBlank(dto.getName())) {
throw new RuntimeException("课程名称不能为空");
}
if (StringUtil.isBlank(dto.getMt())) {
throw new RuntimeException("课程大分类不能为空");
}
if (StringUtil.isBlank(dto.getSt())) {
throw new RuntimeException("课程小分类不能为空");
}
if (StringUtil.isBlank(dto.getGrade())) {
throw new RuntimeException("课程等级不能为空");
}
if (StringUtil.isBlank(dto.getTeachmode())) {
throw new RuntimeException("课程教学模式不能为空");
}
if (StringUtil.isBlank(dto.getUsers())) {
throw new RuntimeException("使用人群不能为空");
}
if (StringUtil.isBlank(dto.getCharge())) {
throw new RuntimeException("课程收费不能为空");
}
// 判断收费课程,价格不能为空,必须要大于0
if (CourseChargeEnum.CHARGE_YES.getCode().equals(dto.getCharge())) {
if (ObjectUtils.isEmpty(dto.getPrice()) || dto.getPrice().floatValue() <= 0) {
throw new RuntimeException("收费课程价格非法,请填入合法的价格");
}
} else {
// 课程为免费课程,价格为0
dto.setPrice(new BigDecimal("0"));
}
// 3.将dto数据转为po数据
// po数据都是全都传过来的信息
// po中对于新增数据一些字段需要在业务层进行赋值
// auditStatus
// status(数据库会默认赋值)
// createdate(mp自动填充)
// changedate(mp自动填充)
CourseBase courseBase = CourseBaseConvert.INSTANCE.dto2entity(dto);
// auditStatus:新增课程的状态为-->未提交
courseBase.setAuditStatus(CourseAuditEnum.AUDIT_UNPAST_STATUS.getCode());
// 4.保持数据-判断保持的结果
// PS:要先保持课程基础信息表,课程营销表要记录coursebaseid
// 课程基础信息表
boolean baseResult = this.save(courseBase);
if (!baseResult) {
throw new RuntimeException("课程基础信息保存失败");
}
// 课程营销表
// PS:如果保持两张表时,其中有一张表保存失败,需要让全部的数据进行回滚:抛出异常
CourseMarket courseMarket = new CourseMarket();
courseMarket.setCourseId(courseBase.getId());
courseMarket.setCharge(dto.getCharge());
courseMarket.setPrice(dto.getPrice().floatValue());
boolean marketResult = courseMarketService.save(courseMarket);
if (!marketResult) {
throw new RuntimeException("课程营销保存失败");
}
// 5.返回数据库最新的数据DTO
CourseBase po = this.getById(courseBase.getId());
CourseBaseDTO resultDTO = CourseBaseConvert.INSTANCE.entity2dto(po);
resultDTO.setCharge(dto.getCharge());
resultDTO.setPrice(dto.getPrice());
return resultDTO;
}
}
业务层对数据的判断,不合理数据以 RuntimeException 进行抛出。使用异常的好处有:
1.RuntimeException 运行异常在编码阶段不需要使用 try/catch 进行处理。
2.异常的抛出可以是的当前业务代码终止,并返回错误数据。
2.Controller 编写
在 ContentBaseController 中新增方法,如下:
@RestController
public class CourseBaseController implements CourseBaseApi {
@Autowired
private CourseBaseService courseBaseService;
//其他代码省略
@PostMapping("course")
public CourseBaseDTO createCourseBase(@RequestBody CourseBaseVO courseBaseVO) {
CourseBaseDTO courseBaseDTO = CourseBaseConvert.INSTANCE.vo2dto(courseBaseVO);
//通过工具类获得公司和用户信息
Long companyId = SecurityUtil.getCompanyId();
//设置公司和登录用户信息
courseBaseDTO.setCompanyId(companyId);
return courseBaseService.createCourseBase(courseBaseDTO);
}
}
课程基础信息下赋值机构 Id 值的原因:
1.一门课程必须要在一个教育机构下。
2.课程中必须要存储教育机构 Id 值。
3.教育机构是在登录状态下,通过 SecurityUtil 来获得教育机构 Id 值, 后面的认证课程中会来讲解其原因。
1.2.3 信息创建接口测试
测试环境需要启动的微服务有:
1.注册中心 Nacos
2.服务网关 xc-gateway-service (端口:63010)
3.内容管理 xc-content-service (端口:63040)
1. 使用 postman 在请求信息中添加请求参数
●请求头(访问令牌)
请求头的 key 值: authorization
请求头的 value 值:
Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9
●请求体信息(课程添加测试数据)
{
"name" : "测试数据",
"mt" : "1-3",
"st" : "1-3-1",
"grade" : "204002",
"teachmode" : "200001",
"charge" : "201001"
}
1.3 系统管理业务实现(已实现)
课程基本信息保存功能中,前端需要在页面中显示课程分类和常量信息(课程等级、学习模式…), 这些数据在 ‘系统给管理微服务’ 中,对此需要构建 ‘系统管理微服务’ 并进行开发。
项目系统管理功能示例图
要对 ‘系统给管理微服务’ 进行开发,开发功能大致是分为两类:<br /> **1.系统数据信息获取**<br /> ● 根据 Code来获得指定系统数据信息<br /> ●查询所有系统数据信息(便于前端用于存储)<br /> **2.课程分类信息获取**<br /> ● 查询所有课程分类树形结构信息<br /> 针对上面两个功能我们需要分别开发三个接口,下面先来开发 ‘系统常量信息获取’ 。
1.3.1 系统数据信息业务实现
1.3.1.1 数据模型(表结构)
系统管理单独使用一个数据库(xc_system)中,下面我们来创建数据库和数据库表结构,并了解表中结构。
1. 导入数据库数据
在今天下发资料里的数据脚本导入到本地 MySQL 数据中,资料位置在 ‘资料/数据库脚本/xc_system.sql’ 。
导入后的数据库内容
2. 数据字典名称解释
在开发中我们会遇到两种情况:
1.开发中有些变量信息,其内容固定,并在多个地方使用。面对多变的需求,这些变量可能会有所调整。
2.在用户界面中显示下拉框菜单里的数据,一般不会写死,需要进行统一管理。
固定变量值示例图
面对上面的两个问题中的变量信息,通常我们使用数据库的表来进行管理,而这张表的名称为:数据字典。<br />简而言之,数据字典是描述系统数据的信息集合。<br />**3. 数据字典表说明**
name :标识数据字典的名称
code:数据字典的编号
item_values:数据字典项集合数据(json格式)
1.3.1.2 环境配置
1.在 xc-parent 父工程下导入模块 xc-system-service
将今天下发资料中的 xc-system-service 系统管理微服务工程解压并导入的 IDEA 工程项目中。
2. 工程结构
●创建包结构本工程为内容管理系统微服务,其包的结构为:
○基础包结构为:com.xuecheng.system
○控制层包:com.xuecheng.system.controller
○服务层包:com.xuecheng.system.service
○持久层:com.xuecheng.system.mapper
○配置:com.xuecheng.system.config
○实体:com.xuecheng.system.entity
●启动类 在基础包结构下创建 Spring Boot 启动类
package com.xuecheng.system;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* <p>
* 系统管理启动类
* </p>
*
* @Description:
*/
@SpringBootApplication
@EnableSwagger2Doc
@EnableDiscoveryClient
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class,args);
}
}
4. 配置文件
●启动配置文件 bootstrap.yml 在 maven 的结构中的 src/main/resources 里创建。
#微服务启动参数
spring:
application:
name: system-service
cloud:
nacos:
discovery: #配置注册中心
server-addr: 192.168.94.129:8848
namespace: 5c0b093c-4084-46b5-bf33-899321cb7ef5
group: ${group.name}
config: #配置中心
server-addr: 192.168.94.129:8848
namespace: 5c0b093c-4084-46b5-bf33-899321cb7ef5
group: ${group.name}
file-extension: properties
shared-configs:
- dataId: mp-config.properites
group: ${group.name}
profiles: # 激活配置环境
active: dev
# 组名称
group:
name: xc-group
# 日志文件配置路径
logging:
config: classpath:./log4j2-dev.xml
# swagger 文档配置
swagger:
title: "学成在线系统管理系统"
description: "系统管理对整个系统数据进行业务管理"
base-package: com.xuecheng.system
enabled: true
version: 1.0.0
上面的配置文件主要信息包括四类:
1.微服务的基本信息
2.日志配置路径
3.swagger配置信息
4.nacos配置信息
5.配置中心配置参数
在 nacos 的 dev环境下创建系统管理微服务的配置 system-service-dev.properties,配置如下:
#srpingboot http 配置信息
server.servlet.context-path = /system
server.port=63110
#srping druid 配置信息
spring.datasource.url = jdbc:mysql://192.168.94.129:3306/xc_system?userUnicode=true&useSSL=false&characterEncoding=utf8
6.公共配置信息
直接应用 mp-config.properties、spring-http-config.properties 、spring-druid-config.properties 公共配置即可
1.3.2 课程分类业务实现
课程基本信息保存中,对系统管理服务的查询常量信息接口开发完毕,下面将开发系统管理服务的课程分类查询。
1.3.2.1 数据模型(表结构)
之前我们已经将系统服务的数据库脚本导入到本次仓库中,无需再次导入。
1. 课程分类表说明
自身信息描述:id,name,label
父信息描述:parentId
树形结构排序:orderby
2.PO实体类定义
package com.xuecheng.system.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 课程分类
* </p>
*/
@Data
@TableName("course_category")
public class CourseCategory implements Serializable {
/**
* 主键
*/
private String id;
/**
* 分类名称
*/
private String name;
/**
* 分类标签默认和名称一样
*/
private String label;
/**
* 父结点id(第一级的父节点是0,自关联字段id)
*/
private String parentid;
/**
* 是否显示
*/
private Integer isShow;
/**
* 排序字段
*/
private Integer orderby;
/**
* 是否叶子
*/
private Integer isLeaf;
}
2.课程基本信息修改-实战
教育机构中的老师对录入课程基本信息,平台来管理应提供对其信息就行修改的业务操作,针对课程基本信息数据修改的需求,我们下面要开发课程基本信息创建的功能实现。
2.1 课程信息修改接口业务需求
在课程基本数据的添加之后,通过课程基本信息列表中的修改按钮对单个信息进行修改操作。在点击后修改按钮后跳入修改页面,并将之前的信息回显到修改页面中的表单当中。将信息修改完毕之后,通过提交按钮向后端微服务提交数据进行修改操作。
2.1.1 接口业务需求
●操作界面展示如下:
课程列表编辑按钮
编辑页面回显内容数据
●具体需要如下:
1.机构老师在课程基本信息列表中点击“修改课程”,进入修改课程页面。
2.页面跳入到课程基本信息编辑页面,并将之前课程基本数据进行回显。
3.课程基本信息修改完信息,点击提交按钮提交数据到后端微服务中。
4.课程基本信息的状态不能为’已提交’ 、’审核通过’、’课程已发布’ 状态。
5.教学机构只能修改自己机构下的数据。
2.1.2 数据模型(表结构)和实体类定义
数据库表所涉及到为 xc_content 中的 course_base 表。之前在信息查询中有所介绍,在此对表结构和实体类PO类不再介绍。
课程基础信息在内容管理中会有状态的显示,课程状态为 5 个状态,分别为:
1.未提交
2.已提交
3.审核通过
4.审核未通过
5.已发布
其中,课程状态为 ‘未提交’、‘审核未通过’状态才可以修改数据。
课程状态示意图
2.2 课程信息修改功能业务实现
下面将会实现课程基本信息修改功能,课程基本信息的修改是在内容管理系统服务,所以此接口的实现将在内容管理微服务中开发,接口信息依然定义在 Api 工程中。
根据课程信息修改功能业务现在需要定义两个接口:
1.根据课程 Id 查询课程基本信息接口—用于前端数据回显。
2.修改课程信息接口—用于前端保存修改后的课程基本信息数据。
1.判断关键数据
2.判断课程的审核状态
3.判断课程是否删除
2.2.1 信息修改接口定义
1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址
传入传出参数—更新课程信息
传入传出参数—根据Id查询课程基本信息
2. 课程数据封装类
在 ‘项目开发规范文档.md — 接口开发规范’ 文档声明,传入的参数使用 VO 实体类来封装传入的参数。 传出参数使用 DTO 已经声明,但根据参数列表,我们需要在 DTO 中将课程营销 DTO 作为属性。
VO 和 DTO 在课程基本信息创建已经声明好,无需再次声明。
2. 接口编写
在 xc-api 接口工程中的 ContentBaseApi 接口定义方法:
package com.xuecheng.api.content;
import com.xuecheng.api.content.model.qo.QueryCourseBaseModel;
import com.xuecheng.common.domain.page.PageVO;
/**
* <p>
* 课程基本信息 Api 接口路径定义
* </p>
*
* @Description: 课程基本信息 Api 接口路径定义
*/
@Api(tags = "课程基本信息服务接口",description = "对课程基本信息业务操作")
public interface CourseBaseApi {
//其他代码省略
@ApiOperation(value = "根据Id获取课程基本信息")
@ApiImplicitParam(name = "courseBaseId", value = "课程基本信息ID", required = true, dataType = "Long", paramType = "path", example = "1")
CourseBaseDTO getCourseBase(Long courseBaseId);
@ApiOperation("更新课程基本信息")
@ApiImplicitParam(name = "courseBaseVO", value = "课程基本信息VO", required = true, dataType = "CourseBaseVO", paramType = "body")
CourseBaseDTO modifyCourseBase(CourseBaseVO courseBaseVO);
}
2.2.2 信息修改接口开发
在之前的代码基础上开发课程基本信息添加功能,已经对数据源、持久层框架的配置信息都已配置,下面我们直接开始应用三层开发。
1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
●接口
public interface CourseBaseService extends IService<CourseBase> {
//其他代码省略
/**
* 根据Id查询课程基础信息
* @param courseBaseId 课程的Id值
* @param companyId 公司的Id值
* @return CourseBaseDTO
*/
CourseBaseDTO getCourseBaseById(Long courseBaseId,Long companyId);
/**
* 修改课程基础信息
* @param dto CourseBaseDTO
* @return
*/
CourseBaseDTO modifyCourseBase(CourseBaseDTO dto);
}
●实现类
@Service
public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper, CourseBase> implements CourseBaseService {
//其他代码省略
/*
* 业务分析:
* 1.判断关键数据:前端传来的数据,需要校验的内容
* courseBaseId, companyId
*
* 2.判断业务数据:根据关键数据来操作的数据内容,在后端存储
* 判断课程信息是否存在
* 根据id来查询
* 判断是否是同一家机构
* 将数据库中的CourseBase里Companyid和前端传来的Companyid进行对比
* 判断课程数据是否删除
* status:1-使用 0-删除
*
* 3.将po数据转为dto并返回
* courseBase
* courseMarket
* */
public CourseBaseDTO getCourseById(Long courseBaseId, Long companyId) {
// 1.判断关键数据:前端传来的数据,需要校验的内容
// courseBaseId, companyId
if (ObjectUtils.isEmpty(courseBaseId)||
ObjectUtils.isEmpty(companyId)
) {
throw new RuntimeException("传入参数和接口不匹配");
}
// 2.判断业务数据:根据关键数据来操作的数据内容,在后端存储
// 判断课程信息是否存在
// 根据id来查询
// 判断是否是同一家机构
// 将数据库中的CourseBase里Companyid和前端传来的Companyid进行对比
CourseBase courseBase = getCourseBaseByBaseId(courseBaseId, companyId);
// 3.将po数据转为dto并返回
// courseBase
// courseMarket
CourseBaseDTO courseBaseDTO = CourseBaseConvert.INSTANCE.entity2dto(courseBase);
// 课程营销
CourseMarket courseMarket = getCourseMarketByCourseId(courseBaseId);
courseBaseDTO.setCharge(courseMarket.getCharge());
courseBaseDTO.setPrice(new BigDecimal(courseMarket.getPrice().toString()));
return courseBaseDTO;
}
private CourseBase getCourseBaseByBaseId(Long courseBaseId, Long companyId) {
// select * from course_base where id = ? and companyId = ?
LambdaQueryWrapper<CourseBase> baseQueryWrapper = new LambdaQueryWrapper<>();
baseQueryWrapper.eq(CourseBase::getId, courseBaseId);
baseQueryWrapper.eq(CourseBase::getCompanyId, companyId);
// baseQueryWrapper.eq(CourseBase::getStatus, CommonEnum.USING_FLAG.getCodeInt());
CourseBase courseBase = this.getOne(baseQueryWrapper);
if (ObjectUtils.isEmpty(courseBase)) {
throw new RuntimeException("课程不存在");
}
Integer status = courseBase.getStatus();
if (!(CommonEnum.USING_FLAG.getCodeInt().equals(status))) {
throw new RuntimeException("课程信息已经被删除");
}
return courseBase;
}
/*
* 业务分析:
* 0.是否开启事务
* 1.判断关键数据
* 可以直接调用添加时对关键数据的判断
* 判断课程的id不能为空
* 2.判断业务数据
* 课程基础信息
* 判断是否存在
* 判断是否是同一家教学机构
* 判断是否删除
* 判断课程审核状态
* 未提交和审核未通过才可以进行修改
* 课程营销(根据courseid)
*
* 3.修改数据
* 修改课程基础信息数据
* 修改课程营销数据
* 如果是收费课程,直接覆盖源数据(charge、price)
* 如果是免费课程,直接覆盖源数据(charge、price=0.0)
*
* 4.将数据库最新数据返回给前端
* coursebase
* coursemarket
* */
@Transactional
public CourseBaseDTO modifyCourseBase(CourseBaseDTO dto) {
//1.判断关键数据
// 可以直接调用添加时对关键数据的判断
// 判断课程的id不能为空
verifyCourseMsg(dto);
Long courseBaseId = dto.getCourseBaseId();
if (ObjectUtils.isEmpty(courseBaseId)) {
throw new RuntimeException("修改课程id不能为空");
}
// 2.判断业务数据
// 课程基础信息
// 判断是否存在
// 判断是否是同一家教学机构
// 判断是否删除
// 判断课程审核状态
// 未提交和审核未通过才可以进行修改
// 课程营销(根据courseid)
getCourseByLogic(dto.getCompanyId(), courseBaseId);
// 课程营销
CourseMarket courseMarket = getCourseMarketByCourseId(courseBaseId);
// 将dto转为po
// 课程基础信息表
// 有些内容是不容修改的:companyid、auditstatus、status
CourseBase po = CourseBaseConvert.INSTANCE.dto2entity(dto);
// 3.修改数据
// 修改课程基础信息数据
// 修改课程营销数据
// 如果是收费课程,直接覆盖源数据(charge、price)
// 如果是免费课程,直接覆盖源数据(charge、price=0.0)
po.setCompanyId(null);
po.setAuditStatus(null);
po.setStatus(null);
boolean baseResult = this.updateById(po);
if (!baseResult) {
throw new RuntimeException("课程信息修改失败");
}
// 课程营销数据表
// charge price
// 如果使用mq修改一张表中的极个别数据,UpdateWrapper
// update course_market set charge=xxx,price=xxx where courseid=xx
LambdaUpdateWrapper<CourseMarket> marketUpdateWrapper = new LambdaUpdateWrapper<>();
marketUpdateWrapper.set(CourseMarket::getCharge,dto.getCharge());
String charge = dto.getCharge();
if (CourseChargeEnum.CHARGE_YES.getCode().equals(charge)) {
BigDecimal price = dto.getPrice();
if (ObjectUtils.isEmpty(price)) {
throw new RuntimeException("收费课程价格不能为空");
}
marketUpdateWrapper.set(CourseMarket::getPrice, dto.getPrice().floatValue());
} else {
// 如果课程为免费,需要价格赋值为0
marketUpdateWrapper.set(CourseMarket::getPrice, 0F);
}
marketUpdateWrapper.eq(CourseMarket::getCourseId, courseBaseId);
boolean marketResult = courseMarketService.update(marketUpdateWrapper);
// 修改数据时要判断修改后结果
if (!marketResult) {
throw new RuntimeException("修改课程营销数据失败");
}
// 4.将修改后的最新数据返回给前端
CourseBaseDTO resultDTO = getLastCourseBaseDTO(dto, courseBaseId);
return resultDTO;
}
private void verifyCourseMsg(CourseBaseDTO dto) {
if (ObjectUtils.isEmpty(dto.getCompanyId())) {
// 业务异常:程序员在做业务判断时,数据有问题。
// 异常:
// 1.终止程序
// 2.传递错误信息
// 3.使用运行时异常来抛出
// 运行时异常不需在编译期间处理
ExceptionCast.cast(ContentErrorCode.E_120018);
}
if (StringUtil.isBlank(dto.getName())) {
ExceptionCast.cast(ContentErrorCode.E_120004);
}
if (StringUtil.isBlank(dto.getMt())) {
ExceptionCast.cast(ContentErrorCode.E_120002);
}
if (StringUtil.isBlank(dto.getSt())) {
ExceptionCast.cast(ContentErrorCode.E_120003);
}
if (StringUtil.isBlank(dto.getGrade())) {
ExceptionCast.cast(ContentErrorCode.E_120007);
}
if (StringUtil.isBlank(dto.getTeachmode())) {
ExceptionCast.cast(ContentErrorCode.E_120006);
}
if (StringUtil.isBlank(dto.getUsers())) {
ExceptionCast.cast(ContentErrorCode.E_120019);
}
if (StringUtil.isBlank(dto.getCharge())) {
ExceptionCast.cast(ContentErrorCode.E_120020);
}
}
private void getCourseByLogic(Long companyId, Long courseBaseId) {
CourseBase courseBase = getCourseBaseByBaseId(courseBaseId, 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)
) {
throw new RuntimeException("课程审核状态异常");
}
}
private CourseMarket getCourseMarketByCourseId(Long courseBaseId) {
LambdaQueryWrapper<CourseMarket> marketQueryWrapper = new LambdaQueryWrapper<>();
marketQueryWrapper.eq(CourseMarket::getCourseId, courseBaseId);
CourseMarket courseMarket = courseMarketService.getOne(marketQueryWrapper);
if (ObjectUtils.isEmpty(courseMarket)) {
throw new RuntimeException("课程营销数据不存在");
}
return courseMarket;
}
private CourseBaseDTO getLastCourseBaseDTO(CourseBaseDTO dto, Long id) {
CourseBase po = this.getById(id);
CourseBaseDTO resultDTO = CourseBaseConvert.INSTANCE.entity2dto(po);
resultDTO.setCharge(dto.getCharge());
resultDTO.setPrice(dto.getPrice());
return resultDTO;
}
}
3. controller编写
@RestController
public class CourseBaseController implements CourseBaseApi {
@Autowired
private CourseBaseService courseBaseService;
//其他代码省略
@GetMapping(value = "course/{courseBaseId}")
public CourseBaseDTO getCourseBase(@PathVariable Long courseBaseId) {
//1.获得公司Id值
Long companyId = SecurityUtil.getCompanyId();
//2.获得课程基本信息数据
CourseBaseDTO courseBase = courseBaseService.getCourseBaseById(courseBaseId,companyId);
return courseBase;
}
@PutMapping("course")
public CourseBaseDTO modifyCourseBase(@RequestBody CourseBaseVO courseBaseVO) {
//1.将VO数据转为DTO数据
CourseBaseDTO courseBaseDTO = CourseBaseConvert.INSTANCE.vo2dto(courseBaseVO);
//2.通过工具类获得公司和用户信息
Long companyId = SecurityUtil.getCompanyId();
courseBaseDTO.setCompanyId(companyId);
return courseBaseService.modifyCourseBase(courseBaseDTO);
}
}
2.2.3 信息修改接口测试
测试环境需要启动的微服务有:
1.注册中心 Nacos
2.服务网关 xc-gateway-service (端口:63010)
3.内容管理 xc-content-service (端口:63040)
1. 使用 postman 在请求信息中添加请求参数
●请求头(访问令牌)
请求头的 key 值: authorization
请求头的 value 值:
Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9
●请求体信息(课程修改测试数据)
{
"courseBaseId": 39,
"name" : "SpringBoot核心1111",
"mt" : "1-3",
"st" : "1-3-2",
"grade": "204003",
"teachmode": "200002",
"charge": "201000"
}
3.课程基本信息删除-实战
教育机构中的老师对录入课程基本信息,平台来管理应提供对其信息就行删除的业务操作,针对课程基本信息数据删除的需求,我们下面要开发课程基本信息删除的功能实现。
3.1 信息删除接口业务需求
通过课程基本信息列表中的删除按钮对单个信息进行删除操作。在点击后删除按钮后提示删除操作信息,需要用户点击 ‘确定’ 按钮后,方可将数据在数据库中逻辑删除 (并非物理上传)。删除成功后,需要返回信息列表界面。
3.1.1 接口业务需求
●操作界面展示如下:
课程列表编辑按钮
●具体需要如下:
1.机构老师在课程基本信息列表中点击“删除” 按钮。
2.页面弹出确认模态窗口,需要用户进行确认删除消息。
3.用户在确认模块窗口中点击 ‘确认’ 按钮,对数据进行删除操作。
4.删除成功后,提示删除结果操作。并返回信息列表界面中。
5.基本信息删除操作为逻辑删除,并非物理删除。
6.课程基本信息的状态不能为’已提交’ 、’审核通过’、’课程已发布’ 状态。
7.教学机构只能删除自己机构下的数据。
●逻辑删除数据修改:
数据模型状态字段示意图
上图解释:
所谓逻辑删除,是对数据库数据不进行直接删除。而是对数据库表的状态字段进行修改操作。
3.2 课程删除功能业务实现
下面将会实现课程基本信息删除功能,课程基本信息的删除是在内容管理系统服务,所以此接口的实现将在内容管理微服务中开发,接口信息依然定义在 Api 工程中。
3.2.1 信息删除接口定义
在 ‘项目开发规范文档.md — 接口开发规范’ 文档声明,参入的参数为删除数据的 ID 集合数据,接口响应为Boolean类型数据。
1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址
传入传出参数—更新课程信息
1. 接口编写
在 xc-api 接口工程中的 ContentBaseApi 接口定义方法:
package com.xuecheng.api.content;
import com.xuecheng.api.content.model.qo.QueryCourseBaseModel;
import com.xuecheng.common.domain.page.PageVO;
/**
* <p>
* 课程基本信息 Api 接口路径定义
* </p>
*
* @Description: 课程基本信息 Api 接口路径定义
*/
@Api(tags = "课程基本信息服务接口",description = "对课程基本信息业务操作")
public interface CourseBaseApi {
//其他代码省略
@ApiOperation("根据Id删除课程信息")
@ApiImplicitParam(name = "courseBaseId", value = "课程id值", required = true, paramType = "path")
void removeCourseBaseById(Long courseBaseId)
}
3.2.2 信息删除接口开发
1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
●接口
public interface CourseBaseService extends IService<CourseBase> {
//其他代码省略
/**
* 根据课程Id进行删除操作
*/
void removeCourseBase(Long courseBaseId,Long companyId);
}
●实现类
@Service
public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper, CourseBase> implements CourseBaseService {
//其他代码省略
/*
* 业务分析:
1.是否要开启事务
需要开启
2.判断关键数据
courseBaseId
companyid
3.判断业务数据
判断课程是否存在
判断是否是同一家机构
判断课程是否已经删除
判断课程的审核状态
未提交
审核未通过
只有这些状态才可以被删除
4.删除课程基础信息(根据id删除)
修改数据库的数据status
* */
@Transactional
public void removeCourseBase(Long courseBaseId, Long companyId) {
// 2.判断关键数据
// courseBaseId
// companyid
if (ObjectUtils.isEmpty(courseBaseId) ||
ObjectUtils.isEmpty(companyId)
) {
ExceptionCast.cast(CommonErrorCode.E_100101);
}
//3.判断业务数据
// 判断课程是否存在
// 判断是否是同一家机构
// 判断课程是否已经删除
// 判断课程的审核状态
// 未提交
// 审核未通过
// 只有这些状态才可以被删除
getCourseByLogic(companyId, courseBaseId);
// 4.删除课程基础信息(根据id删除)
// 修改数据库的数据status
// update course_base set status = ? where course_id = ?
LambdaUpdateWrapper<CourseBase> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(CourseBase::getStatus,CommonEnum.DELETE_FLAG.getCodeInt());
updateWrapper.set(CourseBase::getChangeDate, LocalDateTime.now());
updateWrapper.eq(CourseBase::getId, courseBaseId);
boolean result = update(updateWrapper);
if (!result) {
ExceptionCast.cast(ContentErrorCode.E_120017);
}
}
private void getCourseByLogic(Long companyId, Long courseBaseId) {
CourseBase courseBase = getCourseBaseByBaseId(courseBaseId, 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)
) {
throw new RuntimeException("课程审核状态异常");
}
}
}
3. controller编写
@RestController
@RequestMapping("coursebase")
public class CourseBaseController implements CourseBaseApi {
@Autowired
private CourseBaseService courseBaseService;
@DeleteMapping("course/{courseBaseId}")
public void removeCourseBaseById(@PathVariable Long courseBaseId) {
// 1.获得公司Id
Long companyId = SecurityUtil.getCompanyId();
courseBaseService.removeCourseBase(courseBaseId, companyId);
}
//其他代码省略
}
3.2.3 信息删除接口测试
测试环境需要启动的微服务有:
1.注册中心 xc-discover-service (端口:63000)
2.服务网关 xc-gateway-service (端口:63010)
3.内容管理 xc-content-service (端口:63040)
1. 使用 postman 在请求信息中添加请求参数
DELETE http://localhost:63010/content/course/39
●请求头(访问令牌)
请求头的 key 值: authorization
请求头的 value 值:
Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9