【Java实战篇】Day1.在线教育网课平台

文章目录

  • 1、项目介绍
  • 2、需求分析与接口设计
    • 2.1 分析需求
    • 2.2 定义模型类
    • 2.3 接口实现
  • 3、 持久层开发
  • 4、开发业务层
    • 4.1 数据字典表
    • 4.2 开发Service层
    • 4.3 单元测试service层
  • 5、完善表现层
    • 5.1 return结果集对象
    • 5.2 接口测试工具--Httpclient
  • 6、前后端联调
    • 6.1 环境准备--安装node-js
    • 6.2 解决CORS报错

1、项目介绍

项目背景:

【Java实战篇】Day1.在线教育网课平台_第1张图片

项目业务介绍:

【Java实战篇】Day1.在线教育网课平台_第2张图片

**技术架构图:**

【Java实战篇】Day1.在线教育网课平台_第3张图片

开发配置环境、git等常规配置此处跳过

2、需求分析与接口设计

2.1 分析需求

【Java实战篇】Day1.在线教育网课平台_第4张图片

页面UI分析:
  • 查询条件:
    包括:课程名称、课程审核状态、课程发布状态
    课程名称:可以模糊搜索
    课程审核状态:未提交、已提交、审核通过、审核未通过
    课程发布状态:未发布、已发布、已下线
    因为是分页查询所以查询条件中还要包括当前页码、每页显示记录数。
  • 查询结果:
    查询结果中包括:课程id、课程名称、任务数、创建时间、是否付费、审核状态、类型,操作
    任务数:该课程所包含的课程计划数,即课程章节数。
    是否付费:课程包括免费、收费两种。
    类型:录播、直播。
    因为是分页查询所以查询结果中还要包括总记录数、当前页、每页显示记录数
创建数据库表与PO类:

【Java实战篇】Day1.在线教育网课平台_第5张图片

接口设计分析:
  • 确定协议:
    通常协议采用HTTP,查询类接口通常为get或post,查询条件较少的使用get,较多的使用post

  • 分析请求参数:
    请求参数为:课程名称、课程审核状态、当前页码、每页显示记录数,根据请求参数定义模型类
    【Java实战篇】Day1.在线教育网课平台_第6张图片

  • 分析响应结果:
    。 根据前边对数据模型的分析,响应结果为数据列表加一些分页信息(总记录数、当前页、每页显示记录数)。
    。 数据列表中数据的属性包括:课程id、课程名称、任务数、创建时间、审核状态、类型
    。 根据分析的响应结果定义模型类,如工作中常见的命名如AjaxResult、ResponseResult类

POST /content/course/list?pageNo=2&pageSize=1
Content-Type: application/json

{
  "auditStatus": "202002",
  "courseName": "",
  "publishStatus":""
}
###成功响应结果
{
  "items": [
    {
      "id": 26,
      "companyId": 1232141425,
      "companyName": null,
      "name": "spring cloud实战",
      "users": "所有人",
      "tags": null,
      "mt": "1-3",
      "mtName": null,
      "st": "1-3-2",
      "stName": null,
      "grade": "200003",
      "teachmode": "201001",
      "description": "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud 基础入门 3.实战Spring Boot 4.注册中心eureka。",
      "pic": "https://cdn.educba.com/academy/wp-content/uploads/2018/08/Spring-BOOT-Interview-questions.jpg",
      "createDate": "2019-09-04 09:56:19",
      "changeDate": "2021-12-26 22:10:38",
      "createPeople": null,
      "changePeople": null,
      "auditStatus": "202002",
      "auditMind": null,
      "auditNums": 0,
      "auditDate": null,
      "auditPeople": null,
      "status": 1,
      "coursePubId": null,
      "coursePubDate": null
    }
  ],
  "counts": 23,
  "page": 2,
  "pageSize": 1
}

2.2 定义模型类

分页查询模型类

