今日重点

  1. 内容管理需求(第二层业务支持系统管理):
  2. 内容管理概述: CMS 利用计算机技术,解决企业的数据管理!
  3. 比如:新闻媒体对新闻信息进行管理
  4. 物流对订单内容进行管理
  5. 本项目的内容管理主要是对课程内容进行管理,课程的录入,上传审核等等!
  6. 内容管理主体流程:
  7. 教育机构: 开始----> 添加课程和课程营销内容(课程基本信息,课程营销) ---> 添加课程计划(大纲) ----> 添加课程教师信息 --->结束
  8. 教育机构通过内容管理可以对课程内容、课程营销、课程计划、课程教师进行操作
  9. 学成在线: 分布式业务结构,将一个大业务,拆分成很多小业务来进行具体实现!
  10. 数据模型: 表结构
  11. 项目开发步骤说明(面试)
  12. 项目基于前后的分离架构进行开发,前后端分离架构总体是包括前端和服务端,通常是多人协作并进行开发:
  13. 1. 需求分析
  14. 梳理用户需要,分析业务流程(思路以及项目业务很重要,对业务不懂 代码完成不了!)
  15. 2. 接口定义
  16. 根据上一步的基础下定义微服务接口,提供前端调用
  17. 3.服务端和前端并行开发
  18. 对业务接口进行开发,并自行使用工具测试(POSTMAN,JUNIT)
  19. 前端开发用户操作界面,也会进行伪数据填充查看页面显示效果!
  20. 4.前后端集成测试
  21. 在前后的完成各自的开发后,对其整个业务进行前后端集成测试
  22. 课程查询接口业务说明:
  23. 接口业务需求:
  24. 分页查询课程基本信息 多条件查询如果没有条件就查所有 课程机构只能查属于自己的课程(数据隔离)
  25. 基于Http请求,响应json数据
  26. 接口规范说明:
  27. 查询 GET 添加POST 修改 PUT 删除 DELETE
  28. 前面动词集合,每个动词都有相对于规范的功能不可以乱用)
  29. 后面url路径就不能写动词了(多个单词之间用 "-" 间隔开)尽量使用复数!
  30. qo(请求对象) Query Object: 此包内主要放置前端传输的Requestbody对象方便后端接收!
  31. 接口参数规范:
  32. POST请求 : 分页数据 QuerySting
  33. 查询条件 RequestBody
  34. 虽然查询数据按需求文档应该使用GET请求,但是多条件查询只有param传参是不满足我们的需求的,
  35. 所以为了能够使用@RequestBody 接收前端传送的JSON格式的对象 只能使用带请求体的POST请求!
  36. 请求对象封装命名: Query+功能名称+model
  37. Response响应封装为Pagevo:
  38. 当前页 Page
  39. 每页条数 Pagesize
  40. 总条数 counts
  41. 当前页数据的集合 items
  42. nacos如何配置区分 : 不同开发环境下的配置 namespace
  43. 一个公司不同项目组 group
  44. 一个项目不同工程 DataId
  45. 同一环境下,同一个项目不同种类的配置 profile
  46. nacos如何减少冗余配置: 使用公共配置 bootstrap.yml share.config 公共的配置会优先读取,如果自己模块的配置文件也配置了相同的会覆盖掉公共的配置! dateifd
  47. 数据传输返回对象的设置 :
  48. POJO中是整个对应数据库表结构的,如果直接将查询出来的所有结果返回给前端是不安全的,并且前端如果不需要全部参数
  49. 传输多余的参数也是会带来程序转换json时负担的,解决此问题 数据传输的实体类(entity)会单独定义,称为DTO
  50. 解决三个问题:
  51. 1.保护后端数据库的表字段 不直接暴露给前端完整的数据库结构
  52. 2.前端需要什么参数就给什么参数 (尽量避免资源的浪费)
  53. 3.数据量变小后转换JSON的效率就会大大提高!
  54. DTO(Data Transfer Object,数据传送对象) 是一个普通的Java类,它封装了要传送的数据。当前端需要读取服务器端的数据的时候,服务器端将数据封装在DTO中,这样前端就可以获得它需要的所有数据。
  55. MapStruct工具介绍:
  56. 代码生成器,基于约定优于配置的方法简化java bean直接的属性映射,生成映射代码使用简单的方法调用!
  57. source(表对应对象属性) target(DTO对应对象属性)
  58. 底层源代码实现也是基于get,set方法,如果要批量转换集合类型单个对象转换必须要有 因为集合转换是基于单个对象转换实现的,如果有多个属性名不一致可以使用@Mappers设置多个属性的映射关系!
  59. 机构隔离的依据数据获得:
  60. 教学机构 登陆后会带着令牌(存在用户本地浏览器)
  61. 放置在请求头中 key :authorization value:Bearer es。。。。
  62. 1.教育机构需要通过第三方登录系统完成登录操作。(会面课程会讲解并实现)
  63. 2.登录成功后会向教学机构的客户端保存一份token(令牌,相当于电影票)。
  64. 3.教学机构每次在使用前端系统访问后端微服务时都会将token以请求头的方式发送后端。
  65. 请求头的key:authorization
  66. 请求头的value:Bear ewogICAgImF1ZCI6IFsK..........
  67. 4.后端微服务接收到请求后需要从请求头中获得令牌,并解析后拿到教学机构的id值。
  68. 5.通过教学机构的id值来查询本公司下的课程数据,并返回结果。
  69. 关于1.mapper.xml 文件在本项目中为什么可以放到 src/main/java 路径下,而非resource目录下
  70. MP的公共配置信息中做了配置设置!
  71. 什么是jsr,java 8日期
  72. java 8 日期/时间 API 是 JSR-310 的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时 间 API 的一些设计原则是:
  73. 不变性:新的日期/时间 API 中,所有的类都是不可变的,这对多线程环境有好处。
  74. 关注点分离:新的 API 将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间 (Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
  75. 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用 now()方法, 在所有的类中都定义了 format()和 parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,
  76. 所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  77. 实用操作:所有新的日期/时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从 日期/时间中提取单独部分,等等。
  78. 可扩展性:新的日期/时间 API 是工作在 ISO-8601 日历系统上的,但我们也可以将其应用在非 ISO 的日历上

1. 内容管理需求

1.1 内容管理概述


1. 内容管理是什么 ?
内容管理系统(content management system ),是协助组织和个人,借助信息技术,实现内容的创建、储存、分享、应用、检索,并在企业个人、组织、业务、战略等诸方面产生价值的过程 。能够支撑内容管理的一种工具或一套工具的软件系统。 不同的项目对内容的定位不同,比如:新闻媒体对新闻信息的管理,公司管理对公司内部数据内容管理、物流对订单内容管理等。
2. 本项目的内容管理系统定位是什么?
本项目作为一个大型的在线教育平台,其内容管理主要对课程相关内容进行管理,从课程数据的录入、课程审批、课程内容发布等内容性的业务需求数据进行管理。

1.2 业务介绍(背)


教育机构通过内容管理可以对课程内容、课程营销、课程计划、课程教师等操作,流程如下:

Day02-第二章-内容管理-课程查询和新增 - 图1

需求列表如下:
1、课程内容:包括课程的基本信息课程营销
2、课程计划:包括课程授课的主体大纲和关联的大纲的资料。
3、课程教师:包括课程授课的教师信息(课程不讲解,留作课程作业)。

1.3 业务流程

1.3.1 课程内容管理
1.教育机构用户在门户管理界面中的课程管理链接进入课程管理界面。

Day02-第二章-内容管理-课程查询和新增 - 图2

2.在管理界面中可以对课程进行列表查询和管理

Day02-第二章-内容管理-课程查询和新增 - 图3

3.添加课程时选在课程的类型

Day02-第二章-内容管理-课程查询和新增 - 图4

4 选择课程类型后,添加课程基本信息和课程营销数据

Day02-第二章-内容管理-课程查询和新增 - 图5

1.3.2 课程计划管理
1 对课程基本信息保存后,填写课程计划,如果课程有课程计划需要将其查询出来

Day02-第二章-内容管理-课程查询和新增 - 图6

2 对新课程没有课程计划,需要填写课程计划大章节

Day02-第二章-内容管理-课程查询和新增 - 图7

3 在课程计划大章节下填写课程小章节

Day02-第二章-内容管理-课程查询和新增 - 图8

Day02-第二章-内容管理-课程查询和新增 - 图9
1.3.3 课程教师管理
1 保存课程计划,对课程的教师进行管理,如果课程有教师信息需要查询出来,并对课程的教师进行管理

Day02-第二章-内容管理-课程查询和新增 - 图10

PS:课程中不会进行实现,需要学员自行完成。

1.4 内容管理数据模型


学成在线整个数据结构体系是不同的微服务操作不同的数据库中的内容,而内容管理在 MySQL 数据库里单独操作 xc_content 数据库,如下图:
学成在线数据结构图

Day02-第二章-内容管理-课程查询和新增 - 图11

针对内容管理的业务需求,xc_content 数据库中的表主要包含下面内容:
内容管理数据模型(表结构)

Day02-第二章-内容管理-课程查询和新增 - 图12

PS:后面功能实现时,设计到哪个表,我们在对表哪个表再详讲。

2. 项目的开发步骤(背)


学成在线项目是基于前后端分离的架构进行开发,前后端分离架构总体上包括前端和服务端,通常是多人协作并行开发,开发步骤如下:

1.需求分析


梳理用户的需求,分析业务流程。


2.接口定义


根据需求分析定义服务端微服务接口,提供前端调用。


3.服务端和前端并行开发


●服务端依据接口进行服务端接口开发(Java后端开发人员)。
○对业务接口进行开发
○开发完接口要对其进行测试
●前端开发用户操作界面,并调用服务端接口完成业务处理(全栈或前端开发人员)。
○使用前端技术完成前端界面的构建
○调用服务端来获取数据


4.前后端集成测试


●在前后端完成各自的开发后,对其整个业务进行前后端集成测试。

PS:在上面的前后端开发步骤中,我们Java后端开发人员只关心以下事项(一般)
●功能的业务流程和分析
●后端接口定义
●后端微服务的开发
●后端接口的测试
●前后端集成测试

3. 课程基础信息查询


学成在线第一个后端业务模块 ‘内容管理系统’ 中有不少的数据和业务,大家可以学习前后端开发中的后端微服务接口开发。从中学习数据库表结构和数据库表间的关系后端微服务开发步骤并能根据开发规范来编写代码操作
下面我们来开发第一个功能 :‘课程基本信息管理’ 中的 ‘课程基本信息查询接口’ 。

3.1 课程查询接口业务需求


在开发之前,我们按照前后端开发步骤,先进行 ‘课程基本信息查询接口’ 需求分析,本次定义查询接口,接口中需要供前端请求查询课程基本列表,支持分页及自定义条件查询方式。

3.1.1 接口业务需求


具体需求如下:
1、分页查询 课程基本信息(CourseBase)集合数据
2、根据课程名称和审核状态条件进行数据查询
3、教学机构只能查询到属于自己机构下的课程基本信息
4、接口基于Http 请求,响应Json数据
页面操作:
课程列表示例图

Day02-第二章-内容管理-课程查询和新增 - 图13

3.1.2 数据模型(表结构)


查询数据来源于数据库,从内容管理数据库(xc_content)中 course_base 表中查询数据,下面我们来创建数据库和数据库表结构,并了解表中结构。
1. 导入数据库数据
在今天下发资料里的数据脚本导入到本地 MySQL 数据中,资料位置在 ‘资料/数据库脚本/xc_content.sql’ 。
导入后的数据库内容

Day02-第二章-内容管理-课程查询和新增 - 图14

2. 课程基本信息表说明
下面我来分析下这张表的主要字段。
课程基本信息(course_base)表结构

Day02-第二章-内容管理-课程查询和新增 - 图15

●课程基本信息表分析
机构相关数据
课程基本信息是附属于一个教育架构下,学成在线主要是提供在线教学的平台。教学机构入驻后,在平台创建课程数据。
课程自身信息
对课程数据基本信息的描述,说明课程的教学模式、课程名称等。
课程数据操作数据
课程数据的操作会在相关字段进行记录,例如:课程数据的创建时间、课程数据的修改时间、课程数据的创建者等。
课程审核信息
教育结构创建出课程后,学成在线运营商需要对其进行数据审核,审核的操作也会进行记录,例如:审核人、审核时间等。

3.1.3 实体类 PO定义


对于数据库中的表来说,开发工程中会定义一个类来描述这张表的结构,这个类一般称之为 PO(persistent Object)持久对象。下面我们要创建出课程基本数据(course_base)表的 PO 类,创建出的位置在工程项目包结构位置:com.xuecheng.content.entity 。具体代码如下:
●课程基本信息 CourseBase实体类(MP 代码生成器生成)

  1. package com.xuecheng.content.entity;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableId;
  4. import java.time.LocalDateTime;
  5. import com.baomidou.mybatisplus.annotation.FieldFill;
  6. import com.baomidou.mybatisplus.annotation.TableField;
  7. import java.io.Serializable;
  8. import lombok.Data;
  9. import com.baomidou.mybatisplus.annotation.TableName;
  10. /**
  11. * <p>
  12. * 课程基本信息
  13. * </p>
  14. */
  15. @Data
  16. @TableName("course_base")
  17. public class CourseBase implements Serializable {
  18. /**
  19. * 主键
  20. */
  21. @TableId(value = "id", type = IdType.AUTO)
  22. private Long id;
  23. /**
  24. * 机构ID
  25. */
  26. private Long companyId;
  27. /**
  28. * 机构名称
  29. */
  30. private String companyName;
  31. /**
  32. * 课程名称
  33. */
  34. private String name;
  35. /**
  36. * 适用人群
  37. */
  38. private String users;
  39. /**
  40. * 课程标签
  41. */
  42. private String tags;
  43. /**
  44. * 大分类
  45. */
  46. private String mt;
  47. /**
  48. * 课程大类名称
  49. */
  50. private String mtName;
  51. /**
  52. * 小分类
  53. */
  54. private String st;
  55. /**
  56. * 课程小类名称
  57. */
  58. private String stName;
  59. /**
  60. * 课程等级
  61. */
  62. private String grade;
  63. /**
  64. * 教育模式(common普通,record 录播,live直播等)
  65. */
  66. private String teachmode;
  67. /**
  68. * 课程介绍
  69. */
  70. private String description;
  71. /**
  72. * 课程图片
  73. */
  74. private String pic;
  75. /**
  76. * 创建时间
  77. */
  78. @TableField(fill = FieldFill.INSERT)
  79. private LocalDateTime createDate;
  80. /**
  81. * 修改时间
  82. */
  83. @TableField(fill = FieldFill.INSERT_UPDATE)
  84. private LocalDateTime changeDate;
  85. /**
  86. * 创建人
  87. */
  88. private String createPeople;
  89. /**
  90. * 更新人
  91. */
  92. private String changePeople;
  93. /**
  94. * 审核状态
  95. */
  96. private String auditStatus;
  97. /**
  98. * 审核意见
  99. */
  100. private String auditMind;
  101. /**
  102. * 审核次数
  103. */
  104. private Integer auditNums;
  105. /**
  106. * 审核时间
  107. */
  108. private LocalDateTime auditDate;
  109. /**
  110. * 审核人
  111. */
  112. private String auditPeople;
  113. /**
  114. * 是否删除:1为未删除,0为删除
  115. */
  116. private Integer status;
  117. /**
  118. * 课程发布标识
  119. */
  120. private Long coursePubId;
  121. }

3.2 课程查询接口定义


下面开始课程查询接口定义,在定义接口前,我们先来讨论接口定义的规范,在熟悉规范后再来定义查询接口。为什么要根据规范来定义接口呢?
前端调用接口示意图

Day02-第二章-内容管理-课程查询和新增 - 图16

  1. 前后端分离开发中,前端会根据后端微服务所提供的 HTTP 访问接口地址来获得数据并在前端页面展示,那么前后端的数据交互的格式和规范就显得尤为重要。前端需要传入哪些参数、请求的方式是哪种、获得的数据格式和内容等等,都需要前后端进行讨论并定出接口的规范。<br /> 项目在开发时,为了控制代码风格和接口规范,一般对其进行限定并制作文档。如:xxx开发手册、xxx开发文档、xxx开发规范文档。

3.2.1 接口开发规范文档


参照 ‘项目开发规范文档.md — 接口开发规范’ 文档。

3.2.2 接口定义


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

Day02-第二章-内容管理-课程查询和新增 - 图17

接口传入传出列表

Day02-第二章-内容管理-课程查询和新增 - 图18

2. 查询条件封装类创建
在 xc-api 工程的 com.xuecheng.api.content.model.qo 包下创建类,如下:
●课程条件查询类

  1. package com.xuecheng.api.content.model.qo;
  2. import lombok.Data;
  3. /**
  4. * <p>
  5. * 课程基本信息查询条件封装 QO (query object)
  6. * </p>
  7. *
  8. * @Description: 根据课程名称、审核状态条件进行数据查询
  9. */
  10. @Data
  11. @ApiModel("课程基础信息查询QO对象")
  12. public class QueryCourseModel {
  13. @ApiModelProperty("课程审核状态")
  14. private String auditStatus;
  15. @ApiModelProperty("课程名称")
  16. private String courseName;
  17. }

3. 接口编写
在 xc-api 接口工程专门定义接口,在Api工程单独定义接口的原因如下:
1.接口集中管理
2.Api工程的接口将作为各微服务远程调用使用
在 com.xuecheng.api.content 包下创建接口类,页面查询接口定义如下:
●课程基本信息 Api 接口

  1. package com.xuecheng.api.content;
  2. import com.xuecheng.api.content.model.qo.QueryCourseModel;
  3. import com.xuecheng.common.domain.page.PageRequestParams;
  4. import com.xuecheng.common.domain.page.PageVO;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiImplicitParam;
  7. import io.swagger.annotations.ApiImplicitParams;
  8. import io.swagger.annotations.ApiOperation;
  9. /**
  10. * <p>
  11. * 课程基本信息Api接口
  12. * </p>
  13. *
  14. * @Description:
  15. */
  16. @Api(value = "课程基本信息Api接口",tags = "内容-课程基本业务信息Api接口",description = "课程基本业务信息Api接口")
  17. public interface CourseBaseApi {
  18. /**
  19. * 分页条件查询课程基本信息
  20. * @param params
  21. * @param model
  22. * @return
  23. */
  24. @ApiOperation("分页条件查询课程基本信息")
  25. PageVO queryCourseList(PageRequestParams params, QueryCourseModel model);
  26. }

此接口编写后会在内容管理服务工程编写Controller类实现此接口。
4. 编写 Controller 控制层
在xc-content-service 的 controller 包下创建 CourseBaseController 接口,并实现 CourseBaseApi 接口,如下:

  1. package com.xuecheng.content.controller;
  2. import com.xuecheng.api.content.CourseBaseApi;
  3. import com.xuecheng.api.content.model.qo.QueryCourseModel;
  4. import com.xuecheng.common.domain.page.PageRequestParams;
  5. import com.xuecheng.common.domain.page.PageVO;
  6. import com.xuecheng.content.service.CourseBaseService;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RestController;
  11. /**
  12. * <p>
  13. * 课程基本信息 前端控制器
  14. * </p>
  15. *
  16. * @author itcast
  17. */
  18. @RestController
  19. public class CourseBaseController implements CourseBaseApi {
  20. @Autowired
  21. private CourseBaseService courseBaseService;
  22. @PostMapping("course/list")
  23. public PageVO queryCourseList(PageRequestParams params,
  24. @RequestBody QueryCourseModel model) {
  25. return null;
  26. }
  27. }

3.2.3 接口数据传输


通过上面的代码编写,我们已经将 Controller 对外暴露 Http 请求接口并使用 Swagger 生成文档并测试通过,Service层也可调用 Mapper 获得数据库数据。最后只需要在 Controller 层调用 Service 方法便可以将整个业务员实现。虽然功能实现了,但问题也出现。
前后端数据交换示意图

Day02-第二章-内容管理-课程查询和新增 - 图19

  1. 从上图我们可以看到 DAO 从数据库取出的数据会封装到 PO 类对象中,而且 PO 类对象数据会以 Json 格式一直传送到前端,整个过程是将 PO 作为业务数据传输对象。这样传递数据会出现三个问题:<br />●问题一:假设数据库一张表的字段非常多,前端并非想获得一张表的全部字段值,只是获得关键的数据。<br />●问题二:数据库表字段越多,则 PO 中的属性就越多,从而导致 PO 对象转为 Json 时,由于字段多引起转换效率问题。<br />●问题三:PO 中的属性名称一般情况下会和数据库字段名称保持一致,前端获得 PO 中的数据,可能会暴露数据库字段名称。<br />上述问题解决方案:在微服务开发时,数据传输的实体类(entity)会单独定义,并称为 DTO

3.2.3.1 数据传输对象
DTO(Data Transfer Object,数据传送对象) 是一个普通的Java类,它封装了要传送的数据。当前端需要读取服务器端的数据的时候,服务器端将数据封装在DTO中,这样前端就可以获得它需要的所有数据。
由于 DTO 中的属性都是前端所需要的。相比 PO 来说,属性会精简,这样在转换 Json 时,效率会好。
DTO 中的属性名称可以定义和 PO 属性名称不一致,这样前端工程获得 DTO 对象中的数据,其属性名称不一致,从而提高了应用的安全性。
DTO 数据传输示意图

Day02-第二章-内容管理-课程查询和新增 - 图20

1.课程数据传输对象定义
其实 DTO 为微服务提供对外的数据封装类,那么,DTO 实体类就会作为接口响应数据,要定义到基础工程 xc-api 中。
创建课程基本信息 DTO 位置

Day02-第二章-内容管理-课程查询和新增 - 图21

●课程基本信息 DTO 信息 PS:可以通过xc-mp-generator 来快速生成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. import java.time.LocalDateTime;
  7. /**
  8. * <p>
  9. * 课程基本信息数据传输实体类
  10. * </p>
  11. */
  12. @Data
  13. @ApiModel(value="CourseBaseDTO", description="课程基本信息DTO")
  14. public class CourseBaseDTO implements Serializable {
  15. @ApiModelProperty(value = "courseBaseId")
  16. private Long courseBaseId;
  17. @ApiModelProperty(value = "机构Id")
  18. private Long companyId;
  19. @ApiModelProperty(value = "机构名称")
  20. private String companyName;
  21. @ApiModelProperty(value = "课程名称")
  22. private String name;
  23. @ApiModelProperty(value = "适用人群")
  24. private String users;
  25. @ApiModelProperty(value = "课程标签")
  26. private String tags;
  27. @ApiModelProperty(value = "大分类")
  28. private String mt;
  29. @ApiModelProperty(value = "大分类名称")
  30. private String mtName;
  31. @ApiModelProperty(value = "小分类")
  32. private String st;
  33. @ApiModelProperty(value = "小分类名称")
  34. private String stName;
  35. @ApiModelProperty(value = "课程等级")
  36. private String grade;
  37. @ApiModelProperty(value = "教育模式(common普通,record 录播,live直播等)")
  38. private String teachmode;
  39. @ApiModelProperty(value = "课程介绍")
  40. private String description;
  41. @ApiModelProperty(value = "课程图片")
  42. private String pic;
  43. @ApiModelProperty(value = "创建时间")
  44. private LocalDateTime createDate;
  45. @ApiModelProperty(value = "修改时间")
  46. private LocalDateTime changeDate;
  47. @ApiModelProperty(value = "审核状态:CourseAuditEnum")
  48. private String auditStatus;
  49. @ApiModelProperty(value = "审核意见")
  50. private String auditMind;
  51. @ApiModelProperty(value = "审核次数")
  52. private Integer auditNums;
  53. @ApiModelProperty(value = "审核时间")
  54. private LocalDateTime auditDate;
  55. @ApiModelProperty(value = "审核人")
  56. private String auditPeople;
  57. }

上述代码解释:
1.已经将与前端业务无关的属性删除
2.其中 Id 的属性名称改为 courseId
PS:为数据安全性,DTO 中的属性名称可以和 PO 属性名称不一致。

3.3 课程查询接口开发


学成在线作为微服务后端的接口开发,也是有对应的开发规范文档。其中对命名规范、常量定义规范、注释、方法等都进行了一定的规范说明,代码要按照开发规范文档来进行开发后端。我们先来了解开发的规范,在去做接口开发。

3.3.1 代码开发规范文档


参照 ‘项目开发规范文档.md’ 文档。

3.3.2 微服务工程结构


1.微服务工程 xc-content-service
下面将在内容管理微服务进行开发查询功能。
内容管理微服务工程示例图

Day02-第二章-内容管理-课程查询和新增 - 图22

2. 工程结构
●包结构本工程为内容管理系统微服务,其包的结构为:
○基础包结构为:com.xuecheng.content
○控制层包:com.xuecheng.content.controller
○服务层包:com.xuecheng.content.service
○持久层:com.xuecheng.content.mapper
○配置文件:com.xuecheng.content.config
●启动类 在基础包结构下 Spring Boot 启动类

  1. package com.xuecheng.content;
  2. import com.spring4all.swagger.EnableSwagger2Doc;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. /**
  6. * <p>
  7. * 内容管理启动类
  8. * </p>
  9. *
  10. * @Description:
  11. */
  12. @EnableSwagger2Doc
  13. @EnableApolloConfig
  14. @SpringBootApplication
  15. public class ContentApplication {
  16. public static void main(String[] args) {
  17. SpringApplication.run(ContentApplication.class);
  18. }
  19. }

PS:启动类一定要放在基础包下。
3. 编写配置文件
●启动配置文件 application.yml 在 maven 的结构中的 src/main/resources 里创建。

  1. #微服务配置
  2. spring:
  3. application:
  4. name: content-service
  5. datasource:
  6. username: root
  7. password: root
  8. driver-class-name: com.mysql.jdbc.Driver
  9. url: jdbc:mysql://ip:3306/xc_content?useUnicode=true&characterEncoding=utf8&useSSL=false
  10. # MP 的配置信息
  11. mybatis-plus:
  12. mapper-locations: classpath:com.xuecheng.content.mapper/*.xml #加载 mybatis 映射文件的位置
  13. # 日志文件配置路径
  14. logging:
  15. config: classpath:log4j2-dev.xml
  16. # swagger 文档配置
  17. swagger:
  18. title: "学成在线内容管理系统"
  19. description: "内容系统管理系统对课程相关信息进行业务管理数据"
  20. base-package: com.xuecheng
  21. enabled: true
  22. version: 1.0.0

上面的配置文件主要信息包括三类:
1.微服务的基本信息
PS:数据库的配置以后有Nacos来管理,下面就有讲解。
2.MP配置
3.日志配置路径
4.swagger配置信息
PS:其他的配置会在配置中心 Nacos 中进行配置。

3.3.3 Nacos 多环境配置

1. Nacos 中的多环境
Nacos抽象定义了Namespace、Group、Data ID的概念,具体这几个概念代表什么,取决于我们把它们看成什么,这里推荐给大家一种用法,如下图:
●Namespace:代表不同环境,如开发、测试、生产环境。
●Group:代表某项目,如XX医疗项目、XX电商项目
●DataId:每个项目下往往有若干个工程,每个配置集(DataId)是一个工程的主配置文件
●profile: 在同一个环境下有不同的配置
下面我们需要在Nacos中创建内容:
①.在项目中我们需要创建出多个 Namespace 环境
②.指定组的名称
③.根据spring.application.name来指定Data Id 值

创建出多个 Namespace
创建 Namespace

Day02-第二章-内容管理-课程查询和新增 - 图23

指定环境的名称(开发环境)

Day02-第二章-内容管理-课程查询和新增 - 图24

指定环境的名称(正式环境)

Day02-第二章-内容管理-课程查询和新增 - 图25

在开发环境创建 groupid
在 dev 中创建配置

Day02-第二章-内容管理-课程查询和新增 - 图26

在配置中定义组名(xc-group)

Day02-第二章-内容管理-课程查询和新增 - 图27

2. 配置文件名称的命名
在项目的开发阶段,为了能够区分出不同环境下的配置,我们将使用 spring boot profile 来解决此问题。在Nacos中正好对此也进行了支持。下面是它的格式:

  1. ${prefix}-${spring.profiles.active}.${file-extension}

●prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
●spring.profiles.active 即为当前环境对应的 profile
●file-extension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension来配置。
内容管理配置文件的名称为:
①.content-service-dev.properties 开发环境(现阶段使用)
②.content-service-test.properties 测试环境
内容管理配置文件的名称

Day02-第二章-内容管理-课程查询和新增 - 图28

3.3.4 公共配置
考虑到 MP 的配置在开发阶段,很多服务都是通用的配置信息,所以需要配置公共信息,并在项目中引用,配置如下:
●MP 全局配置

  1. #驼峰下划线转换
  2. mybatis-plus.global-config.db-column-underline = true
  3. #实体扫描,多个package用逗号或者分号分隔
  4. mybatis-plus.typeAliasesPackage = com.xuecheng.*.entity
  5. #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
  6. mybatis-plus.global-config.field-strategy=2
  7. #全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,开发时不需要开启。
  8. mybatis-plus.configuration.cache-enabled = false
  9. #映射文件mapper文件存储位置
  10. mybatis-plus.mapper-locations = classpath:com.xuecheng.*.mapper/*.xml
  11. #主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
  12. mybatis-plus.global-config.id-type = 0
  13. #刷新mapper 调试神器
  14. mybatis-plus.global-config.refresh-mapper = true

添加公共配置

Day02-第二章-内容管理-课程查询和新增 - 图29

添加mp公共配置信息

Day02-第二章-内容管理-课程查询和新增 - 图30

●添加 spring-http-config.properties 配置
spring-http-config.properties 配置信息

  1. #srpingboot http 配置信息
  2. spring.http.encoding.enabled = true
  3. spring.http.encoding.charset = UTF-8
  4. spring.http.encoding.force = true
  5. server.use-forward-headers = true
  6. server.servlet.context-path = /
  7. server.port=8888

●添加 spring-druid-config.properties 配置
spring-druid-config.properties

  1. #spring druid 配置信息
  2. spring.datasource.driver-class-name = com.mysql.jdbc.Driver
  3. spring.datasource.url = jdbc:mysql://ip:3306/xc_content?userUnicode=true&useSSL=false&characterEncoding=utf8
  4. spring.datasource.password = root
  5. spring.datasource.username = root
  6. #初始化连接池的的连接数据量
  7. spring.datasource.druid.initial-size = 5
  8. #连接池最小连接数
  9. spring.datasource.druid.min-idle = 5
  10. #获取连接等待超时时间
  11. spring.datasource.druid.max-wait = 60000
  12. # 要启用PreparedStatementCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
  13. spring.datasource.druid.max-pool-prepared-statement-per-connection-size = 20
  14. #连接池中最大激活连接数
  15. spring.datasource.druid.max-active = 20

3.3.5 项目配置
在 Nacos 的 dev 命名空间 下 content-service-dev.properties 添加配置信息

  1. #spring http 配置信息
  2. server.servlet.context-path = /content
  3. server.port=63040
  4. #spring druid 配置信息
  5. spring.datasource.url = jdbc:mysql://192.168.94.129:3306/xc_content?userUnicode=true&useSSL=false&characterEncoding=utf8

3.3.6 工程引用配置
对于 xc-content 引入 nacos 中的配置,我们需要在项目中创建 bootstrap.yml 文件,如下:

Day02-第二章-内容管理-课程查询和新增 - 图31

  1. 创建出后,需要在此配置文件中,配置下面的信息:<br /> 1.nacos 的注册中心内容<br /> 2.nacos 的配置中心内容<br />●添加配置信息
  1. <!-- spring cloud nacos -->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba.cloud</groupId>
  8. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  9. </dependency>
  10. <!-- druid 配置 -->
  11. <dependency>
  12. <groupId>com.alibaba</groupId>
  13. <artifactId>druid-spring-boot-starter</artifactId>
  14. </dependency>

●bootstrap.yml 配置

  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 #注册中心nacos地址
  12. namespace: 修改为nacos dev 环境的id值 #dev环境的namespace配置
  13. group: ${dev.group}
  14. config:
  15. server-addr: 192.168.94.129:8848 #配置中心nacos地址
  16. namespace: 修改为nacos dev 环境的id值 #dev环境的namespace配置
  17. file-extension: properties #文件的后缀名称
  18. group: ${dev.group}
  19. shared-configs:
  20. - dataId: mp-config.properties
  21. group: ${dev.group}
  22. - dataId: spring-http-config.properties
  23. group: ${dev.group}
  24. - dataId: spring-druid-config.properties
  25. group: ${dev.group}
  26. profiles:
  27. active: dev #开启开发环境配置
  28. #配置开发组的名称
  29. dev:
  30. group: xc-group
  31. # 日志文件配置路径
  32. logging:
  33. config: classpath:log4j2-dev.xml
  34. # swagger 文档配置
  35. swagger:
  36. title: "学成在线内容管理系统"
  37. description: "内容系统管理系统对课程相关信息进行业务管理数据"
  38. base-package: com.xuecheng
  39. enabled: true
  40. version: 1.0.0

●启动类添加注解驱动

  1. @RefreshScope //开启动态刷新
  2. @EnableDiscoveryClient //开启注册中心
  3. @EnableSwagger2Doc //开启swagger
  4. @SpringBootApplication
  5. public class ContentApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(ContentApplication.class, args);
  8. }
  9. }

**3.3.7 查询接口实现

3.3.7.1 DAO层代码实现
我们使用 Mybatis Plus来访问数据库,所有我们的 Mapper 会继承 MP 中的 BaseMapper接口。
●接口Mapper

  1. package com.xuecheng.content.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.xuecheng.content.entity.CourseBase;
  4. /**
  5. * <p>
  6. * 课程基本信息数据访问层
  7. * </p>
  8. *
  9. * @Description:
  10. */
  11. public interface CourseBaseMapper extends BaseMapper<CourseBase> {
  12. }

3.3.7.3 Service层代码实现
服务层代码的实现,需要我们先为服务层定义接口,接着再编写服务层实现类。实现类不仅实现我们编写的服务层接口,还会继承 MP 提供的 ServiceImpl 实现类,简化我们的开发。
1. 首先来编写 Service 接口,定义分页条件查询方法。
●Service接口定义

  1. /**
  2. * <p>
  3. * 课程基本信息 服务类
  4. * </p>
  5. */
  6. public interface CourseBaseService extends IService<CourseBase> {
  7. /**
  8. * 课程基础信息条件分页查询
  9. * @param params 分页数据
  10. * @param model 查询条件数据
  11. * @return PageVO
  12. */
  13. PageVO<CourseBaseDTO> queryCourseList(PageRequestParams params, QueryCourseModel model);
  14. }

2. 编写服务实现类
服务实现类除了实现我们定义的接口,还可继承 MP 提供的 ServiceImpl 实现类。简化分页条件查询功能实现,我们先来实现分页并测试,在此基础上再去实现条件查询。
●分页查询实现 构建 MP 的分页组件,实现分页查询(项目中已经添加,无需添加)

  1. package com.xuecheng.content.config;
  2. import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
  3. import org.mybatis.spring.annotation.MapperScan;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. /**
  7. * <p>
  8. * Mybatis Plus 配置类
  9. * </p>
  10. *
  11. * @Description:
  12. */
  13. @Configuration
  14. @MapperScan("com.xuecheng.content.mapper")
  15. public class MybatisConfig {
  16. @Bean
  17. public PaginationInterceptor paginationInterceptor() {
  18. return new PaginationInterceptor();
  19. }
  20. }

上述代码存放的包为:com.xuecheng.content.config,此包下资源会被程序加载。
PS:此配置类已经使用 @MapperScan 注解,Spring Boot 启动应用类上就不需要在添加此注解配置。
●Service实现类实现分页查询

  1. package com.xuecheng.content.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  5. import com.xuecheng.api.content.model.dto.CourseBaseDTO;
  6. import com.xuecheng.api.content.model.qo.QueryCourseModel;
  7. import com.xuecheng.common.domain.page.PageRequestParams;
  8. import com.xuecheng.common.domain.page.PageVO;
  9. import com.xuecheng.common.util.StringUtil;
  10. import com.xuecheng.content.convert.CourseBaseConvert;
  11. import com.xuecheng.content.entity.CourseBase;
  12. import com.xuecheng.content.mapper.CourseBaseMapper;
  13. import com.xuecheng.content.service.CourseBaseService;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.springframework.stereotype.Service;
  16. import org.springframework.util.CollectionUtils;
  17. import java.util.Collections;
  18. import java.util.List;
  19. /**
  20. * <p>
  21. * 课程基本信息 服务实现类
  22. * </p>
  23. *
  24. * @author itcast
  25. */
  26. @Slf4j
  27. @Service
  28. public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper, CourseBase> implements CourseBaseService {
  29. /*
  30. * 步骤分析:
  31. * 1.是否需要开启事务
  32. * 2.判断关键数据
  33. * 分页数据
  34. * 查询条件
  35. * 3.构建mp分页对象
  36. * 4.构建查询条件对象LambdaQueryWrapper
  37. * 5.查询数据
  38. * 6.获得数据并封装返回结果
  39. * 封装PageVo数据
  40. * */
  41. public PageVO queryCourseBaseList(PageRequestParams params, QueryCourseModel model) {
  42. // 1.判断业务数据
  43. // 1.1 判断分页数据
  44. if (params.getPageNo() < 1) {
  45. params.setPageNo(PageRequestParams.DEFAULT_PAGE_NUM);
  46. }
  47. if (params.getPageSize() < 1) {
  48. params.setPageSize(PageRequestParams.DEFAULT_PAGE_SIZE);
  49. }
  50. LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
  51. // 1.2 判断条件查询数据(如果查询条件有数据,在sql中添加条)
  52. // 完整版
  53. // if (StringUtil.isNotBlank(model.getAuditStatus())) {
  54. // // 添加的审核状态条件(eq)
  55. // queryWrapper.eq(CourseBase::getAuditStatus, model.getAuditStatus());
  56. // }
  57. //
  58. //
  59. //
  60. // if (StringUtil.isNotBlank(model.getCourseName())) {
  61. // // 添加的课程名称条件(like)
  62. // queryWrapper.like(CourseBase::getName, model.getCourseName());
  63. // }
  64. // 简写版
  65. queryWrapper.eq(StringUtil.isNotBlank(model.getAuditStatus()),
  66. CourseBase::getAuditStatus, model.getAuditStatus());
  67. queryWrapper.like(StringUtil.isNotBlank(model.getCourseName()),
  68. CourseBase::getName, model.getCourseName());
  69. // 2.创建分页数据
  70. Page<CourseBase> page = new Page<>(params.getPageNo(),params.getPageSize());
  71. // 3.根据分页和查询调价查询list数据
  72. Page<CourseBase> pageResult = this.page(page, queryWrapper);
  73. List<CourseBase> records = pageResult.getRecords();
  74. long total = pageResult.getTotal();
  75. // 4.将查询结果封装到PageVO中
  76. /*
  77. * PageVO构造方法:
  78. * 1.当前页的集合数据
  79. * 2.数据库中的总条数
  80. * 3.当前页码
  81. * 4.每页条数
  82. * */
  83. PageVO pageVO = new PageVO(records,total,params.getPageNo(),params.getPageSize());
  84. return pageVO;
  85. }
  86. }

分页功能测试

  1. @SpringBootTest
  2. @RunWith(SpringRunner.class)
  3. public class CourseServiceImplTest {
  4. @Autowired
  5. private CourseBaseService courseBaseService;
  6. @Test
  7. public void testQueryCourseList() {
  8. //1.分页数据
  9. PageRequestParams params = new PageRequestParams();
  10. params.setPageSize(10);
  11. //2.查询条件数据
  12. QueryCourseModel model = new QueryCourseModel();
  13. model.setCourseName("SpringBoot");
  14. // model.setAuditStatus(CourseAuditEnum.AUDIT_PASTED_STATUS.getCode());
  15. PageVO<CourseBaseDTO> result = courseBaseService.queryCourseList(params, model, 1L);
  16. System.out.println("result =>"+result.getItems());
  17. System.out.println("total =>" + result.getCounts());
  18. }
  19. }

1.在 Maven 的测试目录下(src/test/java)创建测试类,位置不要放错。
2.测试类一定要放在工程的基础包或基础包子包下:com.xuecheng.content ,否则会出错。原因是测试代码会依赖 Spring Boot 启动类(ContentApplication)。

3.3.7.4 对象属性映射
DTO 解决了数据传输问题,但新的问题出现。问题在于 Service层要将获得的 PO 实体类对象中的属性赋值到 DTO 实体类对象中属性里。如果属性较多,则赋值操作越来越多。
赋值问题示意图

Day02-第二章-内容管理-课程查询和新增 - 图32

针对上面的问题,通过工具 包来解决此问题:MapStruct
(1) MapStruct 工具介绍
MapStruct是一个代码生成器,它基于约定优于配置的方法大大简化了Java Bean之间属性映射。 生成的映射代码使用简单的方法调用,因此速度快,类型安全且易于理解。 通过使其尽可能自动化来简化两个类对象属性转换代码。
MapStruct使用注解的形式在代码编译是生成类对象属性的转换赋值,可在 Maven项目中进行构建,通过生成代码完成繁琐和容易出错的代码逻辑。
官方地址:https://mapstruct.org/
(2) MapStruct 使用说明
在官方文档中,MapStruct 上手使用也是比较简单的,我们根据官方文档步骤来完成两个实体类属性值的映射赋值。
1. 导入 Maven 相关坐标 (项目工程已经导入,无需再导入)

  1. <dependencies>
  2. <!-- MapStruct 依赖包 -->
  3. <dependency>
  4. <groupId>org.mapstruct</groupId>
  5. <artifactId>mapstruct-jdk8</artifactId>
  6. <version>1.3.0.Final</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.mapstruct</groupId>
  10. <artifactId>mapstruct-processor</artifactId>
  11. <version>1.3.0.Final</version>
  12. </dependency>
  13. </dependencies>
  14. <!-- maven 编译插件 -->
  15. <build>
  16. <plugins>
  17. <plugin>
  18. <groupId>org.apache.maven.plugins</groupId>
  19. <artifactId>maven-compiler-plugin</artifactId>
  20. <version>3.7.0</version>
  21. <configuration>
  22. <source>1.8</source>
  23. <target>1.8</target>
  24. <encoding>utf-8</encoding>
  25. </configuration>
  26. </plugin>
  27. </plugins>
  28. </build>

PS:这里的 pom 文件坐标导入和官网不太一样,这是由于项目中同时使用 MapStruct 和 Lombok,MapStruct 会影响到 Lombok 代码的自动生成,所有将 MapStruct 坐标进行改进。
2. 构建实体类
实体类我们用之前创建好的 CourseBase 和 CourseBaseDTO即可。
3. 编写映射接口
在内容管理微服务 xc-content-service 的基础包 xc.xuecheng.content.convert 下创建课程基本信息转换接口。

  1. package com.xuecheng.content.convert;
  2. import com.xuecheng.api.content.model.dto.CourseBaseDTO;
  3. import com.xuecheng.content.entity.CourseBase;
  4. import org.mapstruct.Mapper;
  5. import org.mapstruct.Mapping;
  6. import org.mapstruct.factory.Mappers;
  7. import java.util.List;
  8. /**
  9. * <p>
  10. * 对象属性转换器:
  11. * PS:将MapStruct依赖添加到工程中
  12. * 1.创建一个接口并在类上添加 @Mapper 注解
  13. * 2.在接口通过MapStruct的api创建出接口的实例对象
  14. * 3.转换方法
  15. * 传入参数、传出参数
  16. * 将传入参数里的数据会赋值给传出参数
  17. *
  18. * 特点:
  19. * 1.对象中的属性转换数据时,默认情况下将两个对象中的同属性名进行赋值操作
  20. *
  21. * </p>
  22. *
  23. * @Description:
  24. */
  25. @Mapper
  26. public interface CourseBaseConvert {
  27. CourseBaseConvert INSTANCE = Mappers.getMapper(CourseBaseConvert.class);
  28. // 将po转为dto数据
  29. @Mapping(source = "id",target = "courseBaseId")
  30. CourseBaseDTO entity2dto(CourseBase courseBase);
  31. // 将pos转为dtos数据
  32. /*
  33. * 集合的方法会依赖于单个数据转换的方法
  34. * entitys2dtos-》entity2dto
  35. * Mapping注解是使用在单个数据转换方法上的,不是在集合方法上来使用
  36. * */
  37. List<CourseBaseDTO> entitys2dtos(List<CourseBase> courseBase);
  38. }

编写接口时细节:
1.必须要接口上编写 @Mapper 注解,标注为 MapStruct 属性转换器(映射器)
2.构建类的属性转换器实例对象。
3.构建转换的方法,方法的传入的参数是需要转换源对象,方法的传出参数是需要转换为目标对象。
4.如果遇到两个类的属性名称不一致,需要使用 @Mapping 注解来进行说明。
@Mapping 中的 source 属性是要转换源对象属性名称
@Mapping 中的 target属性是要转换为目标对象的属性名称
上面的编写的接口,MapStruct 会在代码编译期生成接口的实现类。
4. 代码测试

  1. public class MapStructTest {
  2. @Test
  3. public void convert() {
  4. CourseBase base = new CourseBase();
  5. base.setId(Long.valueOf(i));
  6. base.setName("SpringBoot");
  7. CourseBaseDTO dto = CourseBaseConvert.INSTANCE.entity2dto(base);
  8. System.out.println(dtos);
  9. }
  10. }

上面代码要在 Maven 的测试目录下(src/test/java)创建测试类
(4)MapStruct 单实体类属性映射
上面我们演示了 MapStruct 工具单个实体类属性的使用,从 CourseBase 属性映射到 CourseBaseDTO 属性上。当然,从 CourseBaseDTO 属性映射到 CourseBase 上也是同样的道理,我们在转换接口中接着添加方法,如下:

  1. @Mapper
  2. public interface CourseBaseConvert {
  3. //上面代码忽略
  4. //将 CourseBaseDTO 转为 CourseBase
  5. CourseBase dto2entity(CourseBaseDTO dto);
  6. }

MapStruct 只关心传入的源对象和传出的目标对象,上面代码测试请大家自行测试
上面的转换中,CourseBase 和 CourseBaseDTO 属性名称不一致,MapStruct 对此提供了 相应的注解来使得不同属性命名间转换赋值,注解分别为 :@Mapping 和 @Mappings。
@Mapping 注解:是单个属性间的映射。
@Mappings注解:是多个属性间的映射。
●下面修改转换接口中的方法:

  1. @Mapper
  2. public interface CourseBaseConvert {
  3. //构建属性转换示例
  4. CourseBaseConvert INSTANCE = Mappers.getMapper(CourseBaseConvert.class);
  5. //将 CourseBase 转为 CourseBaseDTO
  6. @Mapping(target = "courseBaseId",source = "id")
  7. CourseBaseDTO entity2dTO(CourseBase courseBase);
  8. //将 CourseBaseDTO 转为 CourseBase
  9. @Mappings({
  10. @Mapping(target = "id",source = "courseBaseId")
  11. })
  12. CourseBase dto2entity(CourseBaseDTO student);
  13. }

上面的修改就可以达到类对象间的多个不同属性名间的赋值操作,代码同学们自行测试。
(5) MapStruct 集合类属性映射
MapStruct 除了可以进行单个类对象间的属性转换(映射),还可以对集合类进行属性间的转换。下面我们将 CourseBase 集合转换为 CourseBaseDTO 集合。
首先我们在转换接口类中添加对应的方法:

  1. /**
  2. * <p>
  3. * 课程基本信息 PO -- DTO 转换器
  4. * </p>
  5. *
  6. * @Description:
  7. */
  8. @Mapper
  9. public interface CourseBaseConvert {
  10. //构建属性转换示例
  11. CourseBaseConvert INSTANCE = Mappers.getMapper(CourseBaseConvert.class);
  12. //将 CourseBase 转为 CourseBaseDTO
  13. @Mapping(target = "courseBaseId",source = "id")
  14. CourseBaseDTO entity2DTO(CourseBase courseBase);
  15. //将 CourseBase 集合转为 CourseBaseDTO 集合
  16. List<CourseBaseDTO> entities2dtos(List<CourseBase> courseBases);
  17. // 其他代码略
  18. }

这里我们在注意两点:
1.使用集合类的属性映射必须要在转换接口中编写单个类的属性转换方法。
2.在转换接口中的转换集合方法上无法识别 @Mapping@Mappings
测试代码为:

  1. public class MapStructTest {
  2. @Test
  3. public void convert() {
  4. List<CourseBase> bases = new ArrayList<>();
  5. for (int i = 0; i < 10; i++) {
  6. CourseBase base = new CourseBase();
  7. base.setId(Long.valueOf(i));
  8. base.setName("SpringBoot");
  9. bases.add(base);
  10. }
  11. List<CourseBaseDTO> dtos = CourseBaseConvert.INSTANCE.entitys2dtos(bases);
  12. System.out.println(dtos);
  13. }
  14. }

}

