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

一、分页(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.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zhangguo</groupId>
    <artifactId>mybatisdemo3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatisdemo3</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
View Code

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<Student> selectPager();
}

1.1.4、接口映射

resource/mapper/StudentDao.xml:

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

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

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<Student> selectPager(int pageNum,int pageSize){
        //开始分页,指定页码与每页记录数
        PageHelper.startPage(pageNum,pageSize);
        //执行查询,请求会被分页插件拦截
        List<Student> students = studentDao.selectPager();
        //返回分页对象与数据
        return new PageInfo<Student>(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<Student> 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>Document</title>
</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"

 

  "liveServer.settings.ignoreFiles": [
  
    ".vscode/**",
    "**/*.scss",
    "**/*.sass",
    "**/*.ts",
    "**/*.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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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不再需要安装插件,已经默认整合了。

六、Hutool

6.1、简介

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。

Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;

Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。

Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu”是公司名称的表示,tool表示工具。Hutool谐音“糊涂”,一方面简洁易懂,一方面寓意“难得糊涂”。

Hutool的目标是使用一个工具方法代替一段复杂代码,从而最大限度的避免“复制粘贴”代码的问题,彻底改变我们写代码的方式。

以计算MD5为例:

  • 【以前】打开搜索引擎 -> 搜“Java MD5加密” -> 打开某篇博客-> 复制粘贴 -> 改改好用
  • 【现在】引入Hutool -> SecureUtil.md5()

Hutool的存在就是为了减少代码搜索成本,避免网络上参差不齐的代码出现导致的bug。

GitHub:https://github.com/dromara/hutool/

官网:https://www.hutool.cn/

6.2、包含组件

一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:

模块介绍
hutool-aop JDK动态代理封装,提供非IOC下的切面支持
hutool-bloomFilter 布隆过滤,提供一些Hash算法的布隆过滤
hutool-cache 简单缓存实现
hutool-core 核心,包括Bean操作、日期、各种Util等
hutool-cron 定时任务模块,提供类Crontab表达式的定时任务
hutool-crypto 加密解密模块,提供对称、非对称和摘要算法封装
hutool-db JDBC封装后的数据操作,基于ActiveRecord思想
hutool-dfa 基于DFA模型的多关键字查找
hutool-extra 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等)
hutool-http 基于HttpUrlConnection的Http客户端封装
hutool-log 自动识别日志实现的日志门面
hutool-script 脚本执行封装,例如Javascript
hutool-setting 功能更强大的Setting配置文件和Properties封装
hutool-system 系统参数调用封装(JVM信息等)
hutool-json JSON实现
hutool-captcha 图片验证码实现
hutool-poi 针对POI中Excel和Word的封装
hutool-socket 基于Java的NIO和AIO的Socket封装
hutool-jwt JSON Web Token (JWT)封装实现

可以根据需求对每个模块单独引入,也可以通过引入hutool-all方式引入所有模块。

6.3、文档

📘中文文档

📘中文备用文档

📙参考API

🎬视频介绍

6.4、安装

6.4.1、Maven

在项目的pom.xml的dependencies中加入以下内容:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.5</version>
</dependency>

6.4.2、Gradle

implementation 'cn.hutool:hutool-all:5.8.5'

6.4.3、下载jar

点击以下链接,下载hutool-all-X.X.X.jar即可:

注意 Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 如果你的项目使用JDK7,请使用Hutool 4.x版本(不再更新)

6.4.5、编译安装

访问Hutool的Gitee主页:https://gitee.com/dromara/hutool 下载整个项目源码(v5-master或v5-dev分支都可)然后进入Hutool项目目录执行:

./hutool.sh install

然后就可以使用Maven引入了。

6.5、Spring Boot中使用

1、添加依赖

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>

2、使用工具库

package com.nfit.studentmis2;

import cn.hutool.core.date.ChineseDate;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.swing.clipboard.ClipboardUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class HutoolTests {

    @Test
    void ChineseDateTest() {
        //农历
        ChineseDate chineseDate=new ChineseDate(DateUtil.parseDate("2022-08-29"));
        System.out.println(chineseDate.toString());
    }
    @Test
    void clipboardTest(){
        //获取剪切板的值
        System.out.println(ClipboardUtil.getStr());
    }

}

运行结果:

 

 解决java.awt.HeadlessException的方法:

在VM options中追加-Djava.awt.headless=false

 

 再运行

6.6、日期转换与农历

    @Test
    public void dateTest(){
        //字符串转换成日期
        Date date= DateUtil.parse("2023-06-18");
        System.out.println(date);

        //日期转字符
        DateTime dateTime=new DateTime(new Date());
        System.out.println(dateTime.toString("yyyy-MM-dd hh:mm:ss"));

        //农历
        ChineseDate chineseDate=new ChineseDate(date);
        System.out.println(chineseDate);
        System.out.println(chineseDate.getChineseYear()+","+chineseDate.getChineseMonth()+","+chineseDate.getChineseDay());
    }

6.7、摘要与加密

摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。

但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。

    @Test
    public void md5Test(){
        String result="123456";
        result=SecureUtil.md5(result);
        System.out.println(result);
    }

    @Test
    public void sha1Test(){
        String result="qwer123";
        result=SecureUtil.sha1(result);
        System.out.println(result);
    }

    @Test
    public void sha256Test(){
        String result="qwer-123";
        result=SecureUtil.sha256(result);
        System.out.println(result);
    }


    // 16位自定义密码
    String key = "1234567891011A_a";


    @Test
    public void AES_encryptTest(){
        String content = "我是中国人我爱中国";
        // 生成密钥
        byte[] byteKey = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key.getBytes()).getEncoded();

        SymmetricCrypto aes = SecureUtil.aes(byteKey);

        // 加密
        String encryptData = aes.encryptBase64(content);
        System.out.println(encryptData);
        //X4bUbC5NMVd7qqoj7V/RbX6o1/894wcNqxpjteYNpN0=


    }

    @Test
    public void AES_decryptTest() {
        // 生成密钥
        byte[] byteKey = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key.getBytes()).getEncoded();

        SymmetricCrypto aes = SecureUtil.aes(byteKey);

        String ori="X4bUbC5NMVd7qqoj7V/RbX6o1/894wcNqxpjteYNpN0=";
        //base64 -> byte[]

        // 解密
        String decryptData = aes.decryptStr(ori);
        System.out.println(decryptData);
        // 我是中国人我爱中国
    }

6.8、验证码

  验证码的作用:

  1、为了防止机器冒充人类做暴力破解

  2、防止大规模在线注册滥用服务

  3、防止滥用在线批量操

  4、防止自动发布

  5、防止信息被大量采集聚合

package com.nfit.studentmis1805.controller;

import cn.hutool.captcha.ShearCaptcha;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/hutool")
public class HutoolController {

    ShearCaptcha sc;

    @GetMapping("/captcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //第一个参数是宽度
        //第二个参数是高度
        //第三个参数是验证码中字符的个数
        //第四个参数是干扰线的厚度
        sc=new ShearCaptcha(100,40,4,5);

        //将验证码输出到文件中
        //sc.write("c:\\a.jpg");

        //将验证码的内容输出到响应流中
        sc.write(response.getOutputStream());
    }

    @GetMapping("/captcha/verify/{code}")
    public boolean verify(@PathVariable String code){
        //验证验证码是否正确
        return sc.verify(code);
    }

}

loing.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>Document</title>
  </head>
  <body>
    <div id="app">
      <p>
        <label for="code">验证码:</label>
        <input type="text" id="code" v-model="code" />
        <img
          src="http://127.0.0.1:8081/hutool/captcha"
          onclick="this.src='http://127.0.0.1:8081/hutool/captcha?d='+new Date()"
        />
        <a href="#">看不清,换一张</a>
      </p>
      <p>
        <button type="button" @click="login">登录</button>
      </p>
    </div>

    <script src="js/axios.min.js"></script>
    <script src="js/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          code: "",
        },
        methods: {
          login() {
            axios
              .get(`http://127.0.0.1:8081/hutool/captcha/verify/${this.code}`)
              .then((res) => {
                alert(res.data);
              })
              .catch((err) => alert(err));
          },
        },
      });
    </script>
  </body>
