今日重点

  1. 课程绑定媒资信息:
  2. 关联课程计划之前 先添加视频 运营平台进行审核,审核通过 才可以进行媒资绑定!
  3. 主体业务操作在内容管理中实现!
  4. fengin 调用:
  5. 接口路径规范: 内部微服务调用地址: /项目跟路径/l/ 。。。
  6. 接口类型规范: 响应数据格式JSON 返回数据必备三个数据 : 操作代码 提示信息 数据内容
  7. 成功 代码0 信息success 远程调用数据
  8. 失败 错误代码 具体错误信息 null
  9. 1.绑定课程计划与媒资信息要求:
  10. 1)前端提交 媒资ID与课程计划ID至内容管理服务来请求绑定
  11. 2) 内容管理服务通过媒资ID向媒资管理服务查询要绑定的媒资信息
  12. 3) 内容管理服务通过课程计划ID查询要绑定的课程计划信息
  13. 4) 内容管理服务综合媒资信息和课程计划信息在数据库记录绑定关系,最后返回绑定结果给前端
  14. 2.绑定课程计划与媒资信息业务要求:
  15. 1)三级课程计划(小章节)才可以添加视频的关联。
  16. 2)三级课程计划(小章节)与媒资进行绑定时进行判断,要根据关联的 Teachplan 的 CourseId 和 TeachplanId 来查询 TeachplanMedia 数据。
  17. 2.1) 查询出如果存在,将做 TeachplanMedia 更新操作
  18. 2.2) 查询出如果不存在,将做 TeachplanMedia 新增操作

课程媒资交互流程图:
image.png

1. 课程计划绑定媒资

1.1 需求分析


此模块主要针对教育机构用户将课程计划绑定媒资信息,最终形成视频课程及课程资料的业务过程,主要功能包括
●关键字查找机构内已审核的媒资信息
●内容管理服务远程调用获得媒资信息
●选择媒资信息并绑定至课程计划
○将关联数据保存到内容管理服务(TeachplanMedia)

1.1.1 业务流程描述


课程计划绑定媒资流程如下:

Day07-第三章-媒资管理-课程绑定媒资信息 - 图2

  1. 课程所关联的课程计划信息,其中包括客户才能计划关联媒资信息管理,课程计划为课程内容的大纲,主要方便学员学习和视频播放列表的展示。<br />**1.教育机构用户进入课程管理页面并编辑某一个课程,在"课程大纲"标签页的某一小节后可点击”添加视频“。**<br />课程计划绑定媒资信息

Day07-第三章-媒资管理-课程绑定媒资信息 - 图3

2.弹出添加视频对话框,可通过视频关键字搜索已审核通过的视频媒资。
填写需要关联审核通过的视频

Day07-第三章-媒资管理-课程绑定媒资信息 - 图4

3.选择视频媒资,点击提交按钮,完成课程计划绑定媒资流程。
关联视频

Day07-第三章-媒资管理-课程绑定媒资信息 - 图5

上图解释:
注释①:填写审核通过的视频关键字
注释②:选择关联出的视频名称
注释③:选择好后,点击提交完成视频关联
关联后的课程计划

Day07-第三章-媒资管理-课程绑定媒资信息 - 图6

4.学习中心对课程计划查询
通过内容管理对课程发布后,学员就可以通过学习中心对发布后的课程进行学习,课程学习主要是以课程章节来学习课程。而课程章节就为课程下的课程计划信息。在课程学习页面中,会将课程计划以课程章节的形式进行展示。
学习中心的课程计划

Day07-第三章-媒资管理-课程绑定媒资信息 - 图7

1.1.2 系统交互流程


课程计划绑定媒资交互流程如下:

Day07-第三章-媒资管理-课程绑定媒资信息 - 图8

以上功能中,设计三个主要工程:前端工程、内容管理微服务、媒资管理微服务。
步骤描述,分为以下几个部分,每个部分代表用户一次操作触发:
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 中,添加审核状态属性:
●添加审核状态属性

  1. @Data
  2. @ApiModel("媒资查询封装类")
  3. public class QueryMediaModel {
  4. //其他内容省略
  5. @ApiModelProperty("审核状态")
  6. private String auditStatus;
  7. }

在业务类 MediaServiceImpl 实现类中的 queryMedias 方法中添加对审核状态的判断:
●添加审核状态代码

  1. /**
  2. * 媒资信息 服务实现类
  3. */
  4. @Service
  5. public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {
  6. //其他代码省略
  7. public PageVO<MediaDTO> queryMediaList(PageRequestParams params, QueryMediaModel queryMediaModel, Long companyId) {
  8. //其他代码省略
  9. //3.构建查询条件
  10. //其他代码省略
  11. //添加对媒资审核状态的条件
  12. queryWrapper.eq(StringUtils.isNotBlank(queryMediaModel.getAuditStatus()),
  13. Media::getAuditStatus, queryMediaModel.getAuditStatus());
  14. queryWrapper.eq(!(ObjectUtils.isEmpty(companyId)),Media::getCompanyId, companyId);
  15. //其他代码省略
  16. }
  17. }