3.3.7.5 完善后端服务代码
现在 MapStruct 工具和 DTO 有了基本的认识,我们将修改 Service 业务代码。
●使用 MapStruct 来转换属性

  1. @Service
  2. public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper, CourseBase> implements CourseBaseService {
  3. @Override
  4. public PageVO queryCourseBasesByConditions(Integer page, Integer size, QueryCourseBaseModel queryModel) {
  5. //.....其他代码省略
  6. // 4.分页条件查询
  7. IPage<CourseBase> pageResult = page(page, queryWrapper);
  8. List<CourseBase> list = pageResult.getRecords();
  9. long total = pageResult.getTotal();
  10. // 5.将 po 数据转换为
  11. List<CourseBaseDTO> dtos = Collections.emptyList();
  12. if (!(CollectionUtils.isEmpty(records))) {
  13. dtos = CourseBaseConvert.INSTANCE.entitys2dtos(records);
  14. }
  15. // 6.封装PageVO数据
  16. PageVO pageVO = new PageVO(dtos,total,params.getPageNo(),params.getPageSize());
  17. return pageVO;
  18. }
  19. }


3.4 机构查询数据隔离


学成在线定位为 B2B2C 的产品,提供平台给教学机构来使用。但是,不同的教学机构查询的数据应该为自己机构下的数据,其他的教学机构的数据是无法查询出来的。
机构数据隔离示意图
Day02-第二章-内容管理-课程查询和新增 - 图33