</html>

运行:

6.9、发电子邮件

1、注册一个邮箱,如126

2、在spring boot项目中添加依赖

        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.6.2</version>
        </dependency>

3、邮件服务器配置

在classpath(在标准Maven项目中为src/main/resources)的config目录下新建mail.setting文件,最小配置内容如下,在此配置下,smtp服务器和用户名都将通过from参数识别:

# 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>
host = smtp.126.com
# 邮件服务器的SMTP端口,可选,默认25
port = 465
# 发件人(必须正确,否则发送失败)
from = longwumsg@126.com
# 用户名,默认为发件人邮箱前缀
user = longwumsg
# 密码(注意,某些邮箱需要为SMTP服务单独设置授权码,详情查看相关帮助)
pass = SGWDNOMQME2DDFEGFD
# 使用SSL安全连接
sslEnable = true

4、发送电子邮件

    @Test
    public void sendMailTest(){
        String send = MailUtil.send("99510309@qq.com", "用户注册成功", "尊敬的用户,恭喜您注册成本网站会员" + new Date() + UUID.randomUUID().toString(), false);
        System.out.println(send);
    }

    public static int generateRandomNumber(int numDigits) {
        int min = (int) Math.pow(10, numDigits - 1); // Minimum value
        int max = (int) Math.pow(10, numDigits) - 1; // Maximum value
        int range = max - min + 1; // Range of values
        return (int) (Math.random() * range) + min; // Generate and return random number
    }

    @Test
    public void sendHTMLMailTest(){
        String content="<hr/>你的登录验证码是:<span style='color:red;font-size:30px'>"+generateRandomNumber(6)+"</span>,"+UUID.randomUUID();
        String send = MailUtil.send("99510309@qq.com", "登录验证码", content, true);
        System.out.println(send);
    }

    @Test
    public void sendFileMailTest(){
        String content="<hr/>你的登录验证码是:<span style='color:red;font-size:30px'>"+generateRandomNumber(6)+"</span>,"+UUID.randomUUID();
        String send = MailUtil.send("99510309@qq.com", "登录验证码", content,true, new File("F:\\Images\\desk.JPEG"));
        System.out.println(send);
    }

5、结果

 

6.10、拼音

1、以下为Hutool支持的拼音库的pom坐标,你可以选择任意一个引入项目中,如果引入多个,Hutool会按照以上顺序选择第一个使用

<dependency>
    <groupId>io.github.biezhi</groupId>
    <artifactId>TinyPinyin</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

2、编写测试代码

    @Test
    public void pinyinTest(){
        //获取文字的拼音
        String result1=PinyinUtil.getPinyin("我是中国人我爱中国china666");
        System.out.println(result1);
        //带分隔符
        String result2=PinyinUtil.getPinyin("我是中国人我爱中国",",");
        System.out.println(result2);
        //首字母
        String result3=PinyinUtil.getFirstLetter("我是中国人我爱中国","");
        System.out.println(result3);
    }

3、结果

6.11、条码(一维码),二维码

由于大家对二维码的需求较多,对于二维码的生成和解析我认为应该作为简单的工具存在于Hutool中。考虑到自行实现的难度,因此Hutool针对被广泛接受的的zxing (opens new window)库进行封装。而由于涉及第三方包,因此归类到extra模块中。

1、添加依赖

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.3</version>
</dependency>

2、编写代码

    @Test
    public void qrCodeTest(){
        File generate = QrCodeUtil.generate("张果老师好", 256, 256, FileUtil.file("c:\\qrcode.png"));
    }
    @GetMapping("/qrcode/{content}")
    public void getQrCode(HttpServletRequest request,HttpServletResponse response,@PathVariable String content) throws IOException {
        QrCodeUtil.generate(content,256,256,"",response.getOutputStream());
    }

3、结果

6.12、读取Excel文件

1、更新依赖

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>

2、准备Excel文件,如students.xlsx

 3、写读取代码

    @Test
    public void readExcel(){
        //读指定路径的excel文件
        ExcelReader reader = ExcelUtil.getReader(FileUtil.file("C:\\Users\\Administrator\\Desktop\\students.xlsx"));
        //获取数据
        List<List<Object>> lists = reader.read();
        System.out.println(lists);
    }

4、查看运行结果

6.13、导入

 1、前端

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学生信息管理系统</title>
    <style>
      .studentList {
        width: 85%;
      }
      .studentList,
      .studentList td,
      .studentList th {
        border: 1px solid #666;
        border-collapse: collapse;
      }
      #toolbar {
        padding: 10px 0;
      }
      #toolbar input {
        width: 100px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h2>学生信息管理系统</h2>
      <div id="toolbar">
        编号:<input type="text" v-model="student.id" /> 姓名:<input
          type="text"
          v-model="student.name"
        />
        班级:<input type="text" v-model="student.classes.name" /> 性别:<input
          type="text"
          v-model="student.sex"
        />
        生日:<input type="date" v-model="student.birthday" /> 院系:<input
          type="text"
          v-model="student.college"
        />
        <button type="button" @click="search">搜索</button>
        <button type="button" @click="importExcel">导入</button>
      </div>
      <table class="studentList">
        <tr>
          <th>编号</th>
          <th>姓名</th>
          <th>班级</th>
          <th>性别</th>
          <th>生日</th>
          <th>院系</th>
          <th>操作</th>
        </tr>
        <tr v-for="(student,index) in students" :key="student.id">
          <td>{{student.id}}</td>
          <td>{{student.name}}</td>
          <td>{{student.classes.name}}</td>
          <td>{{student.sex}}</td>
          <td>{{student.birthday}}</td>
          <td>{{student.college}}</td>
          <td>
            <a href="#" @click="deleteStudent(student,index)">删除</a>
            <a :href="'edit.html?id='+student.id">编辑</a>
          </td>
        </tr>
      </table>
      <p>
        <a href="add.html">新增学生</a>
      </p>
    </div>
    <script src="js/axios.min.js"></script>
    <script src="js/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          students: [],
          student: {
            classes: {},
            fileInput: null,
          },
        },
        created() {
          this.search();
        },
        methods: {
          importExcel() {
            if (!this.fileInput) {
              //创建一个input在页面上
              this.fileInput = document.createElement("input");
              this.fileInput.setAttribute("type", "file");
              this.fileInput.setAttribute("hidden", "true");
              this.fileInput.setAttribute("name", "file");
              document.body.appendChild(this.fileInput);

              //触发文件上传
              this.fileInput.addEventListener(
                "change",
                function (e) {
                  //获取当前要上传的文件
                  const file = e.target.files[0];
                  //创建要上传的表单数据
                  const formData = new FormData();
                  formData.append("file", file);

                  axios
                    .post(
                      "http://127.0.0.1:8080/api/student/importExcel",
                      formData,
                      {
                        "Content-Type": "multipart/form-data",
                      }
                    )
                    .then((res) => {
                      alert("上传数据成功,共计添加" + res.data + "行数据!");
                      vm.search();
                    })
                    .catch((err) => alert(err));
                },
                true
              );
            }
            this.fileInput.value;
            this.fileInput.click();
          },
          search() {
            axios
              .post("http://127.0.0.1:8080/api/student/search", this.student)
              .then((response) => {
                this.students = response.data;
              })
              .catch((err) => {
                alert(err);
              });
          },
          deleteStudent(student, index) {
            if (confirm("您确定要删除吗?")) {
              console.log(student, index);
              axios
                .delete(`http://127.0.0.1:8080/api/student/${student.id}`)
                .then((response) => {
                  if (response.data === 1) {
                    alert("删除成功!");
                    vm.students.splice(index, 1);
                  }
                })
                .catch((err) => {
                  alert(err);
                });
            }
          },
        },
      });
    </script>
  </body>
</html>

2、后台

package com.nfit.studentmis1805.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/api/student")
public class StudentController {
    @Autowired
    StudentService studentService;

    @PostMapping("/search")
    public List<Student> findAllStudents(@RequestBody Student entity){
        return studentService.getAllStudents(entity);
    }

    @GetMapping("/pager")
    public PageResult selectPager(@RequestParam(name = "pageNum",required = false,defaultValue = "1") int pageNum,
                                  @RequestParam(name = "pageSize",required = false,defaultValue = "2") int pageSize){
        return studentService.selectPager(pageNum,pageSize);
    }

