Merge remote-tracking branch 'origin/master'

This commit is contained in:
mashuai 2025-11-21 15:24:11 +08:00
commit cbc4faa3d6
4 changed files with 520 additions and 30 deletions

View File

@ -1,14 +1,12 @@
package com.bonus.material.settlement.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@ -31,6 +29,7 @@ import com.bonus.material.common.annotation.PreventRepeatSubmit;
import com.bonus.material.common.domain.dto.SelectDto;
import com.bonus.material.part.domain.PartLeaseInfo;
import com.bonus.material.settlement.domain.*;
import com.bonus.material.settlement.domain.dto.ExportProgressManager;
import com.bonus.material.settlement.domain.vo.*;
import com.bonus.material.settlement.mapper.SltAgreementInfoMapper;
import com.bonus.material.settlement.mapper.SltAgreementReduceMapper;
@ -47,6 +46,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.bonus.common.log.annotation.SysLog;
import com.bonus.common.security.annotation.RequiresPermissions;
@ -88,6 +89,9 @@ public class SltAgreementInfoController extends BaseController {
@Resource
private SltAgreementReduceMapper sltAgreementRecudceMapper;
@Autowired
private ExportProgressManager exportProgressManager;
/**
* 查询结算信息列表
*/
@ -2023,6 +2027,236 @@ public class SltAgreementInfoController extends BaseController {
}
// /**
// * 一键批量导出未结算报表zip
// */
// @ApiOperation(value = "一键批量导出未结算报表")
// @PreventRepeatSubmit
// @SysLog(title = "结算信息", businessType = OperaType.EXPORT, logType = 1,module = "结算管理->一键批量导出未结算报表")
// @PostMapping("/exportUnsettled")
// public void exportUnsettled(HttpServletResponse response, @RequestBody List<SltAgreementInfo> list) throws Exception {
// // 创建临时文件夹
// String tempDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID();
// new File(tempDir).mkdirs();
//
// try {
// for(SltAgreementInfo info : list){
// if(info.getAgreementId() == null){
// continue;
// }
// //根据协议id获取结算单位和结算工程
// SltAgreementInfo agreementInfo = sltAgreementInfoMapper.getUnitAndProjectByAgreementId(info.getAgreementId());
// // 生成文件名
// String fileName = "";
// String unitName = "";
// String projectName = "";
// // 清理非法字符Windows文件名不允许: \ / : * ? " < > |
// unitName = cleanFileName(agreementInfo.getUnitName());
// projectName = cleanFileName(agreementInfo.getProjectName());
// String agreementCode = cleanFileName(agreementInfo.getAgreementCode());
//
// // 构建安全的文件名限制总长度不超过150字符
// String rawFileName = agreementCode + "-" + unitName + "-" + projectName + "_结算单.xls";
// fileName = rawFileName.length() > 150 ?
// rawFileName.substring(0, 150) + ".xls" : rawFileName;
//
// //租赁费用明细
// BigDecimal totalCostLease = BigDecimal.valueOf(0.00);
// List<SltAgreementInfo> leaseList = new ArrayList<>();
// leaseList = sltAgreementInfoMapper.getLeaseList(info);
//
// for (SltAgreementInfo bean : leaseList) {
// if (null == bean.getLeasePrice()) {
// bean.setLeasePrice(BigDecimal.valueOf(0.00));
// }
// if (null == bean.getNum()) {
// bean.setNum(BigDecimal.valueOf(0L));
// }
// if (null == bean.getLeaseDays()) {
// bean.setLeaseDay(0L);
// }
// BigDecimal leasePrice = bean.getLeasePrice();
// BigDecimal num = bean.getNum();
// BigDecimal leaseDays = new BigDecimal(bean.getLeaseDays());
// BigDecimal costs = leasePrice.multiply(num).multiply(leaseDays);
// if(costs!=null){
// totalCostLease = totalCostLease.add(costs);
// }
// bean.setCosts(costs);
// }
// List<SltLeaseInfo> lease = Convert.toList(SltLeaseInfo.class, leaseList);
//
// //丢失费用明细
// BigDecimal totalCostLose = BigDecimal.valueOf(0.00);
// List<SltAgreementInfo> loseList = new ArrayList<>();
//
// loseList = sltAgreementInfoMapper.getLoseList(info);
//
// for (SltAgreementInfo bean : loseList) {
// if (null == bean.getBuyPrice()) {
// bean.setBuyPrice(BigDecimal.valueOf(0.00));
// }
// if (null == bean.getNum()) {
// bean.setNum(BigDecimal.valueOf(0L));
// }
// BigDecimal buyPrice = bean.getBuyPrice();
// BigDecimal num = bean.getNum();
// // 原价 x 数量
// BigDecimal costs = buyPrice.multiply(num);
// if(costs!=null){
// totalCostLose = totalCostLose.add(costs);
// }
// //计算租赁费用
// bean.setCosts(costs);
// }
// List<SltLeaseInfo> lose = Convert.toList(SltLeaseInfo.class, loseList);
//
// //维修费用明细
// BigDecimal totalCostRepair = BigDecimal.valueOf(0.00);
// List<SltAgreementInfo> repairList = new ArrayList<>();
//
//
// List<TmTask> taskRepairList = taskMapper.getTaskIdList(info);
// List<TmTask> taskRepairList2 = new ArrayList<>();
// taskRepairList2 = checkTeamAgreementInfo(info);
// if (null != taskRepairList && !taskRepairList.isEmpty()) {
// if (null != taskRepairList2 && !taskRepairList2.isEmpty()) {
// taskRepairList.addAll(taskRepairList2);
// }
// repairList = sltAgreementInfoMapper.getRepairDetailsList(info, taskRepairList);
// }
//
// for (SltAgreementInfo bean : repairList) {
// if (bean.getCosts()!=null && (bean.getPartType().equals("收费"))) {
// totalCostRepair = totalCostRepair.add(bean.getCosts());
// }
// }
// List<SltLeaseInfo> repair = Convert.toList(SltLeaseInfo.class, repairList);
//
// //报废费用明细
// BigDecimal totalCostScrap = BigDecimal.valueOf(0.00);
// List<SltAgreementInfo> scrapList = new ArrayList<>();
//
// List<TmTask> taskScrapList = taskMapper.getTaskIdList(info);
//
// List<TmTask> taskScrapList2 = new ArrayList<>();
// taskScrapList2 = checkTeamAgreementInfo(info);
//
// if (null != taskScrapList && !taskScrapList.isEmpty()) {
//
// if (null != taskScrapList2 && !taskScrapList2.isEmpty()) {
// taskScrapList.addAll(taskScrapList2);
// }
// scrapList = sltAgreementInfoMapper.getScrapDetailsList(info, taskScrapList);
// }
//
//
// for (SltAgreementInfo bean : scrapList) {
// if (bean.getCosts()!=null && (bean.getPartType().equals("收费"))) {
// totalCostScrap = totalCostScrap.add(bean.getCosts());
// }
// }
// List<SltLeaseInfo> scrap = Convert.toList(SltLeaseInfo.class, scrapList);
//
// //减免费用明细
// BigDecimal totalCostReduction = BigDecimal.valueOf(0.00);
// List<SltAgreementReduce> reductionList = new ArrayList<>();
//
// if (info.getAgreementId() != null){
// SltAgreementReduce bean =new SltAgreementReduce();
// bean.setAgreementId(info.getAgreementId());
// reductionList = sltAgreementRecudceMapper.getReductionList(bean);
// }
//
// for (SltAgreementReduce reduction : reductionList) {
// if(reduction.getLeaseMoney()!=null){
// totalCostReduction = totalCostReduction.add(reduction.getLeaseMoney());
// }
// }
// List<SltLeaseInfo> reduction = Convert.toList(SltLeaseInfo.class, reductionList);
//
//
// List<Map<String, Object>> resultsLease = new ArrayList<>();
// List<Map<String, Object>> resultsLose = new ArrayList<>();
// List<Map<String, Object>> resultsRepair = new ArrayList<>();
// List<Map<String, Object>> resultsScrap = new ArrayList<>();
// List<Map<String, Object>> resultsReduction = new ArrayList<>();
// if (lease!= null) {
// for (SltLeaseInfo bean : lease) {
// Map<String, Object> maps = outReceiveDetailsBeanToMap(bean, 1, 1);
// resultsLease.add(maps);
// }
// }
// if (lose!= null) {
// for (SltLeaseInfo bean : lose) {
// Map<String, Object> maps = outReceiveDetailsBeanToMap(bean, 2, 1);
// resultsLose.add(maps);
// }
// }
// if (repair!= null) {
// for (SltLeaseInfo bean : repair) {
// Map<String, Object> maps = outReceiveDetailsBeanToMap(bean, 3, 1);
// resultsRepair.add(maps);
// }
// }
// if (scrap!= null) {
// for (SltLeaseInfo bean : scrap) {
// Map<String, Object> maps = outReceiveDetailsBeanToMap(bean, 4, 1);
// resultsScrap.add(maps);
// }
// }
// if (reduction!= null) {
// for (SltLeaseInfo bean : reduction) {
// Map<String, Object> maps = outReceiveDetailsBeanToMap(bean, 5, 1);
// resultsReduction.add(maps);
// }
// }
//
// List<String> headersLease = receiveDetailsHeader(1,1);
// List<String> headersLose = receiveDetailsHeader(2,1);
// List<String> headersRepair = receiveDetailsHeader(3,1);
// List<String> headersScrap = receiveDetailsHeader(4,1);
// List<String> headersReduction = receiveDetailsHeader(5,1);
//
//// fileName = agreementInfo.getAgreementCode() + "-" + unitName + "-" + projectName+ "_结算单.xls" ;
// // 导出单个Excel文件
// String filePath = tempDir + File.separator + fileName;
// try (FileOutputStream fos = new FileOutputStream(filePath)) {
// HSSFWorkbook workbook = PoiOutPage.excelForcheckAll(resultsLease,resultsLose,resultsRepair,resultsScrap,resultsReduction, headersLease,headersLose,headersRepair,headersScrap,headersReduction, "结算明细",projectName,unitName,totalCostLease,totalCostLose,totalCostRepair,totalCostScrap,totalCostReduction);
// workbook.write(fos);
// }
//
// }
// // 创建压缩包
// String zipFileName = "结算单_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
// response.setContentType("application/zip");
// response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
//
// try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
// File[] files = new File(tempDir).listFiles();
// if (files != null) {
// for (File file : files) {
// zipOut.putNextEntry(new ZipEntry(file.getName()));
// try (FileInputStream fis = new FileInputStream(file)) {
// byte[] buffer = new byte[1024];
// int len;
// while ((len = fis.read(buffer)) > 0) {
// zipOut.write(buffer, 0, len);
// }
// }
// zipOut.closeEntry();
// }
// }
// }
// } catch (Exception e) {
// log.error("一键批量导出未结算报表失败", e);
// } finally {
// // 删除临时文件
// FileUtils.deleteDirectory(new File(tempDir));
// }
// }
/************************************************************************/
/**
* 一键批量导出未结算报表zip
*/
@ -2030,12 +2264,119 @@ public class SltAgreementInfoController extends BaseController {
@PreventRepeatSubmit
@SysLog(title = "结算信息", businessType = OperaType.EXPORT, logType = 1,module = "结算管理->一键批量导出未结算报表")
@PostMapping("/exportUnsettled")
public void exportUnsettled(HttpServletResponse response, @RequestBody List<SltAgreementInfo> list) throws Exception {
// 创建临时文件夹
String tempDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID();
public ResponseEntity<Map<String, String>> exportUnsettled(HttpServletResponse response, @RequestBody List<SltAgreementInfo> list) throws Exception {
// 生成任务ID
String taskId = UUID.randomUUID().toString();
// 初始化进度
exportProgressManager.initProgress(taskId, list.size());
// 异步执行导出任务
CompletableFuture.runAsync(() -> {
try {
executeExport(taskId, list);
} catch (Exception e) {
log.error("导出任务执行失败, taskId: {}", taskId, e);
exportProgressManager.errorProgress(taskId, "导出失败: " + e.getMessage());
}
});
// 立即返回任务ID
Map<String, String> result = new HashMap<>();
result.put("taskId", taskId);
result.put("message", "导出任务已开始请通过taskId查询进度");
result.put("total", String.valueOf(list.size()));
return ResponseEntity.ok(result);
}
@ApiOperation(value = "查询导出进度")
@GetMapping("/exportProgress/{taskId}")
public ResponseEntity<ExportProgressManager.ExportProgress> getExportProgress(@PathVariable String taskId) {
ExportProgressManager.ExportProgress progress = exportProgressManager.getProgress(taskId);
if (progress == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(progress);
}
@ApiOperation(value = "下载导出文件")
@PostMapping("/downloadExport")
public void downloadExportFile(@RequestBody SltAgreementInfo info, HttpServletResponse response) {
try {
ExportProgressManager.ExportProgress progress = exportProgressManager.getProgress(info.getTaskId());
if (progress == null || !"completed".equals(progress.getStatus())) {
response.setStatus(HttpStatus.NOT_FOUND.value());
response.getWriter().write("文件不存在或导出未完成");
return;
}
File file = new File(progress.getFileUrl());
if (!file.exists()) {
response.setStatus(HttpStatus.NOT_FOUND.value());
response.getWriter().write("文件不存在");
return;
}
String fileName = "结算单_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
}
} catch (Exception e) {
log.error("下载文件失败, taskId: {}", info.getTaskId(), e);
try {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write("下载失败: " + e.getMessage());
} catch (IOException ex) {
log.error("写入响应失败", ex);
}
}
}
@ApiOperation(value = "清理导出任务")
@DeleteMapping("/cleanExport/{taskId}")
public ResponseEntity<Map<String, String>> cleanExportTask(@PathVariable String taskId) {
try {
ExportProgressManager.ExportProgress progress = exportProgressManager.getProgress(taskId);
if (progress != null && progress.getFileUrl() != null) {
File file = new File(progress.getFileUrl());
if (file.exists()) {
file.delete();
}
}
exportProgressManager.removeProgress(taskId);
Map<String, String> result = new HashMap<>();
result.put("message", "任务清理成功");
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("清理任务失败, taskId: {}", taskId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 执行导出任务的核心方法
*/
private void executeExport(String taskId, List<SltAgreementInfo> list) throws Exception {
String tempDir = System.getProperty("java.io.tmpdir") + File.separator + "export_" + taskId;
String zipFilePath = System.getProperty("java.io.tmpdir") + File.separator + "export_" + taskId + ".zip";
new File(tempDir).mkdirs();
try {
int processed = 0;
for(SltAgreementInfo info : list){
if(info.getAgreementId() == null){
continue;
@ -2222,36 +2563,67 @@ public class SltAgreementInfoController extends BaseController {
workbook.write(fos);
}
}
// 创建压缩包
String zipFileName = "结算单_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
processed++;
exportProgressManager.updateProgress(taskId, 1);
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
File[] files = new File(tempDir).listFiles();
if (files != null) {
for (File file : files) {
zipOut.putNextEntry(new ZipEntry(file.getName()));
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zipOut.write(buffer, 0, len);
}
}
zipOut.closeEntry();
}
}
// 每处理完一个文件短暂休息以避免资源过度占用
Thread.sleep(100);
}
// 创建压缩包
createZipFile(tempDir, zipFilePath);
// 标记任务完成
exportProgressManager.completeProgress(taskId, zipFilePath);
} catch (Exception e) {
log.error("一键批量导出未结算报表失败", e);
exportProgressManager.errorProgress(taskId, "导出过程发生错误: " + e.getMessage());
throw e;
} finally {
// 删除临时文件
FileUtils.deleteDirectory(new File(tempDir));
// 清理临时文件夹但保留zip文件
try {
FileUtils.deleteDirectory(new File(tempDir));
} catch (IOException e) {
log.warn("清理临时文件夹失败: {}", e.getMessage());
}
}
}
/**
* 创建压缩包
*/
private void createZipFile(String sourceDir, String zipFilePath) throws IOException {
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath))) {
File[] files = new File(sourceDir).listFiles();
if (files != null) {
for (File file : files) {
zipOut.putNextEntry(new ZipEntry(file.getName()));
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zipOut.write(buffer, 0, len);
}
}
zipOut.closeEntry();
}
}
}
}
/************************************************************************/
// 添加文件名清理工具方法
private String cleanFileName(String name) {
if (name == null) {

View File

@ -289,4 +289,6 @@ public class SltAgreementInfo extends BaseEntity {
/** 工程是否竣工 */
@ApiModelProperty(value = "工程是否竣工")
private Integer isFinish;
private String taskId;
}

View File

@ -0,0 +1,28 @@
package com.bonus.material.settlement.domain.dto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("exportTaskExecutor")
public Executor exportTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("export-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}

View File

@ -0,0 +1,88 @@
package com.bonus.material.settlement.domain.dto;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class ExportProgressManager {
private final Map<String, ExportProgress> progressMap = new ConcurrentHashMap<>();
public void initProgress(String taskId, int total) {
ExportProgress progress = new ExportProgress();
progress.setTotal(total);
progress.setCurrent(0);
progress.setStatus("processing");
progress.setPercentage(0);
progressMap.put(taskId, progress);
}
public void updateProgress(String taskId, int increment) {
ExportProgress progress = progressMap.get(taskId);
if (progress != null) {
progress.setCurrent(progress.getCurrent() + increment);
progress.setPercentage(calculatePercentage(progress.getCurrent(), progress.getTotal()));
}
}
public void completeProgress(String taskId, String fileUrl) {
ExportProgress progress = progressMap.get(taskId);
if (progress != null) {
progress.setStatus("completed");
progress.setFileUrl(fileUrl);
progress.setPercentage(100);
}
}
public void errorProgress(String taskId, String errorMsg) {
ExportProgress progress = progressMap.get(taskId);
if (progress != null) {
progress.setStatus("error");
progress.setErrorMsg(errorMsg);
}
}
public ExportProgress getProgress(String taskId) {
return progressMap.get(taskId);
}
public void removeProgress(String taskId) {
progressMap.remove(taskId);
}
private int calculatePercentage(int current, int total) {
if (total == 0) return 100;
int percentage = (int) ((current * 100.0) / total);
return Math.min(percentage, 100);
}
public static class ExportProgress {
private int total;
private int current;
private int percentage;
private String status; // processing, completed, error
private String fileUrl;
private String errorMsg;
// getters and setters
public int getTotal() { return total; }
public void setTotal(int total) { this.total = total; }
public int getCurrent() { return current; }
public void setCurrent(int current) { this.current = current; }
public int getPercentage() { return percentage; }
public void setPercentage(int percentage) { this.percentage = percentage; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getFileUrl() { return fileUrl; }
public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl; }
public String getErrorMsg() { return errorMsg; }
public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; }
}
}