添加必要注释
This commit is contained in:
parent
dcfdc83444
commit
49c9155533
@ -12,6 +12,7 @@ public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder, ProdApiProperties prodApiProperties) {
|
||||
// 统一使用生产接口配置中的超时参数,避免各调用点各自维护一套 HTTP 超时。
|
||||
return builder
|
||||
.setConnectTimeout(Duration.ofMillis(prodApiProperties.getConnectTimeoutMs()))
|
||||
.setReadTimeout(Duration.ofMillis(prodApiProperties.getReadTimeoutMs()))
|
||||
|
||||
@ -77,6 +77,7 @@ public class ProdSyncCoordinator {
|
||||
String branch = gitRepoProperties.getScanBranch();
|
||||
String sourceVersion = gitClientService.prepareRepositoryAndGetHead(branch);
|
||||
Path exportDirectory = workDirectoryService.getDevToProdStagingDir().resolve("git-" + sourceVersion);
|
||||
// 先导出 Git 工作树快照,再计算内容哈希,避免直接拿工作目录参与后续修改。
|
||||
gitClientService.exportBranchSnapshot(branch, exportDirectory);
|
||||
String contentHash = packageService.calculateDirectoryHash(exportDirectory);
|
||||
|
||||
@ -110,6 +111,7 @@ public class ProdSyncCoordinator {
|
||||
packageBuildResult.getPackageName(),
|
||||
traceId
|
||||
);
|
||||
// Git 提交哈希 + 内容哈希作为业务幂等键,避免同一版本重复推送到生产。
|
||||
syncTaskService.markStatus(task.getTraceId(), SyncStatus.CONSUMING, null);
|
||||
prodConfigApiService.pushPackage(manifest, packageBuildResult.getZipFile());
|
||||
syncTaskService.markStatus(task.getTraceId(), SyncStatus.SUCCESS, null);
|
||||
@ -151,6 +153,7 @@ public class ProdSyncCoordinator {
|
||||
);
|
||||
syncTaskService.markStatus(task.getTraceId(), SyncStatus.CONSUMING, null);
|
||||
|
||||
// 生产快照只写入独立 snapshot 分支,避免与开发主分支形成闭环。
|
||||
String commitMessage = gitRepoProperties.getCommitMessagePrefix()
|
||||
+ ": traceId=" + task.getTraceId()
|
||||
+ " version=" + task.getSourceVersion();
|
||||
@ -182,6 +185,7 @@ public class ProdSyncCoordinator {
|
||||
if (traceId == null) {
|
||||
return;
|
||||
}
|
||||
// 只有达到最大重试次数后才把任务标记为失败,之前保留为可重试状态。
|
||||
syncTaskService.increaseRetryCount(traceId, summarizeException(e));
|
||||
Optional<SyncTask> task = syncTaskService.findByTraceId(traceId);
|
||||
int retryCount = task.map(SyncTask::getRetryCount).orElse(0);
|
||||
|
||||
@ -37,6 +37,7 @@ public class GitClientService {
|
||||
|
||||
public String prepareRepositoryAndGetHead(String branch) throws IOException, GitAPIException {
|
||||
synchronized (lock) {
|
||||
// 同一套本地仓库会被多个定时任务复用,这里串行化避免分支切换互相踩工作区。
|
||||
try (Git git = openOrCloneRepository()) {
|
||||
checkoutBranch(git, branch);
|
||||
pullIfRemoteBranchExists(git, branch);
|
||||
@ -54,6 +55,7 @@ public class GitClientService {
|
||||
try (Git git = openOrCloneRepository()) {
|
||||
checkoutBranch(git, branch);
|
||||
pullIfRemoteBranchExists(git, branch);
|
||||
// 导出纯工作树内容,显式排除 .git,避免把仓库内部文件带入同步包。
|
||||
FileTreeUtils.deleteRecursively(targetDirectory);
|
||||
FileTreeUtils.ensureDirectory(targetDirectory);
|
||||
copyWorkingTreeWithoutGit(getRepositoryPath(), targetDirectory);
|
||||
@ -70,6 +72,7 @@ public class GitClientService {
|
||||
if (!Files.exists(repositoryPath.resolve(".git"))) {
|
||||
throw new IOException("Git repository does not exist: " + repositoryPath);
|
||||
}
|
||||
// 生产快照回写采用“整目录覆盖”语义,确保 Git 分支内容与当前生产快照一致。
|
||||
FileTreeUtils.deleteChildrenExcept(repositoryPath, ".git");
|
||||
FileTreeUtils.copyDirectory(sourceDirectory, repositoryPath);
|
||||
git.add().addFilepattern(".").call();
|
||||
@ -117,6 +120,7 @@ public class GitClientService {
|
||||
Ref remoteRef = repository.findRef("refs/remotes/origin/" + branch);
|
||||
if (localRef == null) {
|
||||
if (remoteRef != null) {
|
||||
// 本地不存在分支时优先跟踪远端分支,保持与仓库约定一致。
|
||||
git.checkout()
|
||||
.setCreateBranch(true)
|
||||
.setName(branch)
|
||||
@ -138,6 +142,7 @@ public class GitClientService {
|
||||
Repository repository = git.getRepository();
|
||||
Ref remoteRef = repository.findRef("refs/remotes/origin/" + branch);
|
||||
if (remoteRef == null) {
|
||||
// 首次启动时本地未必有远端分支缓存,先显式 fetch 一次。
|
||||
git.fetch()
|
||||
.setCredentialsProvider(credentialsProvider())
|
||||
.setRemote("origin")
|
||||
|
||||
@ -47,6 +47,7 @@ public class PackageService {
|
||||
FileTreeUtils.ensureDirectory(zipFile.getParent());
|
||||
try (OutputStream outputStream = Files.newOutputStream(zipFile);
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)) {
|
||||
// 包内始终保留 manifest 和 hash,方便生产接口及后续排障直接核对来源版本。
|
||||
addJsonEntry(zipOutputStream, MANIFEST_FILE, manifest);
|
||||
addTextEntry(zipOutputStream, HASH_FILE, contentHash);
|
||||
addDirectoryEntries(zipOutputStream, sourceDirectory, CONFIG_DIR);
|
||||
@ -68,6 +69,7 @@ public class PackageService {
|
||||
ZipEntry entry;
|
||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||
Path target = extractDir.resolve(entry.getName()).normalize();
|
||||
// 拒绝 Zip Slip,避免恶意或损坏压缩包把文件写到目标目录之外。
|
||||
if (!target.startsWith(extractDir)) {
|
||||
throw new IOException("Zip entry escapes target directory: " + entry.getName());
|
||||
}
|
||||
@ -90,6 +92,7 @@ public class PackageService {
|
||||
throw new IOException("Package config directory is missing");
|
||||
}
|
||||
String actualHash = calculateDirectoryHash(configDir);
|
||||
// 解包后重新计算内容哈希,确保传输过程没有损坏或被篡改。
|
||||
if (manifest.getContentHash() != null
|
||||
&& !manifest.getContentHash().trim().isEmpty()
|
||||
&& !manifest.getContentHash().equals(actualHash)) {
|
||||
@ -101,6 +104,7 @@ public class PackageService {
|
||||
private void addDirectoryEntries(ZipOutputStream zipOutputStream, Path sourceDirectory, String rootName) throws IOException {
|
||||
Path gitDirectory = sourceDirectory.resolve(".git");
|
||||
try (Stream<Path> stream = Files.walk(sourceDirectory)) {
|
||||
// 打包时显式排除 .git,避免仓库元数据进入同步内容。
|
||||
stream.filter(path -> !path.equals(sourceDirectory))
|
||||
.filter(path -> !path.startsWith(gitDirectory))
|
||||
.forEach(path -> {
|
||||
|
||||
@ -50,6 +50,7 @@ public class ProdConfigApiService {
|
||||
HttpHeaders headers = defaultHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
|
||||
// 当前协议约定 push 使用 multipart/form-data 上传标准同步包。
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
|
||||
body.add("file", new FileSystemResource(zipFile.toFile()));
|
||||
body.add("traceId", manifest.getTraceId());
|
||||
@ -67,6 +68,7 @@ public class ProdConfigApiService {
|
||||
public ProdPullResult pullConfigSnapshot() throws IOException {
|
||||
String url = buildUrl(prodApiProperties.getPullPath());
|
||||
HttpHeaders headers = defaultHeaders();
|
||||
// 当前协议约定 pull 直接返回原始配置字节流,由同步工具落成本地文件后再回写 Git。
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
@ -87,6 +89,7 @@ public class ProdConfigApiService {
|
||||
Files.write(targetFile, body);
|
||||
|
||||
String contentHash = FileHashUtils.sha256(body);
|
||||
// 优先取服务端显式版本号;如果服务端没给,就退化为内容哈希做幂等判断。
|
||||
String sourceVersion = firstNonBlank(
|
||||
response.getHeaders().getFirst("X-Config-Version"),
|
||||
response.getHeaders().getETag(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user