![JAR search and dependency download from the Maven repository](/logo.png)
org.dromara.jpom.controller.manage.ProjectFileControl Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.controller.manage;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.controller.manage.vo.DiffFileVo;
import org.dromara.jpom.model.AfterOpt;
import org.dromara.jpom.model.BaseEnum;
import org.dromara.jpom.model.data.AgentWhitelist;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.script.ProjectFileBackupUtil;
import org.dromara.jpom.service.WhitelistDirectoryService;
import org.dromara.jpom.service.manage.ConsoleService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.CompressionFileUtil;
import org.dromara.jpom.util.FileUtils;
import org.dromara.jpom.util.StringUtil;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 项目文件管理
*
* @author bwcx_jzy
* @since 2019/4/17
*/
@RestController
@RequestMapping(value = "/manage/file/")
@Slf4j
public class ProjectFileControl extends BaseAgentController {
private final ConsoleService consoleService;
private final WhitelistDirectoryService whitelistDirectoryService;
private final AgentConfig agentConfig;
public ProjectFileControl(ConsoleService consoleService,
WhitelistDirectoryService whitelistDirectoryService,
AgentConfig agentConfig) {
this.consoleService = consoleService;
this.whitelistDirectoryService = whitelistDirectoryService;
this.agentConfig = agentConfig;
}
@RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage> getFileList(String id, String path) {
// 查询项目路径
NodeProjectInfoModel pim = projectInfoService.getItem(id);
Assert.notNull(pim, "查询失败:项目不存在");
String lib = pim.allLib();
File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR));
boolean exist = FileUtil.exist(fileDir);
if (!exist) {
return JsonMessage.success("查询成功", Collections.emptyList());
}
//
File[] filesAll = fileDir.listFiles();
if (ArrayUtil.isEmpty(filesAll)) {
return JsonMessage.success("查询成功", Collections.emptyList());
}
List arrayFile = FileUtils.parseInfo(filesAll, false, lib);
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
for (JSONObject jsonObject : arrayFile) {
String filename = jsonObject.getString("filename");
jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(whitelist.getAllowEditSuffix(), filename));
}
return JsonMessage.success("查询成功", arrayFile);
}
/**
* 对比文件
*
* @param diffFileVo 参数
* @return json
*/
@PostMapping(value = "diff_file", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage diffFile(@RequestBody DiffFileVo diffFileVo) {
String id = diffFileVo.getId();
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id);
//
List data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
// 扫描项目目录下面的所有文件
String path = FileUtil.file(projectInfoModel.allLib(), Opt.ofBlankAble(diffFileVo.getDir()).orElse(StrUtil.SLASH)).getAbsolutePath();
List files = FileUtil.loopFiles(path);
// 将所有的文件信息组装并签名
List collect = files.stream().map(file -> {
//
JSONObject item = new JSONObject();
item.put("name", StringUtil.delStartPath(file, path, true));
item.put("sha1", SecureUtil.sha1(file));
return item;
}).collect(Collectors.toList());
// 得到 当前下面文件夹下面所有的文件信息 map
Map nowMap = CollStreamUtil.toMap(collect,
jsonObject12 -> jsonObject12.getString("name"),
jsonObject1 -> jsonObject1.getString("sha1"));
// 将需要对应的信息转为 map
Map tryMap = CollStreamUtil.toMap(data, DiffFileVo.DiffItem::getName, DiffFileVo.DiffItem::getSha1);
// 对应需要 当前项目文件夹下没有的和文件内容有变化的
List canSync = tryMap.entrySet()
.stream()
.filter(stringStringEntry -> {
String nowSha1 = nowMap.get(stringStringEntry.getKey());
if (StrUtil.isEmpty(nowSha1)) {
// 不存在
return true;
}
// 如果 文件信息一致 则过滤
return !StrUtil.equals(stringStringEntry.getValue(), nowSha1);
})
.map(stringStringEntry -> {
//
JSONObject item = new JSONObject();
item.put("name", stringStringEntry.getKey());
item.put("sha1", stringStringEntry.getValue());
return item;
})
.collect(Collectors.toList());
// 对比项目文件夹下有对,但是需要对应对信息里面没有对。此类文件需要删除
List delArray = nowMap.entrySet()
.stream()
.filter(stringStringEntry -> !tryMap.containsKey(stringStringEntry.getKey()))
.map(stringStringEntry -> {
//
JSONObject item = new JSONObject();
item.put("name", stringStringEntry.getKey());
item.put("sha1", stringStringEntry.getValue());
return item;
})
.collect(Collectors.toList());
//
JSONObject result = new JSONObject();
result.put("diff", canSync);
result.put("del", delArray);
return JsonMessage.success("", result);
}
private void saveProjectFileBefore(File lib, NodeProjectInfoModel projectInfoModel) throws Exception {
String closeFirstStr = getParameter("closeFirst");
// 判断是否需要先关闭项目
boolean closeFirst = BooleanUtil.toBoolean(closeFirstStr);
if (closeFirst) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.stop, projectInfoModel, null);
Assert.state(result.isSuccess(), "关闭项目失败:" + result.msgStr());
List javaCopyItemList = projectInfoModel.getJavaCopyItemList();
Optional.ofNullable(javaCopyItemList).ifPresent(javaCopyItems -> {
try {
for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItems) {
CommandOpResult result1 = consoleService.execCommand(ConsoleCommandOp.stop, projectInfoModel, javaCopyItem);
Assert.state(result1.isSuccess(), "关闭项目副本" + javaCopyItem.getName() + " - " + javaCopyItem.getId() + "失败:" + result1.msgStr());
}
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
});
}
String clearType = getParameter("clearType");
// 判断是否需要清空
if ("clear".equalsIgnoreCase(clearType)) {
CommandUtil.systemFastDel(lib);
}
}
@RequestMapping(value = "upload-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage uploadSharding(MultipartFile file,
String sliceId,
Integer totalSlice,
Integer nowSlice,
String fileSumMd5) throws Exception {
String tempPathName = agentConfig.getFixedTempPathName();
this.uploadSharding(file, tempPathName, sliceId, totalSlice, nowSlice, fileSumMd5);
return JsonMessage.success("上传成功");
}
@RequestMapping(value = "sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage shardingMerge(String type,
String levelName,
Integer stripComponents,
String sliceId,
Integer totalSlice,
String fileSumMd5,
String after) throws Exception {
String tempPathName = agentConfig.getFixedTempPathName();
File successFile = this.shardingTryMerge(tempPathName, sliceId, totalSlice, fileSumMd5);
// 处理上传文件
return this.upload(successFile, type, levelName, stripComponents, after);
}
/**
* 处理上传文件
*
* @param file 上传的文件
* @param type 上传类型
* @param levelName 文件夹
* @param stripComponents 剔除文件夹
* @param after 上传之后
* @return 结果
* @throws Exception 异常
*/
private IJsonMessage upload(File file, String type, String levelName, Integer stripComponents, String after) throws Exception {
NodeProjectInfoModel pim = getProjectInfoModel();
File lib = StrUtil.isEmpty(levelName) ? new File(pim.allLib()) : FileUtil.file(pim.allLib(), levelName);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
try {
//
this.saveProjectFileBefore(lib, pim);
if ("unzip".equals(type)) {
// 解压
try {
int stripComponentsValue = Convert.toInt(stripComponents, 0);
CompressionFileUtil.unCompress(file, lib, stripComponentsValue);
} finally {
if (!FileUtil.del(file)) {
log.error("删除文件失败:" + file.getPath());
}
}
} else {
// 移动文件到对应目录
FileUtil.mkdir(lib);
FileUtil.move(file, lib, true);
}
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "upload", "levelName", levelName, "fileType", type, "fileName", file.getName());
//
JsonMessage resultJsonMessage = this.saveProjectFileAfter(after, pim);
if (resultJsonMessage != null) {
return resultJsonMessage;
}
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
}
return JsonMessage.success("上传成功");
}
private JsonMessage saveProjectFileAfter(String after, NodeProjectInfoModel pim) throws Exception {
if (StrUtil.isEmpty(after)) {
return null;
}
//
List javaCopyItemList = pim.getJavaCopyItemList();
//
AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(after, AfterOpt.No.getCode()));
if ("restart".equalsIgnoreCase(after) || afterOpt == AfterOpt.Restart) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, pim, null);
// 自动处理副本集
if (javaCopyItemList != null) {
ThreadUtil.execute(() -> javaCopyItemList.forEach(javaCopyItem -> {
try {
consoleService.execCommand(ConsoleCommandOp.restart, pim, javaCopyItem);
} catch (Exception e) {
log.error("重启副本集失败", e);
}
}));
}
return new JsonMessage<>(result.isSuccess() ? 200 : 405, "上传成功并重启", result);
} else if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, pim, null);
if (javaCopyItemList != null) {
int sleepTime = getParameterInt("sleepTime", 30);
ThreadUtil.execute(() -> {
// 副本
for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItemList) {
if (!this.restart(pim, javaCopyItem, afterOpt)) {
return;
}
// 休眠x秒 等待之前项目正常启动
try {
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException ignored) {
}
}
});
}
return new JsonMessage<>(result.isSuccess() ? 200 : 405, "上传成功并重启", result);
}
return null;
}
private boolean restart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem, AfterOpt afterOpt) {
try {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, nodeProjectInfoModel, javaCopyItem);
if (result.isSuccess()) {
return true;
}
} catch (Exception e) {
log.error("重复失败", e);
}
// 完整重启,不再继续剩余的节点项目
return afterOpt != AfterOpt.Order_Must_Restart;
}
@RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage deleteFile(String filename, String type, String levelName) {
NodeProjectInfoModel pim = getProjectInfoModel();
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, StrUtil.SLASH));
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
try {
if ("clear".equalsIgnoreCase(type)) {
// 清空文件
if (FileUtil.clean(file)) {
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
return JsonMessage.success("清除成功");
}
boolean run = AbstractProjectCommander.getInstance().isRun(pim, null);
Assert.state(!run, "文件被占用,请先停止项目");
return new JsonMessage<>(500, "删除失败:" + file.getAbsolutePath());
} else {
// 删除文件
Assert.hasText(filename, "请选择要删除的文件");
file = FileUtil.file(file, filename);
if (FileUtil.del(file)) {
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
return JsonMessage.success("删除成功");
}
return new JsonMessage<>(500, "删除失败");
}
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
}
}
@RequestMapping(value = "batch_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage batchDelete(@RequestBody DiffFileVo diffFileVo) {
String id = diffFileVo.getId();
String dir = diffFileVo.getDir();
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(projectInfoModel);
try {
//
List data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
//
File path = FileUtil.file(projectInfoModel.allLib(), Opt.ofBlankAble(dir).orElse(StrUtil.SLASH));
for (DiffFileVo.DiffItem datum : data) {
File file = FileUtil.file(path, datum.getName());
if (FileUtil.del(file)) {
continue;
}
return new JsonMessage<>(500, "删除失败:" + file.getAbsolutePath());
}
return JsonMessage.success("删除成功");
} finally {
ProjectFileBackupUtil.checkDiff(projectInfoModel, backupId);
}
}
/**
* 读取文件内容 (只能处理文本文件)
*
* @param filePath 相对项目文件的文件夹
* @param filename 读取的文件名
* @return json
*/
@PostMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage readFile(String filePath, String filename) {
NodeProjectInfoModel pim = getProjectInfoModel();
filePath = StrUtil.emptyToDefault(filePath, File.separator);
// 判断文件后缀
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
File file = FileUtil.file(pim.allLib(), filePath, filename);
String ymlString = FileUtil.readString(file, charset);
return JsonMessage.success("", ymlString);
}
/**
* 保存文件内容 (只能处理文本文件)
*
* @param filePath 相对项目文件的文件夹
* @param filename 读取的文件名
* @param fileText 文件内容
* @return json
*/
@PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage
© 2015 - 2025 Weber Informatics LLC | Privacy Policy