diff --git a/web/src/main/java/com/dite/znpt/web/controller/DeployController.java b/web/src/main/java/com/dite/znpt/web/controller/DeployController.java new file mode 100644 index 0000000..c3045e7 --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/DeployController.java @@ -0,0 +1,157 @@ +package com.dite.znpt.web.controller; + +import org.apache.commons.codec.binary.Hex; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; + +@RestController +public class DeployController { + + @Value("${deploy.secret}") + private String secret; + + @Value("${deploy.app-dir}") + private String appDir; + + private Process deploymentProcess; + + @PostMapping("/gitee-webhook") + public ResponseEntity handleWebhook( + @RequestHeader(value = "X-Gitee-Token", required = false) String signature, + @RequestBody String payload) { + + // 验证签名 + if (signature == null || !signature.equals(calculateSignature(payload))) { + return ResponseEntity.status(401).body("无效签名"); + } + + // 安全启动部署 + startDeployment(); + + return ResponseEntity.ok("部署流程已启动"); + } + + @GetMapping("/deployment-status") + public ResponseEntity getDeploymentStatus() { + try { + Path statusFile = Path.of(appDir, "deployment-status.txt"); + if (!Files.exists(statusFile)) { + return ResponseEntity.ok("尚未开始部署"); + } + + String statusContent = Files.readString(statusFile); + return ResponseEntity.ok(statusContent); + } catch (Exception e) { + return ResponseEntity.status(500).body("状态读取错误: " + e.getMessage()); + } + } + + @GetMapping("/deployment-log") + public ResponseEntity getDeploymentLog( + @RequestParam(defaultValue = "20") int lines) { + try { + Path logFile = Path.of(appDir, "deploy.log"); + if (!Files.exists(logFile)) { + return ResponseEntity.ok("无可用日志"); + } + + String logContent; + if (lines > 0) { + logContent = tail(logFile, lines); + } else { + logContent = Files.readString(logFile); + } + + return ResponseEntity.ok("
" + logContent + "
"); + } catch (Exception e) { + return ResponseEntity.status(500).body("日志读取错误: " + e.getMessage()); + } + } + + private synchronized void startDeployment() { + // 防止重复部署 + if (deploymentProcess != null && deploymentProcess.isAlive()) { + return; + } + + try { + // 清理旧状态 + Files.deleteIfExists(Path.of(appDir, "deployment-status.txt")); + + // 启动部署脚本 + ProcessBuilder builder = new ProcessBuilder("./deploy.sh"); + builder.directory(new File(appDir)); + builder.redirectErrorStream(true); + + deploymentProcess = builder.start(); + + // 启动日志监控线程 + new Thread(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(deploymentProcess.getInputStream()))) { + + while (deploymentProcess.isAlive()) { + String line = reader.readLine(); + if (line != null) { + System.out.println("[DEPLOY] " + line); + } + } + + int exitCode = deploymentProcess.waitFor(); + System.out.println("部署进程结束,状态码: " + exitCode); + deploymentProcess = null; + + } catch (Exception e) { + System.err.println("部署日志读取错误: " + e.getMessage()); + } + }).start(); + + } catch (Exception e) { + System.err.println("启动部署脚本失败: " + e.getMessage()); + try { + Files.writeString(Path.of(appDir, "deployment-status.txt"), + "START_FAILED - " + e.getMessage()); + } catch (Exception ex) { + // 忽略 + } + } + } + + private String tail(Path path, int lines) throws IOException { + ProcessBuilder builder = new ProcessBuilder("tail", "-n", String.valueOf(lines), path.toString()); + Process process = builder.start(); + + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()))) { + + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } + return output.toString(); + } + + private String calculateSignature(String payload) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] signatureBytes = digest.digest( + (payload + secret).getBytes(StandardCharsets.UTF_8) + ); + return Hex.encodeHexString(signatureBytes); + } catch (Exception e) { + throw new RuntimeException("签名生成失败", e); + } + } +} diff --git a/web/src/main/resources/application-dev.yml b/web/src/main/resources/application-dev.yml index f1dcea0..04c6399 100644 --- a/web/src/main/resources/application-dev.yml +++ b/web/src/main/resources/application-dev.yml @@ -137,3 +137,7 @@ upload: # 此处仅定义总的父路径,细节定义到 FilePathEnum save-path: D:\Upload\ +# 部署配置 +deploy: + secret: cRc5888KAo4TxRS4y5iv35GM + app-dir: /home/dtyx/znpt-backend