Browse Source

初始化服务配置并更新文档

初始化服务配置文件和服务相关代码,更新README.md以包含新的运行时和服务注册配置说明,并调整了CHANGELOG.md版本号。
SongZihuan 1 week ago
parent
commit
0d6c173f7b
8 changed files with 209 additions and 61 deletions
  1. 2 2
      CHANGELOG.md
  2. 49 2
      README.md
  3. 5 0
      SERVICE.yaml
  4. 1 0
      resource.go
  5. 103 0
      service.go
  6. 18 0
      src/global/service.go
  7. 24 57
      src/mainfunc/cat/v1/main.go
  8. 7 0
      src/utils/exitutils/exit.go

+ 2 - 2
CHANGELOG.md

@@ -5,7 +5,7 @@
 其格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
 且本项目遵循 [语义化版本控制](https://semver.org/lang/zh-CN/)。
 
-## [未发行]
+## [0.3.0] - 2025-04-17
 
 ### 新增
 
@@ -45,7 +45,7 @@
     - 若有`commit hash`,则最终版本号为`0.0.0+1744225466-be8f4ff51e6ed2e01171b38459406dc5dac306ea`,其中`1744225466`为编译时间戳,`be8f4ff51e6ed2e01171b38459406dc5dac306ea`为`commit hash`。
 - `Server.Example1`例子更完善,输出更多信息。
 - 命令行参数`--version`输出更多信息:版本号、编译时间(UTC和Local)、编译的Go版本号、系统、架构。
-- 应用`exitutils.SuccessExitQuite()`函数到命令行参数的阻断执行退出中
+- 应用`exitutils.SuccessExitQuite()`函数到命令行参数的阻断执行退出中
 
 ### 修复
 

+ 49 - 2
README.md

@@ -90,7 +90,9 @@ $ go build -o lionv1 -trimpath -ldflags='-s -w -extldflags "-static"' -gcflags='
 
 **终止运行参数:当命令行出现这些参数时,将只执行参数对应的功能,执行完成后不会继续运行后续服务。**
 
-### 配置文件
+### 运行时配置文件
+
+**注意:此配置文件为运行时配置文件,即编译后的运行时阶段才从指定文件路径中读取。与后面提及的服务注册所用的`SERVICE.yaml`编译时配置文件不同。**
 
 ```yaml
 # 等同于命令行参数的 --name ,但优先级高于命令行参数。
@@ -170,13 +172,52 @@ server:  # 系统执行服务所需要的参数
 
 虽然`lionv1`和`tigerv1`也可以作为后台服务,但是我使用了`catv1`进行了更高层次的抽象,使得在`Windows`和`Linux`上可以安装服务程序。
 
+后台服务采用Go的第三方库`github.com/kardianos/service`实现,主要目的是实现`Windows`上的服务注册。
+但是理论上来说,`MacOS`和`Linux`(`systemd`)也能使用。
+不过,在`Linux`上注册服务,可能自己编辑`systemd`配置文件,或者使用宝塔等辅助面板会更为灵活。
+
+### 配置
+
+服务的相关配置文件位于根目录的`SERVICE.yaml`中,具体如下:
+
+```yaml
+name: TestService  # 服务名称(大小写字母或数字)
+display-name: Test Service  # 服务的显示名称(人类可读形式),若为空则和 name 一致
+describe: 一个简单的Go测试服务  # 服务的秒数
+
+# 参数来源
+#  no      无运行时参数(默认行为)
+#  install 在安装时指定参数,例如:catv1 install a b c,其中 a b c 作为参数
+#  config  在本配置文件中的 argument-list 列表指定运行时参数
+argument-from: install
+# argument-from 为 config 时启用
+argument-list: []
+# 环境变量来源
+#  no      无运行时环境变量(默认行为)
+#  install 在安装时,根据 env-get-list 获取安装时的真实环境变量
+#  config  在本配置文件中的 env-set-list 中指定环境变量
+
+env-from: no
+# env-from 为 install 时启用
+env-get-list: 
+  - a  # 安装程序(catv1 install)运行时,获取环境变量 a,并作为服务运行时的环境变量(例如安装时 a 的值为 b ,服务运行时也将得到环境变量 a 的值为 b)
+  - c
+# env-from 为 config 时启用
+env-set-list: 
+  a: b  # 例如:映射环境变量 a 的值为 b
+  c: d
+
+```
+
+**注意:本配置文件是编译时配置文件,在编译后配置文件包含在二进制文件中,此时可移除和修改文件系统上的配置文件而不影响编译好的程序。**
+
 ### 安装
 
 ```shell
 $ catv1 install <命令行参数列表>
 ```
 
-使用此命令可以在`Windows`中或`Linux`中注册一个服务,服务名称为:`<resource.Name>-cat-v1`。
+使用此命令可以在`Windows`中或`Linux`中注册一个服务.
 
 注意:安装后可执行程序`catv1`仍需保留在原来位置,不可移动。
 
@@ -186,6 +227,12 @@ $ catv1 install <命令行参数列表>
 $ catv1 uninstall
 ```
 
+或者
+
+```shell
+$ catv1 remove
+```
+
 ### 启动
 
 ```shell

+ 5 - 0
SERVICE.yaml

@@ -0,0 +1,5 @@
+name: TestService
+display-name: Test Service
+describe: 一个简单的Go测试服务
+argument-from: install
+env-from: no

+ 1 - 0
resource.go

@@ -43,6 +43,7 @@ func init() {
 	initCleanFile()
 	initBuildDate()
 	initVersion()
+	initServiceConfig()
 }
 
 func initCleanFile() {

+ 103 - 0
service.go

@@ -0,0 +1,103 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package resource
+
+import (
+	_ "embed"
+	"gopkg.in/yaml.v3"
+	"os"
+	"regexp"
+)
+
+const (
+	FromNo      = "no"
+	FromInstall = "install"
+	FromConfig  = "config"
+)
+
+const (
+	Args1Install    = "install"
+	Args1Uninstall1 = "remove"
+	Args1Uninstall2 = "uninstall"
+	Args1Start      = "start"
+	Args1Stop       = "stop"
+	Args1Restart    = "restart"
+)
+
+type ServiceConfigType struct {
+	Name         string            `yaml:"name"`
+	DisplayName  string            `yaml:"display-name"`
+	Describe     string            `yaml:"describe"`
+	ArgumentFrom string            `yaml:"argument-from"`
+	ArgumentList []string          `yaml:"argument-list"`
+	EnvFrom      string            `yaml:"env-from"`
+	EnvGetList   []string          `yaml:"env-get-list"`
+	EnvSetList   map[string]string `yaml:"env-set-list"`
+}
+
+//go:embed SERVICE.yaml
+var serviceConfig []byte
+var ServiceConfig ServiceConfigType
+
+var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z0-9]+$`)
+
+func initServiceConfig() {
+	err := yaml.Unmarshal(serviceConfig, &ServiceConfig)
+	if err != nil {
+		panic(err)
+	}
+
+	if ServiceConfig.Name == "" || !nameRegex.MatchString(ServiceConfig.Name) {
+		panic("service name is invalid")
+	}
+
+	if ServiceConfig.DisplayName == "" {
+		ServiceConfig.DisplayName = ServiceConfig.Name
+	}
+
+	switch ServiceConfig.ArgumentFrom {
+	case FromInstall:
+		if len(os.Args) > 2 && os.Args[1] == Args1Install {
+			ServiceConfig.ArgumentFrom = FromConfig
+			ServiceConfig.ArgumentList = os.Args[2:]
+		} else {
+			ServiceConfig.ArgumentFrom = FromNo
+			ServiceConfig.ArgumentList = nil
+		}
+	case FromConfig:
+		if len(ServiceConfig.ArgumentList) == 0 {
+			ServiceConfig.ArgumentFrom = FromNo
+			ServiceConfig.ArgumentList = nil
+		}
+	default:
+		ServiceConfig.ArgumentFrom = FromNo
+		ServiceConfig.ArgumentList = nil
+	}
+
+	switch ServiceConfig.EnvFrom {
+	case FromInstall:
+		if len(ServiceConfig.EnvGetList) == 0 {
+			ServiceConfig.EnvFrom = FromNo
+			ServiceConfig.EnvGetList = nil
+			ServiceConfig.EnvSetList = nil
+			break
+		}
+
+		ServiceConfig.EnvSetList = make(map[string]string, len(ServiceConfig.EnvGetList))
+		for _, e := range ServiceConfig.EnvGetList {
+			ServiceConfig.EnvSetList[e] = os.Getenv(e)
+		}
+	case FromConfig:
+		ServiceConfig.EnvGetList = nil
+		if len(ServiceConfig.EnvSetList) == 0 {
+			ServiceConfig.EnvFrom = FromNo
+			ServiceConfig.EnvSetList = nil
+		}
+	default:
+		ServiceConfig.EnvFrom = FromNo
+		ServiceConfig.EnvGetList = nil
+		ServiceConfig.EnvSetList = nil
+	}
+}

