This commit is contained in:
2025-12-17 15:05:29 +08:00
21 changed files with 663 additions and 227 deletions

View File

@@ -51,6 +51,12 @@ public enum FileBizTypeEnum {
@Schema(description = "交付物文件", example = "8")
DELIVERABLE_FILE(8),
/**
* 交付物文件
*/
@Schema(description = "试验结果文件", example = "9")
EXPERIMENT_FILE(9),
/**
* 项目文件
*/

View File

@@ -47,7 +47,7 @@ public interface FileMetadataConvert {
@Named("childrenResp")
@Mappings({
@Mapping(target = "children", ignore = true),
@Mapping(target = "mergeSameNameChildren", ignore = true),
@Mapping(target = "totalName", ignore = true),
@Mapping(target = "fileUrl", ignore = true),
@Mapping(target = "creatorName", ignore = true),

View File

@@ -13,7 +13,7 @@ import java.util.List;
public class FileMetadataChildrenDTO extends FileMetadataInfoResp {
@Schema(description = "子节点列表")
private List<FileMetadataInfoResp> children;
private List<FileMetadataInfoResp> mergeSameNameChildren;
/**
* 聚合文件ID列表

View File

@@ -0,0 +1,23 @@
package com.sdm.data.model.enums;
public enum ApproveFileActionENUM {
ADD(1, "新增"),
MODIFY(2, "修改"),
DELETE(3, "删除");
private int code;
private String description;
ApproveFileActionENUM(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}

View File

@@ -1,30 +0,0 @@
package com.sdm.data.schedule;
import com.sdm.common.utils.SpringUtils;
import com.sdm.data.dao.SystemMapper;
import com.sdm.data.service.IDataFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* @Author xuyundi
* @Date 2024/2/27
* @Note
*/
@Slf4j
@Component
public class InitSystemDirectory {
@Autowired
private IDataFileService IDataFileService;
@Resource
private SystemMapper systemMapper;
public void postConstruct() {
}
}

View File

@@ -0,0 +1,161 @@
package com.sdm.data.service.approve;
import com.alibaba.fastjson2.JSONObject;
import com.sdm.common.common.SdmResponse;
import com.sdm.common.common.ThreadLocalContext;
import com.sdm.common.entity.constants.NumberConstants;
import com.sdm.common.entity.enums.ApprovalFileDataStatusEnum;
import com.sdm.common.entity.enums.ApproveFileDataTypeEnum;
import com.sdm.common.entity.enums.ApproveTypeEnum;
import com.sdm.common.entity.req.system.LaunchApproveReq;
import com.sdm.common.feign.inter.system.IApproveFeignClient;
import com.sdm.data.model.bo.ApprovalFileDataContentsModel;
import com.sdm.data.model.entity.FileMetadataInfo;
import com.sdm.data.model.enums.ApproveFileActionENUM;
import com.sdm.data.service.IFileMetadataInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* 审批执行器 - 抽取执行和状态更新逻辑
*/
@Component
@Slf4j
public class FileApproveExecutor {
@Autowired
private IApproveFeignClient approveFeignClient;
@Autowired
private IFileMetadataInfoService fileMetadataInfoService;
/**
* 发起审批并更新文件状态
*
* @param builder 审批请求构建器
* @param filesToUpdate 待更新文件列表
* @param fileApproveType 文件审批类型
*
* @return cidFlowId
* @throws RuntimeException 审批创建失败时抛出
*/
public String launchApproveAndUpdateStatus(FileApproveRequestBuilder builder,
List<FileMetadataInfo> filesToUpdate,
ApproveFileDataTypeEnum fileApproveType) {
String approveContents = buildApproveContents(builder);
Pair<Boolean, String> result = doLaunchApprove(
builder.getTemplateId(),
builder.getTemplateName(),
approveContents,
builder.getApproveFileActionENUM().getCode(),
builder.getApproveType()
);
if (!result.getLeft() || StringUtils.isBlank(result.getRight())) {
log.warn("launchApproveAndUpdateStatus failed:{}", JSONObject.toJSONString(builder));
throw new RuntimeException("创建审批流失败, cidFlowId: " + result.getRight());
}
String cidFlowId = result.getRight();
updateFileApproveStatus(filesToUpdate, cidFlowId, fileApproveType);
return cidFlowId;
}
/**
* 仅发起审批,不更新状态(供需要自定义状态更新的场景使用)
*/
public Pair<Boolean, String> launchApproveOnly(FileApproveRequestBuilder builder) {
String approveContents = buildApproveContents(builder);
return doLaunchApprove(
builder.getTemplateId(),
builder.getTemplateName(),
approveContents,
builder.getApproveFileActionENUM().getCode(),
builder.getApproveType()
);
}
private void updateFileApproveStatus(List<FileMetadataInfo> files,
String cidFlowId,
ApproveFileDataTypeEnum approveType) {
if (CollectionUtils.isEmpty(files)) {
return;
}
for (FileMetadataInfo file : files) {
file.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
file.setApproveType(approveType.getCode());
file.setCidFlowId(cidFlowId);
}
fileMetadataInfoService.updateBatchById(files);
}
// 发起仿真知识库文件审核 true 成功 false失败
private Pair<Boolean, String> doLaunchApprove(String templateId, String templateName, String approveContents, int approveAction, ApproveTypeEnum approveTypeEnum) {
boolean result = false;
String cidFlowId = "";
LaunchApproveReq req = new LaunchApproveReq();
try {
// 评审流程模版ID 前端传过来
req.templateId = templateId;
// 模版名称 前端传过来
req.templateName = templateName;
// 审批内容 拼接json str {"id": "111","contents": "用户xx于YYYY-MM-dd hh:mm:ss 提交 xx 新增知识库文件"}
req.approveContents = approveContents;
// 评审动作 1新增 2修改 3删除
req.approveAction = approveAction;
// 审批类型 1仿真地图审批 2知识库审批 3:流程模板审批 4交付物审批
req.approveType = approveTypeEnum.getCode();
// 审批主题
req.approveTitle = approveTypeEnum.getDescription();
// 租户ID
req.tenantId = ThreadLocalContext.getTenantId();
// 用户ID
req.userId = ThreadLocalContext.getUserId();
// 审批创建者
req.creator = ThreadLocalContext.getUserId();
SdmResponse response = approveFeignClient.launchApproval(req);
if (response.isSuccess()) {
// 成功
result = true;
cidFlowId = Optional.ofNullable(response.getData())
.map(Object::toString)
.orElse("");
}
} catch (Exception e) {
log.error("launchFileDataApprove error :{},", e);
} finally {
if (!result) {
log.warn("launchFileDataApprove fileData failed:{}", JSONObject.toJSONString(req));
}
}
return Pair.of(result, cidFlowId);
}
private String buildApproveContents(FileApproveRequestBuilder builder) {
ApprovalFileDataContentsModel contentsModel = new ApprovalFileDataContentsModel();
contentsModel.setIds(builder.getFileIds());
contentsModel.setContents(builder.getContents());
contentsModel.setApproveAction(builder.getApproveFileActionENUM().getCode());
if(CollectionUtils.isNotEmpty(builder.getBeforeData())){
contentsModel.setBeforeData(builder.getBeforeData());
}
// 只有修改有 afterData 值
if(Objects.equals(NumberConstants.TWO, builder.getApproveFileActionENUM().getCode()) && CollectionUtils.isNotEmpty(builder.getAfterData())){
contentsModel.setAfterData(builder.getAfterData());
}
// 转换为 JSON 字符串并返回
return JSONObject.toJSONString(contentsModel);
}
}

View File

@@ -0,0 +1,25 @@
package com.sdm.data.service.approve;
import com.sdm.common.entity.enums.ApproveTypeEnum;
import com.sdm.data.model.entity.FileMetadataInfo;
import com.sdm.data.model.enums.ApproveFileActionENUM;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 审批请求构建器 - 抽取构建逻辑
*/
@Data
@Builder
public class FileApproveRequestBuilder {
private List<Long> fileIds;
private String contents;
private ApproveTypeEnum approveType;
private ApproveFileActionENUM approveFileActionENUM;
private String templateId;
private String templateName;
private List<FileMetadataInfo> beforeData;
private List<FileMetadataInfo> afterData;
}

View File

@@ -2,7 +2,6 @@ package com.sdm.data.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.sdm.common.common.SdmResponse;
import com.sdm.common.common.ThreadLocalContext;
@@ -40,7 +39,6 @@ import com.sdm.data.service.IFileMetadataInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -313,7 +311,7 @@ public class DimensionTemplateServiceImpl extends ServiceImpl<DimensionTemplateM
children.add(childDto);
}
dto.setChildren(children);
dto.setMergeSameNameChildren(children);
dto.setFileIds(children.stream().map(FileMetadataInfoResp::getId).toList());
result.add(dto);
}
@@ -321,13 +319,13 @@ public class DimensionTemplateServiceImpl extends ServiceImpl<DimensionTemplateM
// 根据children中的最大创建时间对result进行倒序排序
result.sort((dto1, dto2) -> {
LocalDateTime maxCreateTime1 = dto1.getChildren().stream()
LocalDateTime maxCreateTime1 = dto1.getMergeSameNameChildren().stream()
.map(FileMetadataInfoResp::getCreateTime)
.filter(Objects::nonNull)
.max(LocalDateTime::compareTo)
.orElse(LocalDateTime.MIN);
LocalDateTime maxCreateTime2 = dto2.getChildren().stream()
LocalDateTime maxCreateTime2 = dto2.getMergeSameNameChildren().stream()
.map(FileMetadataInfoResp::getCreateTime)
.filter(Objects::nonNull)
.max(LocalDateTime::compareTo)
@@ -402,7 +400,7 @@ public class DimensionTemplateServiceImpl extends ServiceImpl<DimensionTemplateM
private List<Long> extractDirIdsFromResponse(SdmResponse<List<FileMetadataChildrenDTO>> response) {
if (response.isSuccess() && ObjectUtils.isNotEmpty(response.getData())) {
return response.getData().stream()
.flatMap(dto -> dto.getChildren().stream())
.flatMap(dto -> dto.getMergeSameNameChildren().stream())
.map(FileMetadataInfoResp::getId)
.toList();
}

View File

@@ -40,9 +40,12 @@ import com.sdm.data.model.bo.ApprovalFileDataContentsModel;
import com.sdm.data.model.entity.*;
import com.sdm.common.entity.resp.data.PoolInfo;
import com.sdm.data.model.enums.ApproveFileActionENUM;
import com.sdm.data.model.req.*;
import com.sdm.data.model.resp.KKFileViewURLFromMinioResp;
import com.sdm.data.service.*;
import com.sdm.data.service.approve.FileApproveExecutor;
import com.sdm.data.service.approve.FileApproveRequestBuilder;
import com.sdm.data.service.impl.dataFileHandle.ApproveContext;
import com.sdm.data.service.impl.dataFileHandle.ApproveStrategy;
import com.sdm.data.service.impl.dataFileHandle.ApproveStrategyFactory;
@@ -145,6 +148,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Autowired
private ISimuluationTaskPoolFeignClient isSimuluationTaskPoolFeignClient;
@Autowired
private FileApproveExecutor fileApproveExecutor;
@Autowired
@Qualifier(value = "nonSensitiveTaskPool")
private Executor nonSensitiveTaskPool;
@@ -561,17 +567,23 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 知识库
if(dirMetadataInfo!=null&&Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())){
// 发送审批电子流,成功继续西面操作,失败直接返回
// String templateId, String templateName,String approveContents,int approveAction1新增 2修改 3删除
String approveContents = getApproveContents(List.of(delFileId), "知识库文件删除", NumberConstants.THREE, List.of(deleteFileMetadataInfo), null);
Pair<Boolean, String> approvePair = launchFileDataApprove(req.getTemplateId(), req.getTemplateName(), approveContents, NumberConstants.THREE, ApproveTypeEnum.KNOWLEDGE_APPROVE);
if(!approvePair.getLeft()|| org.apache.commons.lang3.StringUtils.isBlank(approvePair.getRight())) {
log.error("delFile approveInit failed, params :{}", JSONObject.toJSONString(req));
throw new RuntimeException("文件删除,创建审批流失败,cidFlowId:"+approvePair.getRight());
FileApproveRequestBuilder deleteFileApproveRequestBuilder = FileApproveRequestBuilder.builder()
.fileIds(List.of(delFileId))
.contents("知识库文件删除")
.approveType(ApproveTypeEnum.KNOWLEDGE_APPROVE)
.approveFileActionENUM(ApproveFileActionENUM.DELETE)
.beforeData(List.of(deleteFileMetadataInfo))
.templateId(req.getTemplateId())
.templateName(req.getTemplateName())
.build();
if(CollectionUtils.isNotEmpty(deleteFileApproveRequestBuilder.getBeforeData())){
setCreatorNames(deleteFileApproveRequestBuilder.getBeforeData());
setProjectName(deleteFileApproveRequestBuilder.getBeforeData());
setAnalysisDirectionName(deleteFileApproveRequestBuilder.getBeforeData());
setSimulationPoolAndTaskInfo(deleteFileApproveRequestBuilder.getBeforeData());
}
deleteFileMetadataInfo.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
deleteFileMetadataInfo.setApproveType(ApproveFileDataTypeEnum.DELETE_REVIEWING.getCode());
deleteFileMetadataInfo.setCidFlowId(approvePair.getRight());
fileMetadataInfoService.updateById(deleteFileMetadataInfo);
fileApproveExecutor.launchApproveAndUpdateStatus(deleteFileApproveRequestBuilder, List.of(deleteFileMetadataInfo), ApproveFileDataTypeEnum.DELETE_REVIEWING);
return SdmResponse.success("操作成功,删除文件待审批");
}else{
// 删除MinIO文件
@@ -1362,20 +1374,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
FileMetadataInfo fileInfo = createFileMetadata(fileMinioObjectKey, originalName, req.getFileType(),
req.getProjectId(), req.getAnalysisDirectionId(), req.getRemarks(), dirMetadataInfo.getId(), req.getFile().getSize()
);
// 只有知识库的文件需要审核
// 1 知识库文件夹
boolean isknowledge = Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType());
if(isknowledge){
fileInfo.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
fileInfo.setApproveType(ApproveFileDataTypeEnum.UPLOAD_REVIEWING.getCode());
}
fileMetadataInfoService.save(fileInfo);
// 需要保存文件的历史版本记录同一文件的所有版本共享一个ID
fileInfo.setFileGroupId(fileInfo.getId());
fileMetadataInfoService.updateById(fileInfo);
// 循环查询当前文件每一级父目录id,并保存为一条file_storage,用户后续文件搜索统计
Long parentDirId = dirMetadataInfo.getId();
FileStorage fileStorage = new FileStorage();
@@ -1412,26 +1412,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
// 调用审批流,开启审批; 上面先入库拿到主键id,审批流创建失败后再回退数据
// String templateId, String templateName,String approveContents,int approveAction1新增 2修改 3删除
if(isknowledge){
String approveContents = getApproveContents(List.of(fileInfo.getId()), "知识库文件新增", NumberConstants.ONE, List.of(fileInfo), null);
Pair<Boolean,String> approvePair = launchFileDataApprove(req.getTemplateId(), req.getTemplateName(), approveContents, NumberConstants.ONE, ApproveTypeEnum.KNOWLEDGE_APPROVE);
if(!approvePair.getLeft()|| org.apache.commons.lang3.StringUtils.isBlank(approvePair.getRight())) {
log.error("uploadFiles create approveInit failed, params :{}", JSONObject.toJSONString(req));
// - 回退MinIO中已上传的文件删除该文件。catch 里统一操作了
//- 新增 file_metadata_info 信息不入表。
//- 向前端返回“上传文件失败”。
throw new RuntimeException("文件上传,创建审批流失败,cidFlowId:"+approvePair.getRight());
}
// cid流程id
fileInfo.setCidFlowId(approvePair.getRight());
}
// 需要保存文件的历史版本记录同一文件的所有版本共享一个ID
fileInfo.setFileGroupId(fileInfo.getId());
fileMetadataInfoService.updateById(fileInfo);
// 创建文件扩展信息并保存到数据库
List<FileMetadataExtension> fileMetadataExtensionList = new ArrayList<>();
List<UploadFilesReq.FileMetadataExtensionRequest> fileMetadataExtensionRequestList = req.getFileMetadataExtensionRequest();
@@ -1450,6 +1430,32 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建默认权限记录
createFilePermission(fileInfo.getId());
// 只有知识库的文件需要审核
if( Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())){
FileApproveRequestBuilder uploadFileApproveRequestBuilder = FileApproveRequestBuilder.builder()
.fileIds(List.of(fileInfo.getId()))
.contents("知识库文件新增")
.approveType(ApproveTypeEnum.KNOWLEDGE_APPROVE)
.approveFileActionENUM(ApproveFileActionENUM.ADD)
.beforeData(List.of(fileInfo))
.templateId(req.getTemplateId())
.templateName(req.getTemplateName())
.build();
if(CollectionUtils.isNotEmpty(uploadFileApproveRequestBuilder.getBeforeData())){
setCreatorNames(uploadFileApproveRequestBuilder.getBeforeData());
setProjectName(uploadFileApproveRequestBuilder.getBeforeData());
setAnalysisDirectionName(uploadFileApproveRequestBuilder.getBeforeData());
setSimulationPoolAndTaskInfo(uploadFileApproveRequestBuilder.getBeforeData());
}
fileApproveExecutor.launchApproveAndUpdateStatus(uploadFileApproveRequestBuilder, List.of(fileInfo), ApproveFileDataTypeEnum.UPLOAD_REVIEWING);
}
// 需要保存文件的历史版本记录同一文件的所有版本共享一个ID
fileInfo.setFileGroupId(fileInfo.getId());
fileMetadataInfoService.updateById(fileInfo);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fileId", fileInfo.getId());
return SdmResponse.success(jsonObject);
@@ -1562,27 +1568,40 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
FileMetadataInfo tempFileMetadataInfo = new FileMetadataInfo();
BeanUtils.copyProperties(fileMetadataInfo, tempFileMetadataInfo);
// 不需要上传minio新文件只更新文件元数据
// 本次待审核的数据
tempFileMetadataInfo.setProjectId(req.getProjectId());
tempFileMetadataInfo.setAnalysisDirectionId(req.getAnalysisDirectionId());
tempFileMetadataInfo.setRemarks(req.getRemarks());
tempFileMetadataInfo.setSimulationPoolInfoList(req.getSimulationPoolInfoList());
fileMetadataInfo.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
fileMetadataInfo.setApproveType(ApproveFileDataTypeEnum.MODIFY_METADATA_REVIEWING.getCode());
// 获取前后变化的file信息
String approveContents = getApproveContents(List.of(fileMetadataInfo.getId()), "知识库文件元数据修改",
NumberConstants.TWO, List.of(fileMetadataInfo), List.of(tempFileMetadataInfo));
// 本次待审核的数据
fileMetadataInfo.setTempMetadata(JSONObject.toJSONString(tempFileMetadataInfo));
// String templateId, String templateName,String approveContents,int approveAction1新增 2修改 3删除
Pair<Boolean, String> approvePair = launchFileDataApprove(req.getTemplateId(), req.getTemplateName(), approveContents, NumberConstants.TWO, ApproveTypeEnum.KNOWLEDGE_APPROVE);
if(!approvePair.getLeft()||org.apache.commons.lang3.StringUtils.isBlank(approvePair.getRight())){
log.error("updateFile meteData approveInit failed, params :{}", JSONObject.toJSONString(req));
// 失败数据还未入表
throw new RuntimeException("文件元数据修改失败,创建审批流失败,cidFlowId:"+approvePair.getRight());
//发起审批
FileApproveRequestBuilder updateFileMetaIntoApproveRequestBuilder = FileApproveRequestBuilder.builder()
.fileIds(List.of(fileMetadataInfo.getId()))
.contents("知识库文件元数据修改")
.approveType(ApproveTypeEnum.KNOWLEDGE_APPROVE)
.approveFileActionENUM(ApproveFileActionENUM.MODIFY)
.beforeData(List.of(fileMetadataInfo))
.afterData(List.of(tempFileMetadataInfo))
.templateId(req.getTemplateId())
.templateName(req.getTemplateName())
.build();
if(CollectionUtils.isNotEmpty(updateFileMetaIntoApproveRequestBuilder.getBeforeData())){
setCreatorNames(updateFileMetaIntoApproveRequestBuilder.getBeforeData());
setProjectName(updateFileMetaIntoApproveRequestBuilder.getBeforeData());
setAnalysisDirectionName(updateFileMetaIntoApproveRequestBuilder.getBeforeData());
setSimulationPoolAndTaskInfo(updateFileMetaIntoApproveRequestBuilder.getBeforeData());
}
// 审批中的cidFlowId
fileMetadataInfo.setCidFlowId(approvePair.getRight());
// 只有修改有 afterData 值
if (CollectionUtils.isNotEmpty(updateFileMetaIntoApproveRequestBuilder.getAfterData())) {
setCreatorNames(updateFileMetaIntoApproveRequestBuilder.getAfterData());
setProjectName(updateFileMetaIntoApproveRequestBuilder.getAfterData());
setAnalysisDirectionName(updateFileMetaIntoApproveRequestBuilder.getAfterData());
setSimulationPoolAndTaskInfo(updateFileMetaIntoApproveRequestBuilder.getAfterData());
}
fileApproveExecutor.launchApproveAndUpdateStatus(updateFileMetaIntoApproveRequestBuilder, List.of(fileMetadataInfo), ApproveFileDataTypeEnum.MODIFY_METADATA_REVIEWING);
}else {
// 其他场景修改
// 不需要上传minio新文件只更新文件元数据
@@ -1627,15 +1646,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
);
fileInfo.setFileGroupId(fileGroupId);
fileInfo.setVersionNo(versionNo + 1);
// 知识库修改
if(dirMetadataInfo!=null&&Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())){
// 修改了文件,增加审批相关字段
// 增加审批的逻辑,先将原始数据改成待审核状态
fileInfo.setApprovalStatus(ApprovalFileDataStatusEnum.PENDING.getKey());
fileInfo.setApproveType(ApproveFileDataTypeEnum.MODIFY_REVIEWING.getCode());
// 新增的一条暂时不展示
fileInfo.setIsLatest(false);
}
fileMetadataInfoService.save(fileInfo);
//绑定文件和工况库的关系
@@ -1652,23 +1663,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
// 知识库创建审批流
if(dirMetadataInfo!=null&&Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())){
// 获取前后变化的file信息
String approveContents = getApproveContents(List.of(fileInfo.getId()), "知识库文件修改",
NumberConstants.TWO,List.of(fileMetadataInfo) , List.of(fileInfo));
Pair<Boolean, String> approvePair = launchFileDataApprove(req.getTemplateId(), req.getTemplateName(), approveContents, NumberConstants.TWO, ApproveTypeEnum.KNOWLEDGE_APPROVE);
if(!approvePair.getLeft()||org.apache.commons.lang3.StringUtils.isBlank(approvePair.getRight())){
log.error("uploadAndUpdateFile updateFile approveInit failed, params :{}", JSONObject.toJSONString(req));
// 失败数据回退删除--事务回滚
throw new RuntimeException("文件修改失败,创建审批流失败,cidFlowId:"+approvePair.getRight());
}
// cid流程id
fileInfo.setCidFlowId(approvePair.getRight());
fileMetadataInfoService.updateById(fileInfo);
}
// 创建文件扩展信息并保存到数据库
List<FileMetadataExtension> fileMetadataExtensionList = new ArrayList<>();
List<UploadFilesReq.FileMetadataExtensionRequest> fileMetadataExtensionRequestList = req.getFileMetadataExtensionRequest();
@@ -1708,8 +1702,40 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
// 创建默认权限记录
createFilePermission(fileInfo.getId());
// 知识库的,设置 历史版本 文件为非最新
if(!(dirMetadataInfo!=null&&Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType()))){
// 知识库创建审批流
boolean isKnowledge = dirMetadataInfo != null && Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType());
// 知识库修改
if(isKnowledge){
// 新增的一条暂时不展示
fileInfo.setIsLatest(false);
FileApproveRequestBuilder uploadAndUpdateFileApproveRequestBuilder = FileApproveRequestBuilder.builder()
.fileIds(List.of(fileInfo.getId()))
.contents("知识库文件修改")
.approveType(ApproveTypeEnum.KNOWLEDGE_APPROVE)
.approveFileActionENUM(ApproveFileActionENUM.MODIFY)
.beforeData(List.of(fileMetadataInfo))
.afterData(List.of(fileInfo))
.templateId(req.getTemplateId())
.templateName(req.getTemplateName())
.build();
if(CollectionUtils.isNotEmpty(uploadAndUpdateFileApproveRequestBuilder.getBeforeData())){
setCreatorNames(uploadAndUpdateFileApproveRequestBuilder.getBeforeData());
setProjectName(uploadAndUpdateFileApproveRequestBuilder.getBeforeData());
setAnalysisDirectionName(uploadAndUpdateFileApproveRequestBuilder.getBeforeData());
setSimulationPoolAndTaskInfo(uploadAndUpdateFileApproveRequestBuilder.getBeforeData());
}
// 只有修改有 afterData 值
if (CollectionUtils.isNotEmpty(uploadAndUpdateFileApproveRequestBuilder.getAfterData())) {
setCreatorNames(uploadAndUpdateFileApproveRequestBuilder.getAfterData());
setProjectName(uploadAndUpdateFileApproveRequestBuilder.getAfterData());
setAnalysisDirectionName(uploadAndUpdateFileApproveRequestBuilder.getAfterData());
setSimulationPoolAndTaskInfo(uploadAndUpdateFileApproveRequestBuilder.getAfterData());
}
fileApproveExecutor.launchApproveAndUpdateStatus(uploadAndUpdateFileApproveRequestBuilder, List.of(fileInfo), ApproveFileDataTypeEnum.UPLOAD_REVIEWING);
}else {
// 非知识库的,设置 历史版本 文件为非最新
fileMetadataInfo.setIsLatest(false);
fileMetadataInfoService.updateById(fileMetadataInfo);
}
@@ -2134,82 +2160,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
// 发起仿真知识库文件审核 true 成功 false失败
private Pair<Boolean,String> launchFileDataApprove(String templateId, String templateName, String approveContents, int approveAction, ApproveTypeEnum approveTypeEnum)
{
boolean result = false;
String cidFlowId="";
LaunchApproveReq req = new LaunchApproveReq();
try {
// 评审流程模版ID 前端传过来
req.templateId = templateId;
// req.templateId = "P202511041416483446SFON";
// 模版名称 前端传过来
req.templateName = templateName;
// req.templateName = "仿真知识文档评审";
// 审批内容 拼接json str {"id": "111","contents": "用户xx于YYYY-MM-dd hh:mm:ss 提交 xx 新增知识库文件"}
req.approveContents = approveContents;
// 评审动作 1新增 2修改 3删除
req.approveAction = approveAction;
// 审批类型 1仿真地图审批 2知识库审批 3:流程模板审批 4交付物审批
req.approveType = approveTypeEnum.getCode();
// 审批主题
req.approveTitle = approveTypeEnum.getDescription();
// 租户ID
req.tenantId = ThreadLocalContext.getTenantId();
// req.tenantId = 1979091834410176514l;
// 用户ID
req.userId = ThreadLocalContext.getUserId();
// req.userId = 1979078323595476993l;
// 审批创建者
req.creator = ThreadLocalContext.getUserId();
// req.creator = 1979078323595476993l;
SdmResponse response = approveFeignClient.launchApproval(req);
if(response.isSuccess()){
// 成功
result = true;
cidFlowId = Optional.ofNullable(response.getData())
.map(Object::toString)
.orElse("");
}
} catch (Exception e) {
log.error("launchFileDataApprove error :{},",e);
}finally {
if(!result){
log.warn("launchFileDataApprove fileData failed:{}",JSONObject.toJSONString(req));
}
}
return Pair.of(result,cidFlowId);
}
/**
* 生成包含 id 和 contents 的 JSON 字符串--审批创建的 approveContents
*/
public String getApproveContents(List<Long> ids, String contents,Integer approveAction,List<FileMetadataInfo> beforeDatas,List<FileMetadataInfo> afterDatas) {
ApprovalFileDataContentsModel contentsModel = new ApprovalFileDataContentsModel();
contentsModel.setIds(ids);
contentsModel.setContents(contents);
contentsModel.setApproveAction(approveAction);
if(CollectionUtils.isNotEmpty(beforeDatas)){
setCreatorNames(beforeDatas);
setProjectName(beforeDatas);
setAnalysisDirectionName(beforeDatas);
setSimulationPoolAndTaskInfo(beforeDatas);
}
contentsModel.setBeforeData(beforeDatas);
// 只有修改有 afterData 值
if(Objects.equals(NumberConstants.TWO,approveAction)){
if(CollectionUtils.isNotEmpty(afterDatas)){
setCreatorNames(afterDatas);
setProjectName(afterDatas);
setAnalysisDirectionName(afterDatas);
setSimulationPoolAndTaskInfo(afterDatas);
}
contentsModel.setAfterData(afterDatas);
}
// 转换为 JSON 字符串并返回
return JSONObject.toJSONString(contentsModel);
}
private void setCreatorNames(List<FileMetadataInfo> list) {
try {
@@ -2545,32 +2495,28 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
if (isKnowledge) {
// 构建审批内容
String approveContents = "";
approveContents = getApproveContents(
succBusinessIds,"知识库文件批量新增", NumberConstants.ONE,succFileMetadataInfos,null );
// 发起审批
Pair<Boolean, String> approvePair = launchFileDataApprove(
firstFile.getTemplateId(),
firstFile.getTemplateName(),
approveContents,
NumberConstants.ONE,
ApproveTypeEnum.KNOWLEDGE_APPROVE
);
// 审批创建失败:抛出异常
if (!approvePair.getLeft() || org.apache.commons.lang3.StringUtils.isBlank(approvePair.getRight())) {
CoreLogger.error("callBackknowledgeFile create approveInit failed, params :{}", JSONObject.toJSONString(approvePair));
throw new RuntimeException("文件上传,创建审批流失败,cidFlowId:"+approvePair.getRight());
FileApproveRequestBuilder batchUploadFileApproveRequestBuilder = FileApproveRequestBuilder.builder()
.fileIds(succBusinessIds)
.contents("知识库文件批量新增")
.approveType(ApproveTypeEnum.KNOWLEDGE_APPROVE)
.approveFileActionENUM(ApproveFileActionENUM.ADD)
.beforeData(succFileMetadataInfos)
.templateId(firstFile.getTemplateId())
.templateName(firstFile.getTemplateName())
.build();
if(CollectionUtils.isNotEmpty(batchUploadFileApproveRequestBuilder.getBeforeData())){
setCreatorNames(batchUploadFileApproveRequestBuilder.getBeforeData());
setProjectName(batchUploadFileApproveRequestBuilder.getBeforeData());
setAnalysisDirectionName(batchUploadFileApproveRequestBuilder.getBeforeData());
setSimulationPoolAndTaskInfo(batchUploadFileApproveRequestBuilder.getBeforeData());
}
cidFlowId = approvePair.getRight();
fileApproveExecutor.launchApproveAndUpdateStatus(batchUploadFileApproveRequestBuilder, succFileMetadataInfos, ApproveFileDataTypeEnum.UPLOAD_REVIEWING);
}
// 3. 批量更新文件状态(上传完成)
FileMetadataInfo updateEntity = new FileMetadataInfo();
updateEntity.setUploadStatus(NumberConstants.ONE_STR);
// 知识库文件关联审批流ID
if ((isKnowledge) && org.apache.commons.lang3.StringUtils.isNotBlank(cidFlowId)) {
updateEntity.setCidFlowId(cidFlowId);
}
// 执行更新
fileMetadataInfoService.update(updateEntity, Wrappers.lambdaQuery(FileMetadataInfo.class)
.in(FileMetadataInfo::getId, succBusinessIds)

View File

@@ -11,10 +11,7 @@ import com.sdm.common.entity.resp.data.BatchAddFileInfoResp;
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
import com.sdm.common.feign.inter.project.ISimulationRunFeignClient;
import com.sdm.common.log.annotation.SysLog;
import com.sdm.project.model.entity.SimulationBaseQuantities;
import com.sdm.project.model.entity.SimulationBaseUnits;
import com.sdm.project.model.entity.SimulationRun;
import com.sdm.project.model.entity.SimulationRunKeyResult;
import com.sdm.project.model.entity.*;
import com.sdm.project.model.req.*;
import com.sdm.project.model.resp.FlowInfoDto;
import com.sdm.project.model.resp.KeyResultAndTaskInfoResp;
@@ -291,4 +288,40 @@ public class SimulationRunController implements ISimulationRunFeignClient {
return runService.listUnits(quantityType);
}
/**
* 试验结果上传
* @return
*/
@SysLog("试验结果上传")
@PostMapping(value = "/batchAddExperimentResult")
public SdmResponse batchAddExperimentResult(@RequestBody ExperimentResultReq req) {
return runService.batchAddExperimentResult(req);
}
/**
* 试验结果查询
* @return
*/
@PostMapping(value = "/listExperimentResult")
public SdmResponse<PageDataResp<List<SimulationExperimentResult>>> listExperimentResult(@RequestBody ExperimentResultReq req) {
return runService.listExperimentResult(req);
}
/**
* 删除试验结果
* @return
*/
@PostMapping(value = "/deleteExperimentResult")
public SdmResponse deleteExperimentResult(@RequestBody ExperimentResultReq req) {
return runService.deleteExperimentResult(req);
}
/**
* 编辑试验结果
* @return
*/
@PostMapping(value = "/editExperimentResult")
public SdmResponse editExperimentResult(@RequestBody ExperimentResultReq req) {
return runService.editExperimentResult(req);
}
}

View File

@@ -0,0 +1,11 @@
package com.sdm.project.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sdm.project.model.entity.SimulationExperimentResult;
import com.sdm.project.model.entity.SimulationRunKeyResult;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SimulationExpResultMapper extends BaseMapper<SimulationExperimentResult> {
}

View File

@@ -0,0 +1,82 @@
package com.sdm.project.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("simulation_experiment_result")
@ApiModel(value="simulationExperimentResult对象", description="任务-试验结果实体")
public class SimulationExperimentResult implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty(value = "主键ID", example = "1")
private Long id;
@TableField("uuid")
@ApiModelProperty(value = "关键结果唯一ID", required = true)
private String uuid;
@TableField("taskId")
@ApiModelProperty(value = "所属任务 UUID", required = true)
private String taskId;
@TableField("expName")
@ApiModelProperty(value = "试验名称")
private String expName;
@TableField("expData")
@ApiModelProperty(value = "试验数据")
private String expData;
@TableField("expDesc")
@ApiModelProperty(value = "试验描述")
private String expDesc;
@TableField("imageId")
@ApiModelProperty(value = "关联试验图片id")
private Long imageId;
@TableField("fileId")
@ApiModelProperty(value = "关联试验文件id")
private String fileId;
@TableField("creator")
@ApiModelProperty(value = "创建人ID")
private Long creator;
@TableField("createTime")
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@TableField("updater")
@ApiModelProperty(value = "更新人ID")
private Long updater;
@TableField("updateTime")
@ApiModelProperty(value = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
@Schema(description= "创建者名称,列表展示使用")
@TableField(value = "creatorName", insertStrategy = FieldStrategy.NEVER,select = false,updateStrategy = FieldStrategy.NEVER)
private String creatorName;
@Schema(description= "更新者名称,列表展示使用")
@TableField(value = "updaterName", insertStrategy = FieldStrategy.NEVER,select = false,updateStrategy = FieldStrategy.NEVER)
private String updaterName;
}

View File

@@ -0,0 +1,72 @@
package com.sdm.project.model.req;
import com.baomidou.mybatisplus.annotation.TableField;
import com.sdm.common.entity.BaseReq;
import com.sdm.common.entity.enums.FileBizTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
@Data
public class ExperimentResultReq extends BaseReq {
private Long id;
private String uuid;
@Schema(description = "所属任务 UUID", required = true)
private String taskId;
@Schema(description = "试验名称")
private String expName;
@Schema(description = "试验数据")
private String expData;
@Schema(description = "试验描述")
private String expDesc;
@Schema(description = "关联试验截图文件id")
private Long imageId;
@Schema(description = "关联试验附件文件id")
private String fileId;
@Schema(description = "上传文件所在父目录id")
private Long dirId;
@Schema(description = "文件名")
private String fileName;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "文件类型",implementation = FileBizTypeEnum.class)
private Integer fileType;
@Schema(description = "文件")
private MultipartFile file;
/**
* ------------------------------------------------------------------------------------------------
*/
@Schema(description = "上传试验截图文件属性信息")
private ExperimentResultReq imageFileInfo;
@Schema(description = "上传试验附件批量文件属性信息")
private List<ExperimentResultReq> fileInfoList = new ArrayList<>();
/**
* 调用data服务使用
*/
@Schema(description = "用户勾选的所有的文件的原始名称,前端限制不能选择相同名称的文件,后端逻辑判断对应dirId下不能和历史文件名相同")
private List<ExperimentResultReq> sourceFiles;
@Schema(description = "本次新增数据的任务id,毫秒值时间戳")
private String uploadTaskId;
}

View File

@@ -0,0 +1,8 @@
package com.sdm.project.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sdm.project.model.entity.SimulationExperimentResult;
public interface ISimulationExpResultService extends IService<SimulationExperimentResult> {
}

View File

@@ -9,11 +9,8 @@ import com.sdm.common.entity.req.system.LaunchApproveReq;
import com.sdm.common.entity.resp.PageDataResp;
import com.sdm.common.entity.resp.data.BatchAddFileInfoResp;
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
import com.sdm.project.model.entity.SimulationBaseQuantities;
import com.sdm.project.model.entity.SimulationBaseUnits;
import com.sdm.project.model.entity.SimulationRun;
import com.sdm.project.model.entity.*;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sdm.project.model.entity.SimulationRunKeyResult;
import com.sdm.project.model.req.*;
import com.sdm.project.model.resp.FlowInfoDto;
import com.sdm.project.model.resp.KeyResultAndTaskInfoResp;
@@ -88,4 +85,12 @@ public interface ISimulationRunService extends IService<SimulationRun> {
SdmResponse<List<SimulationBaseUnits>> listUnits(String quantityType);
SdmResponse batchAddExperimentResult(ExperimentResultReq req);
SdmResponse<PageDataResp<List<SimulationExperimentResult>>> listExperimentResult(ExperimentResultReq req);
SdmResponse deleteExperimentResult(ExperimentResultReq req);
SdmResponse editExperimentResult(ExperimentResultReq req);
}

View File

@@ -0,0 +1,12 @@
package com.sdm.project.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sdm.project.dao.SimulationExpResultMapper;
import com.sdm.project.model.entity.SimulationExperimentResult;
import com.sdm.project.service.ISimulationExpResultService;
import org.springframework.stereotype.Service;
@Service
public class SimulationExpResultServiceImpl extends ServiceImpl<SimulationExpResultMapper, SimulationExperimentResult> implements ISimulationExpResultService {
}

View File

@@ -108,6 +108,9 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
@Autowired
private ISimulationKeyResultService simulationKeyResultService;
@Autowired
private ISimulationExpResultService simulationExpResultService;
@Autowired
SysUserFeignClientImpl sysUserFeignClient;
@@ -1488,6 +1491,78 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse batchAddExperimentResult(ExperimentResultReq req) {
SimulationExperimentResult experimentResult = new SimulationExperimentResult();
BeanUtils.copyProperties(req, experimentResult);
experimentResult.setUuid(RandomUtil.generateString(32));
experimentResult.setCreator(ThreadLocalContext.getUserId());
experimentResult.setUpdater(ThreadLocalContext.getUserId());
if (ObjectUtils.isNotEmpty(req.getImageFileInfo())) {
req.getFileInfoList().add(req.getImageFileInfo());
}
if (CollectionUtils.isNotEmpty(req.getFileInfoList())) {
UploadFilesReq filesReq = new UploadFilesReq();
BeanUtils.copyProperties(req, filesReq);
filesReq.setUuid(req.getTaskId());
filesReq.setSourceFiles(req.getFileInfoList().stream().map(i -> new UploadFilesReq(i.getFileName(), i.getFileSize(), i.getFileType())).toList());
// 批量存储文件信息,返回数据供第二步分片上传使用
SdmResponse<List<BatchAddFileInfoResp>> sdmResponse = dataFeignClient.batchAddFileInfo(filesReq);
if (sdmResponse.isSuccess() && CollectionUtils.isNotEmpty(sdmResponse.getData())) {
List<BatchAddFileInfoResp> batchAddFileInfoResps = sdmResponse.getData();
if (ObjectUtils.isNotEmpty(req.getImageFileInfo())) {
batchAddFileInfoResps.stream().filter(i -> StringUtils.equals(i.getSourceFileName(), req.getImageFileInfo().getFileName())).findFirst().ifPresent(i -> {
experimentResult.setImageId(i.getBusinessId());
});
}
String fileIds = batchAddFileInfoResps.stream().filter(i -> !Objects.equals(i.getBusinessId(), experimentResult.getImageId())).map(i -> String.valueOf(i.getBusinessId())).collect(Collectors.joining(","));
experimentResult.setFileId(fileIds);
simulationExpResultService.save(experimentResult);
}
return sdmResponse;
}
return SdmResponse.success(experimentResult.getUuid());
}
@Override
public SdmResponse<PageDataResp<List<SimulationExperimentResult>>> listExperimentResult(ExperimentResultReq req) {
PageHelper.startPage(req.getCurrent(), req.getSize());
List<SimulationExperimentResult> experimentResults = simulationExpResultService.list();
PageInfo<SimulationExperimentResult> page = new PageInfo<>(experimentResults);
return PageUtils.getJsonObjectSdmResponse(experimentResults, page);
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse deleteExperimentResult(ExperimentResultReq req) {
simulationExpResultService.removeById(req.getId());
// 删除文件
if (ObjectUtils.isNotEmpty(req.getImageId())) {
req.getFileInfoList().add(req.getImageFileInfo());
}
if (CollectionUtils.isNotEmpty(req.getFileInfoList())) {
for (ExperimentResultReq resultReq : req.getFileInfoList()) {
DelFileReq delFileReq = new DelFileReq();
delFileReq.setDelFileId(resultReq.getImageId());
SdmResponse response = dataFeignClient.delFile(delFileReq);
if (!response.isSuccess()) {
return response;
}
}
}
return SdmResponse.success();
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse editExperimentResult(ExperimentResultReq req) {
deleteExperimentResult(req);
batchAddExperimentResult(req);
return SdmResponse.success();
}
public static void deleteFolder(File folder) {
if (folder.isDirectory()) {
File[] files = folder.listFiles();

View File

@@ -179,4 +179,5 @@ security:
- /systemApprove/approveStatusNotice
- /user/getUserToken
- /systemMsg/sendMessage
- /systemLog/saveLog
- /tenant/list

View File

@@ -173,12 +173,18 @@ cid:
log:
saveLog: /spdm-log/saveLog
thirdparty:
api:
# 即时通消息通知
freeLinkUrl: http://freelink.haihui.com/wechat/InformApi/FreelinkAndDingdingInform
security:
whitelist:
paths:
- /systemApprove/approveStatusNotice
- /user/getUserToken
- /systemMsg/sendMessage
- /systemLog/saveLog
- /tenant/list
# 0单机处理可以指向本地1负载均衡轮询

View File

@@ -149,6 +149,7 @@ cid:
queryGroup: /spdm-user/queryGroup
queryGroupDetail: /spdm-user/queryGroupDetail
queryGroupMember: /spdm-user/queryGroupMember
getUserToken: /spdm-user/getUserToken
role:
getRoleByRoleName: /spdm-role/getRoleByRoleName
getRoleByRoleCode: /spdm-role/getRoleByRoleCode

View File

@@ -179,4 +179,5 @@ security:
- /systemApprove/approveStatusNotice
- /user/getUserToken
- /systemMsg/sendMessage
- /systemLog/saveLog
- /tenant/list