zhoulisky 7 months ago
parent
commit
5849b1537b

+ 2 - 0
kd-service-api/kd-scientific-api/src/main/java/org/sky/scientific/pojo/entity/DownloadTask.java

@@ -3,6 +3,7 @@ package org.sky.scientific.pojo.entity;
 
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
@@ -50,6 +51,7 @@ public class DownloadTask extends TenantEntity {
 	private Integer status;
 
 	@Schema(description = "压缩文件的路径")
+	@JsonIgnore
 	private String zipUrl;
 
 	@Schema(description = "压缩文件大小")

+ 54 - 119
kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/ArchiveCenterController.java

@@ -6,17 +6,16 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
-import org.apache.commons.compress.utils.IOUtils;
 import org.sky.core.boot.ctrl.KdController;
 import org.sky.core.secure.utils.SecureUtil;
 import org.sky.core.tool.api.R;
+import org.sky.core.tool.utils.DateUtil;
 import org.sky.scientific.mapper.DownloadTaskMapper;
 import org.sky.scientific.pojo.entity.ArchiveAttachEntity;
 import org.sky.scientific.pojo.entity.DownloadTask;
@@ -29,10 +28,7 @@ import org.springframework.web.context.request.RequestContextHolder;
 
 import java.io.*;
 import java.net.URLEncoder;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Date;
-import java.util.zip.Deflater;
+import java.nio.charset.StandardCharsets;
 
 /**
  * 科研档案管理 控制器
@@ -46,6 +42,8 @@ import java.util.zip.Deflater;
 @Tag(name = "科研档案管理")
 public class ArchiveCenterController extends KdController {
 
+	private static final int BUFFER_SIZE = 8192; // 8KB缓冲区
+
 	@Resource
 	private IArchiveService service;
 	@Resource
@@ -78,12 +76,12 @@ public class ArchiveCenterController extends KdController {
 	@ApiOperationSupport(order = 6)
 	@Operation(summary = "档案中心-一键下载/一键导出高新备查/一键导出加计备查-生成", description = "返回任务Id,后端生产待下载的文件")
 	public R generateCenterZipFiles(@Valid @RequestBody DownloadTask task) {
- 		if (task.getId() != null) {
+		if (task.getId() != null) {
 			DownloadTask dbTask = downloadTaskMapper.selectById(task.getId());
 			if (dbTask == null) {
 				return R.fail("下载任务不存在,请联系管理员");
 			}
-			if (dbTask.getExpireTime().compareTo(new Date()) <= 0) {
+			if (dbTask.getStatus() == 2 && dbTask.getExpireTime() != null && DateUtil.now().compareTo(dbTask.getExpireTime()) > 0) {
 				// 任务已过期
 				dbTask.setStatus(3);
 			}
@@ -104,22 +102,6 @@ public class ArchiveCenterController extends KdController {
 		return R.success();
 	}
 
-//	@Scheduled(cron = "0 0/5 * * * ?")
-//	public void dealWithDownloadTask() {
-//		//处理下载任务,主要针对未完成的任务
-//		LambdaQueryWrapper<DownloadTask> wrapper = Wrappers.<DownloadTask>lambdaQuery()
-//			.eq(DownloadTask::getStatus, 0)
-//			.le(DownloadTask::getCreateTime, DateUtil.minusMinutes(new Date(), 5));
-//		List<DownloadTask> list = downloadTaskMapper.selectList(wrapper);
-//		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
-//		for (DownloadTask task : list) {
-//			new Thread(() -> {
-//				RequestContextHolder.setRequestAttributes(requestAttributes);
-//				service.downloadCenterZipFiles(task);
-//			}).start();
-//		}
-//	}
-
 	@SneakyThrows
 	@GetMapping("center/download")
 	@ApiOperationSupport(order = 6)
@@ -132,128 +114,81 @@ public class ArchiveCenterController extends KdController {
 			writer.write(JSON.toJSONString(R.fail("下载任务不存在,请联系管理员")));
 			writer.flush();
 		} else if (task.getStatus() == 2) {
-			String fileName = null;
-			if (task.getType() == 0) {
-				fileName = "全部档案资料";
-			} else if (task.getType() == 1) {
-				fileName = "高新备查资料";
-			} else {
-				fileName = "加计扣除备查资料";
+			File file = new File(task.getZipUrl());
+			if (!file.exists()) {
+				response.setContentType("application/json;charset=UTF-8");
+				PrintWriter writer = response.getWriter();
+				writer.write(JSON.toJSONString(R.fail("文件不存在")));
+				writer.flush();
 			}
-			Path folderPath = Paths.get(archiveDir, SecureUtil.getTenantId(), task.getYearAndMonth(), fileName);
 
-			File folder = folderPath.toFile();
-			if (!folder.exists() || !folder.isDirectory()) {
-				sendErrorResponse(response, "文件夹不存在或不是目录");
-				return;
-			}
-			setupZipResponseHeaders(response, task.getYearAndMonth() + "年" + fileName + ".zip");
 
-			try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(response.getOutputStream())) {
-				zos.setEncoding("UTF-8"); // 支持中文文件名
-				zos.setLevel(Deflater.BEST_SPEED);
+			try (FileInputStream fis = new FileInputStream(file);
+				 BufferedInputStream bis = new BufferedInputStream(fis);
+				 ServletOutputStream outputStream = response.getOutputStream()) {
 
-				Date date = new Date();
-				compressFolderWithApache(folder, folder.getName(), zos);
-				log.info("压缩耗时:{}秒", (new Date().getTime() - date.getTime()) / 1000);
+				// 设置内容类型
+				response.setContentType("application/octet-stream");
 
-				zos.finish();
-				response.flushBuffer();
+				// 设置文件大小
+				response.setContentLengthLong(file.length());
 
-			} catch (IOException e) {
-				log.error("压缩文件夹失败: {}", folderPath, e);
-				if (!response.isCommitted()) {
-					sendErrorResponse(response, "文件压缩失败");
-				}
-			}
+				// 设置下载文件名(解决中文乱码)
+				String encodedFileName = encodeFileName(file.getName());
+				response.setHeader("Content-Disposition",
+					"attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
 
-		}
-	}
+				// 缓存控制
+				response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+				response.setHeader("Pragma", "no-cache");
+				response.setHeader("Expires", "0");
 
-	/**
-	 * 使用Apache Commons Compress压缩文件夹
-	 */
-	private void compressFolderWithApache(File folder, String basePath,
-										  ZipArchiveOutputStream zos) throws IOException {
-		File[] files = folder.listFiles();
-		if (files == null) return;
-
-		for (File file : files) {
-			String entryName = basePath + "/" + file.getName();
-
-			if (file.isDirectory()) {
+				// 安全头
+				response.setHeader("X-Content-Type-Options", "nosniff");
 
-				// 创建目录条目
-				ZipArchiveEntry dirEntry = new ZipArchiveEntry(entryName + "/");
-				zos.putArchiveEntry(dirEntry);
-				zos.closeArchiveEntry();
+				// 传输文件数据
+				byte[] buffer = new byte[8192];
+				int bytesRead;
+				long totalBytes = 0;
 
-				// 递归处理子目录
-				compressFolderWithApache(file, entryName, zos);
-			} else {
-				// 创建文件条目
-				ZipArchiveEntry fileEntry = new ZipArchiveEntry(file, entryName);
-				zos.putArchiveEntry(fileEntry);
-
-				try (FileInputStream fis = new FileInputStream(file)) {
-					IOUtils.copy(fis, zos);
+				while ((bytesRead = bis.read(buffer)) != -1) {
+					outputStream.write(buffer, 0, bytesRead);
+					totalBytes += bytesRead;
 				}
-				zos.closeArchiveEntry();
-			}
-		}
-	}
 
-	/**
-	 * 发送错误JSON响应
-	 */
-	private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
-		response.setContentType("application/json;charset=UTF-8");
-		response.setCharacterEncoding("UTF-8");
-		response.setStatus(HttpStatus.BAD_REQUEST.value());
+				outputStream.flush();
+				log.info("文件下载完成: {}, 大小: {} mb", task.getZipUrl(), totalBytes/1024/1024);
 
-		try (PrintWriter writer = response.getWriter()) {
-			writer.write(JSON.toJSONString(R.fail(message)));
-			writer.flush();
+			} catch (IOException e) {
+				log.error("文件下载失败: {}", task.getZipUrl(), e);
+				setErrorResponse(response, "文件下载失败", HttpStatus.INTERNAL_SERVER_ERROR);
+			}
 		}
 	}
 
 
 	/**
-	 * 设置ZIP下载响应头
-	 */
-	private void setupZipResponseHeaders(HttpServletResponse response, String fileName) {
-		response.setContentType("application/zip");
-		response.setCharacterEncoding("UTF-8");
-		response.setHeader("Content-Disposition",
-			"attachment; filename=\"" + encodeFileName(fileName) + "\"");
-		response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
-	}
-
-
-	/**
-	 * 文件名编码处理(防止中文乱码)
+	 * 编码文件名
 	 */
 	private String encodeFileName(String fileName) {
 		try {
-			return URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
+			return URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString())
+				.replaceAll("\\+", "%20");
 		} catch (UnsupportedEncodingException e) {
 			return fileName;
 		}
 	}
 
 	/**
-	 * 文件传输
+	 * 设置错误响应
 	 */