    @DeleteMapping("/{id}")
    public int deleteStudentById(@PathVariable String id){
        return studentService.deleteStudentById(id);
    }


    @PostMapping("")
    public int addStudent(@RequestBody Student student){
        return studentService.addStudent(student);
    }

    @GetMapping("/{id}")
    public Student getStudentById(@PathVariable String id){
        return studentService.getStudentById(id);
    }

    @PutMapping("")
    public int updateStudent(@RequestBody Student student){
        return studentService.updateStudent(student);
    }

    @PostMapping("/importExcel")
    public int importExcel(@RequestPart MultipartFile file) throws IOException {
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        List<List<Object>> lists = reader.read();
        //[311089, jack, 202201, 男, 2003-01-02 00:00:00, 软件学院],
        int count=0;
        for (List<Object> obj:lists) {
            //创建出生日期对象
            Date birthday= DateUtil.parse(obj.get(4)+"","yyyy-MM-dd");
            Student student=new Student(obj.get(0)+"",obj.get(1)+"",obj.get(2)+"",obj.get(3).toString(),birthday,obj.get(5)+"",null);
            count+=studentService.addStudent(student);
        }

        return count;
    }
}

3、结果

6.14、多条件组合搜索

1、StudentMapper.java

package com.nfit.studentmis1805.mapper;

import com.nfit.studentmis1805.entity.Student;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface StudentMapper {
    /**获得所有学生*/
    public List<Student> getAllStudents(Student entity);
    /**删除学生通过编号*/
    public int deleteStudentById(String id);
    /**获取学生并分页*/
    public List<Student> selectPager();
    /**添加学生*/
    public int addStudent(Student entity);
    /**根据学生编号获取学生对象*/
    public Student getStudentById(String id);
    /**修改学生信息*/
    public int updateStudent(Student entity);
}

