logo
0
0
WeChat Login
refactor: 拆分核心服务模块并统一平台文件布局

service GoDoc Coverage

跨平台 Go 服务(守护进程)框架,统一 Windows、Linux(systemd/Upstart/SysV/OpenRC)、macOS launchd 的安装、启动、停止和运行接口,可自动识别交互/服务模式。

特性

  • 简化 API:Runner 接口和 ServiceRunner 辅助类,消除 goroutine/channel 样板代码。使用 NewFromRunner 即可将 Runner 实现适配为 ServiceRunner,无需手动包装。
  • StructuredDeps 跨平台依赖管理(after/before/require/want 自动映射,各平台自动降级或告警)。
  • TimeoutConfig 启动 / 停止 / 重启超时统一配置,未设置时采用平台默认值。
  • 重启策略配置:systemd Restart/StartLimit,Windows OnFailure/Delay,保留历史 Restart 选项兼容旧版模板。
  • 生命周期钩子:systemd ExecStartPre/ExecStartPost/ExecStopPost,upstart pre-start/post-stop。
  • 日志配置样板:一行生成 logrotate、systemd journald、Windows EventLog 配置,详见 LOGGING.md。
  • 核心包按 configfactoryrunnerinterface 等职责拆分,减少单文件膨胀,便于测试与审查。
  • 平台适配实现按职责分文件组织,安装、控制、模板与状态逻辑分层,便于维护与审查。
  • 支持安装/卸载/启动/停止/状态查询,保持 API 一致性,避免平台分支。

安装

  • Go 模块(需要 Go 1.23+):go get github.com/darkit/daemon@latest
  • 运行内置示例:go run ./example/simple

快速开始

方式一:使用 Runner 接口(推荐)

实现 Runner 接口的类型会被框架自动包装成 ServiceRunner,无需手动创建。这是最简洁的方式,适合大多数场景。

package main

import (
    "context"
    "log"
    "time"

    "github.com/darkit/daemon"
)

type myService struct{}

func (m *myService) Run(ctx context.Context) error {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            log.Println("服务运行中...")
        case <-ctx.Done():
            return nil
        }
    }
}

func main() {
    cfg := &service.Config{
        Name:        "demo",
        DisplayName: "Demo Service",
        Description: "跨平台 demo 服务",
    }

    svc, err := service.NewFromRunner(&myService{}, cfg)
    if err != nil {
        log.Fatal(err)
    }

    if err := svc.Run(); err != nil {
        log.Fatal(err)
    }
}

方式二:使用 ServiceRunner 辅助类

直接使用 NewServiceRunner 创建函数式服务,无需定义类型。适合快速开发和原型验证。

package main

import (
    "context"
    "log"
    "time"

    "github.com/darkit/daemon"
)

func main() {
    runner := service.NewServiceRunner(func(ctx context.Context) error {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for {
            select {
            case <-ticker.C:
                log.Println("服务运行中...")
            case <-ctx.Done():
                return nil
            }
        }
    })

    cfg := &service.Config{
        Name:        "demo",
        DisplayName: "Demo Service",
        Description: "跨平台 demo 服务",
    }

    svc, err := service.New(runner, cfg)
    if err != nil {
        log.Fatal(err)
    }

    if err := svc.Run(); err != nil {
        log.Fatal(err)
    }
}

方式三:传统方式(向后兼容)

传统的 Interface 方式,需要手动管理 goroutine 和退出信号。保留用于遗留代码或需要精细控制启动/停止逻辑的场景。

package main

import (
    "log"
    "time"

    "github.com/darkit/daemon"
)

type program struct{ quit chan struct{} }

func (p *program) Start(s service.Service) error {
    p.quit = make(chan struct{})
    go func() {
        for {
            select {
            case <-p.quit:
                return
            default:
                time.Sleep(time.Second)
            }
        }
    }()
    return nil
}

func (p *program) Stop(service.Service) error {
    close(p.quit)
    return nil
}

func main() {
    cfg := &service.Config{
        Name:        "demo",
        DisplayName: "Demo Service",
        Description: "跨平台 demo 服务",
    }

    svc, err := service.New(&program{}, cfg)
    if err != nil {
        log.Fatal(err)
    }

    if err := svc.Run(); err != nil {
        log.Fatal(err)
    }
}

API 对比

以下表格对比三种实现方式的特点:

对比维度Runner 接口ServiceRunner 函数式Interface 传统方式
代码量~10 行~5 行~30 行
学习曲线低(仅需理解 context)极低(闭包即可)中(需手动管理生命周期)
goroutine 管理自动自动手动
context 传播自动自动手动
panic 恢复自动自动手动
超时控制支持支持(可配置)手动实现
适用场景大多数新项目快速原型、简单服务遗留代码、复杂启动逻辑
向后兼容