-	private void transferFileToResponse(File file, HttpServletResponse response) throws IOException {
-		try (FileInputStream fileInputStream = new FileInputStream(file);
-			 OutputStream responseOutputStream = response.getOutputStream()) {
-
-			byte[] buffer = new byte[4096];
-			int bytesRead;
-			while ((bytesRead = fileInputStream.read(buffer)) != -1) {
-				responseOutputStream.write(buffer, 0, bytesRead);
-			}
-			responseOutputStream.flush();
+	private void setErrorResponse(HttpServletResponse response, String message, HttpStatus status) {
+		try {
+			response.setStatus(status.value());
+			response.setContentType("application/json");
+			response.getWriter().write("{\"error\": \"" + message + "\"}");
+		} catch (IOException e) {
+			log.error("设置错误响应失败", e);
 		}
 	}
 }

+ 8 - 9
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/YsZjtrfyExcel.java

@@ -21,28 +21,27 @@ public class YsZjtrfyExcel implements Serializable {
 	@Serial
 	private static final long serialVersionUID = 1L;
 
-	@ExcelProperty("序号")
+	@ExcelProperty(index = 0, value = "序号")
 	private int xh;
 
- 	@ExcelProperty("1-物资名称")
+ 	@ExcelProperty(index = 1, value = "1-物资名称")
 	private String wzmc;
 
- 	@ExcelProperty("用量")
+ 	@ExcelProperty(index = 2, value = "用量")
 	private Integer yongLiang;
 
- 	@ExcelProperty("单价")
+ 	@ExcelProperty(index = 3, value = "单价")
 	private Double danJia;
 
- 	@ExcelProperty("2-仪器设备名称")
+ 	@ExcelProperty(index = 5, value = "2-仪器设备名称")
 	private String yqsbmc;
 
- 	@ExcelProperty("预算金额")
+ 	@ExcelProperty(index = 6, value = "预算金额")
 	private Double ysje;
 
- 	@ExcelProperty("3-固定资产名称(租赁)")
+ 	@ExcelProperty(index = 7, value ="3-固定资产名称(租赁)")
 	private String gdzcmc;
 
- 	@ExcelProperty("租赁费用预算")
+ 	@ExcelProperty(index = 8, value = "租赁费用预算")
 	private Double zlfyys;
-
 }

+ 39 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/BaseAnalysisEventListener.java

@@ -0,0 +1,39 @@
+package org.sky.scientific.excel.listener;
+
+import com.alibaba.excel.event.AnalysisEventListener;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public  abstract class BaseAnalysisEventListener extends AnalysisEventListener<Map<Integer, String>> {
+
+	private List<ImportError> errorMessages = new ArrayList<>();
+
+	public List<ImportError> getErrorMessages() {
+		return errorMessages;
+	}
+
+	public boolean hasErrors() {
+		return !errorMessages.isEmpty();
+	}
+
+	// 错误信息封装
+	@Data
+	@AllArgsConstructor
+	public static class ImportError {
+		private Integer rowIndex;
+		private String message;
+
+		@Override
+		public String toString() {
+			if (rowIndex > 0) {
+				return "第" + rowIndex + "行: " + message;
+			}
+			return message;
+		}
+	}
+
+}

+ 68 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/YsZjtrfyImportListener.java

@@ -0,0 +1,68 @@
+package org.sky.scientific.excel.listener;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.sky.core.log.exception.ServiceException;
+import org.sky.core.tool.utils.BeanUtil;
+import org.sky.core.tool.utils.Func;
+import org.sky.scientific.excel.YsZjtrfyExcel;
+import org.sky.scientific.pojo.entity.YsZjtrfyEntity;
+import org.sky.scientific.service.IYsZjtrfyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = true)
+@Component
+@Data
+//public class YsZjtrfyImportListener extends AnalysisEventListener<Map<Integer, String>> {
+public class YsZjtrfyImportListener extends AnalysisEventListener<YsZjtrfyExcel> {
+
+	@Autowired
+	private IYsZjtrfyService ysZjtrfyService;
+	private Long xmId;
+	private List<YsZjtrfyExcel> dataList = new ArrayList<>();
+
+	@Override
+//	public void invoke(Map<Integer, String> map, AnalysisContext analysisContext) {
+	public void invoke(YsZjtrfyExcel data, AnalysisContext analysisContext) {
+//		YsZjtrfyExcel data = new YsZjtrfyExcel();
+//		// 1. 基本数据校验
+//		List<String> validationErrors = validateData(data, rowIndex);
+//		if (!validationErrors.isEmpty()) {
+//			errorMessages.addAll(validationErrors.stream()
+//				.map(msg -> new ImportError(rowIndex, msg))
+//				.collect(Collectors.toList()));
+//			return;
+//		}
+		dataList.add(data);
+ 	}
+
+	@Override
+	public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+
+		if (Func.isEmpty(dataList)) {
+			throw new ServiceException("无数据可导入");
+		}
+		List<YsZjtrfyEntity> insertList = new ArrayList<>();
+		List<YsZjtrfyEntity> dbList = ysZjtrfyService.list(Wrappers.<YsZjtrfyEntity>lambdaQuery().eq(YsZjtrfyEntity::getXmId, xmId));
+		int batchCount = 20;
+		for (YsZjtrfyExcel excel : dataList) {
+			YsZjtrfyEntity entity = new YsZjtrfyEntity();
+			entity.setXmId(xmId);
+			BeanUtil.copyProperties(excel, entity);
+			insertList.add(entity);
+			if (insertList.size() % batchCount == 0 && ysZjtrfyService.saveBatch(insertList)) {
+				insertList.clear();
+			}
+		}
+		if (Func.isNotEmpty(insertList)) {
+			ysZjtrfyService.saveBatch(insertList);
+		}
+	}
+}