由于分页查询这一类的接口在项目较多,这里针对分页查询的参数(当前页码、每页显示记录数)单独在xuecheng-plus-base基础工程中定义。
【Java实战篇】Day1.在线教育网课平台_第7张图片

package com.xuecheng.base.model;

import lombok.Data;
import lombok.ToString;
import lombok.extern.java.Log;

/**
 * @description 分页查询通用参数
 */
@Data
@ToString
public class PageParams {

  //当前页码
  private Long pageNo = 1L;

  //每页记录数默认值
  private Long pageSize =10L;

  public PageParams(){

  }

  public PageParams(long pageNo,long pageSize){
      this.pageNo = pageNo;
      this.pageSize = pageSize;
  }



}

定义j接收前端的查询结果模型类,即dto

【Java实战篇】Day1.在线教育网课平台_第8张图片

package com.xuecheng.content.model.dto;

import lombok.Data;
import lombok.ToString;

/**
 * @description 课程查询参数Dto
 */
 @Data
 @ToString
public class QueryCourseParamsDto {

  //审核状态
 private String auditStatus;
 //课程名称
 private String courseName;
  //发布状态
 private String publishStatus;

}

定义返回给前端的响应模型类

所有分页查询的返回结果是一个固定的格式,定义一个响应类在base下,方便以后复用

【Java实战篇】Day1.在线教育网课平台_第9张图片

package com.xuecheng.base.model;

import lombok.Data;
import lombok.ToString;

import java.io.Serializable;
import java.util.List;

/**
 * @description 分页查询结果模型类
 */
@Data
@ToString
public class PageResult<T> implements Serializable {

    // 数据列表
    private List<T> items;

    //总记录数
    private long counts;

    //当前页码
    private long page;

    //每页记录数
    private long pageSize;

    public PageResult(List<T> items, long counts, long page, long pageSize) {
        this.items = items;
        this.counts = counts;
        this.page = page;
        this.pageSize = pageSize;
    }



}

数据这里使用泛型,以后查询返回的是User对象,则泛型用User,查询返回的是Book对象,则填Book

2.3 接口实现

 @RestController
public class CourseBaseInfoController {

@PostMapping("/course/list")
  public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){

      return null;
  }
}

说明:pageParams分页参数通过url的key/value传入,queryCourseParams通过json数据传入,使用@RequestBody注解将json转成QueryCourseParamsDto对象。

@RequestBody后添加(required=false)表示此参数不是必填项(required默认为true,即必填) ,不加这个注解,当过滤条件为空,即没有传递json内容时,会导致400错误

dto、po、vo的解释:

【Java实战篇】Day1.在线教育网课平台_第10张图片

  • DTO数据传输对象、PO持久化对象、VO视图对象
  • DTO用于接口层向业务层之间传输数据,即controller层的形参常为一个DTO,传给Service层
  • PO用于业务层与持久层之间传输数据,即service层的形参常为PO,调用mapper层的时候,传给mapper层
  • VO对象用在前端与接口层之间传输数据,查询到的结果封装VO对象为data,加上code、message后封装成一个AjaxResult结果类,由controller层返回给前端

【Java实战篇】Day1.在线教育网课平台_第11张图片
VO的意义

场景:

。 手机查询:查询结果只要课程名称和课程状态
。 PC查询:可以根据课程名称、课程状态、课程审核状态等条件查询,查询显示的结果也比手机查询结果内容多
。 此时,Service业务层尽量提供一个业务接口,即使两个前端接口需要的数据不一样,Service可以提供一个最全查询结果,由Controller进行数据整合。