+ 18 - 0
src/global/service.go

@@ -0,0 +1,18 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package global
+
+import resource "github.com/SongZihuan/BackendServerTemplate"
+
+var ServiceConfig = resource.ServiceConfig
+
+const (
+	Args1Install    = resource.Args1Install
+	Args1Uninstall1 = resource.Args1Uninstall1
+	Args1Uninstall2 = resource.Args1Uninstall2
+	Args1Start      = resource.Args1Start
+	Args1Stop       = resource.Args1Stop
+	Args1Restart    = resource.Args1Restart
+)

+ 24 - 57
src/mainfunc/cat/v1/main.go

@@ -5,7 +5,6 @@
 package v1
 
 import (
-	"fmt"
 	"github.com/SongZihuan/BackendServerTemplate/src/global"
 	"github.com/SongZihuan/BackendServerTemplate/src/logger"
 	"github.com/SongZihuan/BackendServerTemplate/src/logger/loglevel"
@@ -33,101 +32,69 @@ func MainV1() (exitCode exitutils.ExitCode) {
 
 	// 定义服务配置
 	svcConfig := &service.Config{
-		Name:        fmt.Sprintf("%s-cat-v1", global.Name),
-		DisplayName: fmt.Sprintf("%s cat v1", global.Name),
-		Description: "简单的Go模板程序",
+		Name:        global.ServiceConfig.Name,
+		DisplayName: global.ServiceConfig.DisplayName,
+		Description: global.ServiceConfig.Describe,
+		Arguments:   global.ServiceConfig.ArgumentList,
+		EnvVars:     global.ServiceConfig.EnvSetList,
+	}
+
+	prg := NewProgram()
+	s, err := service.New(prg, svcConfig)
+	if err != nil {
+		return exitutils.InitFailedError("Service New", err.Error())
 	}
 
 	// 解析命令行参数
 	if len(os.Args) > 1 {
 		cmd := os.Args[1]
 		switch strings.ToLower(cmd) {
-		case "install":
-			if len(os.Args) > 2 {
-				svcConfig.Arguments = os.Args[2:]
-			}
-
-			prg := NewProgram()
-			s, err := service.New(prg, svcConfig)
-			if err != nil {
-				return exitutils.InitFailedError("Service New", err.Error())
-			}
-
+		case global.Args1Install:
 			// 安装服务
 			err = s.Install()
 			if err != nil {
 				return exitutils.InitFailedError("Service Install", err.Error())
 			}
 
-			return exitutils.SuccessExit("Service Install Success")
-		case "remove", "uninstall":
-			prg := NewProgram()
-			s, err := service.New(prg, svcConfig)
-			if err != nil {
-				return exitutils.InitFailedError("Service New", err.Error())
-			}
-
+			return exitutils.SuccessExitSimple("Service Install Success")
+		case global.Args1Uninstall1, global.Args1Uninstall2:
 			// 卸载服务
 			err = s.Uninstall()
 			if err != nil {
-				return exitutils.InitFailedError("Service Install", err.Error())
-			}
-
-			return exitutils.SuccessExit("Service Install Success")
-		case "start":
-			prg := NewProgram()
-			s, err := service.New(prg, svcConfig)
-			if err != nil {
-				return exitutils.InitFailedError("Service New", err.Error())
+				return exitutils.InitFailedError("Service Remove", err.Error())
 			}
 
+			return exitutils.SuccessExitSimple("Service Remove Success")
+		case global.Args1Start:
 			// 启动服务
 			err = s.Start()
 			if err != nil {
 				return exitutils.InitFailedError("Service Start", err.Error())
 			}
 
-			return exitutils.SuccessExit("Service Start Success")
-		case "stop":
-			prg := NewProgram()
-			s, err := service.New(prg, svcConfig)
-			if err != nil {
-				return exitutils.InitFailedError("Service New", err.Error())
-			}
-
+			return exitutils.SuccessExitSimple("Service Start Success")
+		case global.Args1Stop:
 			// 停止服务
 			err = s.Stop()
 			if err != nil {
 				return exitutils.InitFailedError("Service Stop", err.Error())
 			}
 
-			return exitutils.SuccessExit("Service Stop Success")
-		case "restart":
-			prg := NewProgram()
-			s, err := service.New(prg, svcConfig)
-			if err != nil {
-				return exitutils.InitFailedError("Service New", err.Error())
-			}
-
-			// 停止服务
+			return exitutils.SuccessExitSimple("Service Stop Success")
+		case global.Args1Restart:
+			// 重启服务
 			err = s.Restart()
 			if err != nil {
 				return exitutils.InitFailedError("Service Restart", err.Error())
 			}
 
-			return exitutils.SuccessExit("Service Restart Success")
+			return exitutils.SuccessExitSimple("Service Restart Success")
 		default:
+			// 正常运行服务
 			// pass
 		}
 	}
 
-	prg := NewProgram()
-	s, err := service.New(prg, svcConfig)
-	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
-	}
-
 	_ = s.Run()
-
 	return prg.ExitCode()
 }

+ 7 - 0
src/utils/exitutils/exit.go

@@ -123,6 +123,13 @@ func SuccessExit(reason string, exitCode ...int) ExitCode {
 	return ec
 }
 
+func SuccessExitSimple(reason string, exitCode ...int) ExitCode {
+	if reason != "" {
+		fmt.Println(reason)
+	}
+	return getExitCode(0, exitCode...)
+}
+
 func SuccessExitQuite(exitCode ...int) ExitCode {
 	if !logger.IsReady() {
 		return exitCodeErrorLogMustBeReady