上面的数据隔离,在学成在线中将通过下面的两个方面进行实现。
1.机构隔离的依据数据。
2.机构隔离的依据数据的获得。

3.4.1 机构隔离的依据数据


在课程基本信息表中有这么几个字段,如下:
课程信息机构内容

Day02-第二章-内容管理-课程查询和新增 - 图34

既然课程基本信息由判断不同机构的依据数据,那么在对课程基本信息查询时,只需要在查询条件中添加下面的数据即可:

  1. select * from course_base where company_id = xxxxx and .....

通过sql来获得不同机构的数据。

3.4.2 机构隔离的依据数据获得


既然我们可以通过 course_base 中的 company_id 来区分不同机构的数据。那么下面就来讨论依据数据如何获得,下面是获得机构id(company_id) 示意图。
机构id数据获得示意图(面试)

Day02-第二章-内容管理-课程查询和新增 - 图35

学成采用的是前后端分离开发,教学机构在使用前端工程访问后端微服务时,需要做以下事:
1.教育机构需要通过第三方登录系统完成登录操作。(会面课程会讲解并实现)
2.登录成功后会向教学机构的客户端保存一份token(令牌,相当于电影票)。
3.教学机构每次在使用前端系统访问后端微服务时,都会将token以请求头的方式发送给后端。
请求头的key:authorization
请求头的value:Bear ewogICAgImF1ZCI6IFsK……….
4.后端微服务接收到请求后需要从请求头中获得令牌,并解析后拿到教学机构的id值。
5.通过教学机构的id值来操作一个机构下的课程数据。