接口文档生成工具—swapper

  • Swagger是一个在线接口文档的生成工具,前后端开发人员依据接口文档进行开发。 (https://swagger.io/)
  • Spring Boot 可以集成Swagger,Swaager根据Controller类中的注解生成接口文档 ,只要添加Swagger的依赖和配置信息即可使用它

<dependency>
    <groupId>com.spring4allgroupId>
    <artifactId>swagger-spring-boot-starterartifactId>
dependency>

@Api(value = "课程信息编辑接口",tags = "课程信息编辑接口")
@RestController
public class CourseBaseInfoController {

 @ApiOperation("课程查询接口")
 @PostMapping("/course/list")
  public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){

     //....
  }
}

启动服务,工程启动起来,访问http://localhost:63040/content/swagger-ui.html查看接口信息

【Java实战篇】Day1.在线教育网课平台_第12张图片
【Java实战篇】Day1.在线教育网课平台_第13张图片
swagger的常用注解:

 @Api:修饰整个类,描述Controller的作用
 @ApiOperation:描述一个类的一个方法,或者说一个接口
 @ApiParam:单个参数描述
 @ApiModel:用对象来接收参数
 @ApiModelProperty:用对象接收参数时,描述对象的一个字段
 @ApiResponse:HTTP响应其中1个描述
 @ApiResponses:HTTP响应整体描述
 @ApiIgnore:使用该注解忽略这个API
 @ApiError :发生错误返回的信息
 @ApiImplicitParam:一个请求参数
 @ApiImplicitParams:多个请求参数

3、 持久层开发

接口定义出来以后,接下来先mapper层,再写service层:

直接使用插件生成实体类和Mapper接口以及Mapper.xml或者手写Mapper接口,再继承BaseMapper

分页查询

  • 设置MyBatisPlus分页拦截器作为Spring管理的Bean

【Java实战篇】Day1.在线教育网课平台_第14张图片
【Java实战篇】Day1.在线教育网课平台_第15张图片
分页插件的原理:

  • 首先分页参数放到ThreadLocal中,拦截执行的sql,根据数据库类型添加对应的分页语句重写sql,例如:(select * from table where a) 转换为 (select count(*) from table where a)和(select * from table where a limit ,)
  • 计算出了total总条数、pageNum当前第几页、pageSize每页大小和当前页的数据,是否为首页,是否为尾页,总页数等。

【Java实战篇】Day1.在线教育网课平台_第16张图片

测试Mapper

@SpringBootTest
class CourseBaseMapperTests {

    @Autowired
    CourseBaseMapper courseBaseMapper;


    @Test
    void testCourseBaseMapper() {
        CourseBase courseBase = courseBaseMapper.selectById(74L);
        Assertions.assertNotNull(courseBase);

        //测试查询接口
        LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
        //查询条件
        QueryCourseParamsDto queryCourseParamsDto = new QueryCourseParamsDto();
        queryCourseParamsDto.setCourseName("java");
        queryCourseParamsDto.setAuditStatus("202004");
        queryCourseParamsDto.setPublishStatus("203001");

        //拼接查询条件
        //根据课程名称模糊查询  name like '%名称%'
        queryWrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),CourseBase::getName,queryCourseParamsDto.getCourseName());
        //根据课程审核状态
        queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,queryCourseParamsDto.getAuditStatus());



        //使用之前定义的分页模型类,分页参数
        PageParams pageParams = new PageParams();
		pageParams.setPageNo(1L);//页码
        pageParams.setPageSize(3L);//每页记录数


        Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        //分页查询E page 分页参数, @Param("ew") Wrapper queryWrapper 查询条件
        Page<CourseBase> pageResult = courseBaseMapper.selectPage(page, queryWrapper);

        //数据
        List<CourseBase> items = pageResult.getRecords();
        //总记录数
        long total = pageResult.getTotal();

        //准备返回数据 List items, long counts, long page, long pageSize
        PageResult<CourseBase> courseBasePageResult = new PageResult<>(items, total, pageParams.getPageNo(), pageParams.getPageSize());
        System.out.println(courseBasePageResult);
    }

}

queryWrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),CourseBase::getName,queryCourseParamsDto.getCourseName());

