1
0

2 Коммиты 89e9f00d7c ... 24f709fbda

Автор SHA1 Сообщение Дата
  WuYi 24f709fbda Merge remote-tracking branch 'origin/港大医院' into 港大医院 1 неделя назад
  WuYi 06a4919ed3 港大新增上传zip文件后自动解压并且生成文件夹 1 неделя назад

+ 11 - 0
ibps-comp-base-root/modules/comp-file-server-api/src/main/java/com/lc/ibps/file/server/api/IDownloadService.java

@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
 import com.lc.ibps.cloud.entity.APIResult;
 
 import java.util.HashMap;
+import java.util.Map;
 
 /** 文件下载
  *
@@ -39,6 +40,16 @@ public interface IDownloadService {
 			@NotBlank(message = "{com.lc.ibps.cloud.file.attachmentId}") 
 			@RequestParam(name = "attachmentId", required = true) String attachmentId);
 
+	/**
+	 * 解析zip文件
+	 *@param attachmentId
+	 */
+	@RequestMapping(value = "/unzip", method = {RequestMethod.GET})
+	public APIResult<Map<String,Object>> unzip(
+			@NotBlank(message = "{com.lc.ibps.cloud.file.attachmentId}")
+			@RequestParam(name = "attachmentId", required = true) String attachmentId,
+			@RequestParam(name = "attachmentId", required = true) String parentFolderId);
+
 	/**
 	 * 多个文件下载处理成zip
 	 * @param attachmentIds

+ 31 - 0
ibps-comp-base-root/modules/comp-file-server-api/src/main/java/com/lc/ibps/file/server/model/FolderNodeInfo.java

@@ -0,0 +1,31 @@
+package com.lc.ibps.file.server.model;
+
+public class FolderNodeInfo {
+    private String id;
+    private String path;
+    private int depth;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public int getDepth() {
+        return depth;
+    }
+
+    public void setDepth(int depth) {
+        this.depth = depth;
+    }
+}

+ 52 - 0
ibps-comp-base-root/modules/comp-file-server-api/src/main/java/com/lc/ibps/file/server/model/ZipImportStat.java

@@ -0,0 +1,52 @@
+package com.lc.ibps.file.server.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class ZipImportStat {
+
+    private int folderCount;
+
+    private int fileCount;
+
+    private List<Map<String, Object>> fileMapList = new ArrayList<>();
+
+    public int getFolderCount() {
+        return folderCount;
+    }
+
+    public void setFolderCount(int folderCount) {
+        this.folderCount = folderCount;
+    }
+
+    public int getFileCount() {
+        return fileCount;
+    }
+
+    public void setFileCount(int fileCount) {
+        this.fileCount = fileCount;
+    }
+
+    public List<Map<String, Object>> getFileMapList() {
+        return fileMapList;
+    }
+
+    public void setFileMapList(List<Map<String, Object>> fileMapList) {
+        this.fileMapList = fileMapList;
+    }
+
+    public void increaseFolderCount() {
+        this.folderCount++;
+    }
+
+    public void increaseFileCount() {
+        this.fileCount++;
+    }
+
+    public void addFileMap(Map<String, Object> fileMap) {
+        if (fileMap != null) {
+            this.fileMapList.add(fileMap);
+        }
+    }
+}

+ 749 - 2
ibps-comp-root/modules/comp-file-server/src/main/java/com/lc/ibps/cloud/file/provider/DownloadProvider.java

@@ -3,8 +3,9 @@ package com.lc.ibps.cloud.file.provider;
 import java.io.*;
 import java.net.URL;
 import java.net.URLEncoder;
-import java.util.Map;
-import java.util.Scanner;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -19,8 +20,12 @@ import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.lc.ibps.base.core.util.*;
 import com.lc.ibps.base.framework.id.UniqueIdUtil;
+import com.lc.ibps.base.framework.table.ICommonDao;
 import com.lc.ibps.cloud.redis.utils.RedisUtil;
 import com.lc.ibps.common.file.persistence.entity.AttachmentPo;
+import com.lc.ibps.file.server.model.FolderNodeInfo;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.jodconverter.OfficeDocumentConverter;
 import org.jodconverter.office.DefaultOfficeManagerBuilder;
 import org.jodconverter.office.OfficeException;
@@ -44,6 +49,35 @@ import com.lc.ibps.components.upload.baidu.ueditor.ActionEnter;
 import com.lc.ibps.components.upload.util.UploadUtil;
 import com.lc.ibps.file.server.api.IDownloadService;
 
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.lc.ibps.api.base.constants.StateEnum;
+import com.lc.ibps.api.base.file.FileInfo;
+import com.lc.ibps.base.core.util.BeanUtils;
+import com.lc.ibps.base.framework.id.UniqueIdUtil;
+import com.lc.ibps.base.framework.table.ICommonDao;
+import com.lc.ibps.base.web.context.ContextUtil;
+import com.lc.ibps.cloud.entity.APIResult;
+import com.lc.ibps.common.file.persistence.entity.AttachmentPo;
+import com.lc.ibps.components.upload.constants.FileParam;
+import com.lc.ibps.file.server.model.ZipImportStat;
+
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -62,10 +96,723 @@ import io.swagger.annotations.ExtensionProperty;
 @Api(tags = "文件下载", value = "文件下载")
 @Service
 public class DownloadProvider extends GenericUploadProvider implements IDownloadService{
+
+	@Autowired
+	private ICommonDao<?> commonDao;
+
+	private static final String FOLDER_TABLE = "ibps_cat_type";
+	private static final String KEY_WEN_JIAN_ID = "wen_jian_id_";
+	private static final String KEY_WEN_JIAN_JIA_ID = "wen_jian_jia_id_";
+	private static final String KEY_WEN_JIAN_MING = "wen_jian_ming";
+	private static final String KEY_WEN_JIAN_HAO = "wen_jian_hao_";
+	private static final String FOLDER_CATEGORY_KEY = "FILE_TYPE";
+
 	/*上传文件*/
 	@Autowired
 	private UploadProvider uploadFile;
 