3.业务测试
测试环境需要启动的微服务有:
1.服务网关 xc-gateway-service (端口:63010)
2.媒资管理 xc-media-service (端口:63050)
1. 使用 postman 在请求信息中添加请求参数
POST http://127.0.0.1:63010/media/media/list
●QueryString 信息(分页参数)

  1. ......?pageNo=1&pageSize=5

●请求体信息(查询条件参数)

  1. {
  2. "filename":""
  3. "type":"",
  4. "auditStatus":"202003"
  5. }

●请求头(访问令牌)

  1. 请求头的 key 值: authorization
  2. 请求头的 value 值:
  3. Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9

响应内容如下:

Day07-第三章-媒资管理-课程绑定媒资信息 - 图9

1.3 根据id查询媒资信息功能实现

1.3.1 查询媒资信息需求分析


根据业务流程图,此功能需要满足下面的要求:
1.在媒资管理中实现
2.根据 Id 来进行查询
根据项目开发规范文档中:接口开发规范—微服务远程调用接口规范 ,对学成在线微服务间远程调用定义了一些内容。
微服务之间调用接口规范示意图

Day07-第三章-媒资管理-课程绑定媒资信息 - 图10

●HTTP 请求方式规范 同 ‘6.4.2.1 前端调用接口规范’ 中的 HTTP 请求方式规范,对此不在阐述
●HTTP 请求地址URI
○URI 中尽量避免使用动词,单词之间不要以中划线”-“分割
○URI 地址前缀:/服务名称/l/xxx
●HTTP响应数据无论是进行 CRUD 还是 异常信息返回,其中必须包含:操作代码、提示信息、数据内容
○响应数据格式为:Json
■如果是获得正常信息
●操作代码固定为:0
●提示信息固定为:success
●数据内容:远程调用获得数据
■如果获得错误信息
●操作代码:错误代码(不同业务错误代码不同)
●提示信息:错误信息(要看具体的错误信息)
●数据内容:null
针对学成在线微服务间远程相应内容,项目中使用自定义类 RestResponse (xc-common工程中) ,如下:
●RestResponse 响应类

  1. package com.xuecheng.common.domain.response;
  2. import com.xuecheng.common.domain.code.CommonErrorCode;
  3. import com.xuecheng.common.domain.code.ErrorCode;
  4. /**
  5. * 响应通用参数包装
  6. */
  7. public class RestResponse<T> {
  8. /**
  9. * 响应错误编码,0为正常
  10. */
  11. private int code;
  12. /**
  13. * 响应错误信息
  14. */
  15. private String msg;
  16. /**
  17. * 响应内容
  18. */
  19. private T result;
  20. /**
  21. * 错误信息的封装
  22. * @param msg
  23. * @param <T>
  24. * @return
  25. */
  26. public static <T> RestResponse<T> validfail(String msg) {
  27. RestResponse<T> response = new RestResponse<T>();
  28. response.setCode(-2);
  29. response.setMsg(msg);
  30. return response;
  31. }
  32. /**
  33. * 对ErrorCode信息的封装
  34. * @param errorCode {@link ErrorCode} 业务错误信息
  35. * @return RestResponse Rest服务封装相应数据
  36. */
  37. public static <T> RestResponse<T> validfail(ErrorCode errorCode) {
  38. RestResponse<T> response = new
  39. RestResponse<T>(errorCode.getCode(),errorCode.getDesc());
  40. return response;
  41. }
  42. /**
  43. * 添加正常响应数据(包含响应内容)
  44. * @return RestResponse Rest服务封装相应数据
  45. */
  46. public static <T> RestResponse<T> success(T result) {
  47. RestResponse<T> response = new RestResponse<T>();
  48. response.setResult(result);
  49. return response;
  50. }
  51. /**
  52. * 添加正常响应数据(不包含响应内容)
  53. * @return RestResponse Rest服务封装相应数据
  54. */
  55. public static <T> RestResponse<T> success() {
  56. return new RestResponse<T>();
  57. }
  58. public RestResponse() {
  59. this(0, "success");
  60. }
  61. public RestResponse(int code, String msg) {
  62. this.code = code;
  63. this.msg = msg;
  64. }
  65. public int getCode() {
  66. return code;
  67. }
  68. public void setCode(int code) {
  69. this.code = code;
  70. }
  71. public String getMsg() {
  72. return msg;
  73. }
  74. public void setMsg(String msg) {
  75. this.msg = msg;
  76. }
  77. public T getResult() {
  78. return result;
  79. }
  80. public void setResult(T result) {
  81. this.result = result;
  82. }
  83. public Boolean isSuccessful() {
  84. return this.code == CommonErrorCode.SUCCESS.getCode();
  85. }
  86. @Override
  87. public String toString() {
  88. return "RestResponse [code=" + code + ", msg=" + msg + ", result="
  89. + result + "]";
  90. }
  91. }

