​从别人的代码中学习golang系列--01

Golang学习01:Go安装和配置+Vscode、GoLand安装激活+Go环境变量避坑的超详细教程 本文详细介绍了Go语言的环境安装以及各种开发环境的安装,希望本篇文章对您有所帮助。最后,希望您工作顺利,生活愉快! 阅读详情

自己最近在思考一个问题,如何让自己的代码质量逐渐提高,于是想到整理这个系列,通过阅读别人的代码,从别人的代码中学习,来逐渐提高自己的代码质量。本篇是这个系列的第一篇,我也不知道自己会写多少篇,但是希望自己能坚持下去。

第一个自己学习的源码是:https://github.com/LyricTian/gin-admin

自己整理的代码地址:https://github.com/peanut-pg/gin_admin 这篇文章整理的时候只是为了跑起来整体的代码,对作者的代码进行精简。

这篇博客主要是阅读gin-admin的第一篇,整理了从代码项目目录到日志库使用中学习到的内容:

  1. 项目目录规范

  2. 配置文件的加载

  3. github.com/sirupsen/logrus 日志库在项目的使用

  4. 项目的优雅退出

  5. Golang的选项模式

项目目录规范

作者的项目目录还是非常规范的,应该也是按照https://github.com/golang-standards/project-layout 规范写的,这个规范虽然不是官方强制规范的,但是确实很多开源项目都在采用的,所以我们在生产中正式的项目都应该尽可能遵循这个目录规范标准进行代码的编写。关于这个目录的规范使用,自己会在后续实际使用中逐渐完善。

/cmd

main函数文件(比如 /cmd/myapp.go)目录,这个目录下面,每个文件在编译之后都会生成一个可执行的文件。

不要把很多的代码放到这个目录下面,这里面的代码尽可能简单。

/internal

应用程序的封装的代码。我们的应用程序代码应该放在 /internal/app目录中。而这些应用程序共享的代码可以放在 /internal/pkg目录中

/pkg

一些通用的可以被其他项目所使用的代码,放到这个目录下面。

/vendor

应用程序的依赖项,go mod vendor 命令可以创建vendor目录。

「Service Application Directories」

/api

协议文件,Swagger/thrift/protobuf 等

「Web Application Directories」

/web

web服务所需要的静态文件

「Common Application Directories」

/configs

配置文件目录

/init

系统的初始化

/scripts

用于执行各种构建,安装,分析等操作的脚本。

/build

打包和持续集成

/deployments

部署相关的配置文件和模板

/test

其他测试目录,功能测试,性能测试等

「Other Directories」

/docs

设计和用户文档

/tools

常用的工具和脚本,可以引用 /internal 或者 /pkg 里面的库

/examples

应用程序或者公共库使用的一些例子

/assets

其他一些依赖的静态资源

配置文件的加载

作者的gin-admin 项目中配置文件加载库使用的是:github.com/koding/multiconfig

在这之前,听到使用最多的就是大名鼎鼎的viper ,但是相对于viper相对来说就比较轻便了,并且功能还是非常强大的。感兴趣的可以看看这篇文章:https://sfxpt.wordpress.com/2015/06/19/beyond-toml-the-gos-de-facto-config-file/

栗子

程序目录结构为:

configLoad
├── configLoad
├── config.toml
└── main.go


通过一个简单的例子来看看multiconfig的使用

package main


import (
	"fmt"


	"github.com/koding/multiconfig"
)


type (
	// Server holds supported types by the multiconfig package
	Server struct {
		Name     string
		Port     int `default:"6060"`
		Enabled  bool
		Users    []string
		Postgres Postgres
	}


	// Postgres is here for embedded struct feature
	Postgres struct {
		Enabled           bool
		Port              int
		Hosts             []string
		DBName            string
		AvailabilityRatio float64
	}
)


func main() {
	m := multiconfig.NewWithPath("config.toml") // supports TOML and JSON


	// Get an empty struct for your configuration
	serverConf := new(Server)


	// Populated the serverConf struct
	m.MustLoad(serverConf) // Check for error


	fmt.Println("After Loading: ")
	fmt.Printf("%+v\n", serverConf)


	if serverConf.Enabled {
		fmt.Println("Enabled field is set to true")
	} else {
		fmt.Println("Enabled field is set to false")
	}
}


配置文件config.toml内容为:

Name              = "koding"
Enabled           = false
Port              = 6066
Users             = ["ankara", "istanbul"]


[Postgres]
Enabled           = true
Port              = 5432
Hosts             = ["192.168.2.1", "192.168.2.2", "192.168.2.3"]
AvailabilityRatio = 8.23

编译之后执行,效果如下:

➜  configLoad ./configLoad 
After Loading: 
&{Name:koding Port:6066 Enabled:false Users:[ankara istanbul] Postgres:{Enabled:true Port:5432 Hosts:[192.168.2.1 192.168.2.2 192.168.2.3] DBName: AvailabilityRatio:8.23}}
Enabled field is set to false
➜  configLoad ./configLoad -h
Usage of ./configLoad:
  -enabled
        Change value of Enabled. (default false)
  -name
        Change value of Name. (default koding)
  -port
        Change value of Port. (default 6066)
  -postgres-availabilityratio
        Change value of Postgres-AvailabilityRatio. (default 8.23)
  -postgres-dbname
        Change value of Postgres-DBName.
  -postgres-enabled
        Change value of Postgres-Enabled. (default true)
  -postgres-hosts
        Change value of Postgres-Hosts. (default [192.168.2.1 192.168.2.2 192.168.2.3])
  -postgres-port
        Change value of Postgres-Port. (default 5432)
  -users
        Change value of Users. (default [ankara istanbul])


Generated environment variables:
   SERVER_ENABLED
   SERVER_NAME
   SERVER_PORT
   SERVER_POSTGRES_AVAILABILITYRATIO
   SERVER_POSTGRES_DBNAME
   SERVER_POSTGRES_ENABLED
   SERVER_POSTGRES_HOSTS
   SERVER_POSTGRES_PORT
   SERVER_USERS


flag: help requested
➜  configLoad ./configLoad -name=test
After Loading: 
&{Name:test Port:6066 Enabled:false Users:[ankara istanbul] Postgres:{Enabled:true Port:5432 Hosts:[192.168.2.1 192.168.2.2 192.168.2.3] DBName: AvailabilityRatio:8.23}}
Enabled field is set to false
➜  configLoad export SERVER_NAME="test_env"
➜  configLoad ./configLoad                 
After Loading: 
&{Name:test_env Port:6066 Enabled:false Users:[ankara istanbul] Postgres:{Enabled:true Port:5432 Hosts:[192.168.2.1 192.168.2.2 192.168.2.3] DBName: AvailabilityRatio:8.23}}
Enabled field is set to false

从上面的使用中,你能能够看到,虽然multiconfig 非常轻量,但是功能还是非常强大的,可以读配置文件,还可以通过环境变量,以及我们常用的命令行模式。

日志库在项目的使用

这个可能对很多初学者来说都是非常有用的,因为一个项目中,我们基础的就是要记录日志,golang有很多强大的日志库,如:作者的gin-admin 项目使用的github.com/sirupsen/logrus; 还有就是uber开源的github.com/uber-go/zap等等

这里主要学习一下作者是如何在项目中使用logrus,这篇文章对作者使用的进行了精简。当然只是去掉了关于gorm,以及mongo的hook的部分,如果你的项目中没有使用这些,其实也先不用关注这两个hook部分的代码,不影响使用,后续的系列文章也会对hook部分进行整理。

作者封装的logger库是在pkg/loggger目录中,我精简之后如下:

package logger


import (
	"context"
	"fmt"
	"io"
	"os"
	"time"


	"github.com/sirupsen/logrus"
)


// 定义键名
const (
	TraceIDKey      = "trace_id"
	UserIDKey       = "user_id"
	SpanTitleKey    = "span_title"
	SpanFunctionKey = "span_function"
	VersionKey      = "version"
	StackKey        = "stack"
)


// TraceIDFunc 定义获取跟踪ID的函数
type TraceIDFunc func() string


var (
	version     string
	traceIDFunc TraceIDFunc
	pid         = os.Getpid()
)


func init() {
	traceIDFunc = func() string {
		return fmt.Sprintf("trace-id-%d-%s",
			os.Getpid(),
			time.Now().Format("2006.01.02.15.04.05.999999"))
	}
}


// Logger 定义日志别名
type Logger = logrus.Logger


// Hook 定义日志钩子别名
type Hook = logrus.Hook


// StandardLogger 获取标准日志
func StandardLogger() *Logger {
	return logrus.StandardLogger()
}


