From 83a1f05f1ad91d90d6a2d1d45df44ed65b1d8057 Mon Sep 17 00:00:00 2001 From: Zhan Ziyang Date: Sat, 22 Aug 2026 15:41:41 +0800 Subject: [PATCH] feat: add node SSR dual-slot deployment config --- internal/deploymentconfig/config.go | 96 +++++++++++++++++++++++- internal/deploymentconfig/config_test.go | 22 ++++++ packaging/etc/yms-daemon/yms-daemon.toml | 15 ++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/internal/deploymentconfig/config.go b/internal/deploymentconfig/config.go index 1625d63..6cf4884 100644 --- a/internal/deploymentconfig/config.go +++ b/internal/deploymentconfig/config.go @@ -34,6 +34,10 @@ const ( BackendPort8080 = 8080 // BackendPort8081 蓝绿发布中 8081 槽位的端口号。 BackendPort8081 = 8081 + // NodeSsrPort18910 Node SSR 的第一个宿主槽位端口。 + NodeSsrPort18910 = 18910 + // NodeSsrPort28910 Node SSR 的第二个宿主槽位端口。 + NodeSsrPort28910 = 28910 ) const ( @@ -57,6 +61,14 @@ const ( containerName8080 = "backend-8080" // containerName8081 容器后端 8081 槽位的容器名称。 containerName8081 = "backend-8081" + // nodeSsrContainerName18910 Node SSR 18910 槽位的容器名称。 + nodeSsrContainerName18910 = "node-ssr-18910" + // nodeSsrContainerName28910 Node SSR 28910 槽位的容器名称。 + nodeSsrContainerName28910 = "node-ssr-28910" + // nodeSsrHealthURL18910 Node SSR 18910 槽位的健康检查地址。 + nodeSsrHealthURL18910 = "http://127.0.0.1:18910/health" + // nodeSsrHealthURL28910 Node SSR 28910 槽位的健康检查地址。 + nodeSsrHealthURL28910 = "http://127.0.0.1:28910/health" ) const ( @@ -83,6 +95,8 @@ type Config struct { Daemon Daemon `toml:"daemon"` // Backend 记录显式选定的后端运行时及其蓝绿槽位。 Backend Backend `toml:"backend"` + // NodeSsr 记录 Node SSR 容器运行时及其蓝绿槽位。 + NodeSsr NodeSsr `toml:"nodeSsr"` } // Daemon 描述与具体组件运行时无关的机器级行为。 @@ -129,6 +143,28 @@ type BackendSlot struct { HealthEndpoint string `toml:"health_endpoint"` } +// NodeSsr 描述 Node SSR 容器运行时及其蓝绿槽位。 +type NodeSsr struct { + // Type 当前只接受 container。 + Type string `toml:"type"` + // StopGraceSeconds 停止旧容器时的优雅退出等待秒数,可省略。 + StopGraceSeconds *int `toml:"stop_grace_seconds"` + // Slot 包含 18910、28910 两个宿主端口槽位。 + Slot NodeSsrSlots `toml:"slot"` +} + +// NodeSsrSlots 列出 Node SSR 的两个精确宿主端口槽位。 +type NodeSsrSlots struct { + Port18910 NodeSsrSlot `toml:"18910"` + Port28910 NodeSsrSlot `toml:"28910"` +} + +// NodeSsrSlot 包含一个 Node SSR 容器槽位的容器名与健康检查地址。 +type NodeSsrSlot struct { + ContainerName string `toml:"container_name"` + HealthEndpoint string `toml:"health_endpoint"` +} + // Load 打开 path 指向的部署配置文件,进行严格的 TOML 解码与契约校验,返回解析后的 Config。 // 它要求 path 是绝对路径且为普通文件,解码时禁用未知字段(任何大小写、拼写或层级不一致的 // 键都会被拒绝),解码后调用 Validate 精确校验取值。任一步失败都会返回带上下文的 error。 @@ -176,7 +212,7 @@ func validateExactDocumentKeys(document []byte) error { if err := toml.Unmarshal(document, &root); err != nil { return err } - if err := rejectUnknownKeys(root, "", "daemon", "backend"); err != nil { + if err := rejectUnknownKeys(root, "", "daemon", "backend", "nodeSsr"); err != nil { return err } daemon, err := exactTable(root, "", "daemon") @@ -209,6 +245,29 @@ func validateExactDocumentKeys(document []byte) error { return err } } + nodeSsr, err := exactTable(root, "", "nodeSsr") + if err != nil { + return err + } + if err := rejectUnknownKeys(nodeSsr, "nodeSsr", "type", "stop_grace_seconds", "slot"); err != nil { + return err + } + nodeSlots, err := exactTable(nodeSsr, "nodeSsr", "slot") + if err != nil { + return err + } + if err := rejectUnknownKeys(nodeSlots, "nodeSsr.slot", "18910", "28910"); err != nil { + return err + } + for _, port := range []string{"18910", "28910"} { + slot, err := exactTable(nodeSlots, "nodeSsr.slot", port) + if err != nil { + return err + } + if err := rejectUnknownKeys(slot, "nodeSsr.slot."+port, "container_name", "health_endpoint"); err != nil { + return err + } + } return nil } @@ -303,9 +362,33 @@ func (c Config) Validate() error { default: return fmt.Errorf("backend.type must be %q or %q", BackendTypeNative, BackendTypeContainer) } + if c.NodeSsr.Type != BackendTypeContainer { + return fmt.Errorf("nodeSsr.type must be %q", BackendTypeContainer) + } + if c.NodeSsr.StopGraceSeconds != nil && *c.NodeSsr.StopGraceSeconds <= 0 { + return errors.New("nodeSsr.stop_grace_seconds must be a positive integer") + } + if err := validateNodeSsrSlot("nodeSsr.slot.18910", c.NodeSsr.Slot.Port18910, nodeSsrContainerName18910, nodeSsrHealthURL18910); err != nil { + return err + } + if err := validateNodeSsrSlot("nodeSsr.slot.28910", c.NodeSsr.Slot.Port28910, nodeSsrContainerName28910, nodeSsrHealthURL28910); err != nil { + return err + } return nil } +// SlotForNodeSsrPort 返回 Node SSR 宿主端口对应的槽位配置。 +func (n NodeSsr) SlotForNodeSsrPort(port int) (NodeSsrSlot, error) { + switch port { + case NodeSsrPort18910: + return n.Slot.Port18910, nil + case NodeSsrPort28910: + return n.Slot.Port28910, nil + default: + return NodeSsrSlot{}, fmt.Errorf("unsupported Node SSR port: %d", port) + } +} + // SlotForPort 返回 port 对应的后端槽位配置,仅支持 BackendPort8080 或 BackendPort8081, // 其他端口返回带端口号的错误。 func (b Backend) SlotForPort(port int) (BackendSlot, error) { @@ -346,6 +429,17 @@ func validateContainerSlot(field string, slot BackendSlot, containerName string, return nil } +// validateNodeSsrSlot 校验 Node SSR 槽位的容器名称和健康检查地址。 +func validateNodeSsrSlot(field string, slot NodeSsrSlot, containerName string, endpoint string) error { + if slot.ContainerName != containerName { + return fmt.Errorf("%s.container_name must be %q", field, containerName) + } + if slot.HealthEndpoint != endpoint { + return fmt.Errorf("%s.health_endpoint must be %q", field, endpoint) + } + return nil +} + // validateAbsolutePath 校验 value 是一个非空、无首尾空白、经过 Clean 且不含 NUL 字节的绝对路径, // 用于保证配置中的路径标识符不会被意外篡改。field 用于拼出错误信息。 func validateAbsolutePath(field string, value string) error { diff --git a/internal/deploymentconfig/config_test.go b/internal/deploymentconfig/config_test.go index ee10d3f..668f329 100644 --- a/internal/deploymentconfig/config_test.go +++ b/internal/deploymentconfig/config_test.go @@ -26,6 +26,17 @@ health_endpoint = "http://127.0.0.1:8080/yms/actuator/health" unit = "yms-backend@8081.service" jar = "/home/yms/lib/glory-soft-yms-8081.jar" health_endpoint = "http://127.0.0.1:8081/yms/actuator/health" + +[nodeSsr] +type = "container" + +[nodeSsr.slot.18910] +container_name = "node-ssr-18910" +health_endpoint = "http://127.0.0.1:18910/health" + +[nodeSsr.slot.28910] +container_name = "node-ssr-28910" +health_endpoint = "http://127.0.0.1:28910/health" ` // validContainerConfig 一份满足容器后端部署契约的示例 TOML 配置,供各测试用例共享。 @@ -43,6 +54,17 @@ health_endpoint = "http://127.0.0.1:8080/yms/actuator/health" [backend.slot.8081] container_name = "backend-8081" health_endpoint = "http://127.0.0.1:8081/yms/actuator/health" + +[nodeSsr] +type = "container" + +[nodeSsr.slot.18910] +container_name = "node-ssr-18910" +health_endpoint = "http://127.0.0.1:18910/health" + +[nodeSsr.slot.28910] +container_name = "node-ssr-28910" +health_endpoint = "http://127.0.0.1:28910/health" ` // TestLoadValidNativeConfiguration 验证合法原生配置能被完整加载, diff --git a/packaging/etc/yms-daemon/yms-daemon.toml b/packaging/etc/yms-daemon/yms-daemon.toml index 49f98c1..0821a91 100644 --- a/packaging/etc/yms-daemon/yms-daemon.toml +++ b/packaging/etc/yms-daemon/yms-daemon.toml @@ -48,6 +48,21 @@ unit = "yms-backend@8081.service" jar = "/home/yms/lib/glory-soft-yms-8081.jar" health_endpoint = "http://127.0.0.1:8081/yms/actuator/health" +# ============================================================================= +# Node SSR:容器双槽,宿主 18910/28910,Nginx 逻辑入口 8910 +# ============================================================================= +[nodeSsr] +type = "container" +# stop_grace_seconds = 30 + +[nodeSsr.slot.18910] +container_name = "node-ssr-18910" +health_endpoint = "http://127.0.0.1:18910/health" + +[nodeSsr.slot.28910] +container_name = "node-ssr-28910" +health_endpoint = "http://127.0.0.1:28910/health" + # ============================================================================= # 方式二:容器后端(切换到容器时,取消注释本块,并注释掉上面的方式一) # =============================================================================