Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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 updateConfigFile(String filePath, String filename, String fileText) {
NodeProjectInfoModel pim = getProjectInfoModel();
filePath = StrUtil.emptyToDefault(filePath, File.separator);
// 判断文件后缀
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
try {
FileUtil.writeString(fileText, FileUtil.file(pim.allLib(), filePath, filename), charset);
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "edit", "levelName", filePath, "fileName", filename);
return JsonMessage.success("文件写入成功");
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
}
}
/**
* 将执行文件下载到客户端 本地
*
* @param id 项目id
* @param filename 文件名
* @param levelName 文件夹名
*/
@GetMapping(value = "download", produces = MediaType.APPLICATION_JSON_VALUE)
public void download(String id, String filename, String levelName, HttpServletResponse response) {
Assert.hasText(filename, "请选择文件");
// String safeFileName = pathSafe(filename);
// if (StrUtil.isEmpty(safeFileName)) {
// return JsonMessage.getString(405, "非法操作");
// }
try {
NodeProjectInfoModel pim = projectInfoService.getItem(id);
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
if (file.isDirectory()) {
ServletUtil.write(response, JsonMessage.getString(400, "暂不支持下载文件夹"), MediaType.APPLICATION_JSON_VALUE);
return;
}
ServletUtil.write(response, file);
} catch (Exception e) {
log.error("下载文件异常", e);
ServletUtil.write(response, JsonMessage.getString(400, "下载失败。请刷新页面后重试", e.getMessage()), MediaType.APPLICATION_JSON_VALUE);
}
}
/**
* 下载远程文件
*
* @param id 项目id
* @param url 远程 url 地址
* @param levelName 保存的文件夹
* @param unzip 是否为压缩包、true 将自动解压
* @param stripComponents 剔除层级
* @return json
*/
@PostMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage remoteDownload(String id, String url, String levelName, String unzip, Integer stripComponents) {
Assert.hasText(url, "请输入正确的远程地址");
NodeProjectInfoModel pim = projectInfoService.getItem(id);
String tempPathName = agentConfig.getTempPathName();
//
String backupId = null;
try {
File downloadFile = HttpUtil.downloadFileFromUrl(url, tempPathName);
String fileSize = FileUtil.readableFileSize(downloadFile);
// 备份文件
backupId = ProjectFileBackupUtil.backup(pim);
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR));
FileUtil.mkdir(file);
if (BooleanUtil.toBoolean(unzip)) {
// 需要解压文件
try {
int stripComponentsValue = Convert.toInt(stripComponents, 0);
CompressionFileUtil.unCompress(downloadFile, file, stripComponentsValue);
} finally {
if (!FileUtil.del(downloadFile)) {
log.error("删除文件失败:" + file.getPath());
}
}
} else {
// 移动文件到对应目录
FileUtil.move(downloadFile, file, true);
}
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "remoteDownload", "levelName", levelName, "fileName", file.getName(), "url", url);
return JsonMessage.success("下载成功文件大小:" + fileSize);
} catch (Exception e) {
log.error("下载远程文件异常", e);
return new JsonMessage<>(500, "下载远程文件失败:" + e.getMessage());
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
}
}
/**
* 创建文件夹/文件
*
* @param id 项目ID
* @param levelName 二级文件夹名
* @param filename 文件名
* @param unFolder true/1 为文件夹,false/2 为文件
* @return json
*/
@PostMapping(value = "new_file_folder.json", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage newFileFolder(String id, String levelName, @ValidatorItem String filename, String unFolder) {
NodeProjectInfoModel projectInfoModel = projectInfoService.getItem(id);
Assert.notNull(projectInfoModel, "没有对应到项目");
File file = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
//
Assert.state(!FileUtil.exist(file), "文件夹或者文件已存在");
boolean folder = !Convert.toBool(unFolder, false);
if (folder) {
FileUtil.mkdir(file);
} else {
FileUtil.touch(file);
}
AbstractProjectCommander.getInstance().asyncWebHooks(projectInfoModel, null, "fileChange",
"changeEvent", "newFileOrFolder", "levelName", levelName, "fileName", filename, "folder", folder);
return JsonMessage.success("操作成功");
}
/**
* 修改文件夹/文件
*
* @param id 项目ID
* @param levelName 二级文件夹名
* @param filename 文件名
* @param newname 新文件名
* @return json
*/
@PostMapping(value = "rename.json", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage rename(String id, String levelName, @ValidatorItem String filename, String newname) {
NodeProjectInfoModel projectInfoModel = getProjectInfoModel();
File file = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
File newFile = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), newname);
Assert.state(FileUtil.exist(file), "文件不存在");
Assert.state(!FileUtil.exist(newFile), "文件名已经存在拉");
FileUtil.rename(file, newname, false);
AbstractProjectCommander.getInstance().asyncWebHooks(projectInfoModel, null, "fileChange",
"changeEvent", "rename", "levelName", levelName, "fileName", filename, "newname", newname);
return JsonMessage.success("操作成功");
}
}