+ 1 - 1
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/XmIntangibleAssetMapper.xml

@@ -68,7 +68,7 @@
     <select id="selectPageByYear" resultMap="ResultMap">
         SELECT xa.xm_id, xm.XMMC as xmmc, xm.XMBH as xmbh, xa.zcbm, t.name as user_name,<include refid="org.sky.scientific.mapper.AssetMapper.basicColumns"/>
         FROM kd_xm_intangible_asset xa
-        left join kd_asset asset on asset.zcbm = xa.zcbm and asset.year_and_month = (SELECT max(year_and_month) from kd_asset t WHERE t.zcbm = zcbm and left(t.year_and_month,4) =#{ew.yearAndMonth} )
+        left join kd_asset asset on asset.zcbm = xa.zcbm and asset.year_and_month = (SELECT max(year_and_month) from kd_asset t WHERE t.zcbm = asset.zcbm and left(t.year_and_month,4) =#{ew.yearAndMonth} )
         LEFT JOIN kd_technician t on t.unicode=xa.user_unicode and t.year_and_month = xa.year_and_month
         LEFT JOIN
         (

+ 14 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/ArchiveServiceImpl.java

@@ -1,6 +1,8 @@
 package org.sky.scientific.service.impl;
 
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.util.ZipUtil;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
@@ -502,10 +504,22 @@ public class ArchiveServiceImpl implements IArchiveService {
 		}));
 		CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
 		log.info("一键导出耗时:{}", (new Date().getTime() - date.getTime()) / 1000 + "秒");
+
+
+		String zipPath = basePath+".zip";
+		try {
+			ZipUtil.zip(basePath.toString(), zipPath);
+		} catch (IORuntimeException e) {
+			log.error("创建zip文件失败,{}",e.getLocalizedMessage());
+		}
+
+
 		//设置任务状态为 已完成
 		task.setStatus(2);
 		task.setDoneTime(new Date());
 		task.setExpireTime(DateUtil.plusHours(new Date(), 1));
+		task.setZipUrl(zipPath);
+		task.setFileSize(FileUtil.size(FileUtil.file(zipPath)));
 		downloadTaskMapper.updateById(task);
 	}
 

