核酸检测抽签系统(每次在每个班级选择%22)easyexcel+ZipOutputStream

一、前情提要

根据通知,每天从每个年级抽22%的人数去做核酸,五天做到全覆盖,那么问题来了,前四天做完之后是88%的人做完了,第五天12%的人没做,剩下的10%就要从做过的人中抽取,那么要怎么实现,而且要公平呢?

然后,这里又附加了一个要求,从每个年级具体到每个班级,即每个班每天抽取22%。然后我看了看他给的excel,发现有某几个人,数据跟他班的其他人不在一块,emmm……难度进一步提升。

二、整体设计+源代码

【前端】

前端部分:提交总表+开始轮次+运行轮次
懒得美化,纯html实现功能

DOCTYPE html>
<html>
<head>
	<title>核酸检测抽取title>
head>
<body>
<form method="post" style="text-align: center" action="/getTimes" enctype="multipart/form-data">
	起始轮次:<input type="text" name="startTime"><br>(即已经进行了几轮核酸检测了,五天一轮,假设你已经执行三轮了,你就填4)
	<br>
	执行伦次:<input type="text" name="turn"><br>(即你要获取几轮的数据)
	<br>
	<input type="file" name="file"><br>
	<input type="submit" value="提交">
form>
body>
html>

【pom文件】

3+的插件有毛病,改成2的能跑就完事了


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.6.6version>
        <relativePath/> 
    parent>
    <groupId>com.zxgroupId>
    <artifactId>fycqartifactId>
    <version>1version>
    <name>fycqname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.21version>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.5.1version>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>easyexcelartifactId>
            <version>3.0.5version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-resources-pluginartifactId>
                <version>2.4.3version>
            plugin>
            
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.1version>
                <configuration>
                    <source>${java.version}source>
                    <target>${java.version}target>
                    <encoding>UTF-8encoding>
                    <compilerArguments>
                        <verbose/>
                        <bootclasspath>${java.home}/lib/rt.jar${path.separator}${java.home}/lib/jce.jarbootclasspath>
                    compilerArguments>
                configuration>
            plugin>
        plugins>
    build>
project>

【模型层】

只是用于存储临时数据,因为我redis是个半吊子,所以就用了mysql数据库
@ExcelPeoperty是规定表头,@ExcelIgnore是写excel不写这几个字段

Stu.java

用于临时存储表中信息,没主键
加了个uuid以防线程问题,当然是后话了

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@TableName("stu")
public class Stu {
    @ExcelProperty("年级")
    private Integer year;
    @ExcelProperty("专业班级")
    private String cl;
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("学号")
    private String num;
    @ExcelIgnore
    private String uuid;
    @ExcelIgnore
    private Integer avg;//该班级多少个
}
ClassNums.java

主要用于Group By提取班级信息

@Data
public class ClassNums {
    //专业班级
    private String cl;
    //班级人数
    private Integer ct;
}

【持久层】

用的Mybatis-Plus
uuid还是为了防止线程问题(虽说后来感觉可能多此一举)
sql语句是查出所有班级以及对应班级人数
加@Repository是出于不想看到自动注入时候有红色下滑波浪线

@Repository
public interface StuMapper  extends BaseMapper<Stu> {
    @Select("select cl,count(*) ct from stu where uuid=#{uuid, jdbcType=VARCHAR} GROUP BY cl ")
    public List<ClassNums> findByClass(@Param("uuid")String uuid);
}

【服务层】

这里就麻烦一点。
里面有三个方法,逐个细说。

deleteAll(String uuid)方法

主要是将本次请求产生的临时数据删除

getForTimes方法

首先是传入的参数:file:总表excel;st:开始轮次;turn:执行轮次;response:用于获取本次请求的outstream,将压缩包写入。

整体代码分两个部分:数据处理、写入文件流
数据处理阶段

数据处理阶段
本阶段我是打算以 Map>> map = new HashMap<>();来存储所有的数据,具体关系为:年级 -》 班级 -》 学生队列``,之后开辟空间(这里纯属偷懒,假设只存在2018-2021年级,具体可以自己写个sql只读取年级,然后遍历即可)。
首先找到本次所有的班级以及班级对应的人数(上面sql语句起作用了),之后遍历每个班级,先计算每个班级应该每天测多少个人(四舍五入),然后查出该班级学生的列表,然后将该班级的学生塞入一个队列中,让其循环起来,因为是事先设置了一个开始轮次,即已经轮了多少次了,那就先在这个队列中倒腾一下即可。
最后就是将年级、班级、倒腾后的队列塞入map中就ok。

文件生成阶段(包括后续数据处理)
这里用的是java.util里面的ZipOutputStream类来打压缩包,读写excel都是用的excel,ZipOutStream使用需要用到ByteOutputStream,如果用的poi的话是直接就包含了的,但没用poi就得在pom中导一下,见前文pom。
主要就是循环 turn 次来生成 turn*4 个文件,然后压到一个压缩包里面提供下载,具体操作看代码,循环就完事了,中间调用setExcel()方法来写excel中的sheet,最后都是压入response提供下载。

setExcel方法

写五个批次,为一个excel生成五个sheet,顺便倒腾循环队列。