1.3.2 查询媒资信息接口定义


1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址

Day07-第三章-媒资管理-课程绑定媒资信息 - 图11

接口传入传出列表

Day07-第三章-媒资管理-课程绑定媒资信息 - 图12

2. 接口编写
在 xc-api 工程的 com.xuecheng.api.media 包下创建接口类接口定义如下:
●根据Id查询媒资

  1. @Api(value = "媒资管理", tags = "媒资管理API",description = "对媒资信息进行管理")
  2. public interface MediaApi {
  3. //其他代码省略
  4. @ApiOperation("根据Id查询媒资信息")
  5. @ApiImplicitParam(name = "mediaId",
  6. value = "媒资ID", required = true,
  7. dataType = "long", paramType = "path", example = "1")
  8. RestResponse getMediaById(Long mediaId);
  9. }

1.3.2 查询媒资信息接口开发


1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
●接口

  1. public interface MediaService extends IService<Media> {
  2. //其他代码省略
  3. /**
  4. * 根据id查询媒资信息-远端服务调用
  5. * @param mediaId
  6. * @return
  7. */
  8. RestResponse<MediaDTO> getById4Service(Long mediaId);
  9. }

●实现类

  1. /**
  2. * 媒资信息 服务实现类
  3. */
  4. @Service
  5. public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {
  6. /*
  7. * 业务分析:
  8. * PS:由于是学成内部微服务间的调用,此功能只需要提供基础数据即可,不需要加上业务逻辑判断
  9. * 如果为了数据的完整性,可以对传入和传出的参数进行判断
  10. *
  11. * 1.判断mediaid是否为空
  12. * 2.查询数据
  13. * 3.判断数据是否存在
  14. * 4.如果存在,使用RestResponse来封装返回
  15. * */
  16. public RestResponse<MediaDTO> getById4Service(Long mediaId) {
  17. // 1.判断关键数据
  18. if (ObjectUtils.isEmpty(mediaId)) {
  19. return RestResponse.validfail(CommonErrorCode.E_100101);
  20. }
  21. // 2.判断业务数据
  22. Media media = this.getById(mediaId);
  23. if (ObjectUtils.isEmpty(media)) {
  24. return RestResponse.validfail(MediaErrorCode.E_140005);
  25. } else {
  26. MediaDTO mediaDTO = MediaConvert.INSTANCE.entity2dto(media);
  27. return RestResponse.success(mediaDTO);
  28. }
  29. }
  30. }

3. controller编写

  1. @RestController
  2. public class MediaController implements MediaApi {
  3. @Autowired
  4. private MediaService mediaService;
  5. //其他代码省略
  6. @GetMapping("l/media/{mediaId}")
  7. public RestResponse<MediaDTO> getById4Service(@PathVariable Long mediaId) {
  8. RestResponse<MediaDTO> response = mediaService.getById4Service(mediaId);
  9. return response;
  10. }
  11. }

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
响应内容如下:

Day07-第三章-媒资管理-课程绑定媒资信息 - 图13

1.4 课程计划绑定媒资功能实现


本功能是在内容管理微服务开发,来完成课程计划(课程大纲)与媒资信息的关联操作。

1.4.1 数据模型(表结构)


‘ 课程计划与媒资管理的信息保存在内容管理数据库中的 teachplan_media 表当中,在完成数据绑定时,要将课程计划于媒资的关联关系数据保存到此表中。
1.媒资表说明
媒资表结构

Day07-第三章-媒资管理-课程绑定媒资信息 - 图14

在上图中的表结构中,主要字段为:
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 发起远程调用。
微服务调用结构图

Day07-第三章-媒资管理-课程绑定媒资信息 - 图15

在上面的结构图中,需求我们将微服务作出以下操作:
1.将业务微服务注册到 Nacos 中。
2.在内容管理中添加 Feign、Ribbon 的依赖和相关配置信息。
3.编写 Feign 远程调用的接口。

1.4.3.1 微服间远程调用配置