3.4.3 测试环境的结构


对于机构隔离的依据数据获得和操作,需要搭建下面的测试环境,如下图:
学成测试环境搭建

Day02-第二章-内容管理-课程查询和新增 - 图36

根据上面的前后端结构图中可以看到,后端运行环境为使用 Spring Cloud 来进行管理 Spring Boot 微服务。
● 微服务注册中心
业务服务注册到服务注册中心中
● 微服务网关
1.接收所有前端发送的请求
2.从请求头中获得令牌内容,如果获得不到令牌将会报错。
3.将令牌转发到各个微服务中
● 业务微服
接收来自网关的请求,并从请求头中获得令牌,解析后获得机构的id
通过前后端测试环境结构图中,需要我们构建后端运行环境。根据服务结构顺序我们依次进行构建:
1. 微服务注册中心 Nacos
2. 业务服务注册到服务注册中心中
3. 微服务网关 GateWay

3.4.3.1 微服务注册中心
Nacos 已经在项目中搭建完毕,并可以将项目注册到 Nacos 中,无需再次配置。
注册服务

Day02-第二章-内容管理-课程查询和新增 - 图37

3.4.3.3 微服务网关搭建
前端与后端交互主要是和服务网关进行接口调用,通过网关将前端请求转发到各个业务后端微服务中,最终由网关远程调用业务微服务接口。

