|
@@ -1,7 +1,10 @@
|
|
|
package com.jeeplus.modules.WorkKnowledgeBase.service;
|
|
package com.jeeplus.modules.WorkKnowledgeBase.service;
|
|
|
|
|
|
|
|
|
|
+import com.jeeplus.common.config.Global;
|
|
|
|
|
+import com.jeeplus.common.oss.OSSClientUtil;
|
|
|
import com.jeeplus.common.persistence.Page;
|
|
import com.jeeplus.common.persistence.Page;
|
|
|
import com.jeeplus.common.service.CrudService;
|
|
import com.jeeplus.common.service.CrudService;
|
|
|
|
|
+import com.jeeplus.common.utils.DateUtils;
|
|
|
import com.jeeplus.common.utils.IdGen;
|
|
import com.jeeplus.common.utils.IdGen;
|
|
|
import com.jeeplus.common.utils.StringUtils;
|
|
import com.jeeplus.common.utils.StringUtils;
|
|
|
import com.jeeplus.modules.WorkKnowledgeBase.dao.*;
|
|
import com.jeeplus.modules.WorkKnowledgeBase.dao.*;
|
|
@@ -16,7 +19,14 @@ import com.jeeplus.modules.workprojectnotify.util.UtilNotify;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
-
|
|
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileInputStream;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.net.HttpURLConnection;
|
|
|
|
|
+import java.net.URL;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
import java.util.Arrays;
|
|
|
import java.util.Date;
|
|
import java.util.Date;
|
|
@@ -35,6 +45,8 @@ import java.util.Set;
|
|
|
@Transactional(readOnly = true)
|
|
@Transactional(readOnly = true)
|
|
|
public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBaseShareInfoDao, WorkKnowledgeBaseShareInfo> {
|
|
public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBaseShareInfoDao, WorkKnowledgeBaseShareInfo> {
|
|
|
|
|
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(WorkKnowledgeBaseShareService.class);
|
|
|
|
|
+
|
|
|
/** 附件标识(attachmentFlag),进行隔离不同业务的附件 */
|
|
/** 附件标识(attachmentFlag),进行隔离不同业务的附件 */
|
|
|
private static final String ATTACHMENT_FLAG = "199";
|
|
private static final String ATTACHMENT_FLAG = "199";
|
|
|
|
|
|
|
@@ -414,6 +426,16 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
boolean hasValidAttachment = hasValidAttachment(attachments);
|
|
boolean hasValidAttachment = hasValidAttachment(attachments);
|
|
|
|
|
+ // 调试日志:输出附件判定结果
|
|
|
|
|
+ logger.info("saveShare附件判定 - isNew:" + isNew + ", attachments:" + (attachments != null ? attachments.size() : "null")
|
|
|
|
|
+ + ", hasValidAttachment:" + hasValidAttachment
|
|
|
|
|
+ + ", treeNodeId:" + shareInfo.getTreeNodeId());
|
|
|
|
|
+ if (attachments != null) {
|
|
|
|
|
+ for (int idx = 0; idx < attachments.size(); idx++) {
|
|
|
|
|
+ Workattachment att = attachments.get(idx);
|
|
|
|
|
+ logger.info(" attachment[" + idx + "] id='" + att.getId() + "', url='" + att.getUrl() + "', delFlag='" + att.getDelFlag() + "'");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
// 编辑时,若前端未提交附件列表(用户未更换文件),回查DB已有附件进行判定
|
|
// 编辑时,若前端未提交附件列表(用户未更换文件),回查DB已有附件进行判定
|
|
|
if (!isNew && (attachments == null || attachments.isEmpty()) && StringUtils.isNotBlank(shareInfo.getId())) {
|
|
if (!isNew && (attachments == null || attachments.isEmpty()) && StringUtils.isNotBlank(shareInfo.getId())) {
|
|
|
List<Workattachment> existingAttachments = findAttachmentsByFileId(shareInfo.getId());
|
|
List<Workattachment> existingAttachments = findAttachmentsByFileId(shareInfo.getId());
|
|
@@ -445,10 +467,20 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
|
|
|
shareInfo.setAuditStartDate(new Date());
|
|
shareInfo.setAuditStartDate(new Date());
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- // 编辑:仅当状态为草稿(0)或审核未通过(4)时允许重新判定
|
|
|
|
|
|
|
+ // 编辑:仅当状态为草稿(0)、审核未通过(4)或未设置(null)时允许重新判定
|
|
|
String currentStatus = shareInfo.getAuditStatus();
|
|
String currentStatus = shareInfo.getAuditStatus();
|
|
|
- if (currentStatus != null && (currentStatus.equals(AUDIT_STATUS_DRAFT) || currentStatus.equals(AUDIT_STATUS_REJECTED))) {
|
|
|
|
|
|
|
+ // 表单未提交auditStatus时,从数据库查询当前真实状态
|
|
|
|
|
+ if (StringUtils.isBlank(currentStatus) && StringUtils.isNotBlank(shareInfo.getId())) {
|
|
|
|
|
+ WorkKnowledgeBaseShareInfo dbRecord = dao.get(shareInfo.getId());
|
|
|
|
|
+ if (dbRecord != null) {
|
|
|
|
|
+ currentStatus = dbRecord.getAuditStatus();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.info("编辑模式 - currentStatus:'" + currentStatus + "', isQaCategory:" + isQaCategory + ", isInternalDocRootNode:" + isInternalDocRootNode);
|
|
|
|
|
+ // currentStatus为null时视为未初始化,允许重新判定;草稿(0)和审核未通过(4)也允许重新判定
|
|
|
|
|
+ if (currentStatus == null || currentStatus.equals(AUDIT_STATUS_DRAFT) || currentStatus.equals(AUDIT_STATUS_REJECTED)) {
|
|
|
shareInfo.setAuditStatus(hasValidAttachment ? AUDIT_STATUS_PENDING : AUDIT_STATUS_DRAFT);
|
|
shareInfo.setAuditStatus(hasValidAttachment ? AUDIT_STATUS_PENDING : AUDIT_STATUS_DRAFT);
|
|
|
|
|
+ logger.info("状态重新判定 - 新auditStatus:'" + shareInfo.getAuditStatus() + "'");
|
|
|
shareInfo.setAuditPassCount(0);
|
|
shareInfo.setAuditPassCount(0);
|
|
|
shareInfo.setAuditRejectCount(0);
|
|
shareInfo.setAuditRejectCount(0);
|
|
|
// 重新提交时更新提交审核人为当前登录人,并刷新本轮审核起始时间(历史审核记录依靠该时间隔离)
|
|
// 重新提交时更新提交审核人为当前登录人,并刷新本轮审核起始时间(历史审核记录依靠该时间隔离)
|
|
@@ -696,16 +728,25 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 判断附件列表中是否存在有效(未删除的)附件
|
|
* 判断附件列表中是否存在有效(未删除的)附件
|
|
|
|
|
+ * 包括两种情况:
|
|
|
|
|
+ * 1. 已有附件:有id且delFlag为正常(0)
|
|
|
|
|
+ * 2. 新上传附件:id为空/null但url不为空(尚未持久化,delFlag可能未设置)
|
|
|
*/
|
|
*/
|
|
|
private boolean hasValidAttachment(List<Workattachment> attachments) {
|
|
private boolean hasValidAttachment(List<Workattachment> attachments) {
|
|
|
if (attachments == null || attachments.isEmpty()) {
|
|
if (attachments == null || attachments.isEmpty()) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
for (Workattachment attachment : attachments) {
|
|
for (Workattachment attachment : attachments) {
|
|
|
- if (attachment.getId() != null
|
|
|
|
|
|
|
+ // 已有附件:有id且未删除
|
|
|
|
|
+ if (attachment.getId() != null && !attachment.getId().isEmpty()
|
|
|
&& Workattachment.DEL_FLAG_NORMAL.equals(attachment.getDelFlag())) {
|
|
&& Workattachment.DEL_FLAG_NORMAL.equals(attachment.getDelFlag())) {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
+ // 新上传附件:id为空但url有值(尚未持久化)
|
|
|
|
|
+ if ((attachment.getId() == null || attachment.getId().isEmpty())
|
|
|
|
|
+ && StringUtils.isNotBlank(attachment.getUrl())) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
@@ -1844,76 +1885,126 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 导入知识库数据
|
|
|
|
|
- * @param dataList Excel解析后的数据列表
|
|
|
|
|
|
|
+ * 导入知识库数据(动态表头匹配)
|
|
|
|
|
+ * @param headers Excel表头列名列表
|
|
|
|
|
+ * @param dataRows Excel数据行(每行为单元格值列表)
|
|
|
* @param rootId 根节点id
|
|
* @param rootId 根节点id
|
|
|
* @return 导入结果描述
|
|
* @return 导入结果描述
|
|
|
*/
|
|
*/
|
|
|
@Transactional(readOnly = false)
|
|
@Transactional(readOnly = false)
|
|
|
- public String importData(List<WorkKnowledgeBaseImportInfo> dataList, String rootId) {
|
|
|
|
|
- if (dataList == null || dataList.isEmpty()) {
|
|
|
|
|
|
|
+ public String importData(List<String> headers, List<List<String>> dataRows, String rootId) {
|
|
|
|
|
+ if (dataRows == null || dataRows.isEmpty()) {
|
|
|
return "导入数据为空!";
|
|
return "导入数据为空!";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 查询该根节点下的动态字段配置,用于将导入字段映射为动态字段id
|
|
|
|
|
|
|
+ // 1. 从表头中定位“所属节点名称”和“标准名称”列的索引
|
|
|
|
|
+ int nodeNameColIdx = -1;
|
|
|
|
|
+ int nameColIdx = -1;
|
|
|
|
|
+ for (int i = 0; i < headers.size(); i++) {
|
|
|
|
|
+ String h = headers.get(i).trim();
|
|
|
|
|
+ if ("所属节点名称".equals(h)) {
|
|
|
|
|
+ nodeNameColIdx = i;
|
|
|
|
|
+ } else if ("标准名称".equals(h)) {
|
|
|
|
|
+ nameColIdx = i;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (nameColIdx == -1) {
|
|
|
|
|
+ return "导入失败:表头中未找到“标准名称”列!";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询该根节点下的动态字段配置,构建 fieldLabel -> dynamicFieldId 映射
|
|
|
List<WorkKnowledgeBaseDynamicInfo> dynamicFields = dynamicInfoDao.findByRootId(rootId);
|
|
List<WorkKnowledgeBaseDynamicInfo> dynamicFields = dynamicInfoDao.findByRootId(rootId);
|
|
|
- // 构建 fieldKey -> fieldId 的映射
|
|
|
|
|
- Map<String, String> fieldKeyToIdMap = new HashMap<>();
|
|
|
|
|
- for (WorkKnowledgeBaseDynamicInfo field : dynamicFields) {
|
|
|
|
|
- fieldKeyToIdMap.put(field.getFieldKey(), field.getId());
|
|
|
|
|
|
|
+ Map<String, String> labelToFieldIdMap = new HashMap<>();
|
|
|
|
|
+ // 记录每个动态字段在表头中的列索引(-1表示该列不在表头中)
|
|
|
|
|
+ Map<String, Integer> fieldIdToColIdxMap = new HashMap<>();
|
|
|
|
|
+ if (dynamicFields != null) {
|
|
|
|
|
+ for (WorkKnowledgeBaseDynamicInfo field : dynamicFields) {
|
|
|
|
|
+ labelToFieldIdMap.put(field.getFieldLabel(), field.getId());
|
|
|
|
|
+ fieldIdToColIdxMap.put(field.getId(), -1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 遍历表头,将列名与动态字段的fieldLabel进行精确匹配
|
|
|
|
|
+ for (int i = 0; i < headers.size(); i++) {
|
|
|
|
|
+ String h = headers.get(i).trim();
|
|
|
|
|
+ if ("所属节点名称".equals(h) || "标准名称".equals(h)) {
|
|
|
|
|
+ continue; // 跳过固定列
|
|
|
|
|
+ }
|
|
|
|
|
+ String fieldId = labelToFieldIdMap.get(h);
|
|
|
|
|
+ if (fieldId != null) {
|
|
|
|
|
+ fieldIdToColIdxMap.put(fieldId, i);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int successCount = 0;
|
|
int successCount = 0;
|
|
|
int failCount = 0;
|
|
int failCount = 0;
|
|
|
StringBuilder failureMsg = new StringBuilder();
|
|
StringBuilder failureMsg = new StringBuilder();
|
|
|
|
|
|
|
|
- for (int i = 0; i < dataList.size(); i++) {
|
|
|
|
|
- WorkKnowledgeBaseImportInfo importInfo = dataList.get(i);
|
|
|
|
|
|
|
+ for (int i = 0; i < dataRows.size(); i++) {
|
|
|
|
|
+ List<String> rowData = dataRows.get(i);
|
|
|
try {
|
|
try {
|
|
|
- // 1. 根据体系节点名称查找对应的树节点id
|
|
|
|
|
- String systemNodeName = importInfo.getSystemNodeName();
|
|
|
|
|
- if (StringUtils.isBlank(systemNodeName)) {
|
|
|
|
|
- failCount++;
|
|
|
|
|
- failureMsg.append("<br/>第").append(i + 2).append("行:体系节点名称为空,跳过;");
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+ // 3. 获取所属节点名称
|
|
|
|
|
+ String nodeName = "";
|
|
|
|
|
+ if (nodeNameColIdx >= 0 && nodeNameColIdx < rowData.size()) {
|
|
|
|
|
+ nodeName = rowData.get(nodeNameColIdx).trim();
|
|
|
}
|
|
}
|
|
|
- String treeNodeId = treeInfoDao.findIdByTreeName(systemNodeName.trim());
|
|
|
|
|
- if (StringUtils.isBlank(treeNodeId)) {
|
|
|
|
|
- failCount++;
|
|
|
|
|
- failureMsg.append("<br/>第").append(i + 2).append("行:未找到体系节点[").append(systemNodeName).append("],跳过;");
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 查找对应的树节点id
|
|
|
|
|
+ String treeNodeId;
|
|
|
|
|
+ if (nodeName.isEmpty()) {
|
|
|
|
|
+ // 所属节点为空,放入根节点下
|
|
|
|
|
+ treeNodeId = rootId;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ List<String> matchedIds = treeInfoDao.findIdsByTreeNameAndRootId(nodeName, rootId);
|
|
|
|
|
+ if (matchedIds == null || matchedIds.isEmpty()) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:未找到节点[").append(nodeName).append("],跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ } else if (matchedIds.size() > 1) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:节点[").append(nodeName).append("]存在多个同名节点,无法确定归属,请修改节点名称使其唯一,跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ treeNodeId = matchedIds.get(0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 标准名称为必填项
|
|
|
|
|
- String standardName = importInfo.getStandardName();
|
|
|
|
|
- if (StringUtils.isBlank(standardName)) {
|
|
|
|
|
|
|
+ // 5. 标准名称为必填项
|
|
|
|
|
+ String name = "";
|
|
|
|
|
+ if (nameColIdx < rowData.size()) {
|
|
|
|
|
+ name = rowData.get(nameColIdx).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(name)) {
|
|
|
failCount++;
|
|
failCount++;
|
|
|
failureMsg.append("<br/>第").append(i + 2).append("行:标准名称为空,跳过;");
|
|
failureMsg.append("<br/>第").append(i + 2).append("行:标准名称为空,跳过;");
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 3. 新增 work_knowledge_base_share_info 记录
|
|
|
|
|
|
|
+ // 6. 新增 work_knowledge_base_share_info 记录
|
|
|
WorkKnowledgeBaseShareInfo shareInfo = new WorkKnowledgeBaseShareInfo();
|
|
WorkKnowledgeBaseShareInfo shareInfo = new WorkKnowledgeBaseShareInfo();
|
|
|
shareInfo.setIsNewRecord(true);
|
|
shareInfo.setIsNewRecord(true);
|
|
|
shareInfo.setId(IdGen.uuid());
|
|
shareInfo.setId(IdGen.uuid());
|
|
|
shareInfo.setTreeNodeId(treeNodeId);
|
|
shareInfo.setTreeNodeId(treeNodeId);
|
|
|
- shareInfo.setName(standardName.trim());
|
|
|
|
|
|
|
+ shareInfo.setName(name);
|
|
|
shareInfo.preInsert();
|
|
shareInfo.preInsert();
|
|
|
dao.insert(shareInfo);
|
|
dao.insert(shareInfo);
|
|
|
|
|
|
|
|
- // 4. 将其余字段作为动态字段值插入
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "SerialNumber", importInfo.getStandardSystemCode(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "StandardNum", importInfo.getStandardNoOrDocumentNo(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "ForwardingNum", importInfo.getTransmitDocumentNo(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "IssuingDep", importInfo.getPublishDepartment(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "EffectiveDate", importInfo.getEffectiveDate() != null ? new java.text.SimpleDateFormat("yyyy-MM-dd").format(importInfo.getEffectiveDate()) : "", fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "StandardLevel", importInfo.getStandardLevel(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "VoltageLevel", importInfo.getVoltageLevel(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "ProjectType", importInfo.getProjectType(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "PreparationStatus", importInfo.getCompileStatus(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "SupersededStandardNum", importInfo.getReplacedStandardNoAndName(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "StandardRelationship", importInfo.getStandardRelation(), fieldKeyToIdMap);
|
|
|
|
|
- insertDynamicValue(shareInfo.getId(), "remarks", importInfo.getRemark(), fieldKeyToIdMap);
|
|
|
|
|
|
|
+ // 7. 将动态列的值插入
|
|
|
|
|
+ for (WorkKnowledgeBaseDynamicInfo field : dynamicFields) {
|
|
|
|
|
+ Integer colIdx = fieldIdToColIdxMap.get(field.getId());
|
|
|
|
|
+ if (colIdx == null || colIdx == -1) continue;
|
|
|
|
|
+ String value = "";
|
|
|
|
|
+ if (colIdx < rowData.size()) {
|
|
|
|
|
+ value = rowData.get(colIdx).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isNotBlank(value)) {
|
|
|
|
|
+ WorkKnowledgeBaseDynamicValueInfo valueInfo = new WorkKnowledgeBaseDynamicValueInfo();
|
|
|
|
|
+ valueInfo.setId(IdGen.uuid());
|
|
|
|
|
+ valueInfo.setFileId(shareInfo.getId());
|
|
|
|
|
+ valueInfo.setDynamicFieldId(field.getId());
|
|
|
|
|
+ valueInfo.setFieldValue(value);
|
|
|
|
|
+ valueInfo.preInsert();
|
|
|
|
|
+ dynamicValueInfoDao.insert(valueInfo);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
successCount++;
|
|
successCount++;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -1930,6 +2021,230 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 批量导入文件附件(通过URL方式)
|
|
|
|
|
+ * 根据Excel中的“标准名称”匹配已有记录,从“文件完整路径”列的外部URL下载文件,
|
|
|
|
|
+ * 上传到当前系统OSS后创建附件记录,
|
|
|
|
|
+ * 同时更新记录的审核状态为未审核(1)、内容属性为原创(0)
|
|
|
|
|
+ * 该方法独立于importData,不影响现有导入逻辑
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param headers Excel表头列名列表
|
|
|
|
|
+ * @param dataRows Excel数据行(每行为单元格值列表)
|
|
|
|
|
+ * @param rootId 根节点id
|
|
|
|
|
+ * @return 导入结果描述
|
|
|
|
|
+ */
|
|
|
|
|
+ @Transactional(readOnly = false)
|
|
|
|
|
+ public String importFilesByUrl(List<String> headers, List<List<String>> dataRows, String rootId) {
|
|
|
|
|
+ if (dataRows == null || dataRows.isEmpty()) {
|
|
|
|
|
+ return "导入数据为空!";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 从表头中定位“标准名称”和“文件完整路径”列的索引
|
|
|
|
|
+ int nameColIdx = -1;
|
|
|
|
|
+ int filePathColIdx = -1;
|
|
|
|
|
+ for (int i = 0; i < headers.size(); i++) {
|
|
|
|
|
+ String h = headers.get(i).trim();
|
|
|
|
|
+ if ("标准名称".equals(h)) {
|
|
|
|
|
+ nameColIdx = i;
|
|
|
|
|
+ } else if ("文件完整路径".equals(h)) {
|
|
|
|
|
+ filePathColIdx = i;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (nameColIdx == -1) {
|
|
|
|
|
+ return "导入失败:表头中未找到“标准名称”列!";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filePathColIdx == -1) {
|
|
|
|
|
+ return "导入失败:表头中未找到“文件完整路径”列!";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int successCount = 0;
|
|
|
|
|
+ int failCount = 0;
|
|
|
|
|
+ StringBuilder failureMsg = new StringBuilder();
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < dataRows.size(); i++) {
|
|
|
|
|
+ List<String> rowData = dataRows.get(i);
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 2. 获取标准名称
|
|
|
|
|
+ String name = "";
|
|
|
|
|
+ if (nameColIdx < rowData.size()) {
|
|
|
|
|
+ name = rowData.get(nameColIdx).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(name)) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:标准名称为空,跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 获取文件完整路径(外部系统URL)
|
|
|
|
|
+ String fileUrl = "";
|
|
|
|
|
+ if (filePathColIdx < rowData.size()) {
|
|
|
|
|
+ fileUrl = rowData.get(filePathColIdx).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(fileUrl)) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:文件完整路径为空,跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 根据名称和rootId查找匹配的记录
|
|
|
|
|
+ List<WorkKnowledgeBaseShareInfo> matchedRecords = dao.findByNameAndRootId(name, rootId);
|
|
|
|
|
+ if (matchedRecords == null || matchedRecords.isEmpty()) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:未找到名称为[").append(name).append("]的记录,跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (matchedRecords.size() > 1) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行:名称[").append(name).append("]存在多条记录,无法确定唯一目标,跳过;");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WorkKnowledgeBaseShareInfo record = matchedRecords.get(0);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 从路径中提取原始文件名和文件类型(兼容本地路径和URL)
|
|
|
|
|
+ String originalFileName = fileUrl;
|
|
|
|
|
+ // 去除URL参数(?后面的部分)
|
|
|
|
|
+ if (originalFileName.contains("?")) {
|
|
|
|
|
+ originalFileName = originalFileName.substring(0, originalFileName.indexOf("?"));
|
|
|
|
|
+ }
|
|
|
|
|
+ // 提取文件名(兼容 / 和 \ 两种路径分隔符)
|
|
|
|
|
+ if (originalFileName.contains("/")) {
|
|
|
|
|
+ originalFileName = originalFileName.substring(originalFileName.lastIndexOf("/") + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (originalFileName.contains("\\")) {
|
|
|
|
|
+ originalFileName = originalFileName.substring(originalFileName.lastIndexOf("\\") + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ // URL解码(处理中文文件名等编码情况)
|
|
|
|
|
+ try {
|
|
|
|
|
+ originalFileName = java.net.URLDecoder.decode(originalFileName, "UTF-8");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 解码失败则使用原始名称
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtils.isBlank(originalFileName)) {
|
|
|
|
|
+ originalFileName = "unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+ String fileType = "";
|
|
|
|
|
+ if (originalFileName.contains(".")) {
|
|
|
|
|
+ fileType = originalFileName.substring(originalFileName.lastIndexOf(".") + 1).toLowerCase();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 读取文件流并上传到OSS(支持本地路径和HTTP URL两种方式,兼容Windows/Linux)
|
|
|
|
|
+ InputStream fileStream = null;
|
|
|
|
|
+ HttpURLConnection connection = null;
|
|
|
|
|
+ String ossFullUrl;
|
|
|
|
|
+ boolean isLocalFile = false;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 判定是本地路径还是HTTP URL
|
|
|
|
|
+ String trimmedPath = fileUrl.trim();
|
|
|
|
|
+ // 获取当前操作系统类型
|
|
|
|
|
+ String osName = System.getProperty("os.name", "").toLowerCase();
|
|
|
|
|
+ boolean isWindows = osName.contains("win");
|
|
|
|
|
+
|
|
|
|
|
+ if (trimmedPath.startsWith("http://") || trimmedPath.startsWith("https://")) {
|
|
|
|
|
+ // HTTP/HTTPS URL:任何系统都走网络下载
|
|
|
|
|
+ isLocalFile = false;
|
|
|
|
|
+ } else if (trimmedPath.startsWith("file://")) {
|
|
|
|
|
+ // file:// 协议:任何系统都走本地读取
|
|
|
|
|
+ isLocalFile = true;
|
|
|
|
|
+ } else if (isWindows) {
|
|
|
|
|
+ // Windows系统:识别盘符开头的路径(如 D:\ 或 C:/)
|
|
|
|
|
+ isLocalFile = trimmedPath.matches("^[A-Za-z]:[/\\\\].*");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Linux/Mac系统:识别以 / 开头的绝对路径
|
|
|
|
|
+ isLocalFile = trimmedPath.startsWith("/");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isLocalFile) {
|
|
|
|
|
+ // ========== 本地文件路径:直接读取 ==========
|
|
|
|
|
+ String localPath = trimmedPath;
|
|
|
|
|
+ if (localPath.startsWith("file://")) {
|
|
|
|
|
+ localPath = localPath.substring("file://".length());
|
|
|
|
|
+ }
|
|
|
|
|
+ File localFile = new File(localPath);
|
|
|
|
|
+ if (!localFile.exists()) {
|
|
|
|
|
+ throw new RuntimeException("本地文件不存在:" + localPath + "(当前系统:" + osName + ")");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!localFile.isFile()) {
|
|
|
|
|
+ throw new RuntimeException("路径不是文件:" + localPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ fileStream = new FileInputStream(localFile);
|
|
|
|
|
+ logger.info("读取本地文件[{}]:{}", osName, localPath);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // ========== HTTP URL:通过HTTP下载 ==========
|
|
|
|
|
+ URL url = new URL(fileUrl);
|
|
|
|
|
+ connection = (HttpURLConnection) url.openConnection();
|
|
|
|
|
+ connection.setRequestMethod("GET");
|
|
|
|
|
+ connection.setConnectTimeout(30000);
|
|
|
|
|
+ connection.setReadTimeout(60000);
|
|
|
|
|
+ connection.setRequestProperty("User-Agent", "Mozilla/5.0");
|
|
|
|
|
+ connection.setRequestProperty("Accept", "*/*");
|
|
|
|
|
+
|
|
|
|
|
+ int responseCode = connection.getResponseCode();
|
|
|
|
|
+ if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
|
|
|
+ throw new RuntimeException("外部文件下载失败,HTTP状态码:" + responseCode);
|
|
|
|
|
+ }
|
|
|
|
|
+ fileStream = connection.getInputStream();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建OSS存储路径
|
|
|
|
|
+ String datePath = DateUtils.formatDate(new Date(), "yyyy/MM/dd/");
|
|
|
|
|
+ String ossDir = "attachment-file/workKnowledgeBaseShareInfo/" + datePath;
|
|
|
|
|
+ String ossFileName = System.currentTimeMillis() + "_" + originalFileName;
|
|
|
|
|
+
|
|
|
|
|
+ // 上传到OSS
|
|
|
|
|
+ OSSClientUtil ossUtil = new OSSClientUtil();
|
|
|
|
|
+ ossUtil.uploadFile2OSS(fileStream, ossDir, ossFileName);
|
|
|
|
|
+
|
|
|
|
|
+ // 拼接完整OSS访问URL
|
|
|
|
|
+ ossFullUrl = Global.getAliyunUrl() + "/" + ossDir + ossFileName;
|
|
|
|
|
+ logger.info("文件上传OSS成功 - 原始路径:{}, OSS路径:{}", fileUrl, ossFullUrl);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (fileStream != null) {
|
|
|
|
|
+ try { fileStream.close(); } catch (Exception ignored) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ if (connection != null) {
|
|
|
|
|
+ connection.disconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 创建附件记录(使用OSS URL)
|
|
|
|
|
+ Workattachment attachment = new Workattachment();
|
|
|
|
|
+ attachment.setUrl(ossFullUrl);
|
|
|
|
|
+ attachment.setAttachmentName(originalFileName);
|
|
|
|
|
+ attachment.setType(fileType);
|
|
|
|
|
+ attachment.setAttachmentId(record.getId());
|
|
|
|
|
+ attachment.setAttachmentFlag(ATTACHMENT_FLAG);
|
|
|
|
|
+ attachment.setAttachmentUser(UserUtils.getUser().getId());
|
|
|
|
|
+ attachment.setCompanyId(UserUtils.getSelectCompany().getId());
|
|
|
|
|
+ attachment.setDelFlag(Workattachment.DEL_FLAG_NORMAL);
|
|
|
|
|
+ workattachmentService.insertOnWorkAttachment(attachment);
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 更新记录的审核状态为未审核(1)、内容属性为原创(0)
|
|
|
|
|
+ record.setAuditStatus(AUDIT_STATUS_PENDING);
|
|
|
|
|
+ record.setContentAttribute("0");
|
|
|
|
|
+ record.setAuditPassCount(0);
|
|
|
|
|
+ record.setAuditRejectCount(0);
|
|
|
|
|
+ record.setSubmitAuditUserId(UserUtils.getUser().getId());
|
|
|
|
|
+ record.setAuditStartDate(new Date());
|
|
|
|
|
+ record.preUpdate();
|
|
|
|
|
+ dao.update(record);
|
|
|
|
|
+
|
|
|
|
|
+ successCount++;
|
|
|
|
|
+ logger.info("文件导入成功 - 记录名称:{}, 原始URL:{}, OSS地址:{}", name, fileUrl, ossFullUrl);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ failureMsg.append("<br/>第").append(i + 2).append("行导入失败:").append(e.getMessage()).append(";");
|
|
|
|
|
+ logger.error("文件导入异常 - 第{}行", i + 2, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String resultMsg = "成功导入 " + successCount + " 条文件记录";
|
|
|
|
|
+ if (failCount > 0) {
|
|
|
|
|
+ resultMsg += ",失败 " + failCount + " 条:" + failureMsg.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ return resultMsg;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
* 插入单个动态字段值
|
|
* 插入单个动态字段值
|
|
|
*/
|
|
*/
|
|
|
private void insertDynamicValue(String fileId, String fieldKey, String fieldValue, Map<String, String> fieldKeyToIdMap) {
|
|
private void insertDynamicValue(String fileId, String fieldKey, String fieldValue, Map<String, String> fieldKeyToIdMap) {
|