典型迁移示例

传统方式(~30 行):

type program struct{ quit chan struct{} }

func (p *program) Start(s service.Service) error {
    p.quit = make(chan struct{})
    go func() {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for {
            select {
            case <-ticker.C:
                log.Println("运行中...")
            case <-p.quit:
                return
            }
        }
    }()
    return nil
}

func (p *program) Stop(service.Service) error {
    close(p.quit)
    time.Sleep(100 * time.Millisecond) // 等待 goroutine 退出
    return nil
}

Runner 方式(~10 行):

type program struct{}

func (p *program) Run(ctx context.Context) error {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            log.Println("运行中...")
        case <-ctx.Done():
            return nil
        }
    }
}

ServiceRunner 方式(~5 行):

runner := service.NewServiceRunner(func(ctx context.Context) error {
    // 业务逻辑
    <-ctx.Done()
    return nil
})

选择指南

  • 新项目:优先使用 Runner 接口,简洁且标准
  • 快速开发:使用 ServiceRunner,无需定义类型
  • 自定义启动:使用 ServiceRunner.StartFunc/StopFunc
  • 超时需求:配置 ServiceRunner.StartTimeout/StopTimeout
  • 遗留代码:保持 Interface 方式,或渐进式迁移

平台能力矩阵

说明:✅ 完整支持;◑ 部分支持;⏳ 实验性;— 暂不支持。

能力/平台WindowsLinux systemdLinux UpstartLinux SysVLinux OpenRCmacOS launchd
StructuredDeps◑(after/require/want,before 忽略并告警)◑(after/require)⏳(after/require 软等待脚本)
TimeoutConfig✅(Start/Stop)✅(Start/Stop/RestartSec)◑(Restart 间隔)
重启策略配置✅(OnFailure/Delay)✅(Restart/StartLimit)◑(respawn 策略)◑(LSB retry)◑(respawn/rc-service)
生命周期钩子✅(ExecStartPre/Post、ExecStopPost)✅(pre-start/post-stop)
日志配置样板✅(EventLog 脚本)✅(journald + logrotate)✅(logrotate)✅(logrotate)

新 API 示例

以下示例均复用最小实现:

package main

import "github.com/darkit/daemon"

type program struct{}

func (p *program) Start(service.Service) error { return nil }
func (p *program) Stop(service.Service) error  { return nil }

StructuredDeps 结构化依赖

package main

import "github.com/darkit/daemon"

func main() {
    cfg := &service.Config{
        Name: "myapp",
        StructuredDeps: []service.Dependency{
            {Name: "network.target", Type: service.DependencyAfter},
            {Name: "postgresql", Type: service.DependencyRequire},
            {Name: "logger", Type: service.DependencyBefore},
        },
    }
    svc, _ := service.New(&program{}, cfg)
    _ = svc.Run()
}

TimeoutConfig 超时配置

package main

import (
    "time"

    "github.com/darkit/daemon"
)

func main() {
    cfg := &service.Config{
        Name: "myapp",
        Timeout: service.TimeoutConfig{
            Start:   10 * time.Second,
            Stop:    20 * time.Second,
            Restart: 5 * time.Second,
        },
    }
    svc, _ := service.New(&program{}, cfg)
    _ = svc.Run()
}

生命周期钩子(systemd 示例)

package main

import "github.com/darkit/daemon"

func main() {
    cfg := &service.Config{
        Name: "myapp",
        Option: service.KeyValue{
            service.OptionExecStartPre:  []string{"/usr/local/bin/prepare.sh"},
            service.OptionExecStartPost: "echo started",
            service.OptionExecStopPost:  "rm -f /var/run/myapp.pid",
            service.OptionOnFailureUnit: []string{"alert@%n.service"},
        },
    }
    svc, _ := service.New(&program{}, cfg)
    _ = svc.Run()
}

日志配置样板

// 生成 logrotate、journald、Windows EventLog 配置文件内容
files := map[string]string{
    "logrotate.conf":       service.GenerateLogrotateConfig("myapp", "/var/log/myapp.log"),
    "journald.conf":        service.GenerateSystemdJournalConfig("myapp"),
    "windows-eventlog.ps1": service.GenerateWindowsEventLogConfig("Application", "MyApp"),
}

更多细节见 LOGGING.md

BUGS

  • Dependencies 字段在 Linux 系统与 launchd 上未完整实现(建议改用 StructuredDeps)。
  • OS X 以 UserService Interactive 运行时状态检测仍不准确。