项目实战 - 前后端分离、后端校验、Swagger
Layui同源策略SpringMVC 实现 CORS后端校验 - hibernate-validator方法的 Model 参数校验方法的非 Model 参数校验常见注解自定义校验快速失败Swagger基本配置与使用常用注解Bug:NumberFormatException全局异常处理驾考 - 前后端分离MyBatis 的 QueryWrapper 的增强实现mapstruct前后端分离使用 Session企业中的文件上传(图片、视频)Java 从 0 到架构师目录:【Java从0到架构师】学习记录
驾考系统 Gitee 代码(前端 + 后端):/szluyu99/jiakao
早期的前后端协作模式:
前端:切图仔、页面仔后台:动态模板技术组装成
以前的协作模式的问题:
前端地位比较低,大部分工作都在后台调试、修改页面比较麻烦,需要前端、后台充分配合浪费流量(每次请求都会返回整个页面,这就意味着返回了很多重复的内容)
前后端分离:
前端:切图、页面、交互、路由、业务逻辑后台:返回 JSON
前后端分离:
页面保持使用静态页面即可(html)静态页面(浏览器)发送异步请求(AJAX)给服务器,服务器返回 JSON浏览器解析 JSON 数据,动态生成对应的 HTML 标签,显示到用户眼前前端一个项目、后端一个项目,分开开发(跨域)前端一个服务器、后端一个服务器,分开部署
Layui
Layui 是一款前端 UI 框架,可以帮助开发者快速搭建后台管理系统的前端页面
免费版:/LayuiMini(更强大的免费版):/
本课程使用的是单页版:v2-onepage
基本使用:下载后解压,将以下内容放到项目中
同源策略
浏览器有个同源策略 (Same-Origin Policy)
它规定了:默认情况下,AJAX请求只能发给同源的URL同源是指3个相同:协议、域名(IP)、端口
img、script、link、iframe、video、audio 等标签不受同源策略的约束
解决 AJAX 跨域请求的常用方法:
CORS (Cross-Origin Resource Sharing),跨域资源共享
CORS 的实现需要客户端和服务器同时支持:
客户端:基本所有的浏览器都支持(IE 至少是 IE10 版本)服务器:需要返回相应的响应头(比如 Access-Control-Allow-Origin),告知浏览器这是一个允许跨域的请求
Access-Control-Allow-Origin 用于允许跨域。
如果不设置允许跨域,则会出现下面情况:
在返回响应时要设置允许跨域,即可跨域访问资源。
设置跨域后,即可成功访问页面。
SpringMVC 实现 CORS
局部设置:在某个 Controller 上使用@CrossOrigin
注解,允许当前 Controller 被跨域访问
@RequestMapping("/plateRegions")@CrossOrigin("http://localhost:63342")public class PlateRegionController {}
全局设置:
需要客户端设置后才会发送 Cookie
let xhr = new XMLHttpRequest()xhr.withCredentials = true
服务器设置:
@Configurationpublic class WebCfg implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// /**表示对所有的路径开放全局跨域访问权限registry.addMapping("/**")// 开放哪些IP、端口、域名的访问权限.allowedOrigins("*")// 是否允许发送Cookie信息.allowCredentials(true)// 哪些HTTP方法允许跨域访问.allowedMethods("GET", "POST");// 允许HTTP请求中携带的哪些Hedaer信息.allowHeaders("*");// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)// .exposeHeaders("*");}}
后端校验 - hibernate-validator
前端哪怕页面上的交互做好了校验,但是仍然可以通过 postman 等工具直接发送请求,因此前端校验是可以被跳过的,因此后端也应当有校验的功能;但是如果直接在 Controller 中写,又会增加大量代码,使得控制层十分的冗余,因此可以借助一些便利的框架来做后端校验。
hibernate-validator 是 Java 中常用的一款后端校验框架:
参考文档:
/hibernate/stable/validator/reference/en-US/html_single/
/hibernate/stable/validator/reference/en-US/pdf/hibernate_validator_reference.pdf
<!-- 后端校验 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
方法的 Model 参数校验
使用步骤:
① 在 Model 的 getter 或成员变量上加相关的校验注解
@NotBlank(message = "名称不能为空")private String name;@NotBlank(message = "车牌不能为空")private String plate;
② 在 Model 参数上加@Valid
注解
@PostMapping("/save")public R save(@Valid T entity) {if (getService().saveOrUpdate(entity)) {return Rs.ok(CodeMsg.SAVE_SUCCESS);} else {return Rs.raise(CodeMsg.SAVE_ERROR);}}
校验失败时,会抛出异常:org.springframework.validation.BindException
可以通过 BindException.getBindResult().getAllErrors() 拿到所有的错误信息
方法的非 Model 参数校验
使用步骤:
① 在 Controller 上加@Validated
注解
② 在非 Model 参数上加相关的校验注解
@Validatedpublic abstract class BaseController<T> {@PostMapping("/remove")public R remove(@NotBlank(message = "id不能为空") String id) {// code...}}
校验失败时,会抛出异常:javax.validation.ConstraintViolationException
可以通过 ConstraintViolationException.getConstraintViolations() 拿到所有的错误信息
常见注解
@NotNull:不能为 null,但可以为 empty
@NotEmpty:不能为 null,而且长度必须大于 0
@NotBlank:只能作用在 String 上,不能为 null,且去除空格后长度必须大于 0
@AssertFalse:必须为 false
@AssertTrue:必须为 true
@Max,@DecimalMax:必须为一个不大于指定值的数字
@Min,@DecimalMin:必须为一个不小于指定值的数字
@Digits:必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction
@Email:必须是 Email,也可以通过正则表达式和 flag 指定自定义的 Email 格式
@Future:必须是一个将来的日期
@Past:必须是一个过去的日期
@Range:必须在合适的范围内,min 到 max 之间
@Size,@Length:长度必须在 min 到 max 之间
@Pattern:必须符合指定的正则表达式
自定义校验
示例代码:自定义校验
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = BoolNumber.BoolNumberValidator.class)public @interface BoolNumber {// 这3个方法必须实现,不然会报错String message() default "只能是0和1";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};class BoolNumberValidator implements ConstraintValidator<BoolNumber, Short> {@Overridepublic boolean isValid(Short num, ConstraintValidatorContext constraintValidatorContext) {return num == null || num == 0 || num == 1;}}}
@BoolNumber(message = "disabled只能是0或1")private Short disabled;
快速失败
默认情况下是检测完所有的错误后再统一抛出异常,也可以设置快速失败
快速失败:只要检测到一个错误,就直接抛出异常,不再往下检查
@Configurationpublic class ValidatorCfg {@Beanpublic Validator validator() {return Validation.byProvider(HibernateValidator.class).configure().failFast(true) // 快速失败.buildValidatorFactory().getValidator();}}
Swagger
Swagger 可以快速生成接口文档,极大地节省了开发人员编写接口文档的时间
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>3.0.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>3.0.0</version></dependency>
也可以使用官方的 starter 一步到位:
<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency>
基本配置与使用
代码:Swagger 的配置与使用
注:如果使用 starter,需要去掉@EnableSwagger2
@Configuration@EnableSwagger2 // 如果使用的 starter, 需要去掉这个注解public class SwaggerCfg {@Beanpublic Docket docket(Environment environment) {boolean enable = environment.acceptsProfiles(Profiles.of("dev", "test"));return new Docket(DocumentationType.SWAGGER_2).enable(enable).apiInfo(apiInfo());}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("驾考接口接口文档").description("已经包含了所有可用的请求接口").version("1.0.0").build();}}
配置好后,可以通过 URL 去访问 Swagger 文档页面,访问路径:
如果使用了 starter:context_path/swagger-ui/index.html
如果没有使用 swagger:context_path/swagger-ui.html
被 Swagger 显示的 API 应当由我们自己来配置,API 选择:
以 dict 开头的包下的所有的接口:
new Docket(DocumentationType.SWAGGER_2).select().paths(PathSelectors.ant("/dict*/**")).build();
使用了@RestController
注解的接口:
new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)).build();
指定包下面的所有接口:
new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage("com.mj.jk.controller")).build();
有时候某些参数不希望显示出来被其他人看见,可以配置忽略参数:
new Docket(DocumentationType.SWAGGER_2).ignoredParameterTypes(HttpSession.class,HttpServletRequest.class,HttpServletResponse.class);
可以将 API分组后进行显示(根据下拉框选择某个分组,展示对应的 API),例如:将满足条件的 API 接口配置到 “元数据” 这一组:
new Docket(DocumentationType.SWAGGER_2).groupName("元数据").select().paths(PathSelectors.regex("/(dict.+|plateRegions.+)")).build();
全局参数:
Parameter token = new ParameterBuilder().name("token").description("用户登录令牌").parameterType("header") // 还可以是query.modelRef(new ModelRef("String")) // 参数类型.required(true) // 是否必要.build();new Docket(DocumentationType.SWAGGER_2).globalOperationParameters(List.of(token)); // List.of是jdk11的语法
常用注解
Bug:NumberFormatException
在使用 Swagger 2.9.2 版本时,可能经常在控制台看到一个异常:
java.lang.NumberFormatException:For input string:""这是 io.swagger:swagger-models:1.5.20 内部的一个 Bug升级到最新版本的 io.swagger:swagger-models 即可解决问题
直接在 pom.xml 中,使用下面的 io.swagger 则会覆盖 starter 中的版本
<dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>1.6.2</version></dependency>
全局异常处理
@RestControllerAdvice@Slf4jpublic class CommonExceptionHandler {@ExceptionHandler(Throwable.class)@ResponseStatus(code = HttpStatus.BAD_REQUEST)public JsonVo handle(Throwable t) {log.error("handleThrowable", t);return null;}}
驾考 - 前后端分离
MyBatis 的 QueryWrapper 的增强实现
使用原生的 MyBatis 对于多个关键字的模糊查询写法比较累赘,对使用方法进行增强:
public class MpLambdaQueryWrapper<T> extends LambdaQueryWrapper<T> {// 返回值返回自身实现链式编程@SafeVarargspublic final MpLambdaQueryWrapper<T> like(Object val, SFunction<T, ?>... funcs) {if (val == null) return this;String str = val.toString();if (str.length() == 0) return this;return (MpLambdaQueryWrapper<T>) nested((w) -> {for (SFunction<T, ?> func : funcs) {w.like(func, str).or();}});}}
增强后,使用起来就很方便:
// 查询条件MpLambdaQueryWrapper<DictType> wrapper = new MpLambdaQueryWrapper<>();// 根据关键字查询wrapper.like(query.getKeyword(),DictType::getName,DictType::getValue,DictType::getIntro);
mapstruct
参考文章:mapstruct 使用详解
前后端分离使用 Session
后台:
allowCredentials(true)allowedOrigins(origins):origins 要指定特定主机地址,不能使用 *
前端:
$.ajax({url: url,xhrFields: {withCredentials: true}})
企业中的文件上传(图片、视频)
1、文件数据跟随表单数据一起提交
2、文件数据先单独提交,从文件服务器获取一个文件的 uri,文件的 uri 跟随表单数据一起提交