從別人的代碼中學習golang系列--02

来源:https://www.cnblogs.com/zhaof/archive/2020/07/03/13232768.html
-Advertisement-
Play Games

這篇博客還是整理從https://github.com/LyricTian/gin-admin 這個項目中學習的golang相關知識 作者在項目中使用了https://github.com/google/wire 做依賴註入,這個庫我之前沒有使用過,看了作者代碼中的使用,至少剛開始是看著優點懵,不知 ...


這篇博客還是整理從https://github.com/LyricTian/gin-admin 這個項目中學習的golang相關知識

作者在項目中使用了https://github.com/google/wire 做依賴註入,這個庫我之前沒有使用過,看了作者代碼中的使用,至少剛開始是看著優點懵,不知道是做什麼,所以這篇博客主要就是整理這個包的使用

依賴註入是什麼?

如果你搜索依賴註入,百度百科里可能先看到的是控制反轉,下麵是百度百科的解釋

控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它。也可以說,依賴被註入到對象中。

這樣的解釋可能還是不好理解,所以我們通過一個簡單的代碼來理解應該就清楚很多。

我們用程式實現:小明對世界說:"hello golang"

這裡將小明抽象為People 說的內容抽象為: Message 小明說 hello golang 抽象為:Event, 代碼如下:

package main

import "fmt"

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func main() {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   event.start()
}

從上面這個代碼我們可以看出,我們必須先初始化一個NewMessage, 因為NewPeople 依賴它,NewEvent 依賴NewPeople. 這還是一種比較簡單的依賴關係,實際生產的依賴關係可能會更複雜,那麼什麼好的辦法來處理這種依賴,https://github.com/google/wire 就是來乾這件事情的。

wire依賴註入例子

慄子1

安裝: go get github.com/google/wire/cmd/wire

上面的代碼,我們用wire的方式實現,代碼如下:

package main

import (
   "fmt"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() Event {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}
}

func main() {
   e := InitializeEvent()
   e.start()
}

這裡我們不用再手動初始化NewEvent, NewPeople, NewMessage,而是通過需要初始化的函數傳遞給wire.Build , 這三者的依賴關係,wire 會幫我們處理,我們通過wire . 的方式生成代碼:

➜  useWireBaseExample2 wire .
wire: awesomeProject/202006/useWireBaseExample2: wrote /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample2/wire_gen.go
➜  useWireBaseExample2 

會在當前目錄下生成wire_gen.go的代碼,內容如下:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
   "fmt"
)

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

// main.go:

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg2 := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg2
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg2 := e.people.SayHello()
   fmt.Println(msg2)
}

func main() {
   e := InitializeEvent()
   e.start()
}

代碼中wire為我們生成瞭如下代碼:

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

在看看我們剛開始寫的代碼,發現其實是一樣的,是不是感覺方便了很多。

註意:當使用 Wire 時,我們將同時提交 Wire.go 和 Wire _ gen 到代碼倉庫

wire 能做的事情很多,如果我們相互依賴的初始化其中有初始化失敗的,wire也能幫我們很好的處理。

慄子2

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾氣是否暴躁
}

// 小明這個人會說話
func (p People) SayHello() string {
   if p.grumpy {
      // 脾氣暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}
https://github.com/LyricTian/gin-admin
// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   e, err := InitializeEvent()
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

更改之後的代碼初始化NewEvent 可能就會因為People.grumpy 的值而失敗,通過wire生成之後的代碼

// Injectors from main.go:

func InitializeEvent() (Event, error) {
   message := NewMessage()
   people := NewPeople(message)
   event, err := NewEvent(people)
   if err != nil {
      return Event{}, err
   }
   return event, nil
}

慄子3

我們再將上面的代碼進行更改:

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

func NewMessage(msg string) Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾氣是否暴躁
}

// 小明這個人會說話
func (p People) SayHello() string {
   if p.grumpy {
      // 脾氣暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   msg := "Hello Golang"https://github.com/LyricTian/gin-admin
   e, err := InitializeEvent(msg)
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

上面的更改主要是NewPeople 函數增加了msg參數,同時InitializeEvent增加了msg參數,這個時候我們通過wire生成代碼則可以看到如下:

// Injectors from main.go:

func InitializeEvent(msg string) (Event, error) {
	message := NewMessage(msg)
	people := NewPeople(message)
	event, err := NewEvent(people)
	if err != nil {
		return Event{}, err
	}
	return event, nil
}

wire 會檢查註入器的參數,並檢查到NewMessage 需要msg的參數,所以它將msg傳遞給了NewMessage

慄子4

如果我們傳給wire.Build 的依賴關係存在問題,wire會怎麼處理呢? 我們調整InitializeEvent 的代碼:

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewMessage)
   return Event{}, nil
}

然後執行wire 進行代碼的生成:

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:63:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample4.People
        needed by awesomeProject/202006/useWireBaseExample4.Event in provider "NewEvent" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:46:6)
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure
➜  useWireBaseExample4 

