SpringBoot学习笔记(六)——分页、跨域、上传、定制banner、Lombok


一、分页(pagehelper)

 pagehelper 是一个强大实用的 MyBatis 分页插件,可以帮助我们快速的实现MyBatis分页功能,而且pagehelper有个优点是,分页和Mapper.xml完全解耦,并以插件的形式实现,对Mybatis执行的流程进行了强化,这有效的避免了我们需要直接写分页SQL语句来实现分页功能。

github项目地址:https://github.com/pagehelper/Mybatis-PageHelper

中文帮助:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md

1.1、快速起步

1.1.1、添加依赖

        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelper-spring-boot-starterartifactId>
            <version>1.3.0version>
        dependency>

这里需要注意也MyBatis的兼容问题,如果springboot pagehelper插件启动报错 [com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration]则需要更换版本,我使用2.5.13的Spring Boot与1.3.0的pagehelper是兼容的,示例项目完整的pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.13version>
        <relativePath/> 
    parent>
    <groupId>com.zhangguogroupId>
    <artifactId>mybatisdemo3artifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>mybatisdemo3name>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.2.2version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <optional>trueoptional>
            <scope>truescope>
        dependency>

        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelper-spring-boot-starterartifactId>
            <version>1.3.0version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <fork>truefork>
                configuration>
            plugin>
        plugins>
    build>

project>

1.1.2、添加配置

在application.yaml文件中添加如下配置信息:

# pagehelper
pagehelper:
  helperDialect: mysql  #数据库类型
  reasonable: true #查询合理化 当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页
  supportMethodsArguments: true  #支持方法参数 支持通过 Mapper 接口参数来传递分页参数
  params: count=countSql  #参数

1.1.3、数据访问接口

StudentDao.java:

package com.zhangguo.mybatisdemo3.dao;

import com.zhangguo.mybatisdemo3.entity.Student;
import java.util.List;

public interface StudentDao {
    //查询学生并分页
    public List selectPager();
}

1.1.4、接口映射

resource/mapper/StudentDao.xml:

<?xml version="1.0" encoding="utf-8"?>
"-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

"com.zhangguo.mybatisdemo3.dao.StudentDao">
    <select id="selectPager" resultType="Student">
        SELECT
            student.id,
            student.`name`,
            student.sex
        FROM
            student
    select>

1.1.4、实现分页

StudentService.java:

package com.zhangguo.mybatisdemo3.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhangguo.mybatisdemo3.dao.StudentDao;
import com.zhangguo.mybatisdemo3.entity.Student;
import com.zhangguo.mybatisdemo3.util.PageRequest;
import com.zhangguo.mybatisdemo3.util.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentService {
    @Autowired
    StudentDao studentDao;

    public PageInfo selectPager(int pageNum,int pageSize){
        //开始分页,指定页码与每页记录数
        PageHelper.startPage(pageNum,pageSize);
        //执行查询,请求会被分页插件拦截
        List students = studentDao.selectPager();
        //返回分页对象与数据
        return new PageInfo(students);
    }
}

1.1.5、调用分页方法

 PageController.java:

package com.zhangguo.mybatisdemo3.controller;

import com.github.pagehelper.PageInfo;
import com.zhangguo.mybatisdemo3.entity.Student;
import com.zhangguo.mybatisdemo3.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PageController {

    @Autowired
    StudentService studentService;

    @GetMapping
    public PageInfo hello(@RequestParam(value = "pageNum",required = false,defaultValue = "1") int pageNum, @RequestParam(value = "pageSize",required = false,defaultValue = "3")int pageSize){
        return studentService.selectPager(pageNum,pageSize);
    }
}

pageNum参数用于指定页号,默认值为1,pageSize用于指定每页记录数,默认值为3。

运行结果:

默认值情况

带参数情况

pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]

1.2、封装请求与结果

默认情况下请求参数并没有使用对象封装,返回结果包含冗余信息且需要与具体的业务关联。

1.2.1、请求参数封装

PageRequest.java 

package com.zhangguo.mybatisdemo3.util;
/**
 * 分页请求
 */
public class PageRequest {
    /**
     * 当前页码
     */
    private int pageNum;
    /**
     * 每页数量
     */
    private int pageSize;
    
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
}

1.2.2、响应结果封装

 PageResult.java

package com.zhangguo.mybatisdemo3.util;
import com.github.pagehelper.PageInfo;

import java.util.List;
/**
 * 分页返回结果
 */