4、开发业务层

4.1 数据字典表

【Java实战篇】Day1.在线教育网课平台_第17张图片
前面提到,下拉框取值可以为:

  • 课程审核状态:未提交、已提交、审核通过、审核未通过
  • 课程发布状态:未发布、已发布、已下线

如果在课程table中新增一个字段,似乎也可以实现,但当用户需求变更,如想改"审核未通过"为"未通过",每次去改成千上万行数据,可维护性太差。

和审核状态同类的有好多这样的信息,比如:课程状态、课程类型、用户类型等等,这一类数据有一个共同点就是它有一些分类项,且这些分类项较为固定。针对这些数据,为了提高系统的可扩展性,专门定义数据字典表去维护

【Java实战篇】Day1.在线教育网课平台_第18张图片

4.2 开发Service层

服务层是controller层来调,所以service层接口定义时,关于方法的返回值类型,思路有:

  • 看controller层的返回值类型,如我们这个练习中的PageResult<>,那service层方法返回同一个类型也好,此时controller直接 return service.method();即可

  • 或者返回一个VO对象,再调用公司的统一结果类,比如若依的AjaxResult,传入VO对象做为data,此时service层方法返回值类型为VO对应的类型
    【Java实战篇】Day1.在线教育网课平台_第19张图片

  • 返回其他类型,此时和controller层类型不一样,在controller层不能直接return,可通过set、get或者其他common类中的方法包装成需要的类型来return

关于形参:

  • controller层调用service层,controller层方法的形参要传给service,那service层用同样的形参也是中思路
  • controller常常接收到前端传来的Dto对象,传给service层,service层包装出一个po,再调用mapper层方法并传入po
/**
 * @description 课程基本信息管理业务接口
 */
 
public interface CourseBaseInfoService  {

 /*
  * @description 课程查询接口
  * @param pageParams 分页参数
  * @param queryCourseParamsDto 条件条件
 */
  PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto);

 }
}

写接口的实现类:!!!!!!!!!!!!!!!

/**
 * @description 课程信息管理业务接口实现类
 */
@Service
public class CourseBaseInfoServiceImpl  implements CourseBaseInfoService {


 @Autowired
 CourseBaseMapper courseBaseMapper;

 @Override
 public PageResult<CourseBase> queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto) {


  //构建查询条件对象
  LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
  //构建查询条件,根据课程名称查询
     queryWrapper.like(StringUtils.isNotEmpty(queryCourseParamsDto.getCourseName()),CourseBase::getName,queryCourseParamsDto.getCourseName());
  //构建查询条件,根据课程审核状态查询
     queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParamsDto.getAuditStatus()),CourseBase::getAuditStatus,queryCourseParamsDto.getAuditStatus());
  //构建查询条件,根据课程发布状态查询
	 queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParamDto.getPublishStatus()),CourseBase:getPublishStatus,queryCourseParamDto.getPublishStatus());

  //分页对象
  Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<CourseBase> pageResult = courseBaseMapper.selectPage(page, queryWrapper);
  // 获取数据列表
  List<CourseBase> list = pageResult.getRecords();
  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<CourseBase> courseBasePageResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return courseBasePageResult;


 }


}

4.3 单元测试service层

@SpringBootTest
class CourseBaseInfoServiceTests {

    @Autowired
    CourseBaseInfoService courseBaseInfoService;


    @Test
    void testCourseBaseInfoService() {
        //查询条件,相当于前端筛选框输入后点查询传过来的
        QueryCourseParamsDto queryCourseParamsDto = new QueryCourseParamsDto();
        queryCourseParamsDto.setCourseName("java");
        queryCourseParamsDto.setAuditStatus("202004");
        queryCourseParamsDto.setPublishStatus("203001");

        //分页参数
        PageParams pageParams = new PageParams();
        pageParams.setPageNo(1L);//页码
        pageParams.setPageSize(3L);//每页记录数

        PageResult<CourseBase> courseBasePageResult = courseBaseInfoService.queryCourseBaseList(pageParams, queryCourseParamsDto);
        System.out.println(courseBasePageResult);
    }

}

