feat 构建项目发布支持配置发布到二级目录

This commit is contained in:
bwcx_jzy 2022-12-19 13:48:59 +08:00
parent 3d37b26cac
commit 15aaf2b7c5
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
23 changed files with 215 additions and 122 deletions

View File

@ -5,6 +5,8 @@
1. 【all】外置 `logback` 配置文件
2. 【server】服务端管理相关功能独立页面菜单
3. 【server】新增项目触发器用于管理项目状态
4. 【all】新增 构建项目发布支持配置发布到二级目录
5. 【server】新增 节点分发发布支持配置发布到二级目录
### 🐞 解决BUG、优化功能

View File

@ -26,6 +26,7 @@ import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
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;
@ -130,7 +131,7 @@ public class ProjectFileControl extends BaseAgentController {
List<DiffFileVo.DiffItem> data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
// 扫描项目目录下面的所有文件
String path = projectInfoModel.allLib();
String path = FileUtil.file(projectInfoModel.allLib(), Opt.ofBlankAble(diffFileVo.getDir()).orElse(StrUtil.SLASH)).getAbsolutePath();
List<File> files = FileUtil.loopFiles(path);
// 将所有的文件信息组装并签名
List<JSONObject> collect = files.stream().map(file -> {
@ -336,6 +337,7 @@ public class ProjectFileControl extends BaseAgentController {
@RequestMapping(value = "batch_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public JsonMessage<String> batchDelete(@RequestBody DiffFileVo diffFileVo) {
String id = diffFileVo.getId();
String dir = diffFileVo.getDir();
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(projectInfoModel.getId(), projectInfoModel.allLib());
@ -344,13 +346,13 @@ public class ProjectFileControl extends BaseAgentController {
List<DiffFileVo.DiffItem> data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
//
String path = projectInfoModel.allLib();
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, "删除失败");
return new JsonMessage<>(500, "删除失败" + file.getAbsolutePath());
}
return JsonMessage.success("删除成功");
} finally {

View File

@ -22,57 +22,39 @@
*/
package io.jpom.controller.manage.vo;
import lombok.Data;
import java.util.List;
/**
* @author bwcx_jzy
* @since 2021/12/16
*/
@Data
public class DiffFileVo {
private String id;
private List<DiffItem> data;
/**
* 项目id
*/
private String id;
/**
* 需要对比的数据
*/
private List<DiffItem> data;
/**
* 需要对比的目录
*/
private String dir;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<DiffItem> getData() {
return data;
}
public void setData(List<DiffItem> data) {
this.data = data;
}
public static class DiffItem {
/**
* 名称
*/
private String name;
/**
* 文件签名
*/
private String sha1;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSha1() {
return sha1;
}
public void setSha1(String sha1) {
this.sha1 = sha1;
}
}
@Data
public static class DiffItem {
/**
* 名称
*/
private String name;
/**
* 文件签名
*/
private String sha1;
}
}

View File

@ -39,6 +39,7 @@ import io.jpom.system.AgentConfig;
import io.jpom.system.ConfigBean;
import io.jpom.util.CommandUtil;
import io.jpom.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import java.io.File;
@ -51,6 +52,7 @@ import java.util.stream.Collectors;
* @author bwcx_jzy
* @since 2022/5/10
*/
@Slf4j
public class ProjectFileBackupUtil {
/**
@ -152,47 +154,51 @@ public class ProjectFileBackupUtil {
}
// 考虑到大文件对比比较耗时需要异步对比文件
ThreadUtil.execute(() -> {
File backupItemPath = ProjectFileBackupUtil.path(pathId, backupId);
File backupPath = ProjectFileBackupUtil.path(pathId);
// 获取文件列表
Map<String, File> backupFiles = ProjectFileBackupUtil.listFiles(backupItemPath.getAbsolutePath());
Map<String, File> nowFiles = ProjectFileBackupUtil.listFiles(projectPath);
nowFiles.forEach((fileSha1, file) -> {
// 当前目录存在的但是备份目录也存在的相同文件则删除
File backupFile = backupFiles.get(fileSha1);
if (backupFile != null) {
CommandUtil.systemFastDel(backupFile);
backupFiles.remove(fileSha1);
}
});
// 判断保存指定后缀
String[] backupSuffix = Optional.ofNullable(dslYmlDto)
.map(DslYmlDto::getFile)
.map(DslYmlDto.FileConfig::getBackupSuffix)
.orElseGet(() -> {
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig project = agentConfig.getProject();
return project.getFileBackupSuffix();
try {
File backupItemPath = ProjectFileBackupUtil.path(pathId, backupId);
File backupPath = ProjectFileBackupUtil.path(pathId);
// 获取文件列表
Map<String, File> backupFiles = ProjectFileBackupUtil.listFiles(backupItemPath.getAbsolutePath());
Map<String, File> nowFiles = ProjectFileBackupUtil.listFiles(projectPath);
nowFiles.forEach((fileSha1, file) -> {
// 当前目录存在的但是备份目录也存在的相同文件则删除
File backupFile = backupFiles.get(fileSha1);
if (backupFile != null) {
CommandUtil.systemFastDel(backupFile);
backupFiles.remove(fileSha1);
}
});
if (ArrayUtil.isNotEmpty(backupSuffix)) {
backupFiles.values()
.stream()
.filter(file -> {
String name = FileUtil.getName(file);
for (String reg : backupSuffix) {
if (ReUtil.isMatch(reg, name)) {
// 满足正则条件
return false;
// 判断保存指定后缀
String[] backupSuffix = Optional.ofNullable(dslYmlDto)
.map(DslYmlDto::getFile)
.map(DslYmlDto.FileConfig::getBackupSuffix)
.orElseGet(() -> {
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig project = agentConfig.getProject();
return project.getFileBackupSuffix();
});
if (ArrayUtil.isNotEmpty(backupSuffix)) {
backupFiles.values()
.stream()
.filter(file -> {
String name = FileUtil.getName(file);
for (String reg : backupSuffix) {
if (ReUtil.isMatch(reg, name)) {
// 满足正则条件
return false;
}
}
}
return !StrUtil.endWithAny(name, backupSuffix);
})
.forEach(CommandUtil::systemFastDel);
return !StrUtil.endWithAny(name, backupSuffix);
})
.forEach(CommandUtil::systemFastDel);
}
// 删除空文件夹
loopClean(backupItemPath);
// 检查备份保留个数
clearOldBackup(backupPath, dslYmlDto);
} catch (Exception e) {
log.warn("对比清空项目文件备份失败", e);
}
// 删除空文件夹
loopClean(backupItemPath);
// 检查备份保留个数
clearOldBackup(backupPath, dslYmlDto);
});
}

View File

@ -61,6 +61,14 @@ public class AgentAuthorize implements InitializingBean {
*/
private String authorize;
public void setAuthorize(String authorize) {
// 不能外部 set
}
public String getAuthorize() {
throw new UnsupportedOperationException("不能调用此方法");
}
private final ConfigBean configBean;
/**
* 注入控制加载顺序必须先加载数据目录才能初始化
@ -123,9 +131,13 @@ public class AgentAuthorize implements InitializingBean {
if (StrUtil.isEmpty(this.agentName)) {
throw new JpomRuntimeException("The agent login name cannot be empty");
}
this.checkPwd();
// 生成密码授权字符串
this.authorize = SecureUtil.sha1(this.agentName + "@" + this.agentPwd);
if (StrUtil.isEmpty(this.authorize)) {
this.checkPwd();
// 生成密码授权字符串
this.authorize = SecureUtil.sha1(this.agentName + "@" + this.agentPwd);
} else {
log.warn("authorized 不能重复加载");
}
//
JvmUtil.checkJpsNormal();
}

View File

@ -13,9 +13,9 @@ jpom:
# 停止、启动项目(项目状态检测)等待的时长 单位秒
status-wait-time: 10
# 项目文件备份保留个数,大于 0 才会备份
file-backup-count: 0
file-backup-count: 1
# 限制备份指定文件后缀(支持正则)
#file-backup-suffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ]
file-backup-suffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ]
# 项目状态检测间隔时间 单位毫秒最小为1毫秒
status-detection-interval: 500
log:

View File

@ -64,13 +64,15 @@ public class WebAopLog {
Object proceed;
Object logResult = null;
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
String requestUri = requestAttributes.getRequest().getRequestURI();
try {
aopLogInterface.forEach(aopLogInterface -> aopLogInterface.before(joinPoint));
proceed = joinPoint.proceed();
logResult = proceed;
log.debug("{} {}", requestAttributes.getRequest().getRequestURI(), Optional.ofNullable(proceed).orElse(StrUtil.EMPTY));
log.debug("{} {}", requestUri, Optional.ofNullable(proceed).orElse(StrUtil.EMPTY));
} catch (Throwable e) {
log.debug("{}", requestAttributes.getRequest().getRequestURI(), e);
// 不用记录异常日志全局异常拦截里面会记录此处不用重复记录
// log.debug("发生异常 {}", requestUri, e);
logResult = e;
throw e;
} finally {

View File

@ -32,6 +32,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Lombok;
import org.springframework.util.AntPathMatcher;
import java.io.File;
@ -41,6 +42,7 @@ import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@ -260,4 +262,19 @@ public class FileUtils {
});
return paths;
}
/**
* 判断目录是否有越级问题
*
* @param dir 目录
* @param function 异常
*/
public static void checkSlip(String dir, Function<Exception, Exception> function) {
try {
File userHomeDir = FileUtil.getUserHomeDir();
FileUtil.checkSlip(userHomeDir, FileUtil.file(userHomeDir, dir));
} catch (IllegalArgumentException e) {
throw Lombok.sneakyThrow(function.apply(e));
}
}
}

View File

@ -151,6 +151,10 @@ public class BuildExtraModule extends BaseModel {
* 镜像标签
*/
private String dockerImagesLabels;
/**
* 项目二级目录
*/
private String projectSecondaryDirectory;
public String getResultDirFile() {
if (resultDirFile == null) {

View File

@ -30,6 +30,7 @@ import cn.hutool.core.date.SystemClock;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@ -494,6 +495,9 @@ public class ReleaseManage implements Runnable {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", projectId);
jsonObject.put("data", collect);
String directory = this.buildExtraModule.getProjectSecondaryDirectory();
directory = Opt.ofBlankAble(directory).orElse(StrUtil.SLASH);
jsonObject.put("dir", directory);
JsonMessage<JSONObject> requestBody = NodeForward.requestBody(nodeModel, NodeUrl.MANAGE_FILE_DIFF_FILE, this.userModel, jsonObject);
if (requestBody.getCode() != HttpStatus.HTTP_OK) {
throw new JpomRuntimeException("对比项目文件失败:" + requestBody);
@ -523,6 +527,7 @@ public class ReleaseManage implements Runnable {
File file = FileUtil.file(resultFileParent, name);
//
String startPath = StringUtil.delStartPath(file, resultFileParent, false);
startPath = FileUtil.normalize(startPath + StrUtil.SLASH + directory);
//
JsonMessage<String> jsonMessage = OutGivingRun.fileUpload(file, startPath,
projectId, false, last ? afterOpt : AfterOpt.No, nodeModel, this.userModel, false);
@ -540,7 +545,7 @@ public class ReleaseManage implements Runnable {
* 发布项目
*/
private void doProject() {
// AfterOpt afterOpt, boolean clearOld, boolean diffSync
//AfterOpt afterOpt, boolean clearOld, boolean diffSync
AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, this.buildExtraModule.getAfterOpt(), AfterOpt.No);
boolean clearOld = this.buildExtraModule.isClearOld();
boolean diffSync = this.buildExtraModule.isDiffSync();
@ -563,7 +568,7 @@ public class ReleaseManage implements Runnable {
zipFile = this.resultFile;
unZip = false;
}
JsonMessage<String> jsonMessage = OutGivingRun.fileUpload(zipFile, null,
JsonMessage<String> jsonMessage = OutGivingRun.fileUpload(zipFile, this.buildExtraModule.getProjectSecondaryDirectory(),
projectId,
unZip,
afterOpt,

View File

@ -24,6 +24,7 @@ package io.jpom.controller.build;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.lang.Validator;
@ -60,6 +61,7 @@ import io.jpom.service.node.ssh.SshService;
import io.jpom.service.script.ScriptServer;
import io.jpom.system.extconf.BuildExtConfig;
import io.jpom.util.CommandUtil;
import io.jpom.util.FileUtils;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
@ -210,12 +212,7 @@ public class BuildInfoController extends BaseServerController {
if (StrUtil.isNotEmpty(webhook)) {
Validator.validateMatchRegex(RegexPool.URL_HTTP, webhook, "WebHooks 地址不合法");
}
try {
File userHomeDir = FileUtil.getUserHomeDir();
FileUtil.checkSlip(userHomeDir, FileUtil.file(userHomeDir, resultDirFile));
} catch (Exception e) {
return new JsonMessage<>(405, "产物目录不能越级:" + e.getMessage());
}
FileUtils.checkSlip(resultDirFile, e -> new IllegalArgumentException("产物目录不能越级:" + e.getMessage()));
buildInfoModel.setAutoBuildCron(this.checkCron(autoBuildCron));
buildInfoModel.setWebhook(webhook);
buildInfoModel.setRepositoryId(repositoryId);
@ -230,7 +227,7 @@ public class BuildInfoController extends BaseServerController {
BuildReleaseMethod releaseMethod1 = BaseEnum.getEnum(BuildReleaseMethod.class, releaseMethod);
Assert.notNull(releaseMethod1, "发布方法不正确");
buildInfoModel.setReleaseMethod(releaseMethod1.getCode());
// extraData 信息转换成 JSON 字符串
// extraData 信息转换成 JSON 字符串 ,不能直接使用 io.jpom.build.BuildExtraModule
JSONObject jsonObject = JSON.parseObject(extraData);
// 验证发布方式 extraData 信息
@ -389,6 +386,11 @@ public class BuildInfoController extends BaseServerController {
String clearOld = jsonObject.getString("clearOld");
jsonObject.put("afterOpt", afterOpt1.getCode());
jsonObject.put("clearOld", Convert.toBool(clearOld, false));
//
String projectSecondaryDirectory = jsonObject.getString("projectSecondaryDirectory");
Opt.ofBlankAble(projectSecondaryDirectory).ifPresent(s -> {
FileUtils.checkSlip(s, e -> new IllegalArgumentException("二级目录不能越级:" + e.getMessage()));
});
}
/**

View File

@ -93,7 +93,8 @@ public class BuildInfoManageController extends BaseServerController {
String resultDirFile,
String branchName,
String branchTagName,
String checkRepositoryDiff) {
String checkRepositoryDiff,
String projectSecondaryDirectory) {
BuildInfoModel item = buildInfoService.getByKey(id, getRequest());
Assert.notNull(item, "没有对应数据");
// 更新数据
@ -101,8 +102,16 @@ public class BuildInfoManageController extends BaseServerController {
Opt.ofBlankAble(resultDirFile).ifPresent(update::setResultDirFile);
Opt.ofBlankAble(branchName).ifPresent(update::setBranchName);
Opt.ofBlankAble(branchTagName).ifPresent(update::setBranchTagName);
Opt.ofBlankAble(projectSecondaryDirectory).ifPresent(s -> {
FileUtils.checkSlip(s, e -> new IllegalArgumentException("二级目录不能越级:" + e.getMessage()));
//
String extraData = item.getExtraData();
JSONObject jsonObject = JSONObject.parseObject(extraData);
jsonObject.put("projectSecondaryDirectory", s);
update.setExtraData(jsonObject.toString());
});
if (!StrUtil.isAllBlank(resultDirFile, branchName, branchTagName)) {
if (!StrUtil.isAllBlank(resultDirFile, branchName, branchTagName, projectSecondaryDirectory)) {
update.setId(id);
buildInfoService.update(update);
}

View File

@ -24,6 +24,7 @@ package io.jpom.controller.outgiving;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
@ -50,6 +51,7 @@ import io.jpom.service.dblog.BuildInfoService;
import io.jpom.service.node.ProjectInfoCacheService;
import io.jpom.service.outgiving.DbOutGivingLogService;
import io.jpom.service.outgiving.OutGivingServer;
import io.jpom.util.FileUtils;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
@ -207,7 +209,12 @@ public class OutGivingController extends BaseServerController {
outGivingModel.setIntervalTime(intervalTime);
//
outGivingModel.setClearOld(Convert.toBool(getParameter("clearOld"), false));
//
String secondaryDirectory = getParameter("secondaryDirectory");
Opt.ofBlankAble(secondaryDirectory).ifPresent(s -> {
FileUtils.checkSlip(s, e -> new IllegalArgumentException("二级目录不能越级:" + e.getMessage()));
outGivingModel.setSecondaryDirectory(secondaryDirectory);
});
}
/**

View File

@ -181,7 +181,7 @@ public class OutGivingProjectController extends BaseServerController {
*/
@RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.UPLOAD)
public JsonMessage<Object> upload(String id, String afterOpt, String clearOld, String autoUnzip) throws IOException {
public JsonMessage<Object> upload(String id, String afterOpt, String clearOld, String autoUnzip, String secondaryDirectory) throws IOException {
OutGivingModel outGivingModel = this.check(id);
AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0));
Assert.notNull(afterOpt1, "请选择分发后的操作");
@ -201,6 +201,7 @@ public class OutGivingProjectController extends BaseServerController {
//outGivingModel = outGivingServer.getItem(id);
outGivingModel.setClearOld(Convert.toBool(clearOld, false));
outGivingModel.setAfterOpt(afterOpt1.getCode());
outGivingModel.setSecondaryDirectory(secondaryDirectory);
outGivingServer.update(outGivingModel);
// 开启
@ -228,7 +229,7 @@ public class OutGivingProjectController extends BaseServerController {
*/
@RequestMapping(value = "remote_download", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.REMOTE_DOWNLOAD)
public JsonMessage<String> remoteDownload(String id, String afterOpt, String clearOld, String url, String autoUnzip) {
public JsonMessage<String> remoteDownload(String id, String afterOpt, String clearOld, String url, String autoUnzip, String secondaryDirectory) {
OutGivingModel outGivingModel = this.check(id);
AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0));
Assert.notNull(afterOpt1, "请选择分发后的操作");
@ -242,6 +243,7 @@ public class OutGivingProjectController extends BaseServerController {
//outGivingModel = outGivingServer.getItem(id);
outGivingModel.setClearOld(Convert.toBool(clearOld, false));
outGivingModel.setAfterOpt(afterOpt1.getCode());
outGivingModel.setSecondaryDirectory(secondaryDirectory);
outGivingServer.update(outGivingModel);
//下载
File file = FileUtil.file(serverConfig.getUserTempPath(), ServerConst.OUTGIVING_FILE, id);

View File

@ -24,6 +24,7 @@ package io.jpom.controller.outgiving;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
@ -51,6 +52,7 @@ import io.jpom.permission.MethodFeature;
import io.jpom.service.dblog.BuildInfoService;
import io.jpom.service.node.ProjectInfoCacheService;
import io.jpom.service.outgiving.OutGivingServer;
import io.jpom.util.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
@ -404,7 +406,12 @@ public class OutGivingProjectEditController extends BaseServerController {
deleteProject(outGivingModel, outGivingNodeProjects, userModel);
outGivingModel.outGivingNodeProjectList(outGivingNodeProjects);
//
String secondaryDirectory = getParameter("secondaryDirectory");
Opt.ofBlankAble(secondaryDirectory).ifPresent(s -> {
FileUtils.checkSlip(s, e -> new IllegalArgumentException("二级目录不能越级:" + e.getMessage()));
outGivingModel.setSecondaryDirectory(secondaryDirectory);
});
return tuples;
}

View File

@ -75,6 +75,11 @@ public class OutGivingModel extends BaseWorkspaceModel {
*/
private Integer status;
/**
* 二级目录
*/
private String secondaryDirectory;
public boolean clearOld() {
return clearOld != null && clearOld;

View File

@ -69,7 +69,8 @@ public class OutGivingItemRun implements Callable<OutGivingNodeProject.Status> {
private final UserModel userModel;
private final boolean unzip;
private final boolean clearOld;
private Integer sleepTime;
private final Integer sleepTime;
private final String secondaryDirectory;
/**
* 数据库记录id
*/
@ -82,8 +83,9 @@ public class OutGivingItemRun implements Callable<OutGivingNodeProject.Status> {
boolean unzip,
Integer sleepTime) {
this.outGivingId = item.getId();
this.unzip = unzip;
this.secondaryDirectory = item.getSecondaryDirectory();
this.clearOld = item.clearOld();
this.unzip = unzip;
this.outGivingNodeProject = outGivingNodeProject;
this.file = file;
this.afterOpt = ObjectUtil.defaultIfNull(EnumUtil.likeValueOf(AfterOpt.class, item.getAfterOpt()), AfterOpt.No);
@ -105,7 +107,7 @@ public class OutGivingItemRun implements Callable<OutGivingNodeProject.Status> {
this.updateStatus(this.outGivingId, this.outGivingNodeProject,
OutGivingNodeProject.Status.Ing, "开始分发");
//
JsonMessage<String> jsonMessage = OutGivingRun.fileUpload(file, null,
JsonMessage<String> jsonMessage = OutGivingRun.fileUpload(file, this.secondaryDirectory,
this.outGivingNodeProject.getProjectId(),
unzip,
afterOpt,

View File

@ -61,7 +61,7 @@ public class DbExtConfig {
/**
* 缓存大小
* <p>
* http://www.h2database.com/html/features.html#cache_settings
* <a href="http://www.h2database.com/html/features.html#cache_settings">http://www.h2database.com/html/features.html#cache_settings</a>
*/
private DataSize cacheSize = DataSize.ofMegabytes(10);

View File

@ -73,7 +73,7 @@ public class OperateLogController implements AopLogInterface {
private final DbUserOperateLogService dbUserOperateLogService;
private String[] logFilterPar = new String[]{"pwd", "pass", "password"};
private final String[] logFilterPar = new String[]{"pwd", "pass", "password"};
public OperateLogController(DbUserOperateLogService dbUserOperateLogService) {

View File

@ -25,3 +25,6 @@ ALTER TABLE NODE_INFO DROP COLUMN IF EXISTS `cycle`;
ALTER TABLE PROJECT_INFO
ADD IF NOT EXISTS triggerToken VARCHAR (100) comment '触发器token';
ALTER TABLE OUT_GIVING
ADD IF NOT EXISTS secondaryDirectory VARCHAR (200) comment '二级目录';

View File

@ -361,14 +361,19 @@
</a-select>
</a-form-model-item>
<!-- 项目 -->
<a-form-model-item v-if="temp.releaseMethod === 2" label="发布项目" prop="releaseMethodDataIdList">
<a-cascader v-model="temp.releaseMethodDataIdList" :options="cascaderList" placeholder="请选择节点项目" />
</a-form-model-item>
<a-form-model-item v-if="temp.releaseMethod === 2" label="发布后操作" prop="afterOpt">
<a-select show-search allowClear v-model="tempExtraData.afterOpt" placeholder="请选择发布后操作">
<a-select-option v-for="opt in afterOptListSimple" :key="opt.value">{{ opt.title }}</a-select-option>
</a-select>
</a-form-model-item>
<template v-if="temp.releaseMethod === 2">
<a-form-model-item label="发布项目" prop="releaseMethodDataIdList">
<a-cascader v-model="temp.releaseMethodDataIdList" :options="cascaderList" placeholder="请选择节点项目" />
</a-form-model-item>
<a-form-model-item label="发布后操作" prop="afterOpt">
<a-select show-search allowClear v-model="tempExtraData.afterOpt" placeholder="请选择发布后操作">
<a-select-option v-for="opt in afterOptListSimple" :key="opt.value">{{ opt.title }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item prop="projectSecondaryDirectory" label="二级目录">
<a-input v-model="tempExtraData.projectSecondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-model-item>
</template>
<!-- SSH -->
<template v-if="temp.releaseMethod === 3">
<a-form-model-item prop="releaseMethodDataId">
@ -872,6 +877,10 @@
</a-space>
</a-form-model-item>
<a-form-model-item v-if="temp.releaseMethod === 2" prop="projectSecondaryDirectory" label="二级目录">
<a-input v-model="temp.projectSecondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-model-item>
<a-form-model-item label="构建备注" prop="buildRemark">
<a-textarea v-model="temp.buildRemark" :maxLength="240" placeholder="请输入构建备注,长度小于 240" :auto-size="{ minRows: 3, maxRows: 5 }" />
</a-form-model-item>
@ -1555,8 +1564,10 @@ export default {
this.buildConfirmVisible = true;
this.branchList = [];
this.branchTagList = [];
//
try {
this.temp = { ...this.temp, checkRepositoryDiff: (JSON.parse(record.extraData) || {}).checkRepositoryDiff };
const extraData = JSON.parse(record.extraData) || {};
this.temp = { ...this.temp, checkRepositoryDiff: extraData.checkRepositoryDiff, projectSecondaryDirectory: extraData.projectSecondaryDirectory };
} catch (e) {
//
}
@ -1570,6 +1581,7 @@ export default {
branchTagName: this.temp.branchTagName,
branchName: this.temp.branchName,
checkRepositoryDiff: this.temp.checkRepositoryDiff,
projectSecondaryDirectory: this.temp.projectSecondaryDirectory,
},
true
).then(() => {

View File

@ -235,6 +235,9 @@
</template>
<a-input-number :min="0" v-model="temp.intervalTime" placeholder="分发间隔时间 (顺序重启、完整顺序重启)方式才生效" style="width: 100%" />
</a-form-model-item>
<a-form-model-item prop="secondaryDirectory" label="二级目录">
<a-input v-model="temp.secondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-model-item>
<a-form-model-item prop="clearOld">
<template slot="label">
清空发布
@ -392,6 +395,9 @@
</template>
<a-input-number :min="0" v-model="temp.intervalTime" placeholder="分发间隔时间 (顺序重启、完整顺序重启)方式才生效" style="width: 100%" />
</a-form-model-item>
<a-form-model-item prop="secondaryDirectory" label="二级目录">
<a-input v-model="temp.secondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-model-item>
<a-form-model-item prop="clearOld">
<template slot="label">
清空发布
@ -540,6 +546,9 @@
<a-select-option v-for="item in afterOptList" :key="item.value">{{ item.title }}</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item prop="secondaryDirectory" label="二级目录">
<a-input v-model="temp.secondaryDirectory" placeholder="不填写则发布至项目的根目录" />
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 项目文件组件 -->
@ -800,6 +809,7 @@ export default {
id: record.id,
intervalTime: record.intervalTime,
clearOld: record.clearOld,
secondaryDirectory: record.secondaryDirectory,
};
// console.log(this.temp);
this.linkDispatchVisible = true;
@ -927,6 +937,7 @@ export default {
nodeIdList: [],
intervalTime: record.intervalTime,
clearOld: record.clearOld,
secondaryDirectory: record.secondaryDirectory,
};
}
// nodeIdList
@ -1094,6 +1105,7 @@ export default {
formData.append("afterOpt", this.temp.afterOpt);
formData.append("clearOld", this.temp.clearOld);
formData.append("autoUnzip", this.temp.autoUnzip);
formData.append("secondaryDirectory", this.temp.secondaryDirectory);
uploadDispatchFile(formData).then((res) => {
if (res.code === 200) {
this.$notification.success({

View File

@ -120,7 +120,7 @@
</a-form-model-item>
<a-form-model-item label="SSH节点" required>
<a-select show-search option-filter-prop="children" mode="multiple" v-model="chooseSsh">
<a-select show-search option-filter-prop="children" mode="multiple" v-model="chooseSsh" placeholder="请选择 SSH节点">
<a-select-option v-for="item in sshList" :key="item.id" :value="item.id">
{{ item.name }}
</a-select-option>