1.在内容管理中引入相关依赖
在内容管理中,需要 Nacos、Feign、Ribbon 的依赖配置到 pom 文件中。但当前项目已经将其进行引入。
2.配置 spring cloud 相关内容
在Nacos中添加下面的公共配置信息,如下
feign-config.properties 的配置:

  1. # 开启 feign 的远程调用使用熔断
  2. feign.sentinel.enabled = true
  3. # 配置请求GZIP压缩
  4. feign.compression.request.enabled = true
  5. # 配置压缩数据大小的下限
  6. feign.compression.request.min-request-size = 2048
  7. # 配置响应GZIP压缩
  8. feign.compression.response.enabled = true
  9. # 配置压缩支持的MIME TYPE
  10. feign.compression.request.mime-types[0] = text/xml
  11. feign.compression.request.mime-types[1] = application/xml
  12. feign.compression.request.mime-types[2] = application/json

ribbon-config.properties 的配置:

  1. #对当前实例的重试次数 default 0
  2. ribbon.MaxAutoRetries = 1
  3. #设置连接超时时间 default 2000
  4. ribbon.ConnectTimeout = 3000
  5. #对所有操作请求都进行重试 default false
  6. ribbon.OkToRetryOnAllOperations = false
  7. #设置读取超时时间 default 5000
  8. ribbon.ReadTimeout = 20000
  9. #切换实例的重试次数 default 1
  10. ribbon.MaxAutoRetriesNextServer = 1

我们只需在媒资服务中引入公共配置到应用中:

  1. #微服务配置
  2. spring:
  3. application:
  4. name: content-service
  5. jackson:
  6. date-format: yyyy-MM-dd HH:mm:ss
  7. time-zone: GMT+8
  8. cloud:
  9. nacos:
  10. discovery: #配置注册中心
  11. server-addr: 192.168.94.129:8848
  12. namespace: 5c0b093c-4084-46b5-bf33-899321cb7ef5
  13. group: ${group.name}
  14. config: #配置中心
  15. server-addr: 192.168.94.129:8848
  16. namespace: 5c0b093c-4084-46b5-bf33-899321cb7ef5
  17. group: ${group.name}
  18. file-extension: properties
  19. shared-configs:
  20. - dataId: mp-config.properites
  21. group: ${group.name}
  22. - dataId: aliyun-vod.properties
  23. group: ${group.name}
  24. - dataId: feign-config.properties
  25. group: ${group.name}
  26. - dataId: ribbon-config.properties
  27. group: ${group.name}
  28. profiles: # 激活配置环境
  29. active: dev
  30. # 组名称
  31. group:
  32. name: xc-group

1.4.3.2 编写 Feign 远程代理


1.Feign代理接口
由于是内容管理远程调用媒资管理,所有要在 xc-api 创建对 Media 的 Feign 远程调用接口类,内容如下:

  1. package com.xuecheng.feign.media;
  2. import com.xuecheng.api.media.model.dto.MediaDTO;
  3. import com.xuecheng.common.constant.XcFeignServiceNameList;
  4. import com.xuecheng.common.domain.response.RestResponse;
  5. import com.xuecheng.feign.media.sentinel.MediaApiAgentFallback;
  6. import org.springframework.cloud.openfeign.FeignClient;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.PathVariable;
  9. /**
  10. * <p></p>
  11. *
  12. * @Description:
  13. */
  14. @FeignClient(value = XcFeignServiceNameList.XC_MEDIA_SERVICE)
  15. public interface MediaApiAgent {
  16. String PREFIX_TAG = "/media/";
  17. @GetMapping(PREFIX_TAG+"l/media/{mediaId}")
  18. RestResponse<MediaDTO> getMediaById4s(@PathVariable Long mediaId);
  19. }

2.开启 Feign 注解配置
xc-api 工程下添加 Spring Cloud Feign 发起对媒资微服务的远程调用,所有要在内容管理启动类上添加开启 Feign 的注解配置,如下:

  1. package com.xuecheng.feign.config;
  2. import org.springframework.cloud.openfeign.EnableFeignClients;
  3. import org.springframework.context.annotation.ComponentScan;
  4. import org.springframework.context.annotation.Configuration;
  5. /**
  6. * <p></p>
  7. *
  8. * @Description:
  9. */
  10. @Configuration
  11. @ComponentScan(basePackages = "com.xuecheng.feign")
  12. @EnableFeignClients(basePackages = {"com.xuecheng.feign"})
  13. public class FeignConfig {
  14. }

1.4.3.3 编写 Feign 熔断器


为了保护微服务间远程调用,不会出现链路失效(雪崩),我们需要对Feign接口编写Sentinel的熔断降级方法,内容如下:
xc-api 添加Sentinel依赖

  1. <!--sentinel-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-validation</artifactId>
  9. </dependency>