5、完善表现层

在开发完mapper层和service层后,controller层不再用return null来占位,自动注入service层对象,调用service层方法。

5.1 return结果集对象

@RestController
public class CourseBaseInfoController {

@Autowire
CourseBaseInfoService courseBaseInfoService;

@PostMapping("/course/list")
  public PageResult<CourseBase> list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){
	  //service层这里返回的本来就是PageResult类型,直接return就行
      return courseBaseInfoService.queryCourseBaseList(pageParams,queryCourseParams);
  }
}

到此,接口开发完成,总结下以上的流程

  • 定义controller层接口,先return null占位
  • 开发mapper层,写实体类,定义接口,写mapper.xml,直接继承BaseMapper
  • 开发Service层,写实现类
  • 回头完善变现层接口,不再return null,而是return new AjaxResult(xxx,xxx);

5.2 接口测试工具–Httpclient

安装:

Swagger是一个在线接口文档,虽然使用它也能测试但需要浏览器进入Swagger,最关键的是它并不能保存测试数据,可使用IDEA中的插件HttpClient:

【Java实战篇】Day1.在线教育网课平台_第20张图片

用法:

  • 在controller层接口上点击Generate request in HTTP Client
    【Java实战篇】Day1.在线教育网课平台_第21张图片

  • 可以看到自己生成了一个.http结尾的文件
    【Java实战篇】Day1.在线教育网课平台_第22张图片

  • 添加测试json
    【Java实战篇】Day1.在线教育网课平台_第23张图片

  • 点击运行
    【Java实战篇】Day1.在线教育网课平台_第24张图片

优化:

  • 为了统一保存,在项目工程的根目录创建一个目录单独存放.http文件
    【Java实战篇】Day1.在线教育网课平台_第25张图片
    并以模块为单位创建.http文件,拷贝刚才的http文件内容
    【Java实战篇】Day1.在线教育网课平台_第26张图片

  • 为了方便将来和网关集成测试,这里我们把测试主机地址在配置文件http-client.env.json 中配置
    【Java实战篇】Day1.在线教育网课平台_第27张图片

{
  "dev": {
    "access_token": "",
    "gateway_host": "localhost:63010",
    "content_host": "localhost:63040",
    "system_host": "localhost:63110",
    "media_host": "localhost:63050",
    "search_host": "localhost:63080",
    "auth_host": "localhost:63070",
    "checkcode_host": "localhost:63075",
    "learning_host": "localhost:63020"
  }
}

此时:再回到xc-content-api.http文件,将http://localhost:63040 用变量代替
【Java实战篇】Day1.在线教育网课平台_第28张图片

6、前后端联调

实现设计澄清后,前后端开始照着接口文档同时开发前后端,此时前端开发人员会使用mock数据(假数据)进行开发。后端开发完成后,前端工程师将mock数据改为请求后端接口获取,前端代码请求后端服务测试接口是否正常,这个过程是前后端联调

6.1 环境准备–安装node-js

  • 使用msi包安装nodejs,安装完成后查看版本
    【Java实战篇】Day1.在线教育网课平台_第29张图片

  • 拷贝前端工程,并使用IDEA启动:从前端工程拷贝project-xczx2-portal-vue-ts.zip到代码目录并解压,并使用IDEA或VS Code打开project-xczx2-portal-vue-ts目录
    【Java实战篇】Day1.在线教育网课平台_第30张图片

  • 点击show npm Script 打开npm窗口【Java实战篇】Day1.在线教育网课平台_第31张图片

  • 点击“Edit ‘serve’” setting,下边对启动项目的一些参数进行配置,选择nodejs、npm
    【Java实战篇】Day1.在线教育网课平台_第32张图片
    【Java实战篇】Day1.在线教育网课平台_第33张图片

  • 右键server,点击run
    【Java实战篇】Day1.在线教育网课平台_第34张图片

  • 启动成功
    【Java实战篇】Day1.在线教育网课平台_第35张图片