public class PageResult {
    /**
     * 当前页码
     */
    private int pageNum;
    /**
     * 每页数量
     */
    private int pageSize;
    /**
     * 记录总数
     */
    private long totalSize;
    /**
     * 页码总数
     */
    private int totalPages;
    /**
     * 数据模型
     */
    private List<?> content;

    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
    public long getTotalSize() {
        return totalSize;
    }
    public void setTotalSize(long totalSize) {
        this.totalSize = totalSize;
    }
    public int getTotalPages() {
        return totalPages;
    }
    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }
    public List<?> getContent() {
        return content;
    }
    public void setContent(List<?> content) {
        this.content = content;
    }

    /**
     * 将分页信息封装到统一的接口
     * @return
     */
    public static PageResult getPageResult(PageInfo<?> pageInfo) {
        PageResult pageResult = new PageResult();
        pageResult.setPageNum(pageInfo.getPageNum());
        pageResult.setPageSize(pageInfo.getPageSize());
        pageResult.setTotalSize(pageInfo.getTotal());
        pageResult.setTotalPages(pageInfo.getPages());
        pageResult.setContent(pageInfo.getList());
        return pageResult;
    }
}

二、跨域

2.1、跨域概要

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

例如:a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。注意:跨域限制访问,其实是浏览器的限制。理解这一点很重要!!!

同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;

 2.2、返回新的CorsFilter(全局跨域)

CORS,全称Cross-Origin Resource Sharing  ,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。

示例:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    
    <script src="axios/axios.min.js">script>
    <script>
        axios.get("http://localhost:8080/students").then(response=>{
            console.log(response.data);
        }).catch(error=>{
            console.log(error);
        });
    script>
body>
html>

前端访问提示

在任意配置类,返回一个 新的 CorsFIlter Bean ,并添加映射路径和具体的CORS配置路径。

package com.zhangguo.mybatis4.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        //1. 添加 CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOrigin("http://127.0.0.1:5500/");
        //是否发送 Cookie
        config.setAllowCredentials(true);
        //放行哪些请求方式
        config.addAllowedMethod("*");
        //放行哪些原始请求头部信息
        config.addAllowedHeader("*");
        //暴露哪些头部信息
        config.addExposedHeader("*");
        //2. 添加映射路径
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**",config);
        //3. 返回新的CorsFilter
        return new CorsFilter(corsConfigurationSource);
    }
}

2.3、重写WebMvcConfigurer(全局跨域)

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //放行哪些原始域
                .allowedOrigins("*")
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

2.4、使用注解 (局部跨域)

在控制器(类上)上使用注解 @CrossOrigin:,表示该类的所有方法允许跨域。

@RestController
@CrossOrigin(origins = "*")
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

在方法上使用注解 @CrossOrigin:

  @RequestMapping("/hello")
  @CrossOrigin(origins = "*")
   //@CrossOrigin(value = "http://localhost:8081") //指定具体ip允许跨域
  public String hello() {
        return "hello world";
  }

2.5、手动设置响应头(局部跨域)

使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin的值也可以设置为 “*”,表示全部放行。

@RequestMapping("/index")
public String index(HttpServletResponse response) {

    response.addHeader("Access-Allow-Control-Origin","*");
    return "index";
}

2.6、使用自定义filter实现跨域

首先编写一个过滤器,可以起名字为MyCorsFilter.java

package cn.wideth.aop;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class MyCorsFilter implements Filter {

  public void doFilter(ServletRequest req, ServletResponse res, 
  FilterChain chain) throws IOException, ServletException {
  
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
    chain.doFilter(req, res);
    
  }
  
  public void init(FilterConfig filterConfig) {}
  public void destroy() {}
}

三、上传

前端使用Vue+Axios实现AJAX上传文件,upfile.html如下:

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传文件示例title>
head>

