根据通知,每天从每个年级抽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>
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不写这几个字段
用于临时存储表中信息,没主键
加了个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;//该班级多少个
}
主要用于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
来存储所有的数据,具体关系为:年级 -》 班级 -》 学生队列``,之后开辟空间(这里纯属偷懒,假设只存在2018-2021年级,具体可以自己写个sql只读取年级,然后遍历即可)。>> map = new HashMap<>();
首先找到本次所有的班级以及班级对应的人数(上面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=账号