项目工程化--添加CLI

使用命令启动服务

例如

初始化

vblog-api init

启动服务

vblog-api start -f etc/confit.toml

版本号

vblog-api -v

需要借助一个强大的CLI库 Cobra

https://github.com/spf13/cobra

安装

go get -u github.com/spf13/cobra@latest

迁移服务

修改入口文件main.go

package main

import (
	_ "gitee.com/hexug/vblog/tree/master/api/apps/all"
	"gitee.com/hexug/vblog/tree/master/api/cmd"
)

func main() {
	//conf.LoadConfigFromToml("etc/config.toml")
	//conf.LoadConfigFromEnv()
	//logger.L().Infoln("配置读取完成。")
	//apps.InitApps()
	//logger.L().Infoln("app初始化完成")
	////gin.SetMode(gin.ReleaseMode)
	//engine := gin.Default()
	//router := engine.Group("/vblog/api/v1")
	//apps.InitApis(router)
	//svc := conf.C().Server
	//engine.Run(fmt.Sprintf("%s:%d", svc.Host, svc.Port))
    
    
    //将上面的逻辑都迁移到start命令下
    //在这里只要执行命令的入口函数就好了
	cmd.Execute()
}

创建最顶层的命令

package cmd

import (
	"fmt"
	"gitee.com/hexug/vblog/tree/master/api/cmd/initial"
	"gitee.com/hexug/vblog/tree/master/api/cmd/start"
	"github.com/spf13/cobra"
)

var (
	version bool
    //创建根命令
	rootCmd = &cobra.Command{
		/*
			Use是单行用法消息。
			建议的语法如下:
			[]标识可选参数。不包含在括号中的参数是必需的。
			...表示可以为上一个参数指定多个值。
			\表示互斥信息。可以使用分隔符左侧的参数或分隔符右侧的参数。不能在一次使用命令时同时使用两个参数。
			{}在需要其中一个参数时分隔一组互斥参数。如果参数为可选,它们用括号([])括起来。
			示例:add [-F file | -D dir]... [-f format] file
		*/
		Use: "vblog-api",
		// short是“help”输出中显示的简短描述。
		Short: "vblog的后端服务",
		// Long是“help<this command>”输出中显示的长消息。
		Long: "一个前后端分离的vblog demo",
		//Example是如何使用该命令的示例。
		Example: "vblog-api  -v",

		/*
			//钩子函数
			// PersistentPeRun:此命令的子级将继承并执行。
			PersistentPreRun func(cmd *Command, args []string)
			// PreRun: 此命令的子级将不会继承。
			PreRun func(cmd *Command, args []string)
			// Run: 通常为实际工作的函数。大多数命令只会实现这一点。
			Run func(cmd *Command, args []string)
			// PostRun: 在“Run”之后运行。
			PostRun func(cmd *Command, args []string)
			// PersistentPostRun: 此命令的子级将在PostRun之后继承并执行。
			PersistentPostRun func(cmd *Command, args []string)
		*/
		Run: func(cmd *cobra.Command, args []string) {
            //当带了version参数,就打印版本号
			if version {
				fmt.Println("vblog-api Version 1.0.1")
				return
			}
            //否则打印帮助信息
			err := cmd.Help()
			cobra.CheckErr(err)
		},
	}
)

func Execute() {
    //添加其他子命令
	rootCmd.AddCommand(initial.Cmd)
	rootCmd.AddCommand(start.Cmd)
    
    //执行根命令的入口函数
	if err := rootCmd.Execute(); err != nil {
		//fmt.Fprintln(os.Stderr, err)
		//os.Exit(1)
		return
	}
}

//初始化的时候获取参数的值 绑定了一个本地变量version 用来显示版本
func init() {
	rootCmd.Flags().BoolVarP(&version, "version", "v", false, "版本")
}

创建子命令

初始化子命令,这里未实现具体逻辑

package initial

import (
	"github.com/spf13/cobra"
)

var (
	Cmd = &cobra.Command{
		Use:   "init",
		Short: "初始化",
		Long:  "初始化",

		Run: func(cmd *cobra.Command, args []string) {
			cmd.Println("这是初始化的命令")
			cmd.Println("尚未实现")
		},
	}
)

启动服务的子命令

package start

import (
	"fmt"
	"gitee.com/hexug/vblog/tree/master/api/apps"
	"gitee.com/hexug/vblog/tree/master/api/conf"
	"gitee.com/hexug/vblog/tree/master/api/logger"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
)

var (
	configType string
	configPath string
	Cmd        = &cobra.Command{
		Use:     "start",
		Short:   "启动服务",
		Long:    "启动服务",
		Example: "vblog-api start -t file -f etc/config.toml",
		Run: func(cmd *cobra.Command, args []string) {
            //判断指定的配置文件类型
			switch configType {
			case "file":
				cmd.Println("从file加载配置文件:", configPath)
				conf.LoadConfigFromToml(configPath)
			case "env":
				cmd.Println("从环境变量加载配置")
				conf.LoadConfigFromEnv()
			case "etcd":
				cmd.Println("尚未实现")
				return
			default:
				cmd.PrintErrln("选择的加载配置类型有误,目前只允许['file','env','etcd']")
				return
			}
			logger.L().Infoln("配置加载完成。")
			apps.InitApps()
			logger.L().Infoln("app初始化完成")
			//gin.SetMode(gin.ReleaseMode)
			engine := gin.Default()
			router := engine.Group("/vblog/api/v1")
			apps.InitApis(router)
			svc := conf.C().Server
			err := engine.Run(fmt.Sprintf("%s:%d", svc.Host, svc.Port))
			if err != nil {
				cmd.Println(err.Error())
			}
		},
	}
)

func init() {
    //给start命令添加flag
	Cmd.Flags().StringVarP(&configType, "config-type", "t", "file", "指定加载配置的类型")
	Cmd.Flags().StringVarP(&configPath, "config-path", "f", "etc/config.toml", "指定配置文件")
}

补充

在子命令中读取全局标志的值

举例说明

目录结构

/myapp
  ├── cmd
  │   ├── foo.go       # foo 子命令
  ├── main.go          # 启动程序和根命令定义

根命令

// main.go
package main

import (
	"fmt"
	"os"
	"myapp/cmd" // 导入 cmd 包以注册命令
    "github.com/spf13/cobra"
)

var config string // 全局标志变量

// 根命令
var rootCmd = &cobra.Command{
	Use:   "myapp",
	Short: "A simple CLI app",
}

func init() {
	// 在根命令中定义全局标志 --config
	rootCmd.PersistentFlags().StringVarP(&config, "config", "c", "", "Configuration file")

	// 注册子命令
	rootCmd.AddCommand(cmd.CmdFoo)
}

func main() {
	// 执行根命令
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

子命令

// cmd/foo.go
package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
)

var CmdFoo = &cobra.Command{
	Use:   "foo",
	Short: "Foo command",
	Run: func(cmd *cobra.Command, args []string) {
		// 通过 cmd.Flags() 获取 --config 标志的值
		config, _ := cmd.Flags().GetString("config")
		fmt.Println("Foo command executed.")
		fmt.Println("Config flag value:", config) // 通过 cmd 获取标志的值
	},
}

直接使用 config, _ := cmd.Flags().GetString("config") 来获取全局标志的值,未定义就是空

运行

# 设置 --config 标志并执行 foo 子命令
$ myapp foo --config my_config.yaml
Foo command executed.
Config flag value: my_config.yaml
posted @ 2023-01-08 06:18  厚礼蝎  阅读(49)  评论(0)    收藏  举报