xc-content-service 添加Sentinel配置

  1. #微服务配置
  2. spring:
  3. application:
  4. name: content-service
  5. cloud:
  6. sentinel:
  7. transport:
  8. dashboard: 192.168.94.129:8858 #sentinel控制台地址

xc-content-service 添加Sentinel降级方法

  1. package com.xuecheng.feign.media.sentinel;
  2. import com.xuecheng.api.media.model.dto.MediaDTO;
  3. import com.xuecheng.common.domain.code.CommonErrorCode;
  4. import com.xuecheng.common.domain.response.RestResponse;
  5. import com.xuecheng.feign.media.MediaApiAgent;
  6. import feign.hystrix.FallbackFactory;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.stereotype.Component;
  9. /**
  10. * <p></p>
  11. *
  12. * @Description:
  13. */
  14. @Slf4j
  15. @Component
  16. public class MediaApiAgentFallback implements FallbackFactory<MediaApiAgent> {
  17. @Override
  18. public MediaApiAgent create(Throwable throwable) {
  19. return new MediaApiAgent() {
  20. @Override
  21. public RestResponse<MediaDTO> getMediaById4s(Long mediaId) {
  22. log.error(CommonErrorCode.E_999982.getDesc()+"error msg : {}",throwable.getMessage());
  23. return RestResponse.validfail(CommonErrorCode.E_999982);
  24. }
  25. };
  26. }
  27. }

编写好后,需要在 MediaApiAgent 的 FeignClient 注解属性中 fallback 添加值,如下:

  1. package com.xuecheng.feign.media;
  2. import com.xuecheng.api.media.model.dto.MediaDTO;
  3. import com.xuecheng.common.constant.XcFeignServiceNameList;
  4. import com.xuecheng.common.domain.response.RestResponse;
  5. import com.xuecheng.feign.media.sentinel.MediaApiAgentFallback;
  6. import org.springframework.cloud.openfeign.FeignClient;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.PathVariable;
  9. /**
  10. * <p></p>
  11. *
  12. * @Description:
  13. */
  14. @FeignClient(value = XcFeignServiceNameList.XC_MEDIA_SERVICE,fallbackFactory = MediaApiAgentFallback.class)
  15. public interface MediaApiAgent {
  16. String PREFIX_TAG = "/media/";
  17. @GetMapping(PREFIX_TAG+"l/media/{mediaId}")
  18. RestResponse<MediaDTO> getMediaById4s(@PathVariable Long mediaId);
  19. }


1.4.3.4 开启自动扫包


在xc-api工程下的 resource目录下创建 META-INF/spring.factories 文件,文件内容如下

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xuecheng.feign.config.FeignConfig

1.4.4 课程计划绑定媒资接口定义


1.接口参数列表
根据前后端传入参数列表来定义接口
Http接口地址

Day07-第三章-媒资管理-课程绑定媒资信息 - 图16

接口传入传出列表

Day07-第三章-媒资管理-课程绑定媒资信息 - 图17

2. 传入传出的参数封装类
根据前后端传入参数封装 VO 类
●VO 封装类

  1. package com.xuecheng.api.media.model.vo;
  2. import io.swagger.annotations.ApiModel;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. /**
  6. * <p>
  7. * 教学计划-媒资绑定提交数据
  8. * </p>
  9. *
  10. * @Description:
  11. */
  12. @Data
  13. @ApiModel(value="BindTeachplanMediaVO", description="教学计划-媒资绑定提交数据")
  14. public class BindTeachplanMediaVO {
  15. @ApiModelProperty(value = "媒资信息标识", required = true)
  16. private Long mediaId;
  17. @ApiModelProperty(value = "课程计划标识", required = true)
  18. private Long teachplanId;
  19. }

根据前后端传出参数封装 DTO 类
●DTO 封装类 (代码生成器生成)

  1. package com.xuecheng.api.content.model;
  2. import io.swagger.annotations.ApiModel;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. import java.io.Serializable;
  6. @Data
  7. @ApiModel(value="TeachplanMediaDTO", description="")
  8. public class TeachplanMediaDTO implements Serializable {
  9. @ApiModelProperty(value = "主键")
  10. private Long teachplanMediaId;
  11. @ApiModelProperty(value = "媒资信息标识")
  12. private Long mediaId;
  13. @ApiModelProperty(value = "课程计划标识")
  14. private Long teachplanId;
  15. @ApiModelProperty(value = "课程标识")
  16. private Long courseId;
  17. @ApiModelProperty(value = "课程发布标识")
  18. private Long coursePubId;
  19. @ApiModelProperty(value = "媒资文件原始名称")
  20. private String mediaFilename;
  21. }