+ 0 - 9
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/TechnicianServiceImpl.java

@@ -88,15 +88,6 @@ public class TechnicianServiceImpl extends BaseServiceImpl<TechnicianMapper, Tec
 		} else {
 			return this.updateById(technician);
 		}
-//		LambdaQueryWrapper<TechnicianEntity> wrapper = Wrappers.<TechnicianEntity>lambdaQuery()
-//			.eq(TechnicianEntity::getYearAndMonth, technician.getYearAndMonth())
-//			.eq(TechnicianEntity::getUnicode, technician.getUnicode());
-//		TechnicianEntity dbTechnician = baseMapper.selectOne(wrapper);
-//		if (dbTechnician == null) {
-//
-//		}else {
-//			return this.updateById(technician);
-//		}
 	}
 
 	@Override

+ 46 - 31
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/YsZjtrfyServiceImpl.java

@@ -15,9 +15,9 @@ import org.sky.core.log.exception.ServiceException;
 import org.sky.core.mp.base.BaseServiceImpl;
 import org.sky.core.mp.support.Condition;
 import org.sky.core.mp.support.Query;
-import org.sky.core.tool.utils.BeanUtil;
 import org.sky.core.tool.utils.Func;
 import org.sky.scientific.excel.YsZjtrfyExcel;
