【Java实战篇】Day1.
创始人
2025-05-31 18:06:56
0

文章目录

  • 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、项目介绍

项目背景:

在这里插入图片描述

项目业务介绍:

在这里插入图片描述

**技术架构图:**

在这里插入图片描述

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

2、需求分析与接口设计

2.1 分析需求

在这里插入图片描述

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

在这里插入图片描述

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

  • 分析请求参数:
    请求参数为:课程名称、课程审核状态、当前页码、每页显示记录数,根据请求参数定义模型类
    在这里插入图片描述

  • 分析响应结果:
    。 根据前边对数据模型的分析,响应结果为数据列表加一些分页信息(总记录数、当前页、每页显示记录数)。
    。 数据列表中数据的属性包括:课程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基础工程中定义。
在这里插入图片描述

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

在这里插入图片描述

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下,方便以后复用

在这里插入图片描述

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 implements Serializable {// 数据列表private List items;//总记录数private long counts;//当前页码private long page;//每页记录数private long pageSize;public PageResult(List 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 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的解释:

在这里插入图片描述

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

在这里插入图片描述
VO的意义

场景:

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

接口文档生成工具—swapper

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

com.spring4allswagger-spring-boot-starter
@Api(value = "课程信息编辑接口",tags = "课程信息编辑接口")
@RestController
public class CourseBaseInfoController {@ApiOperation("课程查询接口")@PostMapping("/course/list")public PageResult list(PageParams pageParams, @RequestBody(required=false) QueryCourseParamsDto queryCourseParams){//....}
}

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

在这里插入图片描述
在这里插入图片描述
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

在这里插入图片描述
在这里插入图片描述
分页插件的原理:

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

在这里插入图片描述

测试Mapper

@SpringBootTest
class CourseBaseMapperTests {@AutowiredCourseBaseMapper courseBaseMapper;@Testvoid testCourseBaseMapper() {CourseBase courseBase = courseBaseMapper.selectById(74L);Assertions.assertNotNull(courseBase);//测试查询接口LambdaQueryWrapper 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 page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());//分页查询E page 分页参数, @Param("ew") Wrapper queryWrapper 查询条件Page pageResult = courseBaseMapper.selectPage(page, queryWrapper);//数据List items = pageResult.getRecords();//总记录数long total = pageResult.getTotal();//准备返回数据 List items, long counts, long page, long pageSizePageResult 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 数据字典表

在这里插入图片描述
前面提到,下拉框取值可以为:

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

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

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

在这里插入图片描述

4.2 开发Service层

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

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

  • 或者返回一个VO对象,再调用公司的统一结果类,比如若依的AjaxResult,传入VO对象做为data,此时service层方法返回值类型为VO对应的类型
    在这里插入图片描述

  • 返回其他类型,此时和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 queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto);}
}

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

/*** @description 课程信息管理业务接口实现类*/
@Service
public class CourseBaseInfoServiceImpl  implements CourseBaseInfoService {@AutowiredCourseBaseMapper courseBaseMapper;@Overridepublic PageResult queryCourseBaseList(PageParams pageParams, QueryCourseParamsDto queryCourseParamsDto) {//构建查询条件对象LambdaQueryWrapper 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 page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());// 查询数据内容获得结果Page pageResult = courseBaseMapper.selectPage(page, queryWrapper);// 获取数据列表List list = pageResult.getRecords();// 获取数据总数long total = pageResult.getTotal();// 构建结果集PageResult courseBasePageResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());return courseBasePageResult;}}

4.3 单元测试service层

@SpringBootTest
class CourseBaseInfoServiceTests {@AutowiredCourseBaseInfoService courseBaseInfoService;@Testvoid 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 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 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:

在这里插入图片描述

用法:

  • 在controller层接口上点击Generate request in HTTP Client
    在这里插入图片描述

  • 可以看到自己生成了一个.http结尾的文件
    在这里插入图片描述

  • 添加测试json
    在这里插入图片描述

  • 点击运行
    在这里插入图片描述

优化:

  • 为了统一保存,在项目工程的根目录创建一个目录单独存放.http文件
    在这里插入图片描述
    并以模块为单位创建.http文件,拷贝刚才的http文件内容
    在这里插入图片描述

  • 为了方便将来和网关集成测试,这里我们把测试主机地址在配置文件http-client.env.json 中配置
    在这里插入图片描述

{"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 用变量代替
在这里插入图片描述

6、前后端联调

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

6.1 环境准备–安装node-js

  • 使用msi包安装nodejs,安装完成后查看版本
    在这里插入图片描述

  • 拷贝前端工程,并使用IDEA启动:从前端工程拷贝project-xczx2-portal-vue-ts.zip到代码目录并解压,并使用IDEA或VS Code打开project-xczx2-portal-vue-ts目录
    在这里插入图片描述

  • 点击show npm Script 打开npm窗口在这里插入图片描述

  • 点击“Edit ‘serve’” setting,下边对启动项目的一些参数进行配置,选择nodejs、npm
    在这里插入图片描述
    在这里插入图片描述

  • 右键server,点击run
    在这里插入图片描述

  • 启动成功
    在这里插入图片描述

如果存在问题通过以下命令启动:
--------------------
1、cmd进入工程根目录 2、运行以下命令npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i
npm run serve

6.2 解决CORS报错

在浏览器通过http://localhost:8601/地址访问前端工程,有个接口报错:
在这里插入图片描述
即System服务异常,这个接口是前端请求后端获取数据字典数据的接口
在这里插入图片描述
进入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

在这里插入图片描述
解决思路3:Nginx做代理
由于服务端之间没有跨域,浏览器通过nginx去访问跨域地址
在这里插入图片描述

解决跨域报错

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

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

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

在这里插入图片描述

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...