From bd4e71203152cdb31ecb939c6113e9db78d1d767 Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期一, 05 十月 2026 11:57:04 +0800
Subject: [PATCH] docs: fix handoff todo section reference
---
traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java | 83 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 83 insertions(+), 0 deletions(-)
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java
new file mode 100644
index 0000000..a4d9de6
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/system/controller/VersionController.java
@@ -0,0 +1,83 @@
+package com.trafficaudit.system.controller;
+
+import com.trafficaudit.common.Result;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Exposes the product version so the UI (and operators) can confirm which build
+ * is actually running on a server. Kept public on purpose: the login page shows
+ * the version before any token exists.
+ */
+@RestController
+@RequestMapping("/api")
+public class VersionController {
+
+ private static final String VERSION_FILE = "app-version.properties";
+
+ @GetMapping("/version")
+ public Result<Map<String, Object>> version() {
+ Map<String, Object> info = new LinkedHashMap<>();
+ info.put("version", readVersion());
+ info.put("buildTime", resolveBuildTime());
+ return Result.ok(info);
+ }
+
+ private String readVersion() {
+ try (InputStream in = new ClassPathResource(VERSION_FILE).getInputStream()) {
+ Properties props = new Properties();
+ props.load(in);
+ String value = props.getProperty("version");
+ return value == null || value.trim().isEmpty() ? "unknown" : value.trim();
+ } catch (Exception e) {
+ return "unknown";
+ }
+ }
+
+ /**
+ * Build time of the running artifact. In a packaged run this is the jar file's
+ * last-modified time; when running from exploded classes (mvn spring-boot:run)
+ * there is no jar, so the value is null.
+ */
+ private String resolveBuildTime() {
+ try {
+ URI location = VersionController.class.getProtectionDomain()
+ .getCodeSource().getLocation().toURI();
+ Path path = resolveArtifactPath(location);
+ if (path != null && Files.isRegularFile(path)) {
+ return Files.getLastModifiedTime(path).toInstant().toString();
+ }
+ } catch (Exception ignored) {
+ // Fall through: build time is optional information.
+ }
+ return null;
+ }
+
+ /**
+ * Spring Boot executable jars expose a nested classpath such as
+ * "jar:file:/app.jar!/BOOT-INF/classes!/". Strip the nested part before
+ * asking the filesystem for the jar timestamp.
+ */
+ private Path resolveArtifactPath(URI location) {
+ String value = location.toString();
+ int nested = value.indexOf("!/");
+ if (nested >= 0) {
+ value = value.substring(0, nested);
+ }
+ if (value.startsWith("jar:")) {
+ value = value.substring(4);
+ }
+ return Paths.get(URI.create(value));
+ }
+}
--
Gitblit v1.9.1