3. 接口编写
在 xc-api 工程的 com.xuecheng.api.content 包下创建接口类接口定义如下:
●根据Id查询媒资

  1. /**
  2. * <p>
  3. * 课程计划服务API
  4. * </p>
  5. */
  6. @Api(value = "课程计划信息管理",tags = "内容-课程计划信息管理Api接口",description = "课程计划信息管理Api接口")
  7. public interface TeachplanApi {
  8. //其他代码省略
  9. @ApiOperation("课程计划绑定媒资信息")
  10. TeachplanMediaDTO associateMedia(BindTeachplanMediaVO vo);
  11. }

1.4.5 课程计划绑定媒资接口开发


1.DAO编写
Mybatis Plus 已经简化了单表操作,它提供的 Api 就可以完成添加数据操作,所有不需要进行编写。
2. service编写
对于service层,除了进行业务层逻辑判断和操作外,还需要将 PO 数据转为 DTO 数据,所以需要编写课程计划的对象属性映射。
●对象属性转换类

  1. package com.xuecheng.content.convert;
  2. import com.xuecheng.api.content.model.TeachplanMediaDTO;
  3. import com.xuecheng.api.content.model.vo.BindTeachplanMediaVO;
  4. import com.xuecheng.content.entity.TeachplanMedia;
  5. import org.mapstruct.Mapper;
  6. import org.mapstruct.Mapping;
  7. import org.mapstruct.factory.Mappers;
  8. import java.util.List;
  9. /**
  10. * 课程计划媒资的PO和DTO转换器
  11. */
  12. @Mapper
  13. public interface TeachplanMediaConvert {
  14. TeachplanMediaConvert INSTANCE = Mappers.getMapper(TeachplanMediaConvert.class);
  15. TeachplanMedia dto2entity(TeachplanMediaDTO teachplanDTO);
  16. TeachplanMediaDTO entity2dto(TeachplanMedia teachplanMedia);
  17. }

●接口

  1. package com.xuecheng.content.service;
  2. import com.xuecheng.api.content.model.TeachplanMediaDTO;
  3. import com.xuecheng.content.entity.TeachplanMedia;
  4. import com.baomidou.mybatisplus.extension.service.IService;
  5. /**
  6. * 课程计划和媒资服务类
  7. */
  8. public interface TeachplanService extends IService<Teachplan> {
  9. //其他代码省略
  10. /**
  11. * 课程计划绑定媒资信息
  12. * @param dto
  13. * @param companyId
  14. * @return
  15. */
  16. TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto,Long companyId);
  17. }