2、StudentMapper.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nfit.studentmis1805.mapper.StudentMapper">

    <resultMap id="studentMap" type="Student">
        <result property="id" column="id"></result>
        <result property="name" column="name"></result>
        <result property="classId" column="class_id"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <result property="college" column="college"></result>
        <association property="classes" javaType="Classes">
            <result property="id" column="class_id"></result>
            <result property="name" column="classname"></result>
        </association>
    </resultMap>

    <select id="getAllStudents" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id

        <where>
            <if test="id!=null and id!=''">
                and students.id like concat('%',concat(#{id},'%'))
            </if>

            <if test="name!=null and name!=''">
                and students.name like concat('%',concat(#{name},'%'))
            </if>

            <if test="sex!=null and sex!=''">
                and sex like concat('%',concat(#{sex},'%'))
            </if>

            <if test="birthday!=null">
                and birthday=#{birthday}
            </if>

            <if test="classes.name!=null and classes.name!=''">
                and classes.`name` like concat('%',concat(#{classes.name},'%'))
            </if>

            <if test="college!=null and college!=''">
                and college like concat('%',concat(#{college},'%'))
            </if>
        </where>

        order by students.id desc
    </select>

    <select id="selectPager" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id
        order by students.id
    </select>

    <delete id="deleteStudentById">
        delete from students where id=#{id}
    </delete>

    <insert id="addStudent">
        insert into students(
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college
        )
        values(#{id},#{name},#{classId},#{sex},#{birthday},#{college})
    </insert>

    <select id="getStudentById" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id and students.id=#{id}
    </select>

    <update id="updateStudent">
        update students set `name`=#{name},
                            class_id=#{classId},
                            sex=#{sex},
                            birthday=#{birthday},
                            college=#{college}
                where id=#{id}
    </update>

</mapper>

3、StudentService.java

package com.nfit.studentmis1805.service;

import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;

import java.util.List;

public interface StudentService {
    /**获得所有学生
     * @param entity*/
    public List<Student> getAllStudents(Student entity);
    /**删除学生通过编号*/
    public int deleteStudentById(String id);
    /**获取学生并分页*/
    public PageResult selectPager(int pageNum, int pageSize);
    /**添加学生*/
    public int addStudent(Student entity);
    /**根据学生编号获取学生对象*/
    public Student getStudentById(String id);
    /**修改学生信息*/
    public int updateStudent(Student entity);
}

4、StudentServiceImpl.java

package com.nfit.studentmis1805.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.mapper.StudentMapper;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    StudentMapper studentMapper;

    @Override
    public List<Student> getAllStudents(Student entity) {
        return studentMapper.getAllStudents(entity);
    }

    @Override
    public int deleteStudentById(String id) {
        return studentMapper.deleteStudentById(id);
    }

    @Override
    public PageResult selectPager(int pageNum, int pageSize) {
        //开始分页,指定第几页,每页多少记录
        PageHelper.startPage(pageNum,pageSize);
        //查询学生列表
        List<Student> students = studentMapper.getAllStudents(null);
        //返回分页对象
        return PageResult.getPageResult(new PageInfo<Student>(students));
    }

    @Override
    public int addStudent(Student entity) {
        return studentMapper.addStudent(entity);
    }

    @Override
    public Student getStudentById(String id) {
        return studentMapper.getStudentById(id);
    }

    @Override
    public int updateStudent(Student entity) {
        return studentMapper.updateStudent(entity);
    }
}

5、StudentController.java

package com.nfit.studentmis1805.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/api/student")
public class StudentController {
    @Autowired
    StudentService studentService;

    @PostMapping("/search")
    public List<Student> findAllStudents(@RequestBody Student entity){
        return studentService.getAllStudents(entity);
    }

    @GetMapping("/pager")
    public PageResult selectPager(@RequestParam(name = "pageNum",required = false,defaultValue = "1") int pageNum,
                                  @RequestParam(name = "pageSize",required = false,defaultValue = "2") int pageSize){
        return studentService.selectPager(pageNum,pageSize);
    }

    @DeleteMapping("/{id}")
    public int deleteStudentById(@PathVariable String id){
        return studentService.deleteStudentById(id);
    }


    @PostMapping("")
    public int addStudent(@RequestBody Student student){
        return studentService.addStudent(student);
    }

    @GetMapping("/{id}")
    public Student getStudentById(@PathVariable String id){
        return studentService.getStudentById(id);
    }

    @PutMapping("")
    public int updateStudent(@RequestBody Student student){
        return studentService.updateStudent(student);
    }

    @PostMapping("/importExcel")
    public int importExcel(@RequestPart MultipartFile file) throws IOException {
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        List<List<Object>> lists = reader.read();
        //[311089, jack, 202201, 男, 2003-01-02 00:00:00, 软件学院],
        int count=0;
        for (List<Object> obj:lists) {
            //创建出生日期对象
            Date birthday= DateUtil.parse(obj.get(4)+"","yyyy-MM-dd");
            Student student=new Student(obj.get(0)+"",obj.get(1)+"",obj.get(2)+"",obj.get(3).toString(),birthday,obj.get(5)+"",null);
            count+=studentService.addStudent(student);
        }

        return count;
    }
}

6、index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学生信息管理系统</title>
    <style>
      .studentList {
        width: 85%;
      }
      .studentList,
      .studentList td,
      .studentList th {
        border: 1px solid #666;
        border-collapse: collapse;
      }
      #toolbar {
        padding: 10px 0;
      }
      #toolbar input {
        width: 100px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h2>学生信息管理系统</h2>
      <div id="toolbar">
        编号:<input type="text" v-model="student.id" /> 姓名:<input
          type="text"
          v-model="student.name"
        />
        班级:<input type="text" v-model="student.classes.name" /> 性别:<input
          type="text"
          v-model="student.sex"
        />
        生日:<input type="date" v-model="student.birthday" /> 院系:<input
          type="text"
          v-model="student.college"
        />
        <button type="button" @click="search">搜索</button>
        <button type="button" @click="importExcel">导入</button>
      </div>
      <table class="studentList">
        <tr>
          <th>编号</th>
          <th>姓名</th>
          <th>班级</th>
          <th>性别</th>
          <th>生日</th>
          <th>院系</th>
          <th>操作</th>
        </tr>
        <tr v-for="(student,index) in students" :key="student.id">
          <td>{{student.id}}</td>
          <td>{{student.name}}</td>
          <td>{{student.classes.name}}</td>
          <td>{{student.sex}}</td>
          <td>{{student.birthday}}</td>
          <td>{{student.college}}</td>
          <td>
            <a href="#" @click="deleteStudent(student,index)">删除</a>
            <a :href="'edit.html?id='+student.id">编辑</a>
          </td>
        </tr>
      </table>
      <p>
        <a href="add.html">新增学生</a>
      </p>
    </div>
    <script src="js/axios.min.js"></script>
    <script src="js/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          students: [],
          student: {
            classes: {},
            fileInput: null,
          },
        },
        created() {
          this.search();
        },
        methods: {
          importExcel() {
            if (!this.fileInput) {
              //创建一个input在页面上
              this.fileInput = document.createElement("input");
              this.fileInput.setAttribute("type", "file");
              this.fileInput.setAttribute("hidden", "true");
              this.fileInput.setAttribute("name", "file");
              document.body.appendChild(this.fileInput);

              //触发文件上传
              this.fileInput.addEventListener(
                "change",
                function (e) {
                  //获取当前要上传的文件
                  const file = e.target.files[0];
                  //创建要上传的表单数据
                  const formData = new FormData();
                  formData.append("file", file);

                  axios
                    .post(
                      "http://127.0.0.1:8080/api/student/importExcel",
                      formData,
                      {
                        "Content-Type": "multipart/form-data",
                      }
                    )
                    .then((res) => {
                      alert("上传数据成功,共计添加" + res.data + "行数据!");
                      vm.search();
                    })
                    .catch((err) => alert(err));
                },
                true
              );
            }
            this.fileInput.value;
            this.fileInput.click();
          },
          search() {
            axios
              .post("http://127.0.0.1:8080/api/student/search", this.student)
              .then((response) => {
                this.students = response.data;
              })
              .catch((err) => {
                alert(err);
              });
          },
          deleteStudent(student, index) {
            if (confirm("您确定要删除吗?")) {
              console.log(student, index);
              axios
                .delete(`http://127.0.0.1:8080/api/student/${student.id}`)
                .then((response) => {
                  if (response.data === 1) {
                    alert("删除成功!");
                    vm.students.splice(index, 1);
                  }
                })
                .catch((err) => {
                  alert(err);
                });
            }
          },
        },
      });
    </script>
  </body>
</html>

7、结果

6.14、导出

1、导出测试

@Test
    public void writeExcel(){
        //创建一个Excel写入器,指定写入位置
        ExcelWriter writer = ExcelUtil.getWriter("C:\\Users\\Administrator\\Desktop\\"+UUID.randomUUID()+".xlsx");
        //获取所有学生信息
        Student student = new Student();
        student.setClasses(new Classes());
        List<Student> allStudents = studentService.getAllStudents(student);
        //添加中文标题
        //id    name    classId    sex    birthday    college    classes
        writer.addHeaderAlias("id","编号");
        writer.addHeaderAlias("name","姓名");
        writer.addHeaderAlias("classId","班级号");
        writer.addHeaderAlias("sex","性别");
        writer.addHeaderAlias("birthday","生日");
        writer.addHeaderAlias("college","院系");
        //默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);
        // 合并单元格后的标题行,使用默认标题样式
        writer.merge(5, "学生信息列表");
        //将集合中的所有数据写入Excel中,并使用名称作为Excel列的标题
        writer.write(allStudents,true);
        //关闭
        writer.close();
    }

2、导出学生-前端

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学生信息管理系统</title>
    <style>
      .studentList {
        width: 85%;
      }
      .studentList,
      .studentList td,
      .studentList th {
        border: 1px solid #666;
        border-collapse: collapse;
      }
      #toolbar {
        padding: 10px 0;
      }
      #toolbar input {
        width: 100px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h2>学生信息管理系统</h2>
      <div id="toolbar">
        编号:<input type="text" v-model="student.id" /> 姓名:<input
          type="text"
          v-model="student.name"
        />
        班级:<input type="text" v-model="student.classes.name" /> 性别:<input
          type="text"
          v-model="student.sex"
        />
        生日:<input type="date" v-model="student.birthday" /> 院系:<input
          type="text"
          v-model="student.college"
        />
        <button type="button" @click="search">搜索</button>
        <button type="button" @click="importExcel">导入</button>
        <button type="button" @click="exportExcel">导出</button>
      </div>
      <table class="studentList">
        <tr>
          <th>编号</th>
          <th>姓名</th>
          <th>班级</th>
          <th>性别</th>
          <th>生日</th>
          <th>院系</th>
          <th>操作</th>
        </tr>
        <tr v-for="(student,index) in students" :key="student.id">
          <td>{{student.id}}</td>
          <td>{{student.name}}</td>
          <td>{{student.classes.name}}</td>
          <td>{{student.sex}}</td>
          <td>{{student.birthday}}</td>
          <td>{{student.college}}</td>
          <td>
            <a href="#" @click="deleteStudent(student,index)">删除</a>
            <a :href="'edit.html?id='+student.id">编辑</a>
          </td>
        </tr>
      </table>
      <p>
        <a href="add.html">新增学生</a>
      </p>
    </div>
    <script src="js/axios.min.js"></script>
    <script src="js/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          students: [],
          student: {
            id: "",
            name: "",
            classId: "",
            sex: "",
            birthday: "",
            college: "",
            classes: { id: "", name: "" },
            fileInput: null,
          },
        },
        created() {
          this.search();
        },
        methods: {
          exportExcel() {
            const stu = this.student;
            let url = "http://127.0.0.1:8080/api/student/exportExcel";
            //id    name    classId    sex    birthday    college    classes
            //http://127.0.0.1:8080/api/student/exportExcel?id=undefined&name=undefined&classId=undefined&sex=undefined&birthday=undefined&college=undefined&classname=undefined
            //http://127.0.0.1:8080/api/student/exportExcel?id=&name=&classId=&sex=&birthday=&college=&classname=
            url += `?id=${stu.id}&name=${stu.name}&classId=${stu.classId}&sex=${stu.sex}&birthday=${stu.birthday}&college=${stu.college}&classname=${stu.classes.name}`;
            location.href = url;
          },
          importExcel() {
            if (!this.fileInput) {
              //创建一个input在页面上
              this.fileInput = document.createElement("input");
              this.fileInput.setAttribute("type", "file");
              this.fileInput.setAttribute("hidden", "true");
              this.fileInput.setAttribute("name", "file");
              document.body.appendChild(this.fileInput);

              //触发文件上传
              this.fileInput.addEventListener(
                "change",
                function (e) {
                  //获取当前要上传的文件
                  const file = e.target.files[0];
                  //创建要上传的表单数据
                  const formData = new FormData();
                  formData.append("file", file);

                  axios
                    .post(
                      "http://127.0.0.1:8080/api/student/importExcel",
                      formData,
                      {
                        "Content-Type": "multipart/form-data",
                      }
                    )
                    .then((res) => {
                      alert("上传数据成功,共计添加" + res.data + "行数据!");
                      vm.search();
                    })
                    .catch((err) => alert(err));
                },
                true
              );
            }
            this.fileInput.value;
            this.fileInput.click();
          },
          search() {
            axios
              .post("http://127.0.0.1:8080/api/student/search", this.student)
              .then((response) => {
                this.students = response.data;
              })
              .catch((err) => {
                alert(err);
              });
          },
          deleteStudent(student, index) {
            if (confirm("您确定要删除吗?")) {
              console.log(student, index);
              axios
                .delete(`http://127.0.0.1:8080/api/student/${student.id}`)
                .then((response) => {
                  if (response.data === 1) {
                    alert("删除成功!");
                    vm.students.splice(index, 1);
                  }
                })
                .catch((err) => {
                  alert(err);
                });
            }
          },
        },
      });
    </script>
  </body>
</html>

3、导出学生-后台

package com.nfit.studentmis1805.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Classes;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/student")
public class StudentController {
    @Autowired
    StudentService studentService;

    @PostMapping("/search")
    public List<Student> findAllStudents(@RequestBody Student entity){
        return studentService.getAllStudents(entity);
    }

    @GetMapping("/pager")
    public PageResult selectPager(@RequestParam(name = "pageNum",required = false,defaultValue = "1") int pageNum,
                                  @RequestParam(name = "pageSize",required = false,defaultValue = "2") int pageSize){
        return studentService.selectPager(pageNum,pageSize);
    }

    @DeleteMapping("/{id}")
    public int deleteStudentById(@PathVariable String id){
        return studentService.deleteStudentById(id);
    }


    @PostMapping("")
    public int addStudent(@RequestBody Student student){
        return studentService.addStudent(student);
    }

    @GetMapping("/{id}")
    public Student getStudentById(@PathVariable String id){
        return studentService.getStudentById(id);
    }

    @PutMapping("")
    public int updateStudent(@RequestBody Student student){
        return studentService.updateStudent(student);
    }

    @PostMapping("/importExcel")
    public int importExcel(@RequestPart MultipartFile file) throws IOException {
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        List<List<Object>> lists = reader.read();
        //[311089, jack, 202201, 男, 2003-01-02 00:00:00, 软件学院],
        int count=0;
        for (List<Object> obj:lists) {
            //创建出生日期对象
            Date birthday= DateUtil.parse(obj.get(4)+"","yyyy-MM-dd");
            Student student=new Student(obj.get(0)+"",obj.get(1)+"",obj.get(2)+"",obj.get(3).toString(),birthday,obj.get(5)+"",null);
            count+=studentService.addStudent(student);
        }

        return count;
    }

    @GetMapping("/exportExcel")
    //id    name    classId    sex    birthday    college    classes
    public void exportExcel(@RequestParam(required = false,defaultValue = "")String id,
                            @RequestParam(required = false,defaultValue = "")String name,
                            @RequestParam(required = false,defaultValue = "")String classId,
                            @RequestParam(required = false,defaultValue = "")String sex,
                            @RequestParam(required = false,defaultValue = "")String birthday,
                            @RequestParam(required = false,defaultValue = "")String college,
                            @RequestParam(required = false,defaultValue = "")String classname,
                            HttpServletRequest request, HttpServletResponse response) throws IOException {
        //Excel写入器
        ExcelWriter writer = ExcelUtil.getWriter();

        //获取所有学生信息
        Student student = new Student();
        student.setId(id);
        student.setSex(sex);
        student.setCollege(college);
        student.setName(name);
        student.setClassId(classId);
        if(!birthday.equals("")){
            student.setBirthday(DateUtil.parse(birthday,"yyyy-MM-dd"));
        }

        student.setClasses(new Classes());
        student.getClasses().setName(classname);

        List<Student> allStudents = studentService.getAllStudents(student);

        //写入
        writer.write(allStudents,true);

        //当前响应对象输出流
        ServletOutputStream outputStream = response.getOutputStream();

        //设置Http响应的内容为Excel类型
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        //添加附件,指定文件名称
        response.setHeader("Content-Disposition","attachment;filename="+ UUID.randomUUID() +".xls");

        //将Excel输出到响应流中
        writer.flush(outputStream,true);

        //关闭
        writer.close();
        IoUtil.close(outputStream);

    }
}

七、Element UI plus admin

基于Vue3 + Element Plus 的后台管理系统解决方案

7.1、运行效果

 

 7.2、介绍

基于Vue3 + Element Plus 的后台管理系统解决方案

该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。

github:https://github.com/99510309/vue-manage-system

  •  Element Plus
  •  vite 3
  •  pinia
  •  typescript
  •  登录/注销
  •  Dashboard
  •  表格
  •  Tab 选项卡
  •  表单
  •  图表 📊
  •  富文本/markdown编辑器
  •  图片拖拽/裁剪上传
  •  权限管理
  •  三级菜单
  •  自定义图标

7.3、安装方法

因为使用vite3,node版本需要 14.18+

git clone https://github.com/lin-xin/vue-manage-system.git      // 把模板下载到本地
cd vue-manage-system    // 进入模板目录
npm install         // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn

// 运行
npm run dev

// 执行构建命令,生成的dist文件夹放在服务器下即可访问
npm run build

7.4、完成学生管理

1、src\utils\request.ts

import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';

const $http:AxiosInstance = axios.create({
    timeout: 5000,
    baseURL:'http://127.0.0.1:8080/api/'
});

$http.interceptors.request.use(
    (config: AxiosRequestConfig) => {
        return config;
    },
    (error: AxiosError) => {
        console.log(error);
        return Promise.reject();
    }
);

$http.interceptors.response.use(
    (response: AxiosResponse) => {
        if (response.status === 200) {
            return response;
        } else {
            Promise.reject();
        }
    },
    (error: AxiosError) => {
        console.log(error);
        return Promise.reject();
    }
);

export default $http;

2、src/api/student/index.ts

import $http from '../../utils/request';

//获取学生并分页,搜索
export const getStudentPager = (query:any,stu:any) => {
    let url=`student/pager?pageNum=${query.pageIndex}&pageSize=${query.pageSize}`;
    url += `&id=${stu.id}&name=${stu.name}&classId=${stu.classId}&sex=${stu.sex}&birthday=${stu.birthday}&college=${stu.college}&classname=${stu.classes.name}`;
    return $http({
        url:url,
        method: 'get'
    });
};

//新增加学生
export const addStudent=(student:any)=>{
    return $http.post("student",student);
}

//删除学生
export const deleteStudent=(id:string)=>{
    return $http.delete(`student/${id}`);
}

//更新学生
export const updateStudent=(student:any)=>{
    return $http.put(`student`,student);
}

//导入Excel文件
export const importExcel=(formData:any)=>{
    return $http.post("student/importExcel", formData,{
      headers:{
        "Content-Type":"multipart/form-data"
      }
    });
}

//多删除
export const deleteStudents=(ids:string[])=>{
    return $http.post(`student/deletes`,ids);
}

classes/index.ts

import $http from '../../utils/request';

export const getAllClasses=()=>{
    return $http.get('/classes');
}

3、src/views/student.vue

<template>
  <div>
    <div class="container">
      <div class="handle-box" style="display: flex">
        <el-input
          v-model="student.name"
          placeholder="姓名"
          maxLength="20"
          class="handle-input mr10"
          style="width: 70px"
        ></el-input>

        <el-input
          v-model="student.id"
          placeholder="编号"
          maxLength="8"
          class="handle-input mr10"
        ></el-input>

        <el-input
          v-model="student.sex"
          placeholder="性别"
          maxLength="1"
          class="handle-input mr10"
        ></el-input>

        <el-date-picker
          v-model="student.birthday"
          type="date"
          placeholder="出生日期"
          value-format="YYYY-MM-DD"
          style="margin-right: 10px"
        />

        <el-input
          v-model="student.college"
          placeholder="院系"
          class="handle-input mr10"
        ></el-input>

        <el-input
          v-model="student.classes.name"
          placeholder="班级名"
          class="handle-input mr10"
        ></el-input>

        <el-button type="primary" :icon="Search" @click="handleSearch"
          >搜索</el-button
        >
        <el-button type="primary" :icon="Plus" @click="saveStudent"
          >新增</el-button
        >
        <el-button type="primary" :icon="UploadFilled" @click="importData">
          导入
        </el-button>
        <el-button type="primary" :icon="Download" @click="exportData">
          导出
        </el-button>
        <el-button type="danger" :icon="Delete" @click="deleteData">
          删除
        </el-button>
      </div>
      <el-table
        :data="tableData"
        border
        class="table"
        ref="multipleTable"
        header-cell-class-name="table-header"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="55" />

        <el-table-column
          prop="id"
          label="学生编号"
          width="110"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="name"
          label="姓名"
          width="70"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="classId"
          label="班级编号"
          width="100"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="classes.name"
          label="班级名称"
          width="100"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="sex"
          label="性别"
          width="70"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="birthday"
          label="生日"
          width="140"
          align="center"
        ></el-table-column>

        <el-table-column
          prop="college"
          label="院系"
          width="140"
          align="center"
        ></el-table-column>

        <el-table-column label="操作" align="center">
          <template #default="scope">
            <el-button
              text
              :icon="Edit"
              @click="handleEdit(scope.$index, scope.row)"
              v-permiss="15"
            >
              编辑
            </el-button>
            <el-button
              text
              :icon="Delete"
              class="red"
              @click="handleDelete(scope.row.id, scope.$index)"
              v-permiss="16"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination">
        <el-pagination
          background
          layout="total, prev, pager, next"
          :current-page="query.pageIndex"
          :page-size="query.pageSize"
          :total="pageTotal"
          @current-change="handlePageChange"
        ></el-pagination>
      </div>
    </div>

    <!-- 编辑弹出框 -->
    <el-dialog :title="title" v-model="editVisible" width="30%">
      <el-form label-width="70px">
        <el-form-item label="编号">
          <el-input v-model="formdata.id"></el-input>
        </el-form-item>
        <el-form-item label="姓名">
          <el-input v-model="formdata.name"></el-input>
        </el-form-item>
        <el-form-item label="班级">
          <el-select
            v-model="formdata.classId"
            class="m-2"
            placeholder="请选择班级"
          >
            <el-option
              v-for="item in classes"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="性别">
          <el-radio-group v-model="formdata.sex">
            <el-radio label="男"></el-radio>
            <el-radio label="女"></el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item label="生日">
          <el-date-picker
            v-model="formdata.birthday"
            type="date"
            placeholder="出生日期"
            value-format="YYYY-MM-DD"
          />
        </el-form-item>

        <el-form-item label="院系">
          <el-input v-model="formdata.college"></el-input>
        </el-form-item>
      </el-form>

      <template #footer>
        <span class="dialog-footer">
          <el-button @click="editVisible = false">取 消</el-button>
          <el-button type="primary" @click="saveEdit">确 定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts" name="basetable">
import { ref, reactive } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  Delete,
  Edit,
  Search,
  Plus,
  UploadFilled,
  Download,
} from "@element-plus/icons-vue";
import {
  getStudentPager,
  addStudent,
  deleteStudent,
  updateStudent,
  importExcel,
  deleteStudents,
} from "../api/student/index";
import { getAllClasses } from "../api/classes/index";

const query = reactive({
  pageIndex: 1,
  pageSize: 5,
});
const tableData = ref([]);
const pageTotal = ref(0);

let student = reactive({
  id: "",
  name: "",
  classId: "",
  sex: "",
  birthday: "",
  college: "",
  classes: { id: "", name: "" },
});

// 获取表格数据
const getData = () => {
  getStudentPager(query, student).then((res) => {
    tableData.value = res.data.list;
    pageTotal.value = res.data.pageTotal || 50;
    query.pageIndex = res.data.pageNum;
    query.pageSize = res.data.pageSize;
  });
};

getData();

const multipleSelection = ref([]);
//多删除
const handleSelectionChange = (val: any) => {
  multipleSelection.value = val;
  console.log(val);
};
const deleteData = () => {
  if (multipleSelection.value.length <= 0) {
    ElMessage.error("请至少选择一项");
    return;
  } else {
    // 二次确认删除
    ElMessageBox.confirm("确定要删除吗?", "提示", {
      type: "warning",
    }).then(() => {
      const ids: string[] = [];
      multipleSelection.value.forEach((item: any) => {
        ids.push(item.id);
      });
      deleteStudents(ids)
        .then((res) => {
          if (res.data >= 1) {
            ElMessage.success(`成功删除${res.data}行数据!`);
            getData();
          } else {
            ElMessage.error("删除失败!");
          }
        })
        .catch((error) => ElMessage.error(error));
    });
  }
};
let fileInput: any = null;
//导入
const importData = () => {
  if (!fileInput) {
    //创建一个input在页面上
    fileInput = document.createElement("input");
    fileInput.setAttribute("type", "file");
    fileInput.setAttribute("hidden", "true");
    fileInput.setAttribute("name", "file");
    document.body.appendChild(fileInput);

    //触发文件上传
    fileInput.addEventListener(
      "change",
      function (e: any) {
        //获取当前要上传的文件
        const file = e.target.files[0];
        //创建要上传的表单数据
        const formData = new FormData();
        formData.append("file", file);

        importExcel(formData)
          .then((res) => {
            ElMessage.success("上传数据成功,共计添加" + res.data + "行数据!");
            getData();
          })
          .catch((err) => ElMessage.error(err));
      },
      true
    );
  }
  fileInput.value;
  fileInput.click();
};

//导出
const exportData = () => {
  const stu = student;
  let url = "http://127.0.0.1:8080/api/student/exportExcel";
  url += `?id=${stu.id}&name=${stu.name}&classId=${stu.classId}&sex=${stu.sex}&birthday=${stu.birthday}&college=${stu.college}&classname=${stu.classes.name}`;
  location.href = url;
};

// 查询操作
const handleSearch = () => {
  getData();
};
// 分页导航
const handlePageChange = (val: number) => {
  query.pageIndex = val;
  getData();
};

// 删除操作
const handleDelete = (id: string, index: number) => {
  // 二次确认删除
  ElMessageBox.confirm("确定要删除吗?", "提示", {
    type: "warning",
  })
    .then(() => {
      deleteStudent(id)
        .then((res) => {
          if (res.data === 1) {
            ElMessage.success("删除成功!");
            //移除表格中的当前行,不用重新绑定绑定数据
            tableData.value.splice(index, 1);
          } else {
            ElMessage.error("删除失败!");
          }
        })
        .catch((error) => ElMessage.error(error));
    })
    .catch(() => {});
};

// 表格编辑时弹窗和保存
const editVisible = ref(false);
const title = ref("");
let formdata: any = reactive({});
let classes: any = ref([]);

getAllClasses()
  .then((res) => {
    classes.value = res.data;
  })
  .catch((err) => alert(err));

//保存学生,完成新增与编辑功能
function saveStudent() {
  title.value = "新增学生";
  editVisible.value = true;
}

const handleEdit = (index: number, row: any) => {
  title.value = "编辑学生";
  Object.assign(formdata, row);
  editVisible.value = true;
};

const saveEdit = () => {
  if (title.value === "新增学生") {
    addStudent(formdata)
      .then((res) => {
        if (res.data === 1) {
          ElMessage.success("新增成功!");
          getData();
        } else {
          ElMessage.error("新增失败!");
        }
      })
      .catch((error) => ElMessage.error(error));
  } else {
    updateStudent(formdata)
      .then((res) => {
        if (res.data === 1) {
          ElMessage.success("更新成功!");
          editVisible.value = false;
          getData();
        } else {
          ElMessage.error("更新失败!");
        }
      })
      .catch((error) => ElMessage.error(error));
  }
};
</script>

<style scoped>
.handle-box {
  margin-bottom: 20px;
}

.handle-select {
  width: 120px;
}

.handle-input {
  width: 100px;
}
.table {
  width: 100%;
  font-size: 14px;
}
.red {
  color: #f56c6c;
}
.mr10 {
  margin-right: 10px;
}
.table-td-thumb {
  display: block;
  margin: auto;
  width: 40px;
  height: 40px;
}
</style>

4、路由

sidebar.vue

<template>
  <div class="sidebar">
    <el-menu
      class="sidebar-el-menu"
      :default-active="onRoutes"
      :collapse="sidebar.collapse"
      background-color="#324157"
      text-color="#bfcbd9"
      active-text-color="#20a0ff"
      unique-opened
      router
    >
      <template v-for="item in items">
        <template v-if="item.subs">
          <el-sub-menu
            :index="item.index"
            :key="item.index"
            v-permiss="item.permiss"
          >
            <template #title>
              <el-icon>
                <component :is="item.icon"></component>
              </el-icon>
              <span>{{ item.title }}</span>
            </template>
            <template v-for="subItem in item.subs">
              <el-sub-menu
                v-if="subItem.subs"
                :index="subItem.index"
                :key="subItem.index"
                v-permiss="item.permiss"
              >
                <template #title>{{ subItem.title }}</template>
                <el-menu-item
                  v-for="(threeItem, i) in subItem.subs"
                  :key="i"
                  :index="threeItem.index"
                >
                  {{ threeItem.title }}
                </el-menu-item>
              </el-sub-menu>
              <el-menu-item
                v-else
                :index="subItem.index"
                v-permiss="item.permiss"
              >
                {{ subItem.title }}
              </el-menu-item>
            </template>
          </el-sub-menu>
        </template>
        <template v-else>
          <el-menu-item
            :index="item.index"
            :key="item.index"
            v-permiss="item.permiss"
          >
            <el-icon>
              <component :is="item.icon"></component>
            </el-icon>
            <template #title>{{ item.title }}</template>
          </el-menu-item>
        </template>
      </template>
    </el-menu>
  </div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { useSidebarStore } from "../store/sidebar";
import { useRoute } from "vue-router";

const items = [
  {
    icon: "Odometer",
    index: "/dashboard",
    title: "系统首页",
    permiss: "1",
  },
  {
    icon: "Calendar",
    index: "1",
    title: "表格相关",
    permiss: "2",
    subs: [
      {
        index: "/table",
        title: "常用表格",
        permiss: "2",
      },
      {
        index: "/import",
        title: "导入Excel",
        permiss: "2",
      },
      {
        index: "/export",
        title: "导出Excel",
        permiss: "2",
      },
    ],
  },
  {
    icon: "Calendar",
    index: "10",
    title: "学生管理",
    permiss: "2",
    subs: [
      {
        index: "/student",
        title: "学生管理",
        permiss: "2",
      },
      {
        index: "/import",
        title: "导入Excel",
        permiss: "2",
      },
      {
        index: "/export",
        title: "导出Excel",
        permiss: "2",
      },
    ],
  },
  {
    icon: "DocumentCopy",
    index: "/tabs",
    title: "tab选项卡",
    permiss: "3",
  },
  {
    icon: "Edit",
    index: "3",
    title: "表单相关",
    permiss: "4",
    subs: [
      {
        index: "/form",
        title: "基本表单",
        permiss: "5",
      },
      {
        index: "/upload",
        title: "文件上传",
        permiss: "6",
      },
      {
        index: "4",
        title: "三级菜单",
        permiss: "7",
        subs: [
          {
            index: "/editor",
            title: "富文本编辑器",
            permiss: "8",
          },
          {
            index: "/markdown",
            title: "markdown编辑器",
            permiss: "9",
          },
        ],
      },
    ],
  },
  {
    icon: "Setting",
    index: "/icon",
    title: "自定义图标",
    permiss: "10",
  },
  {
    icon: "PieChart",
    index: "/charts",
    title: "schart图表",
    permiss: "11",
  },
  {
    icon: "Warning",
    index: "/permission",
    title: "权限管理",
    permiss: "13",
  },
  {
    icon: "CoffeeCup",
    index: "/donate",
    title: "支持作者",
    permiss: "14",
  },
];

const route = useRoute();
const onRoutes = computed(() => {
  return route.path;
});

const sidebar = useSidebarStore();
</script>

<style scoped>
.sidebar {
  display: block;
  position: absolute;
  left: 0;
  top: 70px;
  bottom: 0;
  overflow-y: scroll;
}
.sidebar::-webkit-scrollbar {
  width: 0;
}
.sidebar-el-menu:not(.el-menu--collapse) {
  width: 250px;
}
.sidebar > ul {
  height: 100%;
}
</style>

router/index.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { usePermissStore } from '../store/permiss';
import Home from '../views/home.vue';

const routes: RouteRecordRaw[] = [
    {
        path: '/',
        redirect: '/dashboard',
    },
    {
        path: '/',
        name: 'Home',
        component: Home,
        children: [
            {
                path: '/dashboard',
                name: 'dashboard',
                meta: {
                    title: '系统首页',
                    permiss: '1',
                },
                component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
            },
            {
                path: '/table',
                name: 'basetable',
                meta: {
                    title: '表格',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "table" */ '../views/table.vue'),
            },
            {
                path: '/student',
                name: 'student',
                meta: {
                    title: '学生管理',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "student" */ '../views/student.vue'),
            },
            {
                path: '/charts',
                name: 'basecharts',
                meta: {
                    title: '图表',
                    permiss: '11',
                },
                component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
            },
            {
                path: '/form',
                name: 'baseform',
                meta: {
                    title: '表单',
                    permiss: '5',
                },
                component: () => import(/* webpackChunkName: "form" */ '../views/form.vue'),
            },
            {
                path: '/tabs',
                name: 'tabs',
                meta: {
                    title: 'tab标签',
                    permiss: '3',
                },
                component: () => import(/* webpackChunkName: "tabs" */ '../views/tabs.vue'),
            },
            {
                path: '/donate',
                name: 'donate',
                meta: {
                    title: '鼓励作者',
                    permiss: '14',
                },
                component: () => import(/* webpackChunkName: "donate" */ '../views/donate.vue'),
            },
            {
                path: '/permission',
                name: 'permission',
                meta: {
                    title: '权限管理',
                    permiss: '13',
                },
                component: () => import(/* webpackChunkName: "permission" */ '../views/permission.vue'),
            },
            {
                path: '/upload',
                name: 'upload',
                meta: {
                    title: '上传插件',
                    permiss: '6',
                },
                component: () => import(/* webpackChunkName: "upload" */ '../views/upload.vue'),
            },
            {
                path: '/icon',
                name: 'icon',
                meta: {
                    title: '自定义图标',
                    permiss: '10',
                },
                component: () => import(/* webpackChunkName: "icon" */ '../views/icon.vue'),
            },
            {
                path: '/user',
                name: 'user',
                meta: {
                    title: '个人中心',
                },
                component: () => import(/* webpackChunkName: "user" */ '../views/user.vue'),
            },
            {
                path: '/editor',
                name: 'editor',
                meta: {
                    title: '富文本编辑器',
                    permiss: '8',
                },
                component: () => import(/* webpackChunkName: "editor" */ '../views/editor.vue'),
            },
            {
                path: '/markdown',
                name: 'markdown',
                meta: {
                    title: 'markdown编辑器',
                    permiss: '9',
                },
                component: () => import(/* webpackChunkName: "markdown" */ '../views/markdown.vue'),
            },
            {
                path: '/export',
                name: 'export',
                meta: {
                    title: '导出Excel',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "export" */ '../views/export.vue'),
            },
            {
                path: '/import',
                name: 'import',
                meta: {
                    title: '导入Excel',
                    permiss: '2',
                },
                component: () => import(/* webpackChunkName: "import" */ '../views/import.vue'),
            },
        ],
    },
    {
        path: '/login',
        name: 'Login',
        meta: {
            title: '登录',
        },
        component: () => import(/* webpackChunkName: "login" */ '../views/login.vue'),
    },
    {
        path: '/403',
        name: '403',
        meta: {
            title: '没有权限',
        },
        component: () => import(/* webpackChunkName: "403" */ '../views/403.vue'),
    },
];

const router = createRouter({
    history: createWebHashHistory(),
    routes,
});

router.beforeEach((to, from, next) => {
    document.title = `${to.meta.title} | vue-manage-system`;
    const role = localStorage.getItem('ms_username');
    const permiss = usePermissStore();
    if (!role && to.path !== '/login') {
        next('/login');
    } else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
        // 如果没有权限,则进入403
        next('/403');
    } else {
        next();
    }
});