錯誤提示中非常清楚的告訴我它找不到no provider found ,如果我們傳給wire.Build 沒有用的依賴,它依然會給我們提示告訴我們 unused provider "main.NewEventNumber"

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:67:1: inject InitializeEvent: unused provider "main.NewEventNumber"
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure

wire的高級用法

Binding Interfaces

依賴註入通常用於綁定介面的具體實現。通過下麵的例子理解:

// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}package main

import (
	"fmt"

	"github.com/google/wire"
)

type Fooer interface {
	Foo() string
}

type MyFooer string

func (b *MyFooer) Foo() string {
	return string(*b)
}

func provideMyFooer() *MyFooer {
	b := new(MyFooer)
	*b = "Hello, World!"
	return b
}

type Bar string

func provideBar(f Fooer) string {
	// f will be a *MyFooer.
	return f.Foo()
}


func InitializeEvent() string {
	wire.Build(provideMyFooer, provideBar, wire.Bind(new(Fooer), new(*MyFooer)))
	return ""
}
func main() {
	ret := InitializeEvent()
	fmt.Println(ret)
}

我們可以看到Fooer 是一個interface, MyFooer 實現了Fooer 這個介面,同時provideBar 的參數是Fooer 介面類型。可以看到// Run 運行服務

func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}

代碼中我們用了wire.Bind方法,為什麼這麼用呢?如果我們wire.Build的那段代碼寫成如下:

wire.Build(provideMyFooer, provideBar),再次用wire生成代碼則會提示如下錯誤:https://github.com/LyricTian/gin-admin

➜  useWireBaseExample5 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:36:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample5.Fooer
        needed by string in provider "provideBar" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:27:6)
wire: awesomeProject/202006/useWireBaseExample5: generate failed
wire: at least one generate failure

這是因為我們傳遞給provideBar 需要的是 Fooer 介面類型,我們傳給wire.Build 的是provideMyFooer, provideBar 這個時候預設從依賴關係里,provideBar 沒有找能夠提供Fooer的provider, 雖然我們我們都知道MyFooer 實現了Fooer 這個介面。所以我們需要在wire.Build 里告訴它,我們傳遞provideMyFooer 就是provideBar的provider。wire.Bind 就是來做這件事情的。

wire.Bind 的第一個參數是介面類型的值的指針,第二個參數是實現第一個參數介面的類型的值的指針。

這樣當我們在用wire生成代碼的時候就正常了。

Struct Providers

wire還可以用於結構體的構造。先直接看使用的例子:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo int
type Bar int

func ProvideFoo() Foo {
   return Foo(1)
}

func ProvideBar() Bar {
   return Bar(2)// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}
}

type FooBar struct {
   MyFoo Foo
   MyBar Bar
}

var Set = wire.NewSet(
   ProvideFoo,
   ProvideBar,
   wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)

func injectFooBar() FooBar {
   wire.Build(Set)
   return FooBar{}
}

func main() {
   fooBar := injectFooBar()
   fmt.Println(fooBar)
}

上面的例子其實很簡單,我們構造FooBar 結構題我們需要MyFooMyBar ,而ProvideFooProvideBar 就是用於生成MyFooMyBarwire.Struct 也可以幫我們做這件事情。我們通過wire生成的代碼如下:

// Injectors from main.go:

func injectFooBar() FooBar {
   foo := ProvideFoo()
   bar := ProvideBar()
   fooBar := FooBar{
      MyFoo: foo,
      MyBar: bar,
   }
   return fooBar
}