●实现类

  1. /**
  2. * <p>
  3. * 课程计划 服务实现类
  4. * </p>
  5. */
  6. @Slf4j
  7. @Service
  8. public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {
  9. @Autowired
  10. private MediaApiAgent mediaApiAgent;
  11. @Autowired
  12. private TeachplanMediaService teachplanMediaService;
  13. /*
  14. * 业务分析:
  15. * 1.判断关键数据
  16. * teachplanid mediaid
  17. * 2.判断业务数据
  18. * 课程的计划
  19. * 判断是否是第三级
  20. * 判断课程类型是否录播(课程基本信息中获得并判断)
  21. * 判断课程计划是否存在:teachplanId、courseId
  22. *
  23. * 课程基本信息
  24. * 判断是否存在或是否是同一家机构
  25. * 判断是否删除
  26. * 判断审核状态:未提交、审核未通过
  27. *
  28. * 媒资信息
  29. * 判断媒资信息是否存在
  30. * 判断是否是同一家机构
  31. * 判断媒资审核状态
  32. * 3.保存课程计划和媒资信息的数据
  33. * 判断绑定的信息是否存在
  34. * 如果存在
  35. * 修改绑定媒资的信息即可:mediaid mediafilename
  36. * 如果不存在
  37. * 添加课程计划媒资信息
  38. *
  39. * 4.修改课程计划的类型
  40. * mediatype值
  41. *
  42. *
  43. * 5.判断保存的结果并返回数据库最新数据内容
  44. * */
  45. @Transactional
  46. public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
  47. //1.判断关键数据
  48. // teachplanid mediaid
  49. if (ObjectUtils.isEmpty(dto.getTeachplanId())||
  50. ObjectUtils.isEmpty(dto.getMediaId())||
  51. ObjectUtils.isEmpty(companyId)
  52. ) {
  53. ExceptionCast.cast(CommonErrorCode.E_100101);
  54. }
  55. // 2.判断业务数据
  56. // 课程计划
  57. // 判断是否存在
  58. Teachplan teachplan = this.getById(dto.getTeachplanId());
  59. if (ObjectUtils.isEmpty(teachplan)) {
  60. ExceptionCast.cast(ContentErrorCode.E_120402);
  61. }
  62. // 判断课程计划等级
  63. // 只有三级课程计划才可以绑定媒资
  64. if (!(TeachPlanEnum.THIRD_LEVEL.equals(teachplan.getGrade()))) {
  65. ExceptionCast.cast(ContentErrorCode.E_120410);
  66. }
  67. // 课程基础信息
  68. // 判断是否是同一家机构
  69. // 判断该课程的审核状态(课程基础信息)
  70. // 只有:未提交、审核未通过
  71. CourseBaseDTO courseBase = getCourseAndVerify(companyId, teachplan.getCourseId());
  72. // 判断课程的类型:只有是点播课程才可以绑定媒资信息
  73. String teachmode = courseBase.getTeachmode();
  74. if (!(CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode().equals(teachmode))) {
  75. ExceptionCast.cast(ContentErrorCode.E_120418);
  76. }
  77. // 媒资信息
  78. // 判断是否存在
  79. // 判断是否是同一家教学机构
  80. // 判断媒资审核状态
  81. RestResponse<MediaDTO> mediaResponse = mediaApiAgent.getMediaById4s(dto.getMediaId());
  82. if (!(mediaResponse.isSuccessful())) {
  83. ExceptionCast.castWithCodeAndDesc(mediaResponse.getCode(),mediaResponse.getMsg());
  84. }
  85. MediaDTO mediaDTO = mediaResponse.getResult();
  86. if (!(ObjectUtils.nullSafeEquals(companyId,mediaDTO.getCompanyId()))) {
  87. ExceptionCast.cast(CommonErrorCode.E_100108);
  88. }
  89. if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaDTO.getAuditStatus()))) {
  90. ExceptionCast.cast(ContentErrorCode.E_120416);
  91. }
  92. // 3.保存课程计划和媒资信息的数据
  93. // 判断绑定的信息是否存在
  94. // select * from teachplan_media where teachplan_id = ? and courseid = ?
  95. LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
  96. queryWrapper.eq(TeachplanMedia::getTeachplanId, dto.getTeachplanId());
  97. queryWrapper.eq(TeachplanMedia::getCourseId, dto.getCourseId());
  98. TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);
  99. boolean teachplanMeidaResult = false;
  100. if (ObjectUtils.isEmpty(teachplanMedia)) {
  101. // 如果不存在
  102. // 添加课程计划媒资信息
  103. teachplanMedia = TeachplanMedia.builder()
  104. .mediaId(dto.getMediaId())
  105. .mediaFilename(mediaDTO.getFilename())
  106. .teachplanId(dto.getTeachplanId())
  107. .courseId(teachplan.getCourseId())
  108. .build();
  109. teachplanMeidaResult = teachplanMediaService.save(teachplanMedia);
  110. } else {
  111. // 如果存在
  112. // 修改绑定媒资的信息即可:mediaid mediafilename
  113. LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
  114. updateWrapper.set(TeachplanMedia::getMediaId, mediaDTO.getId());
  115. updateWrapper.set(TeachplanMedia::getMediaFilename, mediaDTO.getFilename());
  116. updateWrapper.eq(TeachplanMedia::getId, teachplanMedia.getId());
  117. teachplanMeidaResult = teachplanMediaService.update(updateWrapper);
  118. }
  119. if (!teachplanMeidaResult) {
  120. ExceptionCast.cast(ContentErrorCode.E_120421);
  121. }
  122. // 4.修改课程计划的类型
  123. // mediatype值
  124. changeTeachplanMediaType(teachplan.getId(), mediaDTO.getType());
  125. // 5.判断保存的结果并返回数据库最新数据内容
  126. TeachplanMedia po = teachplanMediaService.getById(teachplanMedia.getId());
  127. TeachplanMediaDTO resultDTO = TeachplanMediaConvert.INSTANCE.entity2dto(po);
  128. return resultDTO;
  129. }
  130. private void changeTeachplanMediaType(Long teachplanId, String mediaType) {
  131. LambdaUpdateWrapper<Teachplan> teachplanUpdateWrapper = new LambdaUpdateWrapper<>();
  132. teachplanUpdateWrapper.set(Teachplan::getMediaType, mediaType);
  133. teachplanUpdateWrapper.set(Teachplan::getChangeDate, LocalDateTime.now());
  134. teachplanUpdateWrapper.eq(Teachplan::getId, teachplanId);
  135. boolean teachplanResult = this.update(teachplanUpdateWrapper);
  136. if (!teachplanResult) {
  137. ExceptionCast.cast(ContentErrorCode.E_120421);
  138. }
  139. }
  140. }