3.4.3.3.1 工程导入
将今天下发资料中的后端网关(xc-gateway-server)工程导入到 xc-parent 工程下。
工程项目

Day02-第二章-内容管理-课程查询和新增 - 图38

Day02-第二章-内容管理-课程查询和新增 - 图39

服务注册中心工程导入后,在 xc-parent 工程的 pom 文件中添加为模块
添加 module

Day02-第二章-内容管理-课程查询和新增 - 图40

3.4.3.3.2 工程运行
工程导入后,里面已经配置类 Spring Cloud 环境。由于此工程为测试环境下的网关,后期要进行更换,所以对里面的配置内容,不在强调。
运行后会自动注册到注册中心中

Day02-第二章-内容管理-课程查询和新增 - 图41

3.4.4 后端接口测试


教学机构需要在教学管理中心中进行登录后,前端在调用内容管理微服务时,除了传入业务参数,还需要在请求头中添加访问的令牌,微服务端需要解析令牌来获得机构相关的信息。
机构访问示意图

Day02-第二章-内容管理-课程查询和新增 - 图42

下面针对内容管理查询课程基础信息数据接口测试,需要做以下几件事情。
1.修改CourseBaseService接口,添加 companyId 参数
2.修改CourseBaseServiceImpl 实现类,添加对公司id查询条件
3.修改CourseBaseController,使用工具类获得公司Id值
4.通过测试工具发送请求给网关进行测试
1. 修改Service 层,添加对机构数据的判断条件
接口修改:

  1. public interface CourseBaseService extends IService<CourseBase> {
  2. /**
  3. * 课程基本信息分页条件查询
  4. * @param params
  5. * @param model
  6. * @param companyId 添加公司Id参数
  7. * @return
  8. */
  9. PageVO<CourseBaseDTO> queryCourseList(PageRequestParams params, QueryCourseModel model,Long companyId);
  10. }