如果存在问题通过以下命令启动:
--------------------
1、cmd进入工程根目录 

2、运行以下命令

npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i
npm run serve

6.2 解决CORS报错

在浏览器通过http://localhost:8601/地址访问前端工程,有个接口报错:
【Java实战篇】Day1.在线教育网课平台_第36张图片
即System服务异常,这个接口是前端请求后端获取数据字典数据的接口
【Java实战篇】Day1.在线教育网课平台_第37张图片
进入system模块,找到resources下的application.yml修改数据库连接参数,系统服务的端口是63110。启动系统管理服务,启动成功,在浏览器请求:http://localhost:63110/system/dictionary/all

跨域报错

在浏览器通过http://localhost:8601/地址访问前端工程。

  • chrome浏览器报错如下:
Access to XMLHttpRequest at 'http://localhost:63110/system/dictionary/all' 
from origin 'http://localhost:8601' 
has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

firefox浏览器报错如下:

已拦截跨源请求:同源策略禁止读取位于
http://localhost:63110/system/dictionary/all 的远程资源。
(原因:CORS 头缺少 'Access-Control-Allow-Origin')。状态码:200。

报错分析:

从http://localhost:8601访问http://localhost:63110/system/dictionary/all被CORS policy阻止,因为没有Access-Control-Allow-Origin 头信息。CORS全称是 cross origin resource share 表示跨域资源共享。

出这个提示的原因是基于浏览器的同源策略,去判断是否跨域请求,同源策略是浏览器的一种安全机制,从一个地址请求另一个地址,如果协议、主机、端口三者全部一致则不属于跨域,否则有一个不一致就是跨域请求。

如:

  • 从http://localhost:8601 到 http://localhost:8602 由于端口不同,是跨域。
  • 从http://192.168.101.10:8601 到 -http://192.168.101.11:8601 由于主机不同,是跨域。
  • 从http://192.168.101.10:8601 到 https://192.168.101.10:8601 由于协议不同,是跨域

解决思路1:

服务器收到请求判断这个Origin是否允许跨域,如果允许则在响应头中说明允许该来源的跨域请求:

Access-Control-Allow-Origin:http://localhost:8601

如果允许任何域名来源的跨域请求,则响应如下:

Access-Control-Allow-Origin:*

解决思路2:JSONP

【Java实战篇】Day1.在线教育网课平台_第38张图片
解决思路3:Nginx做代理
由于服务端之间没有跨域,浏览器通过nginx去访问跨域地址
【Java实战篇】Day1.在线教育网课平台_第39张图片

解决跨域报错

使用上面的方式一来解决:在内容管理的api工程config包下编写GlobalCorsConfig.java

/**
 * @description 跨域过虑器
 */
 @Configuration
 public class GlobalCorsConfig {

  /**
   * 允许跨域调用的过滤器
   */
  @Bean
  public CorsFilter corsFilter() {
   CorsConfiguration config = new CorsConfiguration();
   //允许白名单域名进行跨域调用
   config.addAllowedOrigin("*");
   //允许跨越发送cookie
   config.setAllowCredentials(true);
   //放行全部原始头信息
   config.addAllowedHeader("*");
   //允许所有请求方法跨域调用
   config.addAllowedMethod("*");
   UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
   source.registerCorsConfiguration("/**", config);
   return new CorsFilter(source);
  }
 }

此配置类实现了跨域过虑器,在响应头添加Access-Control-Allow-Origin。

【Java实战篇】Day1.在线教育网课平台_第40张图片

你可能感兴趣的:(Spring,java,开发语言,数据库)