export default router;

5、控制器 StudentController.java

package com.nfit.studentmis1805.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Classes;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/student")
public class StudentController {
    @Autowired
    StudentService studentService;

    @PostMapping("/search")
    public List<Student> findAllStudents(@RequestBody Student entity){
        return studentService.getAllStudents(entity);
    }

    @GetMapping("/pager")
    public PageResult selectPager(@RequestParam(name = "pageNum",required = false,defaultValue = "1") int pageNum,
                                  @RequestParam(name = "pageSize",required = false,defaultValue = "2") int pageSize,
                                  @RequestParam(required = false,defaultValue = "")String id,
                                  @RequestParam(required = false,defaultValue = "")String name,
                                  @RequestParam(required = false,defaultValue = "")String classId,
                                  @RequestParam(required = false,defaultValue = "")String sex,
                                  @RequestParam(required = false,defaultValue = "")String birthday,
                                  @RequestParam(required = false,defaultValue = "")String college,
                                  @RequestParam(required = false,defaultValue = "")String classname
                                  ){
        //获取所有学生信息
        Student student = new Student();
        student.setId(id);
        student.setSex(sex);
        student.setCollege(college);
        student.setName(name);
        student.setClassId(classId);
        if(!birthday.equals("")&&!birthday.equals("null")){
            student.setBirthday(DateUtil.parse(birthday,"yyyy-MM-dd"));
        }

        student.setClasses(new Classes());
        student.getClasses().setName(classname);

        return studentService.selectPager(pageNum,pageSize,student);
    }

