Skip to content

virzz/mulan

Repository files navigation

Mulan 木兰

Go Report Card GoDoc License

Mulan 是一个面向 Go 应用的轻量级启动框架。
它把 命令行入口、配置加载、环境变量注入、日志初始化、服务生命周期管理 这些常见能力收拢到一起,适合拿来做服务程序的统一启动层。

特性

  • 基于 cobra 的命令行入口
  • 基于 viper 的配置加载
  • 自动读取环境变量
  • 默认加载 .env
  • 内置 version 命令
  • 统一管理多个服务的 Serve / Shutdown / Close
  • 默认接入 zap 日志
  • 支持启动前预初始化逻辑

安装

go get github.com/virzz/mulan

适用场景

你可以用它来做:

  • HTTP 服务启动器
  • 多服务组合启动
  • 带配置文件和环境变量的应用入口
  • 需要优雅关闭的服务程序
  • 统一 CLI 风格的微服务项目

核心概念

1. Meta

Meta 用来描述应用自身信息:

  • ID:应用唯一标识
  • Name:应用名
  • CNName:中文名
  • Description:描述
  • Version:版本号
  • Commit:提交号
  • BuildAt:构建时间

这些信息会被内置的 version 命令使用。

2. App

App 是框架的入口对象,负责:

  • 管理根命令
  • 加载配置
  • 绑定环境变量
  • 注册服务
  • 执行启动逻辑
  • 处理优雅退出

3. Servicer

接入 App 的服务都需要实现下面接口:

type Servicer interface {
    Serve() error
    Shutdown(context.Context) error
    Close() error
}
  • Serve():启动服务
  • Shutdown(ctx):优雅关闭
  • Close():立即关闭

快速开始

下面是一个最小可运行示例。

package main

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

	"github.com/gin-gonic/gin"
	"github.com/virzz/mulan"
)

type Config struct {
	Web WebConfig `json:"web" yaml:"web"`
}

type WebConfig struct {
	Port int `json:"port" yaml:"port"`
}

type WebService struct {
	engine *gin.Engine
	server *http.Server
	cfg    *WebConfig
}

func NewWebService(cfg *WebConfig) *WebService {
	engine := gin.Default()
	engine.GET("/", func(c *gin.Context) {
		c.String(200, "Hello, Mulan!")
	})

	return &WebService{
		engine: engine,
		server: &http.Server{
			Addr:    fmt.Sprintf(":%d", cfg.Port),
			Handler: engine,
		},
		cfg: cfg,
	}
}

func (w *WebService) Serve() error {
	go w.server.ListenAndServe()
	return nil
}

func (w *WebService) Shutdown(ctx context.Context) error {
	shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	return w.server.Shutdown(shutdownCtx)
}

func (w *WebService) Close() error {
	return w.server.Close()
}

func main() {
	conf := &Config{}

	meta := &mulan.Meta{
		ID:          "com.example.demo",
		Name:        "demo",
		CNName:      "示例服务",
		Description: "demo service",
		Version:     "1.0.0",
		Commit:      "dev",
		BuildAt:     time.Now().Format(time.RFC3339),
	}

	app := mulan.New(meta, conf)

	webSrv := NewWebService(&conf.Web)
	app.AddService(webSrv)

	if err := app.Execute(context.Background()); err != nil {
		panic(err)
	}
}

配置加载

框架会按下面方式加载配置:

配置文件优先级

  1. 命令行参数指定 -c / --config
  2. 当前目录下默认配置文件 config.*
  3. 环境变量

默认内置参数

-c, --config   配置文件路径
-d, --debug    调试级别,可重复传入

例如:

go run . --config ./config.yaml
go run . -d
go run . -d -d

环境变量规则

执行 app.Execute() 时会自动设置环境变量绑定规则:

  • 前缀:app.Name
  • . 会转成 _
  • - 会转成 _

例如应用名为:

demo

配置字段类似:

web:
  port: 8080

则可以通过环境变量覆盖:

DEMO_WEB_PORT=8080

.env 支持

创建应用时会自动尝试加载当前目录下的 .env 文件。

示例:

DEMO_WEB_PORT=8080

版本命令

框架内置了 version 命令,也支持别名 v

go run . version
go run . v

输出内容可能包含:

  • Name
  • CNName
  • ID
  • Version
  • Commit
  • BuildAt
  • Description

如果 CommitBuildAt 没有手动传入,框架会尝试从构建信息里读取。

常用用法

添加服务

app.AddService(webSrv, jobSrv, grpcSrv)

多个服务会被统一托管。

添加自定义命令

app.AddCommand(cmd1, cmd2)

注意:version 是内置命令,不能被重复注册。

添加 FlagSet

app.AddFlagSet(fs)

绑定 flags 到配置系统

app.BindFlags()

如果你希望把命令行参数也映射到配置系统,可以显式调用这个方法。

启动前预处理

app.SetPreInit(func(ctx context.Context) error {
    // 比如初始化外部资源、校验环境、注册监控
    return nil
})

自定义日志

app.SetLogger(logger)

生命周期说明

默认执行逻辑大致如下:

  1. 创建 App
  2. 注册服务
  3. 加载配置
  4. 执行 preInit
  5. 调用所有服务的 Serve()
  6. 监听退出信号
  7. 收到信号后执行:
    • SIGTERM:调用 Shutdown(ctx)
    • 中断信号:调用 Close()

这让服务既能支持快速退出,也能支持优雅关闭。

推荐项目结构

你可以按下面方式组织启动代码:

.
├── cmd/
│   └── app/
│       └── main.go
├── internal/
│   ├── config/
│   ├── server/
│   └── biz/
├── config.yaml
└── .env

如果你的项目有多个服务,可以把 HTTP、gRPC、任务消费者分别实现成不同的 Servicer,再统一注册到 App

一个简单配置示例

web:
  port: 8080

对应配置结构体:

type Config struct {
    Web WebConfig `json:"web" yaml:"web"`
}

type WebConfig struct {
    Port int `json:"port" yaml:"port"`
}

设计目标

Mulan 不想替你决定业务逻辑怎么写,它主要解决的是“应用怎么启动”和“服务怎么统一管理”这类通用问题。

它更适合做:

  • 项目入口层
  • 服务编排层
  • 配置装配层
  • 生命周期管理层

注意事项

  • Serve() 建议尽快返回错误,避免吞错
  • Shutdown(ctx) 应尽量实现优雅关闭
  • Close() 适合兜底快速关闭
  • 配置结构体建议同时补上 jsonyaml tag
  • 如果你需要将 flags 与配置系统联动,记得调用 BindFlags()

许可证

本项目基于 MIT 开源。

About

Mulan 木兰

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages