Golang中如何创建Windows服务

Golang中如何创建Windows服务 如何为Gin REST应用创建Windows服务

2 回复

pkg.go.dev

svc 包 - golang.org/x/sys/windows/svc - Go Packages

包 svc 提供了构建 Windows 服务所需的一切。

更多关于Golang中如何创建Windows服务的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中为Gin REST应用创建Windows服务,推荐使用golang.org/x/sys/windows/svc标准库配合golang.org/x/sys/windows/svc/mgr。以下是完整实现示例:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/debug"
	"golang.org/x/sys/windows/svc/eventlog"
	"golang.org/x/sys/windows/svc/mgr"
)

// 服务结构体
type ginService struct {
	server *http.Server
	elog   debug.Log
}

// 实现windows/svc.Handler接口
func (s *ginService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
	changes <- svc.Status{State: svc.StartPending}
	
	// 启动Gin服务器
	go func() {
		gin.SetMode(gin.ReleaseMode)
		router := gin.Default()
		router.GET("/", func(c *gin.Context) {
			c.JSON(200, gin.H{"message": "Windows Service with Gin"})
		})
		
		s.server = &http.Server{
			Addr:    ":8080",
			Handler: router,
		}
		
		s.elog.Info(1, "Starting Gin server on :8080")
		if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			s.elog.Error(1, fmt.Sprintf("Server failed: %v", err))
		}
	}()
	
	changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
	
	// 处理服务控制请求
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Interrogate:
				changes <- c.CurrentStatus
			case svc.Stop, svc.Shutdown:
				changes <- svc.Status{State: svc.StopPending}
				s.elog.Info(1, "Shutting down server")
				
				// 优雅关闭Gin服务器
				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
				defer cancel()
				if err := s.server.Shutdown(ctx); err != nil {
					s.elog.Error(1, fmt.Sprintf("Server shutdown failed: %v", err))
				}
				
				s.elog.Info(1, "Service stopped")
				return false, 0
			default:
				s.elog.Error(1, fmt.Sprintf("Unexpected control request: %d", c))
			}
		}
	}
}

// 安装服务
func installService(name, desc string) error {
	exepath, err := os.Executable()
	if err != nil {
		return err
	}
	
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	
	s, err := m.OpenService(name)
	if err == nil {
		s.Close()
		return fmt.Errorf("service %s already exists", name)
	}
	
	s, err = m.CreateService(name, exepath, mgr.Config{
		DisplayName: name,
		Description: desc,
		StartType:   mgr.StartAutomatic,
	})
	if err != nil {
		return err
	}
	defer s.Close()
	
	// 配置恢复选项
	err = s.SetRecoveryActions([]mgr.RecoveryAction{
		{Type: mgr.ServiceRestart, Delay: 60 * time.Second},
		{Type: mgr.ServiceRestart, Delay: 60 * time.Second},
		{Type: mgr.NoAction, Delay: 0},
	}, 0)
	if err != nil {
		return err
	}
	
	return eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
}

// 卸载服务
func removeService(name string) error {
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	
	s, err := m.OpenService(name)
	if err != nil {
		return fmt.Errorf("service %s is not installed", name)
	}
	defer s.Close()
	
	err = s.Delete()
	if err != nil {
		return err
	}
	
	return eventlog.Remove(name)
}

func main() {
	const svcName = "GinRESTService"
	
	isIntSess, err := svc.IsAnInteractiveSession()
	if err != nil {
		log.Fatalf("Failed to determine session: %v", err)
	}
	
	if !isIntSess {
		// 作为服务运行
		elog, err := eventlog.Open(svcName)
		if err != nil {
			log.Fatal(err)
		}
		defer elog.Close()
		
		service := &ginService{elog: elog}
		elog.Info(1, fmt.Sprintf("Starting %s service", svcName))
		err = svc.Run(svcName, service)
		if err != nil {
			elog.Error(1, fmt.Sprintf("%s service failed: %v", svcName, err))
			return
		}
		elog.Info(1, fmt.Sprintf("%s service stopped", svcName))
		return
	}
	
	// 命令行模式
	if len(os.Args) > 1 {
		cmd := os.Args[1]
		switch cmd {
		case "install":
			err := installService(svcName, "Gin REST API Service")
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("Service %s installed successfully\n", svcName)
		case "remove":
			err := removeService(svcName)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("Service %s removed successfully\n", svcName)
		case "debug":
			// 调试模式运行
			elog := debug.New(svcName)
			service := &ginService{elog: elog}
			elog.Info(1, fmt.Sprintf("Starting %s in debug mode", svcName))
			err = svc.Run(svcName, service)
			if err != nil {
				elog.Error(1, fmt.Sprintf("%s service failed: %v", svcName, err))
			}
		default:
			fmt.Printf("Unknown command: %s\n", cmd)
			fmt.Println("Usage:")
			fmt.Println("  install - install service")
			fmt.Println("  remove  - remove service")
			fmt.Println("  debug   - run in debug mode")
		}
		return
	}
	
	// 直接运行(非服务模式)
	fmt.Println("Running Gin server directly on :8080")
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "Running in console mode"})
	})
	router.Run(":8080")
}

编译和部署步骤:

  1. 编译为Windows可执行文件
GOOS=windows GOARCH=amd64 go build -o gin-service.exe
  1. 安装服务
gin-service.exe install
  1. 启动服务
sc start GinRESTService
  1. 查看服务状态
sc query GinRESTService
  1. 调试运行
gin-service.exe debug
  1. 卸载服务
gin-service.exe remove

关键特性说明:

  • 使用svc.IsAnInteractiveSession()检测运行环境
  • 实现svc.Handler接口处理服务生命周期
  • 支持优雅关闭HTTP服务器
  • 集成Windows事件日志系统
  • 支持自动重启的恢复策略
  • 提供安装、卸载、调试等命令行操作

服务安装后可在Windows服务管理器中看到"GinRESTService",支持自动启动和故障恢复。

回到顶部