业务实现类修改:

  1. @Service
  2. public class CourseBaseServiceImpl extends ServiceImpl<CourseBaseMapper,CourseBase> implements CourseBaseService {
  3. public PageVO queryCourseList(PageRequestParams params, QueryCourseModel model,Long companyId) {
  4. // 其他代码省略
  5. // 添加对公司的查询条件
  6. queryWrapper.eq(CourseBase::getCompanyId, companyId);
  7. // 4.分页条件查询
  8. IPage<CourseBase> pageResult = page(page, queryWrapper);
  9. List<CourseBase> list = pageResult.getRecords();
  10. long total = pageResult.getTotal();
  11. // 5.将 po 数据转换为
  12. List<CourseBaseDTO> dtos = Collections.emptyList();
  13. if (!(ObjectUtils.isEmpty(list) && list.size() > 0)) {
  14. dtos = CourseBaseConvert.INSTANCE.entity2dtos(list);
  15. }
  16. // 6.封装PageVO数据
  17. PageVO pageVO = new PageVO(dtos,total,params.getPageNo(),params.getPageSize());
  18. return pageVO;
  19. }
  20. }

2. 修改Controller 层,并返回数据
●Controller调用Service方法

  1. @Slf4j
  2. @RestController
  3. public class CourseBaseController implements CourseBaseApi {
  4. @Autowired
  5. private CourseBaseService courseBaseService;
  6. @PostMapping("/course/list")
  7. public PageVO queryCourseList(PageRequestParams params,
  8. @RequestBody QueryCourseModel model) {
  9. //1.获得访问令牌并从中解析出机构的信息Id数据
  10. Long companyId = SecurityUtil.getCompanyId();
  11. //2.调用service层的方法
  12. PageVO<CourseBaseDTO> pageVo = courseBaseService.queryCourseList(params, model, companyId);
  13. return pageVo;
  14. }
  15. }