+import org.sky.scientific.excel.listener.YsZjtrfyImportListener;
 import org.sky.scientific.mapper.XmMapper;
 import org.sky.scientific.mapper.YsZjtrfyMapper;
 import org.sky.scientific.pojo.entity.YsZjtrfyEntity;
@@ -30,7 +30,6 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -53,14 +52,14 @@ public class YsZjtrfyServiceImpl extends BaseServiceImpl<YsZjtrfyMapper, YsZjtrf
 		return page.setRecords(baseMapper.selectYsZjtrfyPage(page, ysZjtrfy));
 	}
 
-    @Override
-    public boolean saveZjtrfy(YsZjtrfyEntity ysZjtrfy) {
+	@Override
+	public boolean saveZjtrfy(YsZjtrfyEntity ysZjtrfy) {
 		// 检查物资名称是否重复
-		if(this.isFieldDuplicate(YsZjtrfyEntity::getWzmc, ysZjtrfy.getWzmc())){
+		if (this.isFieldDuplicate(YsZjtrfyEntity::getWzmc, ysZjtrfy.getWzmc())) {
 			throw new ServiceException("物资名称已存在");
 		}
 		return this.save(ysZjtrfy);
-    }
+	}
 
 	@Override
 	public boolean physicalDeleteByIds(List<Long> ids) {
@@ -69,12 +68,13 @@ public class YsZjtrfyServiceImpl extends BaseServiceImpl<YsZjtrfyMapper, YsZjtrf
 
 	@Override
 	public boolean updateZjtrfy(YsZjtrfyEntity ysZjtrfy) {
-		if(ysZjtrfy.getXmId() == null){
+		if (ysZjtrfy.getXmId() == null) {
 			throw new ServiceException("请选择科研项目");
 		}
 		return this.updateById(ysZjtrfy);
 	}
 
+	@SneakyThrows
 	@Override
 	public void importZjtrfy(MultipartFile file, Long xmId) {
 		if (Func.isNull(xmId)) {
@@ -85,33 +85,48 @@ public class YsZjtrfyServiceImpl extends BaseServiceImpl<YsZjtrfyMapper, YsZjtrf
 			throw new ServiceException("研发项目不存在");
 		}
 
-		List<YsZjtrfyExcel> dataList = ExcelUtil.read(file, 0, 3, YsZjtrfyExcel.class);
-
-		List<YsZjtrfyEntity> insertList = new ArrayList<>();
-		int batchCount = 20;
-		for (YsZjtrfyExcel excel : dataList) {
-			YsZjtrfyEntity entity = new YsZjtrfyEntity();
-			entity.setXmId(xmId);
-			BeanUtil.copyProperties(excel, entity);
-			insertList.add(entity);
-			if (insertList.size() % batchCount == 0 && this.saveBatch(insertList)) {
-				insertList.clear();
-			}
-		}
-
-		if (Func.isNotEmpty(insertList)) {
-			this.saveBatch(insertList);
-		}
+		YsZjtrfyImportListener listener = new YsZjtrfyImportListener();
+		ExcelUtil.getReaderBuilder(file, listener, YsZjtrfyExcel.class).headRowNumber(3).doReadAll();
+
+
+//		ExcelUtil.read(file, YsZjtrfyExcel.class, new AnalysisEventListener<YsZjtrfyExcel>() {
+//
+//				@Override
+//				public void invoke(YsZjtrfyExcel ysZjtrfyExcel, AnalysisContext analysisContext) {
+//
+//				}
+//
+//				@Override
+//				public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+//
+//				}
+//			}).
+//			EasyExcel.read(new BufferedInputStream(file.getInputStream()))
+//			.registerReadListener(listener)
+//			.sheet(0)
+//			.headRowNumber(3) // 重要:设置表头行数(0-based)
+//			.doRead();
+
+//		if (listener.hasErrors()) {
+//			List<String> errorDetails = listener.getErrorMessages().stream()
+//				.map(DataValidationListener.ImportError::toString)
+//				.collect(Collectors.toList());
+//
+//			return ResponseEntity.badRequest()
+//				.body(ImportResult.error("导入失败", errorDetails));
+//		}
+
+//		return R.data(ImportResult.success("导入成功,共导入数据"));
 	}
 
-    @SneakyThrows
+	@SneakyThrows
 	@Override
-    public void exportYsZjtrfy(OutputStream os, YsZjtrfyVO dto) {
+	public void exportYsZjtrfy(OutputStream os, YsZjtrfyVO dto) {
 		List<YsZjtrfyVO> list = this.selectYsZjtrfyPage(Condition.getPage(new Query().setCurrent(1).setSize(Integer.MAX_VALUE)), dto).getRecords();
 		AtomicInteger index = new AtomicInteger(1);
 		for (YsZjtrfyVO temp : list) {
-			temp.setXh(index.getAndIncrement()+"");
-			temp.setWuziTotal(BigDecimal.valueOf(temp.getYongLiang()*temp.getDanJia()));
+			temp.setXh(index.getAndIncrement() + "");
+			temp.setWuziTotal(BigDecimal.valueOf(temp.getYongLiang() * temp.getDanJia()));
 		}
 		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/研发项目管理/预算编制/直接投入费用.xls");
 		ExcelWriter excelWriter = EasyExcel.write(os)
@@ -126,12 +141,12 @@ public class YsZjtrfyServiceImpl extends BaseServiceImpl<YsZjtrfyMapper, YsZjtrf
 		Map<String, Object> map = new HashMap<>();
 
 		JSONObject xm = xmMapper.getXmmcAndXmbhByXmId(dto.getXmId());
-		map.put("xmmc", Func.notNull(xm)?xm.getString("xmmc"): "");
-		map.put("xmbh", Func.notNull(xm)?xm.getString("xmbh"): "");
+		map.put("xmmc", Func.notNull(xm) ? xm.getString("xmmc") : "");
+		map.put("xmbh", Func.notNull(xm) ? xm.getString("xmbh") : "");
 		excelWriter.fill(map, writeSheet);
 		excelWriter.finish();
 		if (templateFileName != null) {
 			templateFileName.close();
 		}
-    }
+	}
 }

+ 79 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/utils/DataImportUtils.java

@@ -0,0 +1,79 @@
+package org.sky.scientific.utils;
+
+import org.sky.core.tool.utils.StringUtil;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class DataImportUtils {
+	/**
+	 * 安全的日期转换
+	 */
+	public static Date safeParseDate(String dateStr, String pattern) {
+		if (StringUtil.isBlank(dateStr)) {
+			return null;
+		}
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+			sdf.setLenient(false);
+			return sdf.parse(dateStr.trim());
+		} catch (ParseException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * 安全的数字转换
+	 */
+	public static Integer safeParseInteger(String numberStr) {
+		if (StringUtil.isBlank(numberStr)) {
+			return null;
+		}
+		try {
+			return Integer.parseInt(numberStr.trim());
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static Double safeParseDouble(String numberStr) {
+		if (StringUtil.isBlank(numberStr)) {
+			return null;
+		}
+		try {
+			return Double.parseDouble(numberStr.trim());
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static BigDecimal safeParseBigDecimal(String numberStr) {
+		if (StringUtil.isBlank(numberStr)) {
+			return null;
+		}
+		try {
+			return new BigDecimal(numberStr.trim());
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * 验证日期格式
+	 */
+	public static boolean isValidDateFormat(String dateStr, String pattern) {
+		if (StringUtil.isBlank(dateStr)) {
+			return false;
+		}
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+			sdf.setLenient(false);
+			sdf.parse(dateStr.trim());
+			return true;
+		} catch (ParseException e) {
+			return false;
+		}
+	}
+}

+ 34 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/utils/ImportResult.java

@@ -0,0 +1,34 @@
+package org.sky.scientific.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.Collections;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class ImportResult {
+
+	private boolean success;
+	private String message;
+	private List<String> errors;
+	private Integer successCount;
+
+	public static ImportResult success(String message) {
+		return new ImportResult(true, message, null, null);
+	}
+
+	public static ImportResult success(String message, Integer count) {
+		return new ImportResult(true, message, null, count);
+	}
+
+	public static ImportResult error(String message, List<String> errors) {
+		return new ImportResult(false, message, errors, null);
+	}
+
+	public static ImportResult error(String error) {
+		return new ImportResult(false, "导入失败", Collections.singletonList(error), null);
+	}
+
+}

BIN
kd-service/kd-scientific/src/main/resources/export-template/研发项目管理/预算编制/直接投入费用.xls