仿照laravel-artisan實現簡易go開發腳手架
來源:互聯網
上載者:User
# 像Laravel-Artisan一樣執行go命令## 前言作為一個`laravel`重度患者,`artisan`是一個不可或缺的功能,可以說這是`laravel`的開發腳手架可以快速建立需要的檔案,加快開發速度而我目前正在開發的`bingo`架構正是受到`laravel`啟發,希望可以快速構建web應用而一個腳手架是必不可少的東西,所以我實現了一個`bingo sword` 工具### laravel-artisan實現思路我曾經寫過artisan的解析,連結在這裡[laravel artisan 原理解析](https://silsuer.github.io/2018/08/06/laravel-commands/)簡而言之,就是將 `kernel.php` 中註冊的所有 `commands` 都執行個體化一次,然後比對 命令名,對於尋找到的命令,調用 `handle`方法執行即可所以思路就有啦~### bingo sword 實現思路先看圖,隨便畫了畫流程![](http://qiniu-cdn.zhiguanapp.com/6af08b9bb9557c79bdadc7c80282dae3)下面直接上代碼:當命令列中輸入 `bingo sword make:command --name=MakeCommand`那麼在 `CLI` 的 `Run()` 方法中,擷取參數```goswordCmd := flag.NewFlagSet("sword", flag.ExitOnError) // bingo sword 命令err := swordCmd.Parse(os.Args[2:])swordConfig = os.Args[2:]if swordCmd.Parsed() {cli.swordHandle(swordConfig)}``` 此時接收到了需要的參數,然後調用`swordHanle`方法:```gofunc (cli *CLI) swordHandle(args []string) {// 解析這個參數,將資料傳入外部//fmt.Println(args)//擷取env中的kernel路徑//根據kernel去 go shell執行 go run xxx/kernel.go make:controller AdminControllerconsoleKernelPath := bingo.Env.Get("CONSOLE_KERNEL_PATH")// 擷取命令當前執行目錄dir, _ := os.Getwd() // 拼接Kernel的consoleKernelAbsolutePath := dir + "/" + consoleKernelPath + "/Kernel.go"// 使用go shell 調用 go run xxx/Kernel.go arg1 arg2 arg3var tmpSlice = []string{"go", "run", consoleKernelAbsolutePath}args = append(tmpSlice, args...)// 先檢查這個命令是否屬於內部命令// arg第一個就是命令console := Console{}console.Exec(args[2:], InnerCommands)//[run /Users/silsuer/go/src/test/app/Console/Kernel.go aaa bbb ccc] // 執行 go run Kernel.go command:namecmd := exec.Command("go",args[1:]...)var out bytes.Buffercmd.Stdout = &out // 開始執行命令if err := cmd.Start(); err != nil {panic(err)} // 等待命令執行完成if err := cmd.Wait(); err != nil {log.Fatal(err)} // 列印輸出fmt.Println(out.String())}```所以我們使用`bingo sword command:name --name=CommandName` 實際上執行的是`go run app/Console/Kernel.go command:name --name=CommandName`可以查看 `Kernel.go`的源碼,執行個體化了一個 `console`結構體,並調用了 `Exec()`方法這個方法:```gofunc (console *Console) Exec(args []string, commands []interface{}) { // 將參數封裝成了input對象input := console.initInput(args)// 遍曆傳入的commands數組(這是在kernel裡註冊的函數)for _, command := range commands {// 先做檢查,尋找對應的命令名commandValue := reflect.ValueOf(command)// 初始化命令結構體initCommand(&commandValue)// 映射期望參數與實際輸入參數(驗證參數輸入是否正確)target := checkParams(command, &input)// 不是這個命令,跳過這個命令if target == false {continue}// 獲得輸入和輸出並準備作為參數傳入Handle方法中var params = []reflect.Value{reflect.ValueOf(input), reflect.ValueOf(Output{})}commandValue.MethodByName("Handle").Call(params)}}```如果傳入的命令名沒有對上的話,會跳過這次迴圈,否則會執行這個命令值得注意的是,如果命令名一樣的話,這些命令都會執行,如果命令中會報錯的話,使用`panic()`只會拋出 `bing/cli/cli_sword.go` 中 `swordHandle()` 中的那行 `panic`錯誤的代碼### 目前實現的功能目前我只是簡單的實現了建立命令的命令,安裝好bingo後,在控制台輸入 `bingo sword make:command --name=HelloWorld`會在 `app/Console/Commands`目錄下產生一個 `HelloWorld.go` 檔案,內容是:```package Commandsimport ("github.com/silsuer/bingo/cli")type HelloWorld struct {cli.CommandName stringDescription stringArgs map[string]string}// 設定命令名func (c *HelloWorld) SetName() {c.Name = "command:name"}// 設定命令所需參數func (c *HelloWorld) SetArgs() {c.Args = make(map[string]string)c.Args["name"] = ""}// 設定命令描述func (c *HelloWorld) SetDescription() {c.Description = "the command description."}// 設定命令實現的方法func (c *HelloWorld) Handle(input cli.Input, output cli.Output) {}```我們只需要在其中修改我們要更改的資訊就可以了,例如更新handle方法為 `output.Info("Hello,World!")`,更新命令名是 `hello:world`然後無需使用 `go build`等方式重新編譯,直接在命令列使用 `bingo sword hello:world`,將在控制台列印輸出 `Hello,World`當然,這個工具只是為了加速開發,是`bingo`的一個模組,你隨時可以把它拆出來作為獨立模組使用### 知識點1. 使用go執行系統命令 `cmd := exec.Command("go","run","app/Console/Kernel.go","command:name")` 使用 `exec.Command` 將會產生一個 `cmd` 對象,執行 `cmd.Start()` 即可執行命令,這並不會阻塞進程,如果需要獲得結果 需要使用 `cmd.Wait()` 等待執行完成,再擷取標準輸出,當然也可以直接使用 `cmd.Run()` ,這行代碼會阻塞進程,直到命令完成 如果擷取標準輸出呢? ```govar out bytes.Buffer cmd.Stdout = &out ``` 將cmd的標準輸出指向我們設定好的一個`buffer`即可 2. 使用反射調用結構體的方法使用 `commandValue := reflect.ValueOf(command)` 擷取這個結構體的 `Value`使用 `commandValue.NumMethod()` 可以擷取這個結構體的方法數量,如果傳入的 `command`是一個結構體的指標的話得到的方法數量包括了針對結構體的方法數量和針對結構體指標的方法數量之和,如果傳入的是一個結構體對象,那麼得到的方法數量只是包括了針對結構體的方法數量使用 `commandValue.MethodByName("Handle").Call(params)` 調用結構體對應的方法3. 在控制台輸出帶顏色的文字和shell類似,只需要將控制台輸出的資訊使用一些顏色字元包裹起來即可```go //其中0x1B是標記,[開始定義顏色,1代表高亮,48代表黑色背景,32代表綠色前景,0代表恢複預設顏色。fmt.Printf("\n %c[0;48;32m%s%c[0m\n\n", 0x1B, "["+time.Now().Format("2006-01-02 15:04:05")+"]"+content, 0x1B)```#### 最後貼一下項目連結 [bingo](https://github.com/silsuer/bingo) ,歡迎star,更歡迎PR,歡迎提意見~~~ 135 次點擊