<body>
    <div id="app">
        <h2>上传文件示例h2>
        <input type="file" @change="selectFile($event)"/>
        <input type="button" value="上传"  @click="upfile($event)" />
        <p>
            <img :src="'files/'+filename" v-show="isUpload"/>
        p>
    div>
    <script src="vue/vue.js">script>
    <script src="axios/axios.min.js">script>
    <script>
        var app = new Vue({
            el: "#app",
            data:{
                file:null,
                filename:'',
                isUpload:false
            },
            methods:{
                selectFile(event){
                    //从input的文件集合中获取第1个文件对象,就是要上传的文件对象
                    this.file=event.target.files[0];
                    console.log(this.file);
                },
                upfile(event){
                    event.preventDefault(); //阻止默认事件
                    //创建一个表单数据对象
                    var formdata=new FormData();
                    //向表单数据对象中添加表单数据,指定名称与数据内容
                    formdata.append("file",app.file);
                    //使用axios上传数据
                    axios.post("http://localhost:8080/upfile",formdata,{
                        Headers:{
                            "Content-Type":"multipart/form-data"
                        }
                    }).then(response=>{
                        console.log(response.data);
                        //上传成功了
                        app.isUpload=true;
                        //指定上传成功后的文件名
                        app.filename=response.data;
                    }).catch(error=>{
                        console.log(error);
                    });
                    return false;
                }
            }
        });
    script>
body>

html>

application.yaml文件

#文件的限制大小
  servlet:
    multipart:
      max-file-size: 100MB  #文件最大值
      max-request-size: 100MB #请求最大值
prop:
  up-folder: F:\NF\Spring boot\demos\CORSDemo1\uploads\  #上传目标位置

后端控制器

package com.zhangguo.mybatis4.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
public class UpController {
    @Value("${prop.up-folder}")
    String upFolder;

    @PostMapping("/upfile")
    public String upfile(@RequestPart MultipartFile file) throws IOException {
        //获取旧文件名称
        String oldname=file.getOriginalFilename();
        //生成新文件名称
        String newname= UUID.randomUUID().toString()+oldname.substring(oldname.lastIndexOf("."));
        //文件保存路径
        String path=upFolder+newname;
        //保存文件
        file.transferTo(new File(path));
        return newname;
    }
}

运行效果

在vscode中因为安装了Live Server会实时的刷新页面,上传后会自动刷新,可以忽视*.png与*.jpg文件上传到前端目录中,设置如下:

点击设置

点击扩展设置,找到如下项

 在中间添加两项

,  "**/*.jpg", "**/*.png"

保存配置即可。

四、启动Banner定制

我们在应用启动的时候,可以看到控制台显示了Spring的Banner信息,我们可以通过定制这个功能,来放置我们自己的应用信息。

 如果要定制自己的Banner, 只需要在 resources 下放置一个 banner.txt 文件,输入自己的banner字符即可。

 重新启动项目

Banner字符可以通过类似以下网站生成:

http://patorjk.com/software/taag

http://www.network-science.de/ascii/

五、lombok

5.1、lombok概要

Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。

在项目中使用Lombok可以减少很多重复代码的书写。比如说getter/setter/toString等方法的编写。

“Boilerplate”是一个术语,用于描述在应用程序的许多部分中很少改动就重复的代码。对Java语言最常见的批评就是在大多数项目中都可以找到这种类型的代码,由于语言本身的局限性而更加严重。龙目岛计划(Project Lombok)旨在通过用简单的注释集代替众多的代码。

Lombok也存在一定风险,在一些开发工具商店中没有Project Lombok支持选择。 IDE和JDK升级存在破裂的风险,并且围绕项目的目标和实施存在争议。

常用注解:

  1. @Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
  2. @Getter :使用方法同上,区别在于生成的是getter方法。
  3. @ToString :注解在类,添加toString方法。
  4. @EqualsAndHashCode: 注解在类,生成hashCode和equals方法。
  5. @NoArgsConstructor: 注解在类,生成无参的构造方法。
  6. @RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
  7. @AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。
  8. @Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
  9. @Slf4j: 注解在类,生成log变量,严格意义来说是常量。

5.2、引入依赖

在pom文件中添加如下部分。(不清楚版本可以在Maven仓库中搜索)

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

5.3、使用注解

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data  //注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@AllArgsConstructor  // 注解在类,生成包含类中所有字段的构造方法。
@NoArgsConstructor  //注解在类,生成无参的构造方法。
public class Department {
  private Integer departmentId;
  private String departmentName;
}

 5.4、运行测试

测试类:

package com.zhangguo.mybatisdemo3;

import com.zhangguo.mybatisdemo3.entity.Department;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DepartmentTest {
    @Test
    public void lombokTest(){
        Department department=new Department(1,"研发部");
        System.out.println(department);
    }
}

测试结果:

Department(departmentId=1, departmentName=研发部)

可以看到有带参构造方法,toString方法也被重写过了。

需要注意的是新版本的IDEA不再需要安装插件,已经默认整合了。