    @DeleteMapping("/{id}")
    public int deleteStudentById(@PathVariable String id){
        return studentService.deleteStudentById(id);
    }

    @PostMapping("/deletes")
    public int deleteStudents(@RequestBody String[] ids){
        return studentService.deleteStudents(ids);
    }

    @PostMapping("")
    public int addStudent(@RequestBody Student student){
        return studentService.addStudent(student);
    }

    @GetMapping("/{id}")
    public Student getStudentById(@PathVariable String id){
        return studentService.getStudentById(id);
    }

    @PutMapping("")
    public int updateStudent(@RequestBody Student student){
        return studentService.updateStudent(student);
    }

    @PostMapping("/importExcel")
    public int importExcel(@RequestPart MultipartFile file) throws IOException {
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        List<List<Object>> lists = reader.read();
        //[311089, jack, 202201, 男, 2003-01-02 00:00:00, 软件学院],
        int count=0;
        for (List<Object> obj:lists) {
            //创建出生日期对象
            Date birthday= DateUtil.parse(obj.get(4)+"","yyyy-MM-dd");
            Student student=new Student(obj.get(0)+"",obj.get(1)+"",obj.get(2)+"",obj.get(3).toString(),birthday,obj.get(5)+"",null);
            count+=studentService.addStudent(student);
        }

        return count;
    }

