添加课程基本/营销/计划信息
界面原型
第一步: 用户进入课程查询列表,点击添加课程
按钮,选择课程类型是直播还是录播,课程类型不同那么授课方式也不同
- 添加的课程和教学机构是一对一的关系
第二步: 用户选完课程形式后,点击下一步
填写课程的基本信息和营销信息(两张表)
- 用户只要填完课程信息就会把数据保存到数据库中,可以
在课程管理中查看对应的课程发布信息
,当审核状态为通过时发布按钮
点亮
第三步: 填写课程计划信息
即课程的大纲目录包括章节和小节
,每个小节需要上传课程视频,用户点击小节标题即可开始播放视频
,如果是直播课程则会进入直播间
第四步: 填写课程师资信息
数据模型
添加课程涉及到的数据表课程基本信息表
添加课程涉及到的数据表课程营销信息表
添加课程涉及到的数据表课程计划信息表
课程分类表course_category
是一个树型结构表,通过parantid
字段将表中的所有记录组成一个树
请求/响应模型(model工程)
第一步: 根据请求参数定义对应的请求模型类
,添加课程的初始审核状态为未提交
,初始发布状态为未发布
// 新增课程
POST {{content_host}}/content/course
Content-Type: application/json
{
// 机构名称和Id根据当前登陆的用户获取,以下信息对应课程基本信息表
"mt": "",大分类Id
"st": "",小分类Id
"name": "",课程名称
"pic": "",课程图片
"teachmode": "200002",教育模式
"users": "初级人员",适用人群
"tags": "",课程标签
"grade": "204001",课程等级
"description": "",课程介绍
//以下信息对应课程营销表
"charge": "201000",收费规则
"price": 0,现价
"originalPrice":0,原价
"qq": "",咨询QQ
"wechat": "",微信
"phone": "",电话
"validDays": 365,有效期天数
}
package com.xuecheng.content.model.dto;
/**
* @version 1.0
* @description 添加课程dto
*/
@Data
@ApiModel(value = "AddCourseDto", description = "新增课程基本信息")
public class AddCourseDto {
@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 Float price;
@ApiModelProperty(value = "原价")
private Float originalPrice;
@ApiModelProperty(value = "qq")
private String qq;
@ApiModelProperty(value = "微信")
private String wechat;
@ApiModelProperty(value = "电话")
private String phone;
@ApiModelProperty(value = "有效期")
private Integer validDays;
}
第二步: 根据响应结果定义对应的响应模型类
,由于其大部分信息来自课程基本信息表
,所以我们可以将定义的响应结果模型类继承CourseBase
大分类名称和小分类名称
来自于课程分类表
,需要根据请求参数中携带的大分类Id和小分类Id
从数据库中获取
{
"id": 109,
"companyId": 1,
"companyName": null,
"name": "测试课程103",
"users": "初级人员",
"tags": "",
"mt": "1-1",大分类Id
"mtName": null,大分类名称,
"st": "1-1-1",小分类Id
"stName": null,小分类名称
"grade": "204001",
"teachmode": "200002",
"description": "",
"pic": "",
"createDate": "2022-09-08 07:35:16",
"changeDate": null,
"createPeople": null,
"changePeople": null,
"auditStatus": "202002",
"status": 1,
"coursePubId": null,
"coursePubDate": null,
"charge": "201000",
"price": null,
"originalPrice":0,
"qq": "",
"wechat": "",
"phone": "",
"validDays": 365
}
package com.xuecheng.content.model.dto;
/**
* @version 1.0
* @description 课程基本信息dto
*/
@Data
public class CourseBaseInfoDto extends CourseBase {
/**
* 收费规则,对应数据字典
*/
private String charge;
/**
* 价格
*/
private Float price;
/**
* 原价
*/
private Float originalPrice;
/**
* 咨询qq
*/
private String qq;
/**
* 微信
*/
private String wechat;
/**
* 电话
*/
private String phone;
/**
* 有效期天数
*/
private Integer validDays;
/**
* 大分类名称
*/
private String mtName;
/**
* 小分类名称
*/
private String stName;
}
接口定义(api工程)
@ApiOperation("新增课程基础信息接口")
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody AddCourseDto addCourseDto) {
// 获取机构id(添加的课程和教学机构是一对一的关系),暂时以硬编码的方式指定机构Id后期通过当前登陆的教学机构获取
Long companyId = 22L;
return courseBaseInfoService.createCourseBase(companyId, addCourseDto);
}
业务开发(service工程)
第一步: 编写Mapper接口及其SQL映射文件
public interface CourseBaseMapper extends BaseMapper<CourseBase> {
}
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
// 使用递归查询分类
public List<CourseCategoryTreeDto> selectTreeNodes(String id);
}
public interface CourseMarketMapper extends BaseMapper<CourseMarket> {
}
第二步: 编写Service接口及其实现类, 在业务逻辑代码中实现添加课程基本信息和课程营销信息
的逻辑,由于操作两张表所以需要开启事务
- 首先
对请求参数做合法性校验
(前端后端都要校验,Service和Controller层都要校验), 即判断一下用户是否输入了表单必填项以及设置一些参数的默认值 - 然后对请求参数进行封装并调用
课程基本信息表和课程营销信息表
对应的Mapper接口进行数据持久化,课程基本信息和课程营销信息的Id相同
- 最后将添加的课程基本信息和课程营销信息以及课程分类信息统一封装到
CourseBaseInfoDto
对象中并返回给前端
public interface CourseBaseInfoService {
/**
* 新增课程基本信息
* @param companyId 教学机构id
* @param addCourseDto 课程基本信息
* @return
*/
CourseBaseInfoDto createCourseBase(Long companyId, AddCourseDto addCourseDto);
}
@Slf4j
@Service
public class CourseBaseInfoServiceImpl implements CourseBaseInfoService {
@Resource
CourseBaseMapper courseBaseMapper;
@Resource
CourseMarketMapper courseMarketMapper;
@Resource
CourseCategoryMapper courseCategoryMapper;
@Override
@Transactional// 操作两张表需要开启事务
public CourseBaseInfoDto createCourseBase(Long companyId, AddCourseDto addCourseDto) {
// 1. 合法性校验
if (StringUtils.isBlank(addCourseDto.getName())) {
throw new RuntimeException("课程名称为空");
}
if (StringUtils.isBlank(addCourseDto.getMt())) {
throw new RuntimeException("课程分类为空");
}
if (StringUtils.isBlank(addCourseDto.getSt())) {
throw new RuntimeException("课程分类为空");
}
if (StringUtils.isBlank(addCourseDto.getGrade())) {
throw new RuntimeException("课程等级为空");
}
if (StringUtils.isBlank(addCourseDto.getTeachmode())) {
throw new RuntimeException("教育模式为空");
}
if (StringUtils.isBlank(addCourseDto.getUsers())) {
throw new RuntimeException("适应人群为空");
}
if (StringUtils.isBlank(addCourseDto.getCharge())) {
throw new RuntimeException("收费规则为空");
}
// 2. 将请求参数中包含的课程基本信息封装到对应的实体类对象当中
CourseBase courseBase = new CourseBase();
BeanUtils.copyProperties(addCourseDto, courseBase);
// 2.1 设置默认审核状态(去数据字典表中查询状态码)
courseBase.setAuditStatus("202002");
// 2.2 设置默认发布状态
courseBase.setStatus("203001");
// 2.3 设置机构id
courseBase.setCompanyId(companyId);
// 2.4 设置添加时间
courseBase.setCreateDate(LocalDateTime.now());
// 2.5 向课程基本信息表中插入一条记录
int baseInsert = courseBaseMapper.insert(courseBase);
// 3.获取刚添加的课程基本信息记录的Id
Long courseId = courseBase.getId();
// 4. 将请求参数中包含的课程营销信息封装到对应的实体类对象当中
CourseMarket courseMarket = new CourseMarket();
BeanUtils.copyProperties(addCourseDto, courseMarket);
// 设置课程营销信息的Id即我们刚添加的课程基本信息
courseMarket.setId(courseId);
// 4.1 判断收费规则,若课程收费,则价格必须大于0
String charge = courseMarket.getCharge();
if ("201001".equals(charge)) {
// 价格可以使用Float类型存储,计算的时候使用BigDecimal计算
Float price = addCourseDto.getPrice();
if (price == null || price.floatValue() <= 0) {
throw new RuntimeException("课程设置了收费,价格不能为空,且必须大于0");
}
}
// 4.2 向课程营销信息表中插入一条记录
int marketInsert = courseMarketMapper.insert(courseMarket);
// 判断课程基本信息和营销信息是否插入成功
if (baseInsert <= 0 || marketInsert <= 0) {
throw new RuntimeException("新增课程基本信息失败");
}
// 5.根据插入课程基本信息/课程营销信息的Id,去课程基本信息表和课程营销信息表中查询刚添加的记录,组装成CourseBaseInfoDto对象返回给前端
return getCourseBaseInfo(courseId);
}
}
第三步: 根据刚插入的课程基本信息/课程营销信息的Id
,去课程基本信息表和课程营销信息表中查询刚添加的记录,组装成CourseBaseInfoDto对象
返回给前端
private CourseBaseInfoDto getCourseBaseInfo(Long courseId) {
// 创建返回的Dto对象
CourseBaseInfoDto courseBaseInfoDto = new CourseBaseInfoDto();
// 1. 根据课程id查询课程基本信息
CourseBase courseBase = courseBaseMapper.selectById(courseId);
if (courseBase == null)
return null;
// 1.1 拷贝属性
BeanUtils.copyProperties(courseBase, courseBaseInfoDto);
// 2. 根据课程id查询课程营销信息
CourseMarket courseMarket = courseMarketMapper.selectById(courseId);
// 2.1 拷贝属性
if (courseMarket != null)
BeanUtils.copyProperties(courseMarket, courseBaseInfoDto);
// 3. 查询课程分类名称并封装到到CourseBaseInfoDto对象中
// 3.1 根据小分类id(1-1-1)查询对应的课程分类对象
CourseCategory courseCategoryBySt = courseCategoryMapper.selectById(courseBase.getSt());
// 3.2 设置课程的小分类名称
courseBaseInfoDto.setStName(courseCategoryBySt.getName());
// 3.3 根据大分类id(1-1)查询对应的课程分类对象
CourseCategory courseCategoryByMt = courseCategoryMapper.selectById(courseBase.getMt());
// 3.4 设置课程大分类名称
courseBaseInfoDto.setMtName(courseCategoryByMt.getName());
return courseBaseInfoDto;
}
接口测试
使用HTTP Client
测试新增课程接口
POST {{content_host}}/content/course
Content-Type: application/json
{
"mt": "1-1",
"st": "1-1-1",
"name": "测试课程tmp",
"pic": "",
"teachmode": "200002",
"users": "初级人员",
"tags": "",
"grade": "204001",
"description": "这是一门测试课程",
"charge": "201000",
"price": 99,
"originalPrice": 999,
"qq": "123564",
"wechat": "123654",
"phone": "156213",
"validDays": 365
}