// SetLevel设定日志级别
func SetLevel(level int) {
	logrus.SetLevel(logrus.Level(level))
}


// SetFormatter 设定日志输出格式
func SetFormatter(format string) {
	switch format {
	case "json":
		logrus.SetFormatter(new(logrus.JSONFormatter))
	default:
		logrus.SetFormatter(new(logrus.TextFormatter))
	}
}


// SetOutput 设定日志输出
func SetOutput(out io.Writer) {
	logrus.SetOutput(out)
}


// SetVersion 设定版本
func SetVersion(v string) {
	version = v
}


// SetTraceIDFunc 设定追踪ID的处理函数
func SetTraceIDFunc(fn TraceIDFunc) {
	traceIDFunc = fn
}


// AddHook 增加日志钩子
func AddHook(hook Hook) {
	logrus.AddHook(hook)
}


type (
	traceIDKey struct{}
	userIDKey  struct{}
)


// NewTraceIDContext 创建跟踪ID上下文
func NewTraceIDContext(ctx context.Context, traceID string) context.Context {
	return context.WithValue(ctx, traceIDKey{}, traceID)
}


// FromTraceIDContext 从上下文中获取跟踪ID
func FromTraceIDContext(ctx context.Context) string {
	v := ctx.Value(traceIDKey{})
	if v != nil {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return traceIDFunc()
}


// NewUserIDContext 创建用户ID上下文
func NewUserIDContext(ctx context.Context, userID string) context.Context {
	return context.WithValue(ctx, userIDKey{}, userID)
}


// FromUserIDContext 从上下文中获取用户ID
func FromUserIDContext(ctx context.Context) string {
	v := ctx.Value(userIDKey{})
	if v != nil {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return ""
}


type spanOptions struct {
	Title    string
	FuncName string
}


// SpanOption 定义跟踪单元的数据项
type SpanOption func(*spanOptions)


// SetSpanTitle 设置跟踪单元的标题
func SetSpanTitle(title string) SpanOption {
	return func(o *spanOptions) {
		o.Title = title
	}
}


// SetSpanFuncName 设置跟踪单元的函数名
func SetSpanFuncName(funcName string) SpanOption {
	return func(o *spanOptions) {
		o.FuncName = funcName
	}
}


// StartSpan 开始一个追踪单元
func StartSpan(ctx context.Context, opts ...SpanOption) *Entry {
	if ctx == nil {
		ctx = context.Background()
	}
	var o spanOptions
	for _, opt := range opts {
		opt(&o)
	}
	fields := map[string]interface{}{
		VersionKey: version,
	}
	if v := FromTraceIDContext(ctx); v != "" {
		fields[TraceIDKey] = v
	}
	if v := FromUserIDContext(ctx); v != "" {
		fields[UserIDKey] = v
	}
	if v := o.Title; v != "" {
		fields[SpanTitleKey] = v
	}
	if v := o.FuncName; v != "" {
		fields[SpanFunctionKey] = v
	}


	return newEntry(logrus.WithFields(fields))


}


// Debugf 写入调试日志
func Debugf(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Debugf(format, args...)
}


// Infof 写入消息日志
func Infof(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Infof(format, args...)
}


// Printf 写入消息日志
func Printf(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Printf(format, args...)
}


// Warnf 写入警告日志
func Warnf(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Warnf(format, args...)
}


// Errorf 写入错误日志
func Errorf(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Errorf(format, args...)
}


// Fatalf 写入重大错误日志
func Fatalf(ctx context.Context, format string, args ...interface{}) {
	StartSpan(ctx).Fatalf(format, args...)
}


// ErrorStack 输出错误栈
func ErrorStack(ctx context.Context, err error) {
	StartSpan(ctx).WithField(StackKey, fmt.Sprintf("%+v", err)).Errorf(err.Error())
}


// Entry 定义统一的日志写入方式
type Entry struct {
	entry *logrus.Entry
}


func newEntry(entry *logrus.Entry) *Entry {
	return &Entry{entry: entry}
}


func (e *Entry) checkAndDelete(fields map[string]interface{}, keys ...string) *Entry {
	for _, key := range keys {
		_, ok := fields[key]
		if ok {
			delete(fields, key)
		}
	}
	return newEntry(e.entry.WithFields(fields))
}


// WithFields 结构化字段写入
func (e *Entry) WithFields(fields map[string]interface{}) *Entry {
	e.checkAndDelete(fields,
		TraceIDKey,
		SpanTitleKey,
		SpanFunctionKey,
		VersionKey)
	return newEntry(e.entry.WithFields(fields))
}


// WithField 结构化字段写入
func (e *Entry) WithField(key string, value interface{}) *Entry {
	return e.WithFields(map[string]interface{}{key: value})
}


// Fatalf 重大错误日志
func (e *Entry) Fatalf(format string, args ...interface{}) {
	e.entry.Fatalf(format, args...)
}


// Errorf 错误日志
func (e *Entry) Errorf(format string, args ...interface{}) {
	e.entry.Errorf(format, args...)
}


// Warnf 警告日志
func (e *Entry) Warnf(format string, args ...interface{}) {
	e.entry.Warnf(format, args...)
}


// Infof 消息日志
func (e *Entry) Infof(format string, args ...interface{}) {
	e.entry.Infof(format, args...)
}


// Printf 消息日志
func (e *Entry) Printf(format string, args ...interface{}) {
	e.entry.Printf(format, args...)
}


// Debugf 写入调试日志
func (e *Entry) Debugf(format string, args ...interface{}) {
	e.entry.Debugf(format, args...)
}


通过Logrus & Context 实现了统一的 TraceID/UserID 等关键字段的输出,从这里也可以看出来,作者这样封装非常也符合符合pkg目录的要求,我们可以很容易的在internal/app 目录中使用封装好的logger。接着就看一下如何使用,作者在internal/app 目录下通过logger.go 中的InitLogger进行日志的初始化,设置了日志的级别,日志的格式,以及日志输出文件。这样我们在internal/app的其他包文件中只需要导入pkg下的logger即可以进行日志的记录。

项目的优雅退出

在这里不得不提的就是一个基础知识Linux信号signal,推荐看看https://blog.youkuaiyun.com/lixiaogang_theanswer/article/details/80301624

linux系统中signum.h中有对所有信号的宏定义,这里注意一下,我使用的是manjaro linux,我的这个文件路径是/usr/include/bits/signum.h 不同的linux系统可能略有差别,可以通过find / -name signum.h 查找确定

#define SIGSTKFLT       16      /* Stack fault (obsolete).  */
#define SIGPWR          30      /* Power failure imminent.  */


#undef  SIGBUS
#define SIGBUS           7
#undef  SIGUSR1
#define SIGUSR1         10
#undef  SIGUSR2
#define SIGUSR2         12
#undef  SIGCHLD
#define SIGCHLD         17
#undef  SIGCONT
#define SIGCONT         18
#undef  SIGSTOP
#define SIGSTOP         19
#undef  SIGTSTP
#define SIGTSTP         20
#undef  SIGURG
#define SIGURG          23
#undef  SIGPOLL
#define SIGPOLL         29
#undef  SIGSYS
#define SIGSYS          31


在linux终端可以使用kill -l 查看所有的信号

➜  ~ kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS
➜  ~


信号说明:

「信号」「起源」「默认行为」「含义」
SIGHUPPOSIXTerm控制终端挂起
SIGINTANSITerm键盘输入以终端进程(ctrl + C)
SIGQUITPOSIXCore键盘输入使进程退出(Ctrl + \)
SIGILLANSICore非法指令
SIGTRAPPOSIXCore断点陷阱,用于调试
SIGABRTANSICore进程调用abort函数时生成该信号
SIGIOT4.2BSDCore和SIGABRT相同
SIGBUS4.2BSDCore总线错误,错误内存访问
SIGFPEANSICore浮点异常
SIGKILLPOSIXTerm终止一个进程。该信号不可被捕获或被忽略
SIGUSR1POSIXTerm用户自定义信号之一
SIGSEGVANSICore非法内存段使用
SIGUSR2POSIXTerm用户自定义信号二
SIGPIPEPOSIXTerm往读端关闭的管道或socket链接中写数据
SIGALRMPOSIXTerm由alarm或settimer设置的实时闹钟超时引起
SIGTERMANSITerm终止进程。kill命令默认发生的信号就是SIGTERM
SIGSTKFLTLinuxTerm早期的Linux使用该信号来报告数学协处理器栈错误
SIGCLDSystem VIgn和SIGCHLD相同
SIGCHLDPOSIXIgn子进程状态发生变化(退出或暂停)
SIGCONTPOSIXCont启动被暂停的进程(Ctrl+Q)。如果目标进程未处于暂停状态,则信号被忽略
SIGSTOPPOSIXStop暂停进程(Ctrl+S)。该信号不可被捕捉或被忽略
SIGTSTPPOSIXStop挂起进程(Ctrl+Z)
SIGTTINPOSIXStop后台进程试图从终端读取输入
SIGTTOUPOSIXStop后台进程试图往终端输出内容
SIGURG4.3 BSDIgnsocket连接上接收到紧急数据
SIGXCPU4.2 BSDCore进程的CPU使用时间超过其软限制
SIGXFSZ4.2 BSDCore文件尺寸超过其软限制
SIGVTALRM4.2 BSDTermhttps://github.com/LyricTian/gin-admin与SIGALRM类似,不过它只统计本进程用户空间代码的运行时间
SIGPROF4.2 BSDTerm与SIGALRM 类似,它同时统计用户代码和内核的运行时间
SIGWINCH4.3 BSDIgn终端窗口大小发生变化
SIGPOLLSystem VTerm与SIGIO类似
SIGIO4.2 BSDTermIO就绪,比如socket上发生可读、可写事件。因为TCP服务器可触发SIGIO的条件很多,故而SIGIO无法在TCP服务器中用。SIGIO信号可用在UDP服务器中,但也很少见
SIGPWRSystem VThttps://github.com/LyricTian/gin-adminerm对于UPS的系统,当电池电量过低时,SIGPWR信号被触发
SIGSYSPOSIXCore非法系统调用
SIGUNUSED
Core保留,通常和SIGSYS效果相同

作者中代码是这样写的:

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
}

这里监听了syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT,当收到这些信号的,则会进行系统的退出,同时在程序退出之前进行一些清理操作。

这里我们有一个知识点需要回顾一下:golang中的break label 和 goto label

  • break label,break的跳转标签(label)必须放在循环语句for前面,并且在break label跳出循环不再执行for循环里的代码。break标签只能用于for循环

  • goto label的label(标签)既可以定义在for循环前面,也可以定义在for循环后面,当跳转到标签地方时,继续执行标签下面的代码。

Golang的选项模式

其实在很多的开源项目中都可以看到golang 选项模式的使用,推荐看看https://www.sohamkamani.com/golang/options-pattern/

假如我们现在想要建造一个房子,我们思考,建造房子需要木材,水泥......,假如我们现在就想到这两个,我们用代码实现:

type House struct {
	wood   string // 木材
	cement string // 水泥
}


// 造一个房子
func NewHouse(wood, cement shttps://github.com/LyricTian/gin-admintring) *House {
	house := &House{
		wood:   wood,
		cement: cement,
	}
	return house
}

上面这种方式,应该是我们经常可以看到的实现方式,但是这样实现有一个很不好的地方,就是我们突然发现我们建造房子还需要钢筋,这个时候我们就必须更改NewHouse 函数的参数,并且NewHouse的参数还有顺序依赖。

对于扩展性来说,上面的这种实现方式其实不是非常好,而golang的选项模式很好的解决了这个问题。

栗子

type HouseOption func(*House)


type House struct {
	Wood   string // 木材
	Cement string // 水泥
}


func WithWood(wood string) HouseOption {
	return func(house *House) {
		house.Wood = wood
	}
}


func WithCement(Cement string) HouseOption {
	return func(house *House) {
		house.Cement = Cement
	}
}


// 造一个新房子
func NewHouse(opts ...HouseOption) *House {
	h := &House{}
	for _, opt := range opts {
		opt(h)
	}
	return h
}

这样当我们这个时候发现,我建造房子还需要石头,只需要在House结构体中增加对应的字段,同时增加一个

WithStone 函数即可,同时我们我们调用NewHouse的地方也不会有顺序依赖,只需要增加一个参数即可,更改之后的代码如下:

type HouseOption func(*House)


type House struct {
	Wood   string // 木材
	Cement string // 水泥
	Stone  string // 石头
}


func WithWood(wood string) HouseOption {
	return func(house *House) {
		house.Wood = wood
	}
}


func WithCement(Cement string) HouseOption {
	return func(house *House) {
		house.Cement = Cement
	}
}


func WithStone(stone string) HouseOption {
	return func(house *House) {
		house.Stone = stone
	}
}


// 造一个新房子
func NewHouse(opts ...HouseOption) *House {
	h := &House{}
	for _, opt := range opts {
		opt(h)
	}
	return h
}


func main() {
	house := NewHouse(
		WithCement("上好的水泥"),
		WithWood("上好的木材"),
		WithStone("上好的石头"),
	)
	fmt.Println(house)https://github.com/LyricTian/gin-admin
}

选项模式小结

  • 生产中正式的并且较大的项目使用选项模式可以方便后续的扩展

  • 增加了代码量,但让参数的传递更新清晰明了

  • 在参数确实比较复杂的场景推荐使用选项模式

总结

从https://github.com/LyricTian/gin-admin 作者的这个项目中我们首先从大的方面学习了:

  1. golang 项目的目录规范

  2. github.com/koding/multiconfig 配置文件库

  3. logrus 日志库的使用

  4. 项目的优雅退出实现,信号相关知识

  5. golang的选项模式

对于我自己来说,对于之后写golang项目,通过上面这些知识点,可以很快速的构建一个项目的基础部分,也希望看到这篇文章能够帮到你,如果有写的不对的地方,也欢迎评论指出。

延伸阅读

  • https://github.com/LyricTian/gin-admin

  • https://github.com/golang-standards/project-layout

  • https://sfxpt.wordpress.com/2015/06/19/beyond-toml-the-gos-de-facto-config-file/

  • https://github.com/koding/multiconfig

  • https://github.com/sirupsen/logrus

  • https://blog.youkuaiyun.com/lixiaogang_theanswer/article/details/80301624

  • https://www.sohamkamani.com/golang/options-pattern/

一起使用GO(golang) 来做一个后台管理系统系列(1) 载入启动 golang gin 框架 从本篇文章开始逐步带大家使用go gin web框架来做一个后台管理系统。本篇文章是一系列的,连续博文。大家可以关注后,持续观看。 前端代码使用开源代码 x-admin 首先要在gopath 下载gin 框架,这个我就不在这里演示了。 创建启动文件main.go, 下面放出代码: 这里头包含加载 路由控制管理器router的文件加载。这个在后面放出来。 还包括启动了一个定时器任务。五秒打印一次。 代码:https://github.com/Huochengyan/myGoProject .. 阅读详情

相关推荐

multiconfig:从Go中的多个来源加载配置

多配置 从多个来源加载配置。 通过Multiconfig,可以轻松地从不同的配置源进行加载/解析。 任何应用程序的问题在于,随着时间的流逝,如何填充一组配置有很多选择。 通过动态创建所有必要的选项,Multiconfig使其变得容易。 请查看下面的示例以查看其运行情况。 特征 Multiconfig能够基于给定结构的字段名称从以下来源自动读取配置: 结构标签 TOML文件 JSON文件 YAML文件 环境变量 标志 安装 go get github.com/koding/multiconfig 用法与范例 让我们定义和构造定义我们的配置 type Server struct { Name string `required:"true"` Port int `default:"6060"` Enabled bool Users [] string } 将配

探索灵活配置管理的艺术:Multiconfig深度解析与推荐

探索灵活配置管理的艺术:Multiconfig深度解析与推荐 在快速迭代的软件开发中,如何优雅地处理配置加载和解析始终是一个挑战。今天,我们要介绍一个开源瑰宝——Multiconfig,它以强大的灵活性和简洁的API,简化了多源配置加载流程,让开发者能够更加专注于核心业务逻辑。 项目介绍 Multiconfig 是一款专为 Go 语言设计的配置管理库,它能从多种配置来源中自动读取并解析数据,包括结...

gitblog_00110的博客 513

无法联网的情况下,dbeaver离线安装数据库驱动-clickhouse

无法联网的情况下,dbeaver离线安装数据库驱动-clickhouse

u012831187的博客 1万+

推荐开源项目:Multiconfig——多源配置加载库

推荐开源项目:Multiconfig——多源配置加载库 在软件开发中,随着项目的不断演进,如何优雅地管理并加载配置文件变得越来越重要。今天,我们向您推荐一款名为Multiconfig的Go语言库,它能帮助您轻松地从多种不同来源读取和解析配置信息。 1、项目介绍 Multiconfig 是一个强大的配置加载工具,设计用于简化Go应用程序中配置的加载过程。它支持从结构体标签、TOML、JSON、YAM...

gitblog_00085的博客 546

使用Dify打造视频生成工作流:从提示词到视频的完整指南

Dify作为开源LLM应用开发平台,通过可视化工作流编排实现从文本到视频的端到端生成,将创意周期从天级缩短至分钟级。核心流程包括提示词工程、视频API调用、存储分发和结果反馈。

牛奶夹心巧克力 3430

高通Camera AWB跳变问题实战:从夕阳场景到参数调优的完整避坑指南

本文深入解析高通Camera AWB跳变问题,特别是在夕阳场景下的色温突变现象。通过分析灰区统计失效、光源混合干扰等成因,提供从参数调优到诊断工具链的完整解决方案,包括动态灰区扩展算法、色温平滑过渡方案及高通平台特定优化技巧,帮助工程师有效解决AWB跳变问题。

sre5engineer的博客 858

go函数类型的小理解

go 函数类型的理解

weixin_44559085的博客 468

Silvaco Atlas入门指南——从零开始掌握半导体器件仿真

本文为Silvaco Atlas半导体器件仿真软件的入门指南,详细介绍了从零开始掌握该工具的核心流程。内容涵盖Atlas的基本概念、仿真流程、核心文件(输入、结构、输出文件)、交互式与批处理两种操作模式,并逐步演示了如何从定义网格、划分区域、设置电极到进行掺杂、选择物理模型并求解,最终获取和分析器件特性的完整步骤。旨在帮助微电子相关专业人员和工程师快速上手这一强大的“数字实验室”,提升半导体器件设计与研发效率。

weixin_29306317的博客 400

Go中的日志及第三方日志包logrus

有别的语言使用基础的同学工作中都会接触到日志的使用,Go中自然也有log相关的实现。Go log模块主要提供了3类接口,分别是 “Print 、Panic 、Fatal ”,对每一类接口其提供了三种调用方式,分别是 “Xxxx 、Xxxxln 、Xxxxf”,基本和fmt中的相关函数类似。 1. Go中的log包 1. 基本使用 log.Print:打印日志,和fmt包没什么区别,只是加上了上面...

rickiyang的博客 3810

【AUTOSAR-E2E】-2.2-配置E2E:基于E2EPW配置一个TX发送的Signal Group(使用E2E Profile 2)

《共13页精讲》:此法也适用于E2E Profile 4/5/6/7/11/22 目录 1 检查CAN Matrix Arxml/DBC是否满足E2E需求 2 在DaVinci Developer中为Port添加E2E保护 3 在DaVinci Configurator settings设置 4 如何使用应用层的E2E保护 5 结尾 <--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> &lt.

「汽车电子助手」的博客 4218

YOLOv9分割训练自己数据和推理训练好模型,并教你使用Labelme工具标注数据(附YOLOv9分割模型结构图),全网最详细教程

YOLOv9 通过研究数据传输时的信息丢失问题,提出了可编程梯度信息(PGI)和通用高效层聚合网络(GELAN)架构,提高了参数利用率和模型性能。与 SOTA 方法相比,GELAN 仅使用传统卷积算子即可实现更好的参数利用率。PGI 适用于从轻型到大型的各种模型,使从头开始训练的模型能够获得更好的结果。YOLOv9 被评价为新的 SOTA 实时目标检测器。接下来本文手把手教你训练自己分割数据集和推理训练好的分割模型。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1492

Go基础系列 01-Golang简介

人生苦短,Lets's Go.

知命不惧,日日自新。 1154

芒果YOLOv10改进31:特征融合Neck篇:改进特征融合网络 BiFPN 结构,融合更多有效特征

YOLOv10改进:特征融合Neck篇:改进特征融合网络 BiFPN 结构,融合更多有效特征

包括YOLOv5、YOLOv7、YOLOv8等模型改进 684

01 go语言(golang) - 入门

Go(又称为Golang)是由Google开发的一种静态类型、编译型、并发导向的编程语言。它由Robert Griesemer、Rob Pike和Ken Thompson在2007年设计,并于2009年正式对外发布。官网介绍:https://go.dev/doc/effective_go从官网的指南可以了解接下来的大概学习路线,以下是通过kimi总结翻译的几个点:介绍:示例:格式化:命名:控制结构:函数:数据:并发:错误处理:Web服务器示例:每种语言都有核心特征及其设计目标,Java 和 Scala 运行

瑞瑞的博客 1270

基于Stateflow的驱动电机控制系统模式管理与安全策略研究

摘要 本文基于Stateflow状态机建模工具,研究了驱动电机控制系统的模式管理架构与安全策略实现方法。通过分析电机控制系统的工作模式,设计了包含初始化、待机、校准、对齐、运行、故障、复位七种状态的有限状态机模型,并建立了三级故障处理机制和降额保护策略。基于Simulink/Stateflow搭建的仿真模型实现了模式切换逻辑和安全管理功能,验证了系统能够正确响应各类控制事件,故障处理机制可有效保护系统安全。研究成果为电动汽车电机控制系统的工程开发提供了参考。 关键词:Stateflow;驱动电机控制;模式管

AI的内部世界的博客 58

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Golang学习笔记-GO的各种编译-01

Golang学习笔记-GO的各种编译-01 编译go build 使用go build 在项目目录下执行go build 在其他路径下执行go bulid,需要在后面加上项目的路径(项目路径从GOPATH/src后开始写起,编译之后的可执行文件就保存在当前目录下) go build -o hello,exe 可以指定输出文件的名字 go run 像执行脚本文件一样执行Go代码 go install go install分为两步: 先编译得到一个可执行文件 将可执行文件拷贝到GOPATH/bin 跨平

TKXS 403

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Go学习笔记01|Go项目的创建与运行

从零开始学习Golang语言

笔记|思路|总结 6774

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

01_Go语言基础学习_Golang语言特性、环境搭建、第一个Go程序、包

1. Golang语言特性: 垃圾回收: 1.内存自动回收,再也不需要开发人员管理内存; 2.开发人员专注业务实现,降低了心智负担 ; 3.只需要new分配内存,不需要释放 天然并发: 1.从语言层面支持并发,非常简单; 2. goroute,轻量级线程,创建成千上万个goroute成为可能; 3.基于CSP(Communicating Sequential Process)模型实现...

数据知道的博客 2万+

【Go语言学习系列00】Go语言学习系列导航

本文是Go语言学习系列的导航文档,提供了从入门到精通的学习路径,包含各阶段文章链接和内容概述,帮助读者系统化学习Go语言。

GopherTribe的博客 1574

golang学习笔记——基础01

golang学习笔记——基础01

mirana的专栏 4054

Golang学习笔记_01——包

在Go中,每个文件都属于一个包。包的声明位于文件的最顶部,使用package关键字。

LuckyLay的博客 1564

Go语言自学系列 | 如何编写golang代码

视频来源:B站《golang入门到项目实战 [2021最新Go语言教程,没有废话,纯干货!持续更新中...]》 一边学习一边整理老师的课程内容及试验笔记,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:Go语言自学系列 | 汇总_COCOgsta的博客-优快云博客 代码组织 go应用使用包和模块来组织代码,包对应到文件系统就是文件夹,模块就是**.go**的go源文件。一个包中会有多个模块,或者多个子包。 go项目管理工具 早期的go项目使用gopath来管理项目,不方便而且容易出错,从 go

团爸讲算法 978

golang基础学习和安装day01

gopath是所有项目的根路径src放我们自己的源代码bin编译之后的程序之后,使用标准命令go install之后存放位置pkg缓存包import "fmt" //goland会帮我们自动导入程序中使用的包//变量定义: var//常量定义: const//01-先定义变量,再赋值 var 变量名 数据类型name = "duke" //Ctrl + Alt +l 可以快速格式化代码age = 20//02 定义时直接赋值。

2403_89688685的博客 1078

【GO语言卵细胞级别教程】01.GO基础知识

随着互联网的发展,越来越多的公司开始使用Go语言开发后端服务,如腾讯、阿里巴巴、美团等。总体来说,Go语言在中国的发展非常活跃,越来越多的开发者开始学习和使用它,未来也有很大的发展潜力。(5)Go编译器是一行行进行编译的,因此我们就一行就写一条语句,不能把多余的语句写在同一个,否则报错。(4)方法由一条条语句构成的,每一条语句不需要分号(go会自动给加)这也是设计的初衷简洁。Go语言的设计目标是提供一种简单、高效、可靠的编程语言,适用于大规模分布式系统开发。2009年:Go语言的第一个版本发布,同时开源。

5万+

Golang 学习笔记01

golang学习

null_one的博客 181
上一篇: Fix SSH客户端登录会话超时设置
下一篇: Centos下安装Google Chrome做网页性能分析
后场技术
博客等级 码龄11年 156粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值