PS:在Controller 代码中需要使用 xc-common 工程所提供的工具类 SecurityUtil 来从请求头中获得令牌数据,并解析后拿到公司id值。
3. 测试环境需要启动的服务
测试环境需要启动的微服务有:
1.注册中心 xc-discover-service (端口:63000)
2.服务网关 xc-gateway-service (端口:63010)
3.内容管理 xc-content-service (端口:63040)
4. 使用 postman 在请求信息中添加请求参数
测试环境搭建后,就不能通过 Swagger-ui 来测试内容管理微服务的接口。只能使用 postman 来发送请求到网关,具体需要提供下面的参数:
课程基本信息分页条件查询参数:
1.分页数据
通过QueryString(问号传参)方式来传递参数
2.查询条件
通过RequestBody(请求体中json格式数据)方式来传递参数
3.请求的令牌
通过请求头来传递令牌
●请求头(访问令牌)

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

●QueryString 信息(分页参数)

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

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

  1. {
  2. "auditStatus":"202004",
  3. "courseName":"springboot"
  4. }

示意图
Day02-第二章-内容管理-课程查询和新增 - 图43


3.5 功能接口实现小结


通过上面课程基本信息查询,我们将开发中重点的内容进行知识总结,这对后期的开发有很大的帮助。
3.3.10.1 开发规范小结
本次开发中设计到的规范,大致有分为两类,分别为:
1.开发代码规范
项目的功能实现编码,要根据开发规范文档来统一风格,这样利于代码可读性和后期的代码维护。
2.接口开发规范
前后端分离开发,遵循接口开发规范,便于多人协作并行开发。
3.3.10.2 开发步骤小结
学成在线项目是基于前后端分离的架构,其后端开发会按照一定的步骤来进行功能实现。其中 Java 后端开发人员只关心以下事项:
1.功能的需求分析
2.后端接口定义
3.后端微服务的开发
4.后端接口的测试
5.前后端集成测试
3.3.10.3 接口封装实体类小结
在前后端开发中,对于接口中的数据封装类,在本次的开发中使用相应的实体来进行接收和返回数据。
大致的实体为:
1.接收数据封装实体类
对于有查询条件的接口,对查询数据进行封装。
条件查询数据: Query*Model 为名称来定义查询实体类。
分页数据:使用工程中提供的 PageRequestParams 实体类封装分页数据。
2.传出数据封装实体类
微服务端对于接收前端的核心数据,会使用 *
DTO 数据来进行数据传出。在后端使用工具 MapStruct 工具在Service层将 DTO 数据转为 PO 类数据。
3.3.10.4 后端获得登录状态信息小结
教学机构在教学管理中心登录后,前端调用后端接口的时候,会在请求都中添加下面列表中访问令牌,工程中的工具类 SecurityUtil 可以直接获得请求头中的内容,并解析访问令牌获得机构信息和用户信息。
机构访问示意图

| 请求头的Key

| 请求头的Value

| | —- | —- | | authorization

| Bearer eyJtZXJjaGFudElkIjoiMTIz…………

|

Day02-第二章-内容管理-课程查询和新增 - 图44
1