@Service
public class ExcelService {
   @Autowired
   private StuMapper mapper;
   @Transactional
   public void getForTimes(MultipartFile file, Integer st, Integer turn, HttpServletResponse response){
       if(st==null) st = 0;
       if(turn==null) turn = 1;
       String uuid = UUID.randomUUID().toString().replaceAll("-","");
       try {
           EasyExcel.read(file.getInputStream(), Stu.class,new ExcelListener(mapper,uuid)).sheet().doRead();
           /** 开辟空间 年级 -》 班级 -》 学生队列 */
           Map<Integer,Map<String,Queue<Stu>>> map = new HashMap<>();
           map.put(2018,new HashMap<String,Queue<Stu>>());
           map.put(2019,new HashMap<String,Queue<Stu>>());
           map.put(2020,new HashMap<String,Queue<Stu>>());
           map.put(2021,new HashMap<String,Queue<Stu>>());
           /** 找到本次所有班级以及对应班级的人数 */
           List<ClassNums> list = mapper.findByClass(uuid);
           List<Stu> stus = null;
           Queue<Stu> queue = null;
           HashMap<String, Queue<Stu>> clMap = null;
           for(ClassNums n:list){//遍历每个班级
               int stuNums = (int) Math.round(n.getCt()*0.22);//获取到该班级应该每天多少人
               String cl = n.getCl();//班级名
               QueryWrapper<Stu> wrapper = new QueryWrapper<>();
               wrapper.eq("cl",cl).eq("uuid",uuid);
               stus = mapper.selectList(wrapper);//找到该班级的所有学生
               if(!stus.isEmpty()){
                   queue = new LinkedList<>();
                   Integer year = stus.get(0).getYear();//年级
                   //将该班级所有学生塞入队列,然后给每个人设置自己班级每次多少人
                   for(Stu s:stus) { s.setAvg(stuNums); queue.offer(s); }
                   //st为已经进行了多少轮了,循环让这些人先到队尾
                   for(int i=0;i<st*5;i++){
                       for(int j=0;j<stuNums;j++) {
                           Stu s = queue.poll();
                           queue.offer(s);
                       }
                   }
                   //存储
                   map.get(year).put(cl,queue);
               }
           }
           //turn轮 turn * 4 个文件,每个文件5个表
           ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
           for(int i=1;i<=turn;i++){
               for(int y=2018;y<=2021;y++){
                   clMap = (HashMap<String, Queue<Stu>>) map.get(y);
                   ZipEntry entry = new ZipEntry(y+"-第"+i+"轮.xlsx");
                   zipOutputStream.putNextEntry(entry);
                   ByteOutputStream byteOutputStream = new ByteOutputStream();
                   setExcel(byteOutputStream,clMap);
                   byteOutputStream.writeTo(zipOutputStream);
                   byteOutputStream.close();
                   zipOutputStream.closeEntry();
               }
           }
           zipOutputStream.close();
       } catch (Exception e) {
           e.printStackTrace();
       }finally {
           deleteAll(uuid);
       }
       return R.OK();
   }
   @Transactional
   void setExcel(ByteOutputStream byteOutputStream,HashMap<String,Queue<Stu>> clMap){
       ExcelWriter excelWriter = null;
       try {
           excelWriter = EasyExcel.write(byteOutputStream,Stu.class).build();
           Queue<Stu> queue = new LinkedList<>();
           for(int t=1;t<=5;t++){//做五批次的表
               WriteSheet writeSheet = EasyExcel.writerSheet((t-1),"第"+t+"批").build();
               List<Stu> s2018 = new ArrayList<>();//存储该年级该批次的表
               for(Map.Entry<String, Queue<Stu>> entry : clMap.entrySet()){
                   Integer num = entry.getValue().peek().getAvg();
                   queue = entry.getValue();
                   for(int j=0;j<num;j++){
                       Stu s = queue.poll();
                       s2018.add(s);
                       queue.offer(s);
                   }
               }
               excelWriter.write(s2018,writeSheet);
           }
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           if(excelWriter!=null){
               excelWriter.finish();
           }
       }
   }
   private void deleteAll(String uuid){//删除本次产生的临时数据
       QueryWrapper<Stu> wrapper = new QueryWrapper<>();
       wrapper.eq("uuid",uuid);
       mapper.delete(wrapper);
   }
}

【控制器】

@RestController
public class AController {
    @Autowired
    private ExcelService excelService;

    @RequestMapping("/getTimes")
    public void getExcelForTimes(HttpServletRequest request,MultipartFile file, Integer startTime, Integer turn, HttpServletResponse response) throws UnsupportedEncodingException {
        response.setContentType("application/zip; charset=UTF-8");
        //返回客户端浏览器的版本号、类型
        String agent = request.getHeader("USER-AGENT");
        String downloadName = "核酸检测批次.zip";
        //针对IE或者以IE为内核的浏览器:
        if (agent.contains("MSIE") || agent.contains("Trident")) {
            downloadName = java.net.URLEncoder.encode(downloadName, "UTF-8");
        } else {
            downloadName = new String(downloadName.getBytes("UTF-8"), "ISO-8859-1");
        }
        response.setHeader("Content-disposition", "attachment;filename=" + downloadName);
        excelService.getForTimes(file, startTime-1, turn,response);
    }
}

【启动类】

@SpringBootApplication
@MapperScan(value = {"com.zx.fycq.dao"})
public class FycqApplication {
    public static void main(String[] args) {
SpringApplication.run(FycqApplication.class, args);
    }
}

【配置文件】

server.port=端口号
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://你的地址和库?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
spring.datasource.username=密码
spring.datasource.password=账号

你可能感兴趣的:(项目,JAVA,java,springboot,easyexcel,zip)