3. controller编写

  1. /**
  2. * <p>
  3. * 课程计划 前端控制器
  4. * </p>
  5. *
  6. * @author itcast
  7. */
  8. @Slf4j
  9. @RestController
  10. public class CourseBaseController implements CourseBaseApi {
  11. @Autowired
  12. private TeachplanService teachplanService;
  13. //其他代码省略
  14. @PostMapping("teachplan/media/association")
  15. public TeachplanMediaDTO associateMedia(@RequestBody BindTeachplanMediaVO vo) {
  16. Long companyId = SecurityUtil.getCompanyId();
  17. TeachplanMediaDTO teachplanMediaDTO = new TeachplanMediaDTO();
  18. teachplanMediaDTO.setTeachplanId(vo.getTeachplanId());
  19. teachplanMediaDTO.setMediaId(vo.getMediaId());
  20. TeachplanMediaDTO resultDTO = teachplanService.associateMedia(teachplanMediaDTO, companyId);
  21. return resultDTO;
  22. }
  23. }

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
●请求体信息(查询条件参数)

  1. {
  2. "mediaId":25,
  3. "teachplanId":48
  4. }

●请求头(访问令牌)

  1. 请求头的 key 值: authorization
  2. 请求头的 value 值:
  3. Bearer ewogICAgImF1ZCI6IFsKICAgICAgICAieHVlY2hlbmctcmVzb3VyY2UiCiAgICBdLAogICAgInBheWxvYWQiOiB7CiAgICAgICAgIjExNzcxNDQyMDk0NjMxMjgxMjUiOiB7CiAgICAgICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJ1c2VyX2F1dGhvcml0aWVzIjogewogICAgICAgICAgICAgICAgInJfMDAxIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb21wYW55X21vZGlmeSIsCgkJCQkJInhjX2NvbXBhbnlfdmlldyIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2RlbCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2VkaXQiLAoJCQkJCSJ4Y19jb3Vyc2VfYmFzZV9saXN0IiwKCQkJCQkieGNfY291cnNlX2Jhc2Vfc2F2ZSIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX3ZpZXciLAoJCQkJCSJ4Y19jb3Vyc2VfcHVibGlzaCIsCgkJCQkJInhjX21hcmtldF9zYXZlX21vZGlmeSIsCgkJCQkJInhjX21hcmtldF92aWV3IiwKCQkJCQkieGNfbWVkaWFfZGVsIiwKCQkJCQkieGNfbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX3ByZXZpZXciLAoJCQkJCSJ4Y19tZWRpYV9zYXZlIiwKCQkJCQkieGNfdGVhY2hlcl9saXN0IiwKCQkJCQkieGNfdGVhY2hlcl9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaGVyX3NhdmUiLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2NvcnJlY3Rpb24iLAoJCQkJCSJ4Y193b3JrcmVjb3JkX2xpc3QiLAoJCQkJCSJ4Y190ZWFjaHBsYW53b3JrX2RlbCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfbGlzdCIsCgkJCQkJInhjX3RlYWNocGxhbndvcmtfc2F2ZV9tb2RpZnkiLAoJCQkJCSJ4Y190ZWFjaHBsYW5fZGVsIiwKCQkJCQkieGNfdGVhY2hwbGFuX3NhdmVfbW9kaWZ5IiwKCQkJCQkieGNfdGVhY2hwbGFuX3ZpZXciCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJfMDAyIjogWwogICAgICAgICAgICAgICAgICAgICJ4Y19jb3Vyc2VfYWRtaW5fbGlzdCIsCgkJCQkJInhjX2NvdXJzZV9iYXNlX2NvbW1pdCIsCgkJCQkJInhjX3N5c3RlbV9jYXRlZ29yeSIsCgkJCQkJInhjX21fbWVkaWFfbGlzdCIsCgkJCQkJInhjX21lZGlhX2F1ZGl0IgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSwKICAgICJ1c2VyX25hbWUiOiAieGMtdXNlci1maXJzdCIsCiAgICAic2NvcGUiOiBbCiAgICAgICAgInJlYWQiCiAgICBdLAogICAgIm1vYmlsZSI6ICIxNTAxMjM0NTY3OCIsCiAgICAiZXhwIjogMTYwNjUyNTEyMiwKICAgICJjbGllbnRfYXV0aG9yaXRpZXMiOiBbCiAgICAgICAgIlJPTEVfVVNFUiIKICAgIF0sCiAgICAianRpIjogIjFlYjdlOTg3LWQ3YzItNDBmNS1iMGQ2LWNkNjEzOWNiMThlMCIsCiAgICAiY2xpZW50X2lkIjogInhjLWNvbS1wbGF0Zm9ybSIsCiAgICAiY29tcGFueUlkIjogMTIzMjE0MTQyNQp9

响应内容如下(例子):

Day07-第三章-媒资管理-课程绑定媒资信息 - 图18