    @GetMapping("/exportExcel")
    //id    name    classId    sex    birthday    college    classes
    public void exportExcel(@RequestParam(required = false,defaultValue = "")String id,
                            @RequestParam(required = false,defaultValue = "")String name,
                            @RequestParam(required = false,defaultValue = "")String classId,
                            @RequestParam(required = false,defaultValue = "")String sex,
                            @RequestParam(required = false,defaultValue = "")String birthday,
                            @RequestParam(required = false,defaultValue = "")String college,
                            @RequestParam(required = false,defaultValue = "")String classname,
                            HttpServletRequest request, HttpServletResponse response) throws IOException {
        //Excel写入器
        ExcelWriter writer = ExcelUtil.getWriter();

        //获取所有学生信息
        Student student = new Student();
        student.setId(id);
        student.setSex(sex);
        student.setCollege(college);
        student.setName(name);
        student.setClassId(classId);
        if(!birthday.equals("")){
            student.setBirthday(DateUtil.parse(birthday,"yyyy-MM-dd"));
        }

        student.setClasses(new Classes());
        student.getClasses().setName(classname);

        List<Student> allStudents = studentService.getAllStudents(student);

        //写入
        writer.write(allStudents,true);

        //当前响应对象输出流
        ServletOutputStream outputStream = response.getOutputStream();

        //设置Http响应的内容为Excel类型
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        //添加附件,指定文件名称
        response.setHeader("Content-Disposition","attachment;filename="+ UUID.randomUUID() +".xls");

        //将Excel输出到响应流中
        writer.flush(outputStream,true);

        //关闭
        writer.close();
        IoUtil.close(outputStream);

    }
}

