一、需求
现将列表数据,导出到excel,并将文件发送到在线文档,摒弃了以往的直接在前端下载的老旧模式。
二、pom依赖
<!-- redission --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.14.0</version><exclusions><exclusion><groupId>org.redisson</groupId><artifactId>redisson-spring-data-23</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data-20</artifactId><version>3.14.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><!-- easyexcel 主要依赖 这一个基本上就够了--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.4</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version></dependency>
三、定义表实体
import com.fasterxml.jackson.annotation.JsonFormat;import java.util.Date;
import java.util.Objects;/*** @Author: * @Description* @Date: 下午5:18 2023/10/26*/
public class EntityData {private String name;private String code;private Double score;private Integer age;private String phone;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;public EntityData() {}public EntityData(String name, String code, Double score, Integer age, String phone, Date createTime) {this.name = name;this.code = code;this.score = score;this.age = age;this.phone = phone;this.createTime = createTime;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public Double getScore() {return score;}public void setScore(Double score) {this.score = score;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;EntityData that = (EntityData) o;return Objects.equals(name, that.name) &&Objects.equals(code, that.code) &&Objects.equals(score, that.score) &&Objects.equals(age, that.age) &&Objects.equals(phone, that.phone) &&Objects.equals(createTime, that.createTime);}@Overridepublic int hashCode() {return Objects.hash(name, code, score, age, phone, createTime);}@Overridepublic String toString() {return "EntityData{" +"name='" + name + '\'' +", code='" + code + '\'' +", score=" + score +", age=" + age +", phone='" + phone + '\'' +", createTime=" + createTime +'}';}
}
四、定义写入Excel实体
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;import java.util.Date;
import java.util.Objects;/*** @Author:* @Description* @Date: 下午5:18 2023/10/26*/
public class ExcelData {@ExcelProperty(value = "姓名")private String name;@ExcelProperty(value = "学号")private String code;@ExcelProperty(value = "分数")private Double score;@ExcelProperty(value = "统计时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;public ExcelData() {}public ExcelData(String name, String code, Double score, Date createTime) {this.name = name;this.code = code;this.score = score;this.createTime = createTime;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public Double getScore() {return score;}public void setScore(Double score) {this.score = score;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;ExcelData excelData = (ExcelData) o;return Objects.equals(name, excelData.name) &&Objects.equals(code, excelData.code) &&Objects.equals(score, excelData.score) &&Objects.equals(createTime, excelData.createTime);}@Overridepublic int hashCode() {return Objects.hash(name, code, score, createTime);}@Overridepublic String toString() {return "ExcelData{" +"name='" + name + '\'' +", code='" + code + '\'' +", score=" + score +", createTime=" + createTime +'}';}
}
五、定义接口
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author: * @Description* @Date: 下午4:45 2023/10/26*/
@RestController
@RequestMapping("/file")
public interface FileApi {@GetMapping(path = "/export")ResponseData<String> export();}
六、定义service
import com.alibaba.excel.EasyExcel;
import com.example.exception.CustomException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** @Author:* @Description* @Date: 下午4:45 2023/10/26*/
@Service
public class FileService implements FileApi {@Resourceprivate RedissonClient redissonClient; //这里使用redisson分布式锁 需要导入pom依赖和spring配置private static final String DOWNLOAD_EXCEL_SHEETS_KEY = "download:";@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;@Overridepublic ResponseData<String> export() {//TODO 条件允许的话获取当前登录人信息作为redis key 的一部分,请自行填充String token = "";//加锁 防重复下载RLock lock = redissonClient.getLock(DOWNLOAD_EXCEL_SHEETS_KEY + token);try {if (lock.isLocked()) {throw new CustomException("您当前有导出中的任务尚未完成,请稍后再试!");}//这里使用看门狗机制 等待5秒,-1即开启看门狗boolean flag = lock.tryLock(5, -1, TimeUnit.SECONDS);//占用失败,抛出异常if (!flag) {throw new CustomException("锁定导出失败");}//模拟查询列表数据,可以从数据库查询List<EntityData> list = new ArrayList<>();list.add(new EntityData("张三", "001", 78.72, 11, "159888888888", new Date()));list.add(new EntityData("李四", "002", 45.87, 12, "159888888777", new Date()));list.add(new EntityData("王五", "003", 83.5, 13, "159888888666", new Date()));//判断列表数据是否为空if (CollectionUtils.isEmpty(list)) {throw new CustomException("列表没有数据!");}//异步导出 注意这里使用的异步操作,如果需要一些本地变量,如用户token信息,需要当参数透传taskExecutor.submit(() -> this.convertTExpConfirmationSheetExcel(list, "user"));} catch (InterruptedException ee) {Thread.currentThread().interrupt();} catch (CustomException eee) {throw new CustomException(eee.getMessage());} catch (Exception e) {throw new CustomException("导出出错");} finally {lock.unlock();}return ResponseData.ok("请稍后到XXXX查看");}/*** 确认单列表导出逻辑处理** @param data* @param currentUser*/private void convertTExpConfirmationSheetExcel(List<EntityData> data, String currentUser) {List<ExcelData> excelDataList = new ArrayList<>();//将数据拼装为导出数据for (EntityData sheet : data) {ExcelData excelData = new ExcelData();BeanUtils.copyProperties(sheet, excelData);excelDataList.add(excelData);}SimpleDateFormat slf = new SimpleDateFormat("yyyyMMddHHmmss");String time = slf.format(new Date());String fileName = String.format("数据导出%s.xlsx", time);String filePath = "";if (System.getProperty("os.name").toLowerCase().contains("mac")) {filePath = "/Users/admin/Downloads" + File.separator + fileName;} else {//配置服务器磁盘地址
// filePath = "/home" + File.separator + "temp" + File.separator + fileName;}// 2、生成本地 excelEasyExcel.write(filePath, ExcelData.class).sheet("数据导出").doWrite(excelDataList);// 上传osstry (InputStream inputStream = new FileInputStream(new File(filePath))) {//TODO 调用上传服务} catch (Exception e) {throw new CustomException("长传导出异常");} finally {//删除临时文件try {org.apache.commons.io.FileUtils.forceDelete(new File(filePath));} catch (IOException e) {System.out.println("删除文件异常" + e);}}}
}
注意:本地测试需要先注释掉这段代码
try {
org.apache.commons.io.FileUtils.forceDelete(new File(filePath));
} catch (IOException e) {
System.out.println(“删除文件异常” + e);
}
七、配置线程池
这里用到了异步操作,需要配置线程池参数
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;@Configuration
public class ExecutorConfig {private static final int CORE_POOL_SIZE = 30;private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2 + 1;@Bean(name="taskExecutor")public ThreadPoolTaskExecutor taskExecutor(){ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();/*** 此方法返回可用处理器的虚拟机的最大数量; 不小于1* int core = Runtime.getRuntime().availableProcessors();*///设置核心线程数poolTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);//设置最大线程数poolTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);//除核心线程外的线程存活时间poolTaskExecutor.setKeepAliveSeconds(3);//如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueuepoolTaskExecutor.setQueueCapacity(40);//线程名称前缀poolTaskExecutor.setThreadNamePrefix("thread-execute");//设置拒绝策略poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return poolTaskExecutor;}
}
八、配置环境参数
注意:换成自己redis的服务器地址
server.port=8888
spring.redis.database = 1
spring.redis.host = localhost
spring.redis.port = 6379
spring.redis.password =123456
spring.redis.jedis.pool.max-active = 8
spring.redis.jedis.pool.max-wait = -1ms
spring.redis.jedis.pool.min-idle = 0
九、测试类
1、启动项目
2、浏览器访问地址:http://localhost:8888/file/export
十、结果