+
+	@ApiOperation(value = "zip文件解析", notes = "zip文件解析")
+	@Override
+	public APIResult<Map<String, Object>> unzip(
+			@ApiParam(name = "attachmentId", value = "zip附件id", required = true)
+			@RequestParam(name = "attachmentId", required = true) String attachmentId,
+			@ApiParam(name = "parentFolderId", value = "父文件夹id", required = true)
+			@RequestParam(name = "parentFolderId", required = true) String parentFolderId) {
+
+		APIResult<Map<String, Object>> result = new APIResult<>();
+		Map<String, Object> map = new HashMap<>();
+
+		try {
+			if (StringUtils.isBlank(attachmentId)) {
+				result.setState(StateEnum.ERROR.getCode());
+				result.setMessage("zip附件id不能为空");
+				return result;
+			}
+
+			if (StringUtils.isBlank(parentFolderId)) {
+				result.setState(StateEnum.ERROR.getCode());
+				result.setMessage("父文件夹id不能为空");
+				return result;
+			}
+
+			this.getUploadService();
+			FileInfo fileInfo = uploadService.downloadFile(attachmentId);
+			if (BeanUtils.isEmpty(fileInfo)) {
+				result.setState(StateEnum.ERROR.getCode());
+				result.setMessage("zip文件不存在");
+				return result;
+			}
+
+			if (!"zip".equals(fileInfo.getExt())) {
+				logger.error("文件不是ZIP格式: {}", fileInfo.getFileName());
+				result.setState(StateEnum.ERROR.getCode());
+				result.setMessage("文件不是ZIP格式,请上传ZIP文件");
+				return result;
+			}
+
+			byte[] zipContent = getZipContent(fileInfo);
+			if (zipContent == null || zipContent.length == 0) {
+				logger.error("ZIP文件内容为空,attachmentId: {}", attachmentId);
+				result.setState(StateEnum.ERROR.getCode());
+				result.setMessage("ZIP文件内容为空");
+				return result;
+			}
+
+			ZipImportStat stat = importZipToDatabase(zipContent, parentFolderId);
+
+			map.put("folderCount", stat.getFolderCount());
+			map.put("fileCount", stat.getFileCount());
+			map.put("list", stat.getFileMapList());
+			map.put("attachmentId", attachmentId);
+			map.put("parentFolderId", parentFolderId);
+
+			result.setState(StateEnum.SUCCESS.getCode());
+			result.setMessage("ZIP文件解析成功");
+			result.setData(map);
+			return result;
+		} catch (Exception e) {
+			logger.error("/upload/unzip", e);
+			result.setState(StateEnum.ERROR.getCode());
+			result.setMessage("ZIP文件解析失败: " + e.getMessage());
+			return result;
+		}
+	}
+
+	private byte[] getZipContent(FileInfo fileInfo) throws IOException {
+		byte[] zipContent = fileInfo.getFileBytes();
+		if (zipContent != null && zipContent.length > 0) {
+			return zipContent;
+		}
+		if (StringUtils.isBlank(fileInfo.getFilePath())) {
+			return null;
+		}
+		Path filePath = Paths.get(fileInfo.getFilePath());
+		if (!Files.exists(filePath)) {
+			throw new FileNotFoundException("文件不存在: " + fileInfo.getFilePath());
+		}
+		return Files.readAllBytes(filePath);
+	}
+
+
+	private ZipImportStat importZipToDatabase(byte[] zipContent, String parentFolderId) throws IOException {
+		ZipImportStat stat = new ZipImportStat();
+
+		FolderNodeInfo rootFolder = queryFolderNodeById(parentFolderId);
+		if (rootFolder == null) {
+			rootFolder = new FolderNodeInfo();
+			rootFolder.setId(parentFolderId);
+			rootFolder.setPath(parentFolderId);
+			rootFolder.setDepth(0);
+		}
+
+		if (StringUtils.isBlank(rootFolder.getPath())) {
+			rootFolder.setPath(parentFolderId);
+		}
+
+		Map<String, FolderNodeInfo> folderCache = new HashMap<>();
+		folderCache.put("", rootFolder);
+
+		try (ByteArrayInputStream bais = new ByteArrayInputStream(zipContent);
+			 ZipInputStream zipInputStream = new ZipInputStream(bais, Charset.forName("GBK"))) {
+
+			ZipEntry entry;
+			while ((entry = zipInputStream.getNextEntry()) != null) {
+				try {
+					String entryName = normalizeZipEntryName(entry.getName());
+
+					if (StringUtils.isBlank(entryName) || isIgnoredZipEntry(entryName)) {
+						continue;
+					}
+
+					if (entry.isDirectory()) {
+						String folderPathInZip = trimTrailingSlash(entryName);
+						ensureFolderImported(folderPathInZip, rootFolder, folderCache, stat);
+						continue;
+					}
+
+					String parentPathInZip = getParentPathInZip(entryName);
+					FolderNodeInfo dbFolder = ensureFolderImported(parentPathInZip, rootFolder, folderCache, stat);
+
+					String zipFileName = getFileNameFromZipPath(entryName);
+					if (StringUtils.isNotBlank(zipFileName)) {
+						byte[] fileBytes = readZipEntryBytes(zipInputStream);
+						Map<String, Object> fileMap = insertFile(dbFolder.getId(), zipFileName, fileBytes);
+						stat.addFileMap(fileMap);
+						stat.increaseFileCount();
+					}
+				} finally {
+					zipInputStream.closeEntry();
+				}
+			}
+		}
+
+		logger.info("ZIP文件解析完成,新增文件夹数: {}, 新增文件数: {}", stat.getFolderCount(), stat.getFileCount());
+		return stat;
+	}
+
+/*
+	private ZipImportStat importZipToDatabase(byte[] zipContent, String parentFolderId) throws IOException {
+		ZipImportStat stat = new ZipImportStat();
+
+		String parentPath = queryFolderPathById(parentFolderId);
+		if (StringUtils.isBlank(parentPath)) {
+			parentPath = "";
+		}
+
+		Map<String, String> folderIdCache = new HashMap<>();
+		folderIdCache.put("", parentFolderId);
+
+		try (ByteArrayInputStream bais = new ByteArrayInputStream(zipContent);
+			 ZipInputStream zipInputStream = new ZipInputStream(bais, Charset.forName("GBK"))) { //指定GBK编码格式
+			// ZipInputStream zipInputStream = new ZipInputStream(bais)) {
+			 ZipEntry entry;
+			while ((entry = zipInputStream.getNextEntry()) != null) {
+				try {
+					String entryName = normalizeZipEntryName(entry.getName());
+
+					if (StringUtils.isBlank(entryName) || isIgnoredZipEntry(entryName)) {
+						continue;
+					}
+
+					if (entry.isDirectory()) {
+						String folderPathInZip = trimTrailingSlash(entryName);
+						ensureFolderImported(folderPathInZip, parentFolderId, parentPath, folderIdCache, stat);
+						continue;
+					}
+
+					String parentPathInZip = getParentPathInZip(entryName);
+					String dbFolderId = ensureFolderImported(parentPathInZip, parentFolderId, parentPath, folderIdCache, stat);
+
+					String zipFileName = getFileNameFromZipPath(entryName);
+					if (StringUtils.isNotBlank(zipFileName)) {
+						byte[] fileBytes = readZipEntryBytes(zipInputStream);
+						Map<String, Object> fileMap = insertFile(dbFolderId, zipFileName, fileBytes);
+						stat.addFileMap(fileMap);
+						stat.increaseFileCount();
+					}
+				} finally {
+					zipInputStream.closeEntry();
+				}
+			}
+		}
+
+		logger.info("ZIP文件解析完成,新增文件夹数: {}, 新增文件数: {}", stat.getFolderCount(), stat.getFileCount());
+		return stat;
+	}*/
+
+	private FolderNodeInfo ensureFolderImported(
+			String folderPathInZip,
+			FolderNodeInfo rootFolder,
+			Map<String, FolderNodeInfo> folderCache,
+			ZipImportStat stat) {
+
+		if (StringUtils.isBlank(folderPathInZip)) {
+			return rootFolder;
+		}
+
+		String normalizedFolderPath = trimTrailingSlash(folderPathInZip);
+		if (folderCache.containsKey(normalizedFolderPath)) {
+			return folderCache.get(normalizedFolderPath);
+		}
+
+		String[] names = normalizedFolderPath.split("/");
+		String currentZipPath = "";
+		FolderNodeInfo currentParent = rootFolder;
+
+		for (String name : names) {
+			if (StringUtils.isBlank(name)) {
+				continue;
+			}
+
+			currentZipPath = StringUtils.isBlank(currentZipPath) ? name : currentZipPath + "/" + name;
+
+			if (folderCache.containsKey(currentZipPath)) {
+				currentParent = folderCache.get(currentZipPath);
+				continue;
+			}
+
+			FolderNodeInfo existedFolder = querySameLevelFolder(currentParent.getId(), name);
+			if (existedFolder != null) {
+				folderCache.put(currentZipPath, existedFolder);
+				currentParent = existedFolder;
+				continue;
+			}
+
+			FolderNodeInfo newFolder = insertFolder(name, currentParent);
+			folderCache.put(currentZipPath, newFolder);
+			stat.increaseFolderCount();
+
+			currentParent = newFolder;
+		}
+
+		return folderCache.get(normalizedFolderPath);
+	}
+
+	/*
+	private String ensureFolderImported(String folderPathInZip,String rootParentFolderId,String rootParentPath,Map<String, String> folderIdCache,ZipImportStat stat) {
+
+		if (StringUtils.isBlank(folderPathInZip)) {
+			return rootParentFolderId;
+		}
+
+		String normalizedFolderPath = trimTrailingSlash(folderPathInZip);
+		if (folderIdCache.containsKey(normalizedFolderPath)) {
+			return folderIdCache.get(normalizedFolderPath);
+		}
+
+		String[] names = normalizedFolderPath.split("/");
+		String currentZipPath = "";
+		String currentParentId = rootParentFolderId;
+		String currentDbPath = rootParentPath;
+
+		for (String name : names) {
+			if (StringUtils.isBlank(name)) {
+				continue;
+			}
+
+			currentZipPath = StringUtils.isBlank(currentZipPath) ? name : currentZipPath + "/" + name;
+
+			if (folderIdCache.containsKey(currentZipPath)) {
+				currentParentId = folderIdCache.get(currentZipPath);
+				currentDbPath = buildDbPath(currentDbPath, name);
+				continue;
+			}
+			//转化windows读取路径时后缀的/
+			String newDbPath = buildDbPath(currentDbPath, name);
+
+			String newFolderId = insertFolder(name, currentParentId, currentDbPath);
+
+			folderIdCache.put(currentZipPath, newFolderId);
+			stat.increaseFolderCount();
+
+			currentParentId = newFolderId;
+			currentDbPath = newDbPath;
+		}
+
+		return folderIdCache.get(normalizedFolderPath);
+	}*/
+
+	private FolderNodeInfo insertFolder(String folderName, FolderNodeInfo parentFolder) {
+		String folderId = UniqueIdUtil.getId();
+		String folderPath = buildFolderIdPath(parentFolder.getPath(), parentFolder.getId(), folderId);
+		int depth = parentFolder.getDepth() + 1;
+
+		Map<String, Object> map = build(
+				"ID_", folderId,
+				"CATEGORY_KEY_", FOLDER_CATEGORY_KEY,
+				"NAME_", folderName,
+				"TYPE_KEY_", RandomStringUtils.randomAlphabetic(6),
+				"STRU_TYPE_", "1",
+				"PARENT_ID_", parentFolder.getId(),
+				"DEPTH_", depth,
+				"PATH_", folderPath,
+				"IS_LEAF_", "Y",
+				"OWNER_ID_", "0",
+				"TENANT_ID_", "-999",
+				"CREATE_BY_", "1",
+				"CREATE_TIME_", new Date(),
+				"UPDATE_TIME_", new Date()
+		);
+
+		String sql = "INSERT INTO " + FOLDER_TABLE + " ("
+				+ "ID_, CATEGORY_KEY_, NAME_, TYPE_KEY_, STRU_TYPE_, PARENT_ID_, DEPTH_, PATH_, "
+				+ "IS_LEAF_, OWNER_ID_, TENANT_ID_, CREATE_BY_, CREATE_TIME_, UPDATE_TIME_"
+				+ ") VALUES ("
+				+ "#{ID_}, #{CATEGORY_KEY_}, #{NAME_}, #{TYPE_KEY_}, #{STRU_TYPE_}, #{PARENT_ID_}, #{DEPTH_}, #{PATH_}, "
+				+ "#{IS_LEAF_}, #{OWNER_ID_}, #{TENANT_ID_}, #{CREATE_BY_}, #{CREATE_TIME_}, #{UPDATE_TIME_}"
+				+ ")";
+
+		try {
+			commonDao.executeOfMap(sql, map);
+			updateFolderNotLeaf(parentFolder.getId());
+		} catch (Exception e) {
+			logger.warn("添加文件夹数据失败: {}", map, e);
+			throw e;
+		}
+
+		FolderNodeInfo node = new FolderNodeInfo();
+		node.setId(folderId);
+		node.setPath(folderPath);
+		node.setDepth(depth);
+
+		logger.info("插入文件夹: id={}, name={}, parentId={}, path={}, depth={}",
+				folderId, folderName, parentFolder.getId(), folderPath, depth);
+		return node;
+	}
+
+	private FolderNodeInfo querySameLevelFolder(String parentId, String folderName) {
+		String sql = "SELECT ID_, PATH_, DEPTH_ FROM " + FOLDER_TABLE
+				+ " WHERE PARENT_ID_ = #{p0}"
+				+ " AND NAME_ = #{p1}"
+				+ " AND CATEGORY_KEY_ = #{p2}"
+				+ " LIMIT 1";
+
+		List<?> list = commonDao.query(sql, new Object[] { parentId, folderName, FOLDER_CATEGORY_KEY });
+
+		if (list == null || list.isEmpty() || list.get(0) == null) {
+			return null;
+		}
+
+		return toFolderNodeInfo(list.get(0));
+	}
+
+	private FolderNodeInfo queryFolderNodeById(String folderId) {
+		String sql = "SELECT ID_, PATH_, DEPTH_ FROM " + FOLDER_TABLE + " WHERE ID_ = #{p0}";
+		List<?> list = commonDao.query(sql, new Object[] { folderId });
+
+		if (list == null || list.isEmpty() || list.get(0) == null) {
+			return null;
+		}
+
+		return toFolderNodeInfo(list.get(0));
+	}
+
+	private void updateFolderNotLeaf(String folderId) {
+		if (StringUtils.isBlank(folderId)) {
+			return;
+		}
+
+		String sql = "UPDATE " + FOLDER_TABLE
+				+ " SET IS_LEAF_ = #{IS_LEAF_}, UPDATE_TIME_ = #{UPDATE_TIME_}"
+				+ " WHERE ID_ = #{ID_}";
+
+		Map<String, Object> params = build(
+				"ID_", folderId,
+				"IS_LEAF_", "N",
+				"UPDATE_TIME_", new Date()
+		);
+
+		commonDao.executeOfMap(sql, params);
+	}
+/*
+	private String buildFolderIdPath(String parentPath, String parentId, String currentId) {
+		String basePath = parentPath;
+
+		if (StringUtils.isBlank(basePath)) {
+			basePath = parentId;
+		}
+
+		if (StringUtils.isBlank(basePath)) {
+			return currentId;
+		}
+
+		if (basePath.endsWith("/")) {
+			return basePath + currentId;
+		}
+
+		return basePath + "/" + currentId;
+	}*/
+
+	private String buildFolderIdPath(String parentPath, String parentId, String currentId) {
+		String basePath = parentPath;
+
+		if (StringUtils.isBlank(basePath)) {
+			basePath = parentId;
+		}
+
+		if (StringUtils.isBlank(basePath)) {
+			return currentId + ".";
+		}
+
+		if (!basePath.endsWith(".")) {
+			basePath = basePath + ".";
+		}
+
+		return basePath + currentId + ".";
+	}
+	/*
+	private String insertFolder(String folderName, String parentId, String folderPath) {
+		String folderId = UniqueIdUtil.getId(); //当前节点的id,需要拼接在path上,防止前段后续读取不到层级结构
+		String fileType = "FILE_TYPE"; //文件固定分类
+		//先加数据,后面再来考录path的问题
+		Map<String, Object> map = build("ID_", folderId,
+										"CATEGORY_KEY_", fileType,
+										"name_", folderName,
+										"type_key_", RandomStringUtils.randomAlphabetic(6),
+										"stru_type_", "1",
+										"PARENT_ID_", parentId,
+										"DEPTH_", 6,
+										"PATH_", "",
+										"IS_LEAF_", "Y",
+										"OWNER_ID_", "0",
+										"TENANT_ID_", "-999",
+										"CREATE_BY_", "1",
+										"CREATE_TIME_",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),
+										"UPDATE_TIME_",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+		try {
+			commonDao.execute(this.buildInsertSql(map, "ibps_cat_type"));
+		}catch (Exception e){
+			logger.warn("添加文件夹数据失败:"+map);
+		}
+		logger.info("插入文件夹: id={}, name={}, parentId={}, path={}", folderId, folderName, parentId, folderPath);
+		return folderId;
+	}*/
+
+
+	private FolderNodeInfo toFolderNodeInfo(Object row) {
+		if (!(row instanceof Map)) {
+			return null;
+		}
+
+		Map<?, ?> rowMap = (Map<?, ?>) row;
+
+		FolderNodeInfo node = new FolderNodeInfo();
+		node.setId(toStringValue(getMapValue(rowMap, "ID_")));
+		node.setPath(toStringValue(getMapValue(rowMap, "PATH_")));
+		node.setDepth(toIntValue(getMapValue(rowMap, "DEPTH_")));
+
+		return StringUtils.isBlank(node.getId()) ? null : node;
+	}
+
+	private Object getMapValue(Map<?, ?> map, String key) {
+		Object value = map.get(key);
+		if (value != null) {
+			return value;
+		}
+
+		value = map.get(key.toLowerCase());
+		if (value != null) {
+			return value;
+		}
+
+		return map.get(key.toUpperCase());
+	}
+
+	private String toStringValue(Object value) {
+		return value == null ? "" : String.valueOf(value);
+	}
+
+	private int toIntValue(Object value) {
+		if (value == null) {
+			return 0;
+		}
+
+		if (value instanceof Number) {
+			return ((Number) value).intValue();
+		}
+
+		try {
+			return Integer.parseInt(String.valueOf(value));
+		} catch (Exception e) {
+			return 0;
+		}
+	}
+
+
+
+
+
+
+
+
+
+
+
+	private byte[] readZipEntryBytes(ZipInputStream zipInputStream) throws IOException {
+		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+		byte[] buffer = new byte[8192];
+		int len;
+
+		while ((len = zipInputStream.read(buffer)) != -1) {
+			outputStream.write(buffer, 0, len);
+		}
+
+		return outputStream.toByteArray();
+	}
+
+	private Map<String, Object> insertFile(String folderId, String fileName, byte[] fileBytes) throws IOException {
+		String fileId = UniqueIdUtil.getId();
+
+		Map<String, String> fileNameInfo = parseFileNameInfo(fileName);
+		String fileCode = fileNameInfo.get(KEY_WEN_JIAN_HAO);
+		String realFileName = fileNameInfo.get(KEY_WEN_JIAN_MING);
+		AttachmentPo attachmentPo = new AttachmentPo();
+		String attachmentId ="";
+		try {
+		this.getUploadService();
+		Map<String, Object> uploadParams = new HashMap<>();
+		uploadParams.put(FileParam.ORIGINAL_FILE_NAME, fileName);
+		uploadParams.put(FileParam.FILE_SIZE, fileBytes == null ? 0L : Long.valueOf(fileBytes.length));
+		uploadParams.put(FileParam.CUR_USER_ID, ContextUtil.getCurrentUser().getUserId());
+		uploadParams.put(FileParam.CUR_USER_ACCOUNT, ContextUtil.getCurrentUser().getAccount());
+		uploadParams.put(FileParam.CUR_USER_NAME, ContextUtil.getCurrentUser().getFullname());
+
+		 attachmentPo = uploadService.uploadFile(new ByteArrayInputStream(fileBytes), uploadParams);
+		if (attachmentPo == null || StringUtils.isBlank(attachmentPo.getId())) {
+			throw new RuntimeException("解压文件上传失败: " + fileName);
+		}
+
+		 attachmentId = attachmentPo.getId();
+	}catch (Exception e){
+		logger.warn("上传失败");
+	}
+		Map<String, Object> params = build("id_", fileId,
+											"wen_jian_ming_che", attachmentPo.getFileName(),
+											"wen_jian_bian_hao", fileCode,
+											"wen_jian_fen_lei_",folderId,
+											"attachmentid", attachmentId);
+		try {
+			commonDao.execute(this.buildInsertSql(params, "t_filetable"));
+		}catch (Exception e){
+			logger.warn("添加文件表数据失败");
+		}
+		Map<String, Object> fileResultMap = build(
+				KEY_WEN_JIAN_ID, fileId,
+				KEY_WEN_JIAN_JIA_ID, folderId,
+				KEY_WEN_JIAN_MING, realFileName,
+				KEY_WEN_JIAN_HAO, fileCode,
+				"attachment_id_", attachmentId
+		);
+
+		logger.info("插入文件: id={}, folderId={}, attachmentId={}, fileName={}, fileCode={}",
+				fileId, folderId, attachmentId, realFileName, fileCode);
+
+		return fileResultMap;
+	}
+
+	private Map<String, String> parseFileNameInfo(String fileName) {
+		Map<String, String> result = new HashMap<>();
+
+		if (StringUtils.isBlank(fileName)) {
+			result.put(KEY_WEN_JIAN_HAO, "");
+			result.put(KEY_WEN_JIAN_MING, "");
+			return result;
+		}
+
+		String normalizedFileName = fileName.trim();
+		String[] parts = normalizedFileName.split("\\s+", 2);
+
+		if (parts.length < 2 || StringUtils.isBlank(parts[0]) || StringUtils.isBlank(parts[1])) {
+			result.put(KEY_WEN_JIAN_HAO, "");
+			result.put(KEY_WEN_JIAN_MING, normalizedFileName);
+			return result;
+		}
+
+		result.put(KEY_WEN_JIAN_HAO, parts[0].trim());
+		result.put(KEY_WEN_JIAN_MING, parts[1].trim());
+		return result;
+	}
+/*
+	private String queryFolderPathById(String folderId) {
+		String sql = "SELECT path_ FROM " + FOLDER_TABLE + " WHERE id_ = #{p0}";
+		List<?> list = commonDao.query(sql, new Object[] { folderId });
+
+		if (list == null || list.isEmpty() || list.get(0) == null) {
+			return "";
+		}
+
+		Object row = list.get(0);
+		if (row instanceof Map) {
+			Map<?, ?> rowMap = (Map<?, ?>) row;
+			Object path = rowMap.get("path_");
+			if (path == null) {
+				path = rowMap.get("PATH_");
+			}
+			return path == null ? "" : String.valueOf(path);
+		}
+
+		return String.valueOf(row);
+	}
+*/
+
+	private String normalizeZipEntryName(String entryName) {
+		if (StringUtils.isBlank(entryName)) {
+			return "";
+		}
+
+		String normalized = entryName.replace("\\", "/").trim();
+
+		while (normalized.startsWith("/")) {
+			normalized = normalized.substring(1);
+		}
+
+		if (normalized.contains(":")) {
+			throw new IllegalArgumentException("ZIP条目路径非法: " + entryName);
+		}
+
+		String[] parts = normalized.split("/");
+		for (String part : parts) {
+			if ("..".equals(part) || ".".equals(part)) {
+				throw new IllegalArgumentException("ZIP条目路径非法: " + entryName);
+			}
+		}
+
+		return normalized;
+	}
+
+	private boolean isIgnoredZipEntry(String entryName) {
+		return entryName.startsWith("__MACOSX/")
+				|| entryName.endsWith(".DS_Store")
+				|| entryName.contains("/.DS_Store");
+	}
+
+	private String trimTrailingSlash(String path) {
+		if (StringUtils.isBlank(path)) {
+			return "";
+		}
+
+		String result = path;
+		while (result.endsWith("/")) {
+			result = result.substring(0, result.length() - 1);
+		}
+		return result;
+	}
+
+	private String getParentPathInZip(String entryName) {
+		int index = entryName.lastIndexOf('/');
+		if (index < 0) {
+			return "";
+		}
+		return entryName.substring(0, index);
+	}
+
+	private String getFileNameFromZipPath(String entryName) {
+		int index = entryName.lastIndexOf('/');
+		if (index < 0) {
+			return entryName;
+		}
+		return entryName.substring(index + 1);
+	}
+
+	/*
+	private String buildDbPath(String parentPath, String name) {
+		if (StringUtils.isBlank(parentPath)) {
+			return "/" + name;
+		}
+
+		if (parentPath.endsWith("/")) {
+			return parentPath + name;
+		}
+
+		return parentPath + "/" + name;
+	}*/
+
+	public Map<String,Object> build(Object... keyValues){
+		Map<String, Object> map = new HashMap<>();
+		for (int i = 0; i < keyValues.length; i += 2) {
+			map.put((String) keyValues[i], keyValues[i + 1]);
+		}
+		return map;
+	}
+
+	public String buildInsertSql(Map<String, Object> map , String tableName) throws Exception {
+
+		StringBuilder sql = new StringBuilder("insert into "+tableName+" (");
+		for (Object key : map.keySet()) {
+			sql.append(key).append(",");
+		}
+		sql.delete(sql.length()-1,sql.length());
+		sql.append(") values(");
+		for (Object val : map.values()) {
+			sql.append("'").append(val).append("'").append(",");
+		}
+		sql.delete(sql.length()-1,sql.length());
+		sql.append(")");
+		return sql.toString();
+	}
+
+
 	@ApiOperation(value = "文件下载", notes = "文件下载")
 	@Override
 	public void download(