今日重点
课程绑定媒资信息:
关联课程计划之前 先添加视频 运营平台进行审核,审核通过 才可以进行媒资绑定!
主体业务操作在内容管理中实现!
fengin 调用:
接口路径规范: 内部微服务调用地址: /项目跟路径/l/ 。。。
接口类型规范: 响应数据格式JSON 返回数据必备三个数据 : 操作代码 提示信息 数据内容
成功 代码0 信息success 远程调用数据
失败 错误代码 具体错误信息 null
1.绑定课程计划与媒资信息要求:
1)前端提交 媒资ID与课程计划ID至内容管理服务来请求绑定
2) 内容管理服务通过媒资ID向媒资管理服务查询要绑定的媒资信息
3) 内容管理服务通过课程计划ID查询要绑定的课程计划信息
4) 内容管理服务综合媒资信息和课程计划信息在数据库记录绑定关系,最后返回绑定结果给前端
2.绑定课程计划与媒资信息业务要求:
1)三级课程计划(小章节)才可以添加视频的关联。
2)三级课程计划(小章节)与媒资进行绑定时进行判断,要根据关联的 Teachplan 的 CourseId 和 TeachplanId 来查询 TeachplanMedia 数据。
2.1) 查询出如果存在,将做 TeachplanMedia 更新操作
2.2) 查询出如果不存在,将做 TeachplanMedia 新增操作
1. 课程计划绑定媒资
1.1 需求分析
此模块主要针对教育机构用户将课程计划绑定媒资信息,最终形成视频课程及课程资料的业务过程,主要功能包括
●关键字查找机构内已审核的媒资信息
●内容管理服务远程调用获得媒资信息
●选择媒资信息并绑定至课程计划
○将关联数据保存到内容管理服务(TeachplanMedia)
1.1.1 业务流程描述
课程计划绑定媒资流程如下:
课程所关联的课程计划信息,其中包括客户才能计划关联媒资信息管理,课程计划为课程内容的大纲,主要方便学员学习和视频播放列表的展示。<br />**1.教育机构用户进入课程管理页面并编辑某一个课程,在"课程大纲"标签页的某一小节后可点击”添加视频“。**<br />课程计划绑定媒资信息
2.弹出添加视频对话框,可通过视频关键字搜索已审核通过的视频媒资。
填写需要关联审核通过的视频
3.选择视频媒资,点击提交按钮,完成课程计划绑定媒资流程。
关联视频
上图解释:
注释①:填写审核通过的视频关键字
注释②:选择关联出的视频名称
注释③:选择好后,点击提交完成视频关联
关联后的课程计划
4.学习中心对课程计划查询
通过内容管理对课程发布后,学员就可以通过学习中心对发布后的课程进行学习,课程学习主要是以课程章节来学习课程。而课程章节就为课程下的课程计划信息。在课程学习页面中,会将课程计划以课程章节的形式进行展示。
学习中心的课程计划
1.1.2 系统交互流程
课程计划绑定媒资交互流程如下:
以上功能中,设计三个主要工程:前端工程、内容管理微服务、媒资管理微服务。
步骤描述,分为以下几个部分,每个部分代表用户一次操作触发:
1.查询机构内已审核的媒资信息
1)前端组装查询参数并发送查询请求给 媒资管理服务
2) 媒资管理服务获取当前登录用户所在教学机构ID,将此教学机构及审核状态(已审核) 媒资信息数据返回给前端。
2.绑定课程计划与媒资信息
1)前端提交 媒资ID与课程计划ID至内容管理服务来请求绑定
2) 内容管理服务通过媒资ID向媒资管理服务查询要绑定的媒资信息
3) 内容管理服务通过课程计划ID查询要绑定的课程计划信息
4) 内容管理服务综合媒资信息和课程计划信息在数据库记录绑定关系,最后返回绑定结果给前端
1.1.3 业务所需开发功能
此模块主要针对教育机构用户 为内容管理的课程计划添加关联媒资信息的操作,主要功能包括
●查询媒资管理中的 已审核通过 的媒资信息。
●根据媒资 id 查询媒资管理中的媒资文件信息
●在内容管理中关联内容管理和媒资信息
对于功能,下面我们需要分别来进行实现。
1.2 查询媒资信息功能实现
根据业务流程图,此功能需要满足下面的要求:
1.在媒资管理中实现
2.查询媒资信息必须为审核通过
3.查询的媒资信息要属于一个机构下的数据
1.查询媒资信息功能调整
此功能我们之前已经在媒资管理微服务中实现,现在需求对增加查询条件:媒资信息已审核
2.添加查询条件数据
在查询媒资信息接口的条件封装类 QueryMediaModel 中,添加审核状态属性:
●添加审核状态属性
@Data
@ApiModel("媒资查询封装类")
public class QueryMediaModel {
//其他内容省略
@ApiModelProperty("审核状态")
private String auditStatus;
}
在业务类 MediaServiceImpl 实现类中的 queryMedias 方法中添加对审核状态的判断:
●添加审核状态代码
/**
* 媒资信息 服务实现类
*/
@Service
public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {
//其他代码省略
public PageVO<MediaDTO> queryMediaList(PageRequestParams params, QueryMediaModel queryMediaModel, Long companyId) {
//其他代码省略
//3.构建查询条件
//其他代码省略
//添加对媒资审核状态的条件
queryWrapper.eq(StringUtils.isNotBlank(queryMediaModel.getAuditStatus()),
Media::getAuditStatus, queryMediaModel.getAuditStatus());
queryWrapper.eq(!(ObjectUtils.isEmpty(companyId)),Media::getCompanyId, companyId);
//其他代码省略
}
}
3.业务测试
测试环境需要启动的微服务有:
1.服务网关 xc-gateway-service (端口:63010)
2.媒资管理 xc-media-service (端口:63050)
1. 使用 postman 在请求信息中添加请求参数
POST http://127.0.0.1:63010/media/media/list
●QueryString 信息(分页参数)
......?pageNo=1&pageSize=5
●请求体信息(查询条件参数)
{
"filename":""
"type":"",
"auditStatus":"202003"
}
●请求头(访问令牌)
请求头的 key 值: authorization
请求头的 value 值:
Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9
响应内容如下:
1.3 根据id查询媒资信息功能实现
1.3.1 查询媒资信息需求分析
根据业务流程图,此功能需要满足下面的要求:
1.在媒资管理中实现
2.根据 Id 来进行查询
根据项目开发规范文档中:接口开发规范—微服务远程调用接口规范 ,对学成在线微服务间远程调用定义了一些内容。
微服务之间调用接口规范示意图
●HTTP 请求方式规范 同 ‘6.4.2.1 前端调用接口规范’ 中的 HTTP 请求方式规范,对此不在阐述
●HTTP 请求地址URI
○URI 中尽量避免使用动词,单词之间不要以中划线”-“分割
○URI 地址前缀:/服务名称/l/xxx
●HTTP响应数据无论是进行 CRUD 还是 异常信息返回,其中必须包含:操作代码、提示信息、数据内容。
○响应数据格式为:Json
■如果是获得正常信息
●操作代码固定为:0
●提示信息固定为:success
●数据内容:远程调用获得数据
■如果获得错误信息
●操作代码:错误代码(不同业务错误代码不同)
●提示信息:错误信息(要看具体的错误信息)
●数据内容:null
针对学成在线微服务间远程相应内容,项目中使用自定义类 RestResponse (xc-common工程中) ,如下:
●RestResponse 响应类
package com.xuecheng.common.domain.response;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.code.ErrorCode;
/**
* 响应通用参数包装
*/
public class RestResponse<T> {
/**
* 响应错误编码,0为正常
*/
private int code;
/**
* 响应错误信息
*/
private String msg;
/**
* 响应内容
*/
private T result;
/**
* 错误信息的封装
* @param msg
* @param <T>
* @return
*/
public static <T> RestResponse<T> validfail(String msg) {
RestResponse<T> response = new RestResponse<T>();
response.setCode(-2);
response.setMsg(msg);
return response;
}
/**
* 对ErrorCode信息的封装
* @param errorCode {@link ErrorCode} 业务错误信息
* @return RestResponse Rest服务封装相应数据
*/
public static <T> RestResponse<T> validfail(ErrorCode errorCode) {
RestResponse<T> response = new
RestResponse<T>(errorCode.getCode(),errorCode.getDesc());
return response;
}
/**
* 添加正常响应数据(包含响应内容)
* @return RestResponse Rest服务封装相应数据
*/
public static <T> RestResponse<T> success(T result) {
RestResponse<T> response = new RestResponse<T>();
response.setResult(result);
return response;
}
/**
* 添加正常响应数据(不包含响应内容)
* @return RestResponse Rest服务封装相应数据
*/
public static <T> RestResponse<T> success() {
return new RestResponse<T>();
}
public RestResponse() {
this(0, "success");
}
public RestResponse(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public Boolean isSuccessful() {
return this.code == CommonErrorCode.SUCCESS.getCode();
}
@Override
public String toString() {
return "RestResponse [code=" + code + ", msg=" + msg + ", result="
+ result + "]";
}
}
1.3.2 查询媒资信息接口定义
1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址
接口传入传出列表
2. 接口编写
在 xc-api 工程的 com.xuecheng.api.media 包下创建接口类接口定义如下:
●根据Id查询媒资
@Api(value = "媒资管理", tags = "媒资管理API",description = "对媒资信息进行管理")
public interface MediaApi {
//其他代码省略
@ApiOperation("根据Id查询媒资信息")
@ApiImplicitParam(name = "mediaId",
value = "媒资ID", required = true,
dataType = "long", paramType = "path", example = "1")
RestResponse getMediaById(Long mediaId);
}
1.3.2 查询媒资信息接口开发
1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
●接口
public interface MediaService extends IService<Media> {
//其他代码省略
/**
* 根据id查询媒资信息-远端服务调用
* @param mediaId
* @return
*/
RestResponse<MediaDTO> getById4Service(Long mediaId);
}
●实现类
/**
* 媒资信息 服务实现类
*/
@Service
public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {
/*
* 业务分析:
* PS:由于是学成内部微服务间的调用,此功能只需要提供基础数据即可,不需要加上业务逻辑判断
* 如果为了数据的完整性,可以对传入和传出的参数进行判断
*
* 1.判断mediaid是否为空
* 2.查询数据
* 3.判断数据是否存在
* 4.如果存在,使用RestResponse来封装返回
* */
public RestResponse<MediaDTO> getById4Service(Long mediaId) {
// 1.判断关键数据
if (ObjectUtils.isEmpty(mediaId)) {
return RestResponse.validfail(CommonErrorCode.E_100101);
}
// 2.判断业务数据
Media media = this.getById(mediaId);
if (ObjectUtils.isEmpty(media)) {
return RestResponse.validfail(MediaErrorCode.E_140005);
} else {
MediaDTO mediaDTO = MediaConvert.INSTANCE.entity2dto(media);
return RestResponse.success(mediaDTO);
}
}
}
3. controller编写
@RestController
public class MediaController implements MediaApi {
@Autowired
private MediaService mediaService;
//其他代码省略
@GetMapping("l/media/{mediaId}")
public RestResponse<MediaDTO> getById4Service(@PathVariable Long mediaId) {
RestResponse<MediaDTO> response = mediaService.getById4Service(mediaId);
return response;
}
}
1.3.4 信息接口测试
测试环境需要启动的微服务有:
1.注册中心 xc-discover-service (端口:63000)
2.服务网关 xc-gateway-service (端口:63010)
3.媒资管理 xc-media-service (端口:63050)
1. 使用 postman 在请求信息中添加请求参数
GET http://localhost:63010/media/l/media/25
响应内容如下:
1.4 课程计划绑定媒资功能实现
本功能是在内容管理微服务开发,来完成课程计划(课程大纲)与媒资信息的关联操作。
1.4.1 数据模型(表结构)
‘ 课程计划与媒资管理的信息保存在内容管理数据库中的 teachplan_media 表当中,在完成数据绑定时,要将课程计划于媒资的关联关系数据保存到此表中。
1.媒资表说明
媒资表结构
在上图中的表结构中,主要字段为:
1.自身信息描述:id,create_date。
2.数据关联描述:teachplan_id,course_id, media_id ,media_fileName
之前在做 Teachplan 的删除操作时,我们已经讲 TeachplanMedia 的相关代码生成出来,无需再次生成。
1.4.2 课程计划绑定媒资需求分析
1.绑定课程计划与媒资信息要求:
1)前端提交 媒资ID与课程计划ID至内 容管理服务来请求绑定
2) 内容管理服务通过媒资ID向媒资管理服务查询要绑定的媒资信息
3) 内容管理服务通过课程计划ID查询要绑定的课程计划信息
4) 内容管理服务综合媒资信息和课程计划信息在数据库记录绑定关系,最后返回绑定结果给前端
2.绑定课程计划与媒资信息业务要求:
1)三级课程计划(小章节)才可以添加视频的关联。
2)三级课程计划(小章节)与媒资进行绑定时进行判断,要根据关联的 Teachplan 的 CourseId 和 TeachplanId 来查询 TeachplanMedia 数据。
2.1) 查询出如果存在,将做 TeachplanMedia 更新操作
2.2) 查询出如果不存在,将做 TeachplanMedia 新增操作
1.4.3 系统微服间远程调用实现
由于 内容管理微服务和 媒资管理微服务 都属于学成在线的也服务,在项目运行环境中,可以使用 Spring cloud Eureka 注册到 微服务注册中心中,并通过 Spring cloud Feign 发起远程调用。
微服务调用结构图
在上面的结构图中,需求我们将微服务作出以下操作:
1.将业务微服务注册到 Nacos 中。
2.在内容管理中添加 Feign、Ribbon 的依赖和相关配置信息。
3.编写 Feign 远程调用的接口。
1.4.3.1 微服间远程调用配置
1.在内容管理中引入相关依赖
在内容管理中,需要 Nacos、Feign、Ribbon 的依赖配置到 pom 文件中。但当前项目已经将其进行引入。
2.配置 spring cloud 相关内容
在Nacos中添加下面的公共配置信息,如下
feign-config.properties 的配置:
# 开启 feign 的远程调用使用熔断
feign.sentinel.enabled = true
# 配置请求GZIP压缩
feign.compression.request.enabled = true
# 配置压缩数据大小的下限
feign.compression.request.min-request-size = 2048
# 配置响应GZIP压缩
feign.compression.response.enabled = true
# 配置压缩支持的MIME TYPE
feign.compression.request.mime-types[0] = text/xml
feign.compression.request.mime-types[1] = application/xml
feign.compression.request.mime-types[2] = application/json
ribbon-config.properties 的配置:
#对当前实例的重试次数 default 0
ribbon.MaxAutoRetries = 1
#设置连接超时时间 default 2000
ribbon.ConnectTimeout = 3000
#对所有操作请求都进行重试 default false
ribbon.OkToRetryOnAllOperations = false
#设置读取超时时间 default 5000
ribbon.ReadTimeout = 20000
#切换实例的重试次数 default 1
ribbon.MaxAutoRetriesNextServer = 1
我们只需在媒资服务中引入公共配置到应用中:
#微服务配置
spring:
application:
name: content-service
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
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}
- dataId: aliyun-vod.properties
group: ${group.name}
- dataId: feign-config.properties
group: ${group.name}
- dataId: ribbon-config.properties
group: ${group.name}
profiles: # 激活配置环境
active: dev
# 组名称
group:
name: xc-group
1.4.3.2 编写 Feign 远程代理
1.Feign代理接口
由于是内容管理远程调用媒资管理,所有要在 xc-api 创建对 Media 的 Feign 远程调用接口类,内容如下:
package com.xuecheng.feign.media;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.constant.XcFeignServiceNameList;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.feign.media.sentinel.MediaApiAgentFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <p></p>
*
* @Description:
*/
@FeignClient(value = XcFeignServiceNameList.XC_MEDIA_SERVICE)
public interface MediaApiAgent {
String PREFIX_TAG = "/media/";
@GetMapping(PREFIX_TAG+"l/media/{mediaId}")
RestResponse<MediaDTO> getMediaById4s(@PathVariable Long mediaId);
}
2.开启 Feign 注解配置
xc-api 工程下添加 Spring Cloud Feign 发起对媒资微服务的远程调用,所有要在内容管理启动类上添加开启 Feign 的注解配置,如下:
package com.xuecheng.feign.config;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* <p></p>
*
* @Description:
*/
@Configuration
@ComponentScan(basePackages = "com.xuecheng.feign")
@EnableFeignClients(basePackages = {"com.xuecheng.feign"})
public class FeignConfig {
}
1.4.3.3 编写 Feign 熔断器
为了保护微服务间远程调用,不会出现链路失效(雪崩),我们需要对Feign接口编写Sentinel的熔断降级方法,内容如下:
xc-api 添加Sentinel依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
xc-content-service 添加Sentinel配置
#微服务配置
spring:
application:
name: content-service
cloud:
sentinel:
transport:
dashboard: 192.168.94.129:8858 #sentinel控制台地址
xc-content-service 添加Sentinel降级方法
package com.xuecheng.feign.media.sentinel;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.feign.media.MediaApiAgent;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* <p></p>
*
* @Description:
*/
@Slf4j
@Component
public class MediaApiAgentFallback implements FallbackFactory<MediaApiAgent> {
@Override
public MediaApiAgent create(Throwable throwable) {
return new MediaApiAgent() {
@Override
public RestResponse<MediaDTO> getMediaById4s(Long mediaId) {
log.error(CommonErrorCode.E_999982.getDesc()+"error msg : {}",throwable.getMessage());
return RestResponse.validfail(CommonErrorCode.E_999982);
}
};
}
}
编写好后,需要在 MediaApiAgent 的 FeignClient 注解属性中 fallback 添加值,如下:
package com.xuecheng.feign.media;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.constant.XcFeignServiceNameList;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.feign.media.sentinel.MediaApiAgentFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <p></p>
*
* @Description:
*/
@FeignClient(value = XcFeignServiceNameList.XC_MEDIA_SERVICE,fallbackFactory = MediaApiAgentFallback.class)
public interface MediaApiAgent {
String PREFIX_TAG = "/media/";
@GetMapping(PREFIX_TAG+"l/media/{mediaId}")
RestResponse<MediaDTO> getMediaById4s(@PathVariable Long mediaId);
}
1.4.3.4 开启自动扫包
在xc-api工程下的 resource目录下创建 META-INF/spring.factories 文件,文件内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xuecheng.feign.config.FeignConfig
1.4.4 课程计划绑定媒资接口定义
1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址
接口传入传出列表
2. 传入传出的参数封装类
根据前后端传入参数封装 VO 类
●VO 封装类
package com.xuecheng.api.media.model.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* <p>
* 教学计划-媒资绑定提交数据
* </p>
*
* @Description:
*/
@Data
@ApiModel(value="BindTeachplanMediaVO", description="教学计划-媒资绑定提交数据")
public class BindTeachplanMediaVO {
@ApiModelProperty(value = "媒资信息标识", required = true)
private Long mediaId;
@ApiModelProperty(value = "课程计划标识", required = true)
private Long teachplanId;
}
根据前后端传出参数封装 DTO 类
●DTO 封装类 (代码生成器生成)
package com.xuecheng.api.content.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value="TeachplanMediaDTO", description="")
public class TeachplanMediaDTO implements Serializable {
@ApiModelProperty(value = "主键")
private Long teachplanMediaId;
@ApiModelProperty(value = "媒资信息标识")
private Long mediaId;
@ApiModelProperty(value = "课程计划标识")
private Long teachplanId;
@ApiModelProperty(value = "课程标识")
private Long courseId;
@ApiModelProperty(value = "课程发布标识")
private Long coursePubId;
@ApiModelProperty(value = "媒资文件原始名称")
private String mediaFilename;
}
3. 接口编写
在 xc-api 工程的 com.xuecheng.api.content 包下创建接口类接口定义如下:
●根据Id查询媒资
/**
* <p>
* 课程计划服务API
* </p>
*/
@Api(value = "课程计划信息管理",tags = "内容-课程计划信息管理Api接口",description = "课程计划信息管理Api接口")
public interface TeachplanApi {
//其他代码省略
@ApiOperation("课程计划绑定媒资信息")
TeachplanMediaDTO associateMedia(BindTeachplanMediaVO vo);
}
1.4.5 课程计划绑定媒资接口开发
1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
对于service层,除了进行业务层逻辑判断和操作外,还需要将 PO 数据转为 DTO 数据,所以需要编写课程计划的对象属性映射。
●对象属性转换类
package com.xuecheng.content.convert;
import com.xuecheng.api.content.model.TeachplanMediaDTO;
import com.xuecheng.api.content.model.vo.BindTeachplanMediaVO;
import com.xuecheng.content.entity.TeachplanMedia;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 课程计划媒资的PO和DTO转换器
*/
@Mapper
public interface TeachplanMediaConvert {
TeachplanMediaConvert INSTANCE = Mappers.getMapper(TeachplanMediaConvert.class);
TeachplanMedia dto2entity(TeachplanMediaDTO teachplanDTO);
TeachplanMediaDTO entity2dto(TeachplanMedia teachplanMedia);
}
●接口
package com.xuecheng.content.service;
import com.xuecheng.api.content.model.TeachplanMediaDTO;
import com.xuecheng.content.entity.TeachplanMedia;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 课程计划和媒资服务类
*/
public interface TeachplanService extends IService<Teachplan> {
//其他代码省略
/**
* 课程计划绑定媒资信息
* @param dto
* @param companyId
* @return
*/
TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto,Long companyId);
}
●实现类
/**
* <p>
* 课程计划 服务实现类
* </p>
*/
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {
@Autowired
private MediaApiAgent mediaApiAgent;
@Autowired
private TeachplanMediaService teachplanMediaService;
/*
* 业务分析:
* 1.判断关键数据
* teachplanid mediaid
* 2.判断业务数据
* 课程的计划
* 判断是否是第三级
* 判断课程类型是否录播(课程基本信息中获得并判断)
* 判断课程计划是否存在:teachplanId、courseId
*
* 课程基本信息
* 判断是否存在或是否是同一家机构
* 判断是否删除
* 判断审核状态:未提交、审核未通过
*
* 媒资信息
* 判断媒资信息是否存在
* 判断是否是同一家机构
* 判断媒资审核状态
* 3.保存课程计划和媒资信息的数据
* 判断绑定的信息是否存在
* 如果存在
* 修改绑定媒资的信息即可:mediaid mediafilename
* 如果不存在
* 添加课程计划媒资信息
*
* 4.修改课程计划的类型
* mediatype值
*
*
* 5.判断保存的结果并返回数据库最新数据内容
* */
@Transactional
public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
//1.判断关键数据
// teachplanid mediaid
if (ObjectUtils.isEmpty(dto.getTeachplanId())||
ObjectUtils.isEmpty(dto.getMediaId())||
ObjectUtils.isEmpty(companyId)
) {
ExceptionCast.cast(CommonErrorCode.E_100101);
}
// 2.判断业务数据
// 课程计划
// 判断是否存在
Teachplan teachplan = this.getById(dto.getTeachplanId());
if (ObjectUtils.isEmpty(teachplan)) {
ExceptionCast.cast(ContentErrorCode.E_120402);
}
// 判断课程计划等级
// 只有三级课程计划才可以绑定媒资
if (!(TeachPlanEnum.THIRD_LEVEL.equals(teachplan.getGrade()))) {
ExceptionCast.cast(ContentErrorCode.E_120410);
}
// 课程基础信息
// 判断是否是同一家机构
// 判断该课程的审核状态(课程基础信息)
// 只有:未提交、审核未通过
CourseBaseDTO courseBase = getCourseAndVerify(companyId, teachplan.getCourseId());
// 判断课程的类型:只有是点播课程才可以绑定媒资信息
String teachmode = courseBase.getTeachmode();
if (!(CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode().equals(teachmode))) {
ExceptionCast.cast(ContentErrorCode.E_120418);
}
// 媒资信息
// 判断是否存在
// 判断是否是同一家教学机构
// 判断媒资审核状态
RestResponse<MediaDTO> mediaResponse = mediaApiAgent.getMediaById4s(dto.getMediaId());
if (!(mediaResponse.isSuccessful())) {
ExceptionCast.castWithCodeAndDesc(mediaResponse.getCode(),mediaResponse.getMsg());
}
MediaDTO mediaDTO = mediaResponse.getResult();
if (!(ObjectUtils.nullSafeEquals(companyId,mediaDTO.getCompanyId()))) {
ExceptionCast.cast(CommonErrorCode.E_100108);
}
if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaDTO.getAuditStatus()))) {
ExceptionCast.cast(ContentErrorCode.E_120416);
}
// 3.保存课程计划和媒资信息的数据
// 判断绑定的信息是否存在
// select * from teachplan_media where teachplan_id = ? and courseid = ?
LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TeachplanMedia::getTeachplanId, dto.getTeachplanId());
queryWrapper.eq(TeachplanMedia::getCourseId, dto.getCourseId());
TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);
boolean teachplanMeidaResult = false;
if (ObjectUtils.isEmpty(teachplanMedia)) {
// 如果不存在
// 添加课程计划媒资信息
teachplanMedia = TeachplanMedia.builder()
.mediaId(dto.getMediaId())
.mediaFilename(mediaDTO.getFilename())
.teachplanId(dto.getTeachplanId())
.courseId(teachplan.getCourseId())
.build();
teachplanMeidaResult = teachplanMediaService.save(teachplanMedia);
} else {
// 如果存在
// 修改绑定媒资的信息即可:mediaid mediafilename
LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(TeachplanMedia::getMediaId, mediaDTO.getId());
updateWrapper.set(TeachplanMedia::getMediaFilename, mediaDTO.getFilename());
updateWrapper.eq(TeachplanMedia::getId, teachplanMedia.getId());
teachplanMeidaResult = teachplanMediaService.update(updateWrapper);
}
if (!teachplanMeidaResult) {
ExceptionCast.cast(ContentErrorCode.E_120421);
}
// 4.修改课程计划的类型
// mediatype值
changeTeachplanMediaType(teachplan.getId(), mediaDTO.getType());
// 5.判断保存的结果并返回数据库最新数据内容
TeachplanMedia po = teachplanMediaService.getById(teachplanMedia.getId());
TeachplanMediaDTO resultDTO = TeachplanMediaConvert.INSTANCE.entity2dto(po);
return resultDTO;
}
private void changeTeachplanMediaType(Long teachplanId, String mediaType) {
LambdaUpdateWrapper<Teachplan> teachplanUpdateWrapper = new LambdaUpdateWrapper<>();
teachplanUpdateWrapper.set(Teachplan::getMediaType, mediaType);
teachplanUpdateWrapper.set(Teachplan::getChangeDate, LocalDateTime.now());
teachplanUpdateWrapper.eq(Teachplan::getId, teachplanId);
boolean teachplanResult = this.update(teachplanUpdateWrapper);
if (!teachplanResult) {
ExceptionCast.cast(ContentErrorCode.E_120421);
}
}
}
3. controller编写
/**
* <p>
* 课程计划 前端控制器
* </p>
*
* @author itcast
*/
@Slf4j
@RestController
public class CourseBaseController implements CourseBaseApi {
@Autowired
private TeachplanService teachplanService;
//其他代码省略
@PostMapping("teachplan/media/association")
public TeachplanMediaDTO associateMedia(@RequestBody BindTeachplanMediaVO vo) {
Long companyId = SecurityUtil.getCompanyId();
TeachplanMediaDTO teachplanMediaDTO = new TeachplanMediaDTO();
teachplanMediaDTO.setTeachplanId(vo.getTeachplanId());
teachplanMediaDTO.setMediaId(vo.getMediaId());
TeachplanMediaDTO resultDTO = teachplanService.associateMedia(teachplanMediaDTO, companyId);
return resultDTO;
}
}
1.4.6 信息接口测试
测试环境需要启动的微服务有:
1.注册中心 xc-discover-service (端口:63000)
2.服务网关 xc-gateway-service (端口:63010)
3.媒资管理 xc-media-service (端口:63050)
4.内容管理 xc-content-service (端口:63040)
1. 使用 postman 在请求信息中添加请求参数
POST http://127.0.0.1:63010/content/teachplan/media/association
●请求体信息(查询条件参数)
{
"mediaId":25,
"teachplanId":48
}
●请求头(访问令牌)
请求头的 key 值: authorization
请求头的 value 值:
Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9
响应内容如下(例子):