wire.Struct 的第一個參數是所需結構類型的指針,後面的參數是要註入的欄位的名稱。可以使用一個特殊的字元串“ * ”作為告訴註入器註入所有欄位的快捷方式。 所以我們上面的代碼也可以寫成:wire.Struct(new(FooBar), "×") ,而當我們使用* 這種方式的時候可能會把一些不需要註入的欄位註入了,如鎖,那麼類似這種情況,如果我們註入,卡一通過wire:"-" 的方式告訴wire 該欄位不進行註入。

type Foo struct {
    mu sync.Mutex `wire:"-"`
    Bar Bar
}

Binding Values

這個功能主要就是給數據類型綁定一個預設值,代碼例子如下:

https://github.com/LyricTian/gin-adminpackage main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   X int
}

func injectFoo() Foo {
   wire.Build(wire.Value(Foo{X: 11}))
   return Foo{}
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

我通過wire生成的代碼如下:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
   "fmt"
)

// Injectors from main.go:

func injectFoo() Foo {
   foo := _wireFooValue
   return foo
}

var (
   _wireFooValue = Foo{X: 11}
)

// main.go:

type Foo struct {
   X int
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

Use Fields of a Struct as Providers

有時,我們需要獲取結構體的某些欄位,按照我們已經使用的wire的用法,你可能會這樣寫代碼:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func getS(foo Foo) string {
   return foo.S
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      getS,
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

這種用法當然也可以實現,但是wire其實提供了更好的辦法來實現wire.FieldsOf, 我們將上面的代碼進行更改如下,通過wire生成的代碼其實和上面的是一樣的:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      wire.FieldsOf(new(Foo), "S"),
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

Cleanup functions

如果我們的Provider創建了一個需要做clean 的值,例如關閉文件,關閉數據連接..., 這裡也是可以返回一個閉包來清理資源,註入器將使用它向調用者返回一個聚合的清理函數,或者如果稍後在註入器實現中調用的提供程式返回一個錯誤,則使用它來清理資源。

關於這個功能的使用,通過https://github.com/LyricTian/gin-admin 的代碼中的使用,可以更加清楚。

作者在gin-admin/internal/app/app.go 中進行了初始化依賴註入器

// 初始化依賴註入器
injector, injectorCleanFunc, err := injector.BuildInjector()
if err != nil {
   return nil, err
}

我們在看看下wire生成的wire_gen.go代碼:

// Injectors from wire.go:

func BuildInjector() (*Injector, func(), error) {
   auther, cleanup, err := InitAuth()
   if err != nil {
      return nil, nil, err
   }
   db, cleanup2, err := InitGormDB()
   if err != nil {
      cleanup()
      return nil, nil, err
   }
   role := &model.Role{
      DB: db,
   }
   roleMenu := &model.RoleMenu{
      DB: db,
   }
   menuActionResource := &model.MenuActionResource{
      DB: db,
   }
   user := &model.User{
      DB: db,
   }
   userRole := &model.UserRole{
      DB: db,
   }
   casbinAdapter := &adapter.CasbinAdapter{
      RoleModel:         role,
      RoleMenuModel:     roleMenu,
      MenuResourceModel: menuActionResource,
      UserModel:         user,
      UserRoleModel:     userRole,
   }
   syncedEnforcer, cleanup3, err := InitCasbin(casbinAdapter)
   if err != nil {
      cleanup2()
      cleanup()
      return nil, nil, err
   }
   demo := &model.Demo{
      DB: db,
   }
   bllDemo := &bll.Demo{
      DemoModel: demo,
   }
   apiDemo := &api.Demo{
      DemoBll: bllDemo,
   }
   menu := &model.Menu{
      DB: db,
   }
   menuAction := &model.MenuAction{
      DB: db,
   }
   login := &bll.Login{
      Auth:            auther,
      UserModel:       user,
      UserRoleModel:   userRole,
      RoleModel:       role,
      RoleMenuModel:   roleMenu,
      MenuModel:       menu,
      MenuActionModel: menuAction,
   }
   apiLogin := &api.Login{
      LoginBll: login,
   }
   trans := &model.Trans{
      DB: db,
   }
   bllMenu := &bll.Menu{
      TransModel:              trans,
      MenuModel:               menu,
      MenuActionModel:         menuAction,
      MenuActionResourceModel: menuActionResource,
   }
   apiMenu := &api.Menu{
      MenuBll: bllMenu,
   }
   bllRole := &bll.Role{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      RoleModel:     role,
      RoleMenuModel: roleMenu,
      UserModel:     user,
   }
   apiRole := &api.Role{
      RoleBll: bllRole,
   }
   bllUser := &bll.User{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      UserModel:     user,
      UserRoleModel: userRole,
      RoleModel:     role,
   }
   apiUser := &api.User{
      UserBll: bllUser,
   }
   routerRouter := &router.Router{
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      DemoAPI:        apiDemo,
      LoginAPI:       apiLogin,
      MenuAPI:        apiMenu,
      RoleAPI:        apiRole,
      UserAPI:        apiUser,
   }
   engine := InitGinEngine(routerRouter)
   injector := &Injector{
      Engine:         engine,
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      MenuBll:        bllMenu,
   }
   return injector, func() {
      cleanup3()
      cleanup2()
      cleanup()
   }, nil
}

而當程式退出的時候這上面代碼返回的那些清理操作都會被執行:

// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
   var state int32 = 1
   sc := make(chan os.Signal, 1)
   signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
   cleanFunc, err := Init(ctx, opts...)
   if err != nil {
      return err
   }

EXIT:
   for {
      sig := <-sc
      logger.Printf(ctx, "接收到信號[%s]", sig.String())
      switch sig {
      case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
         atomic.CompareAndSwapInt32(&state, 1, 0)
         break EXIT
      case syscall.SIGHUP:
      default:
         break EXIT
      }
   }
   // 在這裡執行了清理工作
   cleanFunc()
   logger.Printf(ctx, "服務退出")
   time.Sleep(time.Second)
   os.Exit(int(atomic.LoadInt32(&state)))
   return nil
}

延伸閱讀


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、列印float類型 %f來表示float和double類型的數字 %e來列印指數記數法的浮點數 如果系統支持十六進位的浮點數,可以使用a和A分別來代替e和E 列印long double類型要使用%Lf或%Le或%La來替代 #include<stdio.h> int D12_1_showf_pt ...
  • 異常處理 把可能會發生的錯誤,提前在代碼里進行捕捉(監測) try : code except Exception: 出錯後要執行的代碼 下麵是常見的異常: attributeError 試圖訪問一個對象沒有的屬性 Nameerror 訪問一個沒有變數 Valueerror 值類型不匹配 impor ...
  • 學習時常常忘記某個容器怎麼用。別怕,只要用時會查。太多也記不過來。 微軟的c++文檔:https://docs.microsoft.com/zh-cn/cpp/standard-library/vector-class?view=vs-2019 又發現一個網上查文檔的網站了,感覺還不錯。鏈接:htt ...
  • volatile 這個關鍵字可能很多朋友都聽說過,或許也都用過。在 Java 5 之前,它是一個備受爭議的關鍵字,因為在程式中使用它往往會導致出人意料的結果。在 Java 5之後,volatile 關鍵字才得以重獲生機。 ...
  • <form action="/search" id="search_form"> <input type="text" name="keywords" value="" placeholder="Furniture Handles" class="jhser" /> <span class="ser ...
  • Java生鮮電商平臺-微服務電商優惠券的架構設計(小程式/APP) 說明:Java生鮮電商平臺的優惠券屬於電子優惠券,不過我們要先看看線下紙質優惠券: 商家決定做促銷,印製了10000張50元代金券; 其中1000張代金券分別發給1000個用戶; 到某一個時刻,這1000個用戶有300個適用了代金券 ...
  • 一、Java基礎知識 二、軟體 1. 軟體是什麼? 軟體=數據+指令[命令]+文檔 2. 軟體開髮指什麼? 軟體開發是根據用戶的需求創建出相應的軟體系統. 軟體開發是一個過程,包含需求的提取,需求分析,軟體的編寫,軟體測試. 三、人與電腦做交互 圖形化界面 vs 命令行方式 dir md rd c ...
  • 前言 剛回到家,又被公司群里的消息轟炸了。讓統計每個人最近是否去過石景山萬達廣場。 這基本上已經是每日必備了,只要有任何風吹草動,就需要我們填各種信息。 我們都知道,北京最近的疫情很不樂觀,從每天的數據就能看出來了。 也許很多小伙伴不在北京,是切實感受不到的。 就拿我自己舉例吧,在去公司的每一天,我 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...