6、StudentServiceImpl.java

package com.nfit.studentmis1805.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;
import com.nfit.studentmis1805.mapper.StudentMapper;
import com.nfit.studentmis1805.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    StudentMapper studentMapper;

    @Override
    public List<Student> getAllStudents(Student entity) {
        return studentMapper.getAllStudents(entity);
    }

    @Override
    public int deleteStudentById(String id) {
        return studentMapper.deleteStudentById(id);
    }

    @Override
    public PageResult selectPager(int pageNum, int pageSize,Student student) {
        //开始分页,指定第几页,每页多少记录
        PageHelper.startPage(pageNum,pageSize);
        //查询学生列表
        List<Student> students = studentMapper.getAllStudents(student);
        //返回分页对象
        return PageResult.getPageResult(new PageInfo<Student>(students));
    }

    @Override
    public int addStudent(Student entity) {
        return studentMapper.addStudent(entity);
    }

    @Override
    public Student getStudentById(String id) {
        return studentMapper.getStudentById(id);
    }

    @Override
    public int updateStudent(Student entity) {
        return studentMapper.updateStudent(entity);
    }

    @Override
    public int deleteStudents(String[] ids) {
        return studentMapper.deleteStudents(ids);
    }
}

7、StudentMapper.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nfit.studentmis1805.mapper.StudentMapper">

    <resultMap id="studentMap" type="Student">
        <result property="id" column="id"></result>
        <result property="name" column="name"></result>
        <result property="classId" column="class_id"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <result property="college" column="college"></result>
        <association property="classes" javaType="Classes">
            <result property="id" column="class_id"></result>
            <result property="name" column="classname"></result>
        </association>
    </resultMap>

    <select id="getAllStudents" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id

        <where>
            <if test="id!=null and id!=''">
                and students.id like concat('%',concat(#{id},'%'))
            </if>

            <if test="name!=null and name!=''">
                and students.name like concat('%',concat(#{name},'%'))
            </if>

            <if test="sex!=null and sex!=''">
                and sex like concat('%',concat(#{sex},'%'))
            </if>

            <if test="birthday!=null">
                and birthday=#{birthday,jdbcType=DATE}
            </if>

            <if test="classes.name!=null and classes.name!=''">
                and classes.`name` like concat('%',concat(#{classes.name},'%'))
            </if>

            <if test="college!=null and college!=''">
                and college like concat('%',concat(#{college},'%'))
            </if>
        </where>

        order by students.id desc
    </select>

    <select id="selectPager" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id
        order by students.id
    </select>

    <delete id="deleteStudentById">
        delete from students where id=#{id}
    </delete>

    <insert id="addStudent">
        insert into students(
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college
        )
        values(#{id},#{name},#{classId},#{sex},#{birthday},#{college})
    </insert>

    <select id="getStudentById" resultMap="studentMap">
        SELECT
            students.id,
            students.`name`,
            students.class_id,
            students.sex,
            students.birthday,
            students.college,
            classes.`name` as classname
        FROM
            classes
                INNER JOIN students ON classes.id = students.class_id and students.id=#{id}
    </select>

    <update id="updateStudent">
        update students set `name`=#{name},
                            class_id=#{classId},
                            sex=#{sex},
                            birthday=#{birthday},
                            college=#{college}
                where id=#{id}
    </update>

    <delete id="deleteStudents">
        DELETE FROM STUDENTS where id in
        <foreach collection="array" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

</mapper>

8、StudentService.java

package com.nfit.studentmis1805.service;

import com.nfit.studentmis1805.config.PageResult;
import com.nfit.studentmis1805.entity.Student;

import java.util.List;

public interface StudentService {
    /**获得所有学生
     * @param entity*/
    public List<Student> getAllStudents(Student entity);
    /**删除学生通过编号*/
    public int deleteStudentById(String id);
    /**获取学生并分页*/
    public PageResult selectPager(int pageNum, int pageSize,Student student);
    /**添加学生*/
    public int addStudent(Student entity);
    /**根据学生编号获取学生对象*/
    public Student getStudentById(String id);
    /**修改学生信息*/
    public int updateStudent(Student entity);
    public int deleteStudents(String[] ids);
}

9、运行效果

八、视频

https://www.bilibili.com/video/BV1fi4y1S79P?share_source=copy_web

https://space.bilibili.com/87194917/video

作业解答:https://www.bilibili.com/video/BV1Hs411F71x?share_source=copy_web

九、作业

1、完成上课的每一个示例,重现老师上课示例

2、使用hutool实现农历、获取与设置剪切板的内容

3、学会使用hutool帮助文档,找出任意3个功能,并在Java中使用

4、下载hutool源代码,增加一个工具类可以实现截取字符串两端的空格的静态方法,打包jar

posted @ 2022-05-10 15:35  张果  阅读(2249)  评论(0编辑  收藏  举报
AmazingCounters.com