Project Icon

viper

Go语言全能配置管理库

Viper是Go语言的全能配置管理库,支持JSON、TOML、YAML等多种格式。它能从文件、环境变量、命令行标志和远程系统读取配置,提供默认值设置、实时监控、别名注册等功能。Viper采用优先级处理机制,简单易用yet功能强大,适用于各类Go项目的配置需求。

Viper v2 反馈

Viper 正在向 v2 版本迈进,我们非常希望听到 对它的想法。请在这里分享你的想法:https://forms.gle/R6faU74qPRPAzchZ9

感谢你!

Viper

在 Awesome Go 中被提及 在 repl.it 上运行

GitHub 工作流状态 加入 Gitter 聊天 Go 报告卡 Go 版本 PkgGoDev

Go 配置利器!

许多 Go 项目都使用 Viper 构建,包括:

安装

go get github.com/spf13/viper

注意: Viper 使用 Go Modules 来管理依赖。

Viper 是什么?

Viper 是一个完整的 Go 应用程序配置解决方案,包括 12-Factor 应用。它被设计用于在应用程序中工作,可以处理所有类型的配置需求和格式。它支持:

  • 设置默认值
  • 读取 JSON、TOML、YAML、HCL、envfile 和 Java properties 配置文件
  • 实时监视和重新读取配置文件(可选)
  • 读取环境变量
  • 从远程配置系统(etcd 或 Consul)读取,并监视变化
  • 读取命令行标志
  • 从缓冲区读取
  • 设置显式值

Viper 可以被视为你的应用程序所有配置需求的注册表。

为什么选择 Viper?

在构建现代应用程序时,你不想担心配置文件格式;你想专注于构建出色的软件。Viper 就是为此而生的。

Viper 为你做以下工作:

  1. 查找、加载并解析 JSON、TOML、YAML、HCL、INI、envfile 或 Java properties 格式的配置文件。
  2. 为不同的配置选项提供设置默认值的机制。
  3. 提供一种机制,通过命令行标志设置覆盖值。
  4. 提供别名系统,轻松重命名参数而不破坏现有代码。
  5. 轻松区分用户提供的命令行或配置文件是否与默认值相同。

Viper 使用以下优先顺序。每个项目优先于其下方的项目:

  • 显式调用 Set
  • 标志
  • 环境变量
  • 配置
  • 键/值存储
  • 默认值

重要: Viper 配置键不区分大小写。 目前正在讨论是否将其设为可选。

将值放入 Viper

设置默认值

一个好的配置系统应该支持默认值。虽然键不需要默认值,但在未通过配置文件、环境变量、远程配置或标志设置键的情况下,默认值很有用。

示例:

viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

读取配置文件

Viper 需要最少的配置,以便知道在哪里查找配置文件。Viper 支持 JSON、TOML、YAML、HCL、INI、envfile 和 Java Properties 文件。Viper 可以搜索多个路径,但目前单个 Viper 实例只支持单个配置文件。Viper 不默认任何配置搜索路径,将默认决策留给应用程序。

以下是如何使用 Viper 搜索和读取配置文件的示例。不需要特定的路径,但应提供至少一个预期存在配置文件的路径。

viper.SetConfigName("config") // 配置文件名(不带扩展名)
viper.SetConfigType("yaml") // 如果配置文件名中没有扩展名,则需要指定
viper.AddConfigPath("/etc/appname/")   // 查找配置文件的路径
viper.AddConfigPath("$HOME/.appname")  // 可多次调用以添加多个搜索路径
viper.AddConfigPath(".")               // 可选,在工作目录中查找配置
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件错误
	panic(fmt.Errorf("fatal error config file: %w", err))
}

你可以这样处理未找到配置文件的特定情况:

if err := viper.ReadInConfig(); err != nil {
	if _, ok := err.(viper.ConfigFileNotFoundError); ok {
		// 未找到配置文件;如果需要可以忽略错误
	} else {
		// 找到配置文件但产生了另一个错误
	}
}

// 找到配置文件并成功解析

注意 [自 1.6 起]: 你也可以使用没有扩展名的文件,并以编程方式指定格式。适用于那些位于用户主目录中没有任何扩展名的配置文件,如 .bashrc

写入配置文件

从配置文件读取很有用,但有时你想存储运行时所做的所有修改。为此,有一系列命令可用,每个命令都有其特定用途:

  • WriteConfig - 将当前 viper 配置写入预定义路径(如果存在)。如果没有预定义路径则报错。如果存在当前配置文件,将覆盖它。
  • SafeWriteConfig - 将当前 viper 配置写入预定义路径。如果没有预定义路径则报错。如果存在当前配置文件,将不会覆盖它。
  • WriteConfigAs - 将当前 viper 配置写入指定文件路径。如果文件存在,将覆盖它。
  • SafeWriteConfigAs - 将当前 viper 配置写入指定文件路径。如果文件存在,将不会覆盖它。

根据经验,所有标记为安全的操作都不会覆盖任何文件,只会在不存在时创建;而默认行为是创建或截断。

以下是一些小例子:

viper.WriteConfig() // 将当前配置写入由 'viper.AddConfigPath()' 和 'viper.SetConfigName' 设置的预定义路径
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为已经写入,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")

监视和重新读取配置文件

Viper 支持让你的应用程序在运行时实时读取配置文件的能力。

不再需要重启服务器来使配置生效,使用 viper 的应用程序可以在运行时读取配置文件的更新,而不会中断。

只需告诉 viper 实例监视配置。你也可以选择提供一个函数,让 Viper 在每次发生变化时运行。

确保在调用 WatchConfig() 之前添加所有的 configPaths

viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("配置文件发生变化:", e.Name)
})
viper.WatchConfig()

从 io.Reader 读取配置

Viper 预定义了许多配置源,如文件、环境变量、标志和远程 K/V 存储,但你并不局限于这些。你也可以实现自己需要的配置源并将其提供给 viper。

viper.SetConfigType("yaml") // 或 viper.SetConfigType("YAML")
// 在程序中引入此配置的任何方法。
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
  jacket: leather
  trousers: denim
age: 35
eyes : brown
beard: true
`)

viper.ReadConfig(bytes.NewBuffer(yamlExample))

viper.Get("name") // 这将返回 "steve"

设置覆盖

这些可能来自命令行标志或您自己的应用程序逻辑。

viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
viper.Set("host.port", 5899)   // 设置子集

注册和使用别名

别名允许单个值被多个键引用

viper.RegisterAlias("loud", "Verbose")

viper.Set("verbose", true) // 与下一行效果相同
viper.Set("loud", true)   // 与上一行效果相同

viper.GetBool("loud") // true
viper.GetBool("verbose") // true

使用环境变量

Viper 完全支持环境变量。这使得 12 因素应用程序开箱即用。有五种方法可以帮助处理环境变量:

  • AutomaticEnv()
  • BindEnv(string...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

在使用环境变量时,重要的是要认识到 Viper 将环境变量视为区分大小写。

Viper 提供了一种机制来尝试确保环境变量是唯一的。通过使用 SetEnvPrefix,您可以告诉 Viper 在读取环境变量时使用前缀。BindEnvAutomaticEnv 都将使用此前缀。

BindEnv 接受一个或多个参数。第一个参数是键名,其余的是要绑定到此键的环境变量名称。如果提供了多个,它们将按指定的顺序优先。环境变量名称区分大小写。如果未提供环境变量名称,则 Viper 将自动假设环境变量匹配以下格式:前缀 + "_" + 全大写的键名。当您明确提供环境变量名称(第二个参数)时,它不会自动添加前缀。例如,如果第二个参数是 "id",Viper 将查找名为 "ID" 的环境变量。

使用环境变量时要认识到的一个重要事项是,每次访问时都会读取该值。Viper 在调用 BindEnv 时不会固定值。

AutomaticEnv 是一个强大的辅助工具,特别是与 SetEnvPrefix 结合使用时。当被调用时,Viper 将在每次进行 viper.Get 请求时检查环境变量。它将应用以下规则。它将检查名称与键名大写并加上 EnvPrefix(如果设置)前缀的环境变量。

SetEnvKeyReplacer 允许您使用 strings.Replacer 对象在一定程度上重写环境变量键。如果您想在 Get() 调用中使用 - 或其他字符,但希望环境变量使用 _ 分隔符,这将非常有用。使用示例可以在 viper_test.go 中找到。

或者,您可以使用 NewWithOptions 工厂函数的 EnvKeyReplacer。与 SetEnvKeyReplacer 不同,它接受一个 StringReplacer 接口,允许您编写自定义的字符串替换逻辑。

默认情况下,空环境变量被视为未设置,将回退到下一个配置源。要将空环境变量视为已设置,请使用 AllowEmptyEnv 方法。

环境变量示例

SetEnvPrefix("spf") // 将自动转为大写
BindEnv("id")

os.Setenv("SPF_ID", "13") // 通常在应用程序外部完成

id := Get("id") // 13

使用标志

Viper 能够绑定到标志。具体来说,Viper 支持 Cobra 库中使用的 Pflags

BindEnv 类似,值不是在调用绑定方法时设置的,而是在访问时设置的。这意味着您可以尽早绑定,甚至在 init() 函数中绑定。

对于单个标志,BindPFlag() 方法提供了这种功能。

示例:

serverCmd.Flags().Int("port", 1138, "应用服务器运行的端口")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))

您还可以绑定现有的 pflags 集合(pflag.FlagSet):

示例:

pflag.Int("flagname", 1234, "flagname 的帮助信息")

pflag.Parse()
viper.BindPFlags(pflag.CommandLine)

i := viper.GetInt("flagname") // 从 viper 而不是 pflag 中检索值

在 Viper 中使用 pflag 并不妨碍使用其他使用标准库 flag 包的包。pflag 包可以通过导入这些标志来处理为 flag 包定义的标志。这是通过调用 pflag 包提供的名为 AddGoFlagSet() 的便利函数来实现的。

示例:

package main

import (
	"flag"
	"github.com/spf13/pflag"
)

func main() {

	// 使用标准库 "flag" 包
	flag.Int("flagname", 1234, "flagname 的帮助信息")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)

	i := viper.GetInt("flagname") // 从 viper 中检索值

	// ...
}

标志接口

如果您不使用 Pflags,Viper 提供了两个 Go 接口来绑定其他标志系统。

FlagValue 表示单个标志。这是一个非常简单的示例,展示如何实现这个接口:

type myFlag struct {}
func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() string { return "string" }

一旦您的标志实现了这个接口,您就可以简单地告诉 Viper 绑定它:

viper.BindFlagValue("my-flag-name", myFlag{})

FlagValueSet 表示一组标志。这是一个非常简单的示例,展示如何实现这个接口:

type myFlagSet struct {
	flags []myFlag
}

func (f myFlagSet) VisitAll(fn func(FlagValue)) {
	for _, flag := range flags {
		fn(flag)
	}
}

一旦您的标志集实现了这个接口,您就可以简单地告诉 Viper 绑定它:

fSet := myFlagSet{
	flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)

远程键/值存储支持

要在 Viper 中启用远程支持,请执行 viper/remote 包的空导入:

import _ "github.com/spf13/viper/remote"

Viper 将读取从键/值存储(如 etcd 或 Consul)的路径中检索到的配置字符串(作为 JSON、TOML、YAML、HCL 或 envfile)。这些值优先于默认值,但会被从磁盘、标志或环境变量检索到的配置值覆盖。

Viper 支持多个主机。要使用,请传递一个由 ; 分隔的端点列表。例如 http://127.0.0.1:4001;http://127.0.0.1:4002

Viper 使用 crypt 从键/值存储中检索配置,这意味着您可以加密存储配置值,如果您有正确的 gpg 密钥环,它们会自动解密。加密是可选的。

您可以将远程配置与本地配置结合使用,也可以独立使用。

crypt 有一个命令行助手,您可以用它将配置放入键/值存储中。crypt 默认使用 http://127.0.0.1:4001 上的 etcd。

$ go get github.com/sagikazarmark/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json

确认您的值已设置:


$ crypt get -plaintext /config/hugo.json

有关如何设置加密值或如何使用 Consul 的示例,请参阅 crypt 文档。

远程键/值存储示例 - 未加密

etcd

viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 因为字节流中没有文件扩展名,支持的扩展名有 "json"、"toml"、"yaml"、"yml"、"properties"、"props"、"prop"、"env"、"dotenv"
err := viper.ReadRemoteConfig()

etcd3

viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 因为字节流中没有文件扩展名,支持的扩展名有 "json"、"toml"、"yaml"、"yml"、"properties"、"props"、"prop"、"env"、"dotenv"
err := viper.ReadRemoteConfig()

Consul

你需要在Consul键/值存储中设置一个包含所需配置的JSON值的键。 例如,创建一个Consul键/值存储键 MY_CONSUL_KEY,值为:

{
    "port": 8080,
    "hostname": "myhostname.com"
}
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要明确设置为json
err := viper.ReadRemoteConfig()

fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // myhostname.com

Firestore

viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

当然,你也可以使用 SecureRemoteProvider

NATS

viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config")
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()

远程键/值存储示例 - 加密

viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // 因为字节流中没有文件扩展名,支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

监听etcd中的变更 - 未加密

// 或者,你可以创建一个新的viper实例。
var runtime_viper = viper.New()

runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // 因为字节流中没有文件扩展名,支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"

// 首次从远程配置读取。
err := runtime_viper.ReadRemoteConfig()

// 解析配置
runtime_viper.Unmarshal(&runtime_conf)

// 开启一个goroutine永久监听远程变更
go func(){
	for {
		time.Sleep(time.Second * 5) // 每次请求后延迟

		// 目前,仅测试了etcd支持
		err := runtime_viper.WatchRemoteConfig()
		if err != nil {
			log.Errorf("无法读取远程配置: %v", err)
			continue
		}

		// 将新配置解析到我们的运行时配置结构中。你也可以使用通道
		// 来实现通知系统变更的信号
		runtime_viper.Unmarshal(&runtime_conf)
	}
}()

从Viper获取值

在Viper中,根据值的类型,有几种获取值的方式。 以下函数和方法可用:

  • Get(key string) : any
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]any
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]any

需要注意的一点是,如果未找到,每个Get函数都会返回零值。要检查给定键是否存在,提供了 IsSet() 方法。

如果值已设置但无法解析为请求的类型,也会返回零值。

示例:

viper.GetString("logfile") // 不区分大小写的设置和获取
if viper.GetBool("verbose") {
	fmt.Println("已启用详细模式")
}

访问嵌套键

访问器方法也接受格式化的路径来深度嵌套的键。例如,如果加载了以下JSON文件:

{
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

Viper可以通过传递以 . 分隔的键路径来访问嵌套字段:

GetString("datastore.metric.host") // (返回 "127.0.0.1")

这遵循上述建立的优先级规则;将通过剩余的配置注册表级联搜索路径,直到找到为止。

例如,给定这个配置文件,datastore.metric.hostdatastore.metric.port 都已定义(并可能被覆盖)。如果此外在默认值中定义了 datastore.metric.protocol,Viper也会找到它。

但是,如果 datastore.metric 被覆盖(通过标志、环境变量、Set() 方法等)为一个直接值,那么 datastore.metric 的所有子键都将变为未定义,它们被更高优先级的配置级别"遮蔽"了。

Viper可以使用路径中的数字访问数组索引。例如:

{
    "host": {
        "address": "localhost",
        "ports": [
            5799,
            6029
        ]
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetInt("host.ports.1") // 返回 6029

最后,如果存在与分隔键路径匹配的键,将返回其值。例如:

{
    "datastore.metric.host": "0.0.0.0",
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetString("datastore.metric.host") // 返回 "0.0.0.0"

提取子树

在开发可重用模块时,提取配置的子集并将其传递给模块通常很有用。这样,模块可以使用不同的配置多次实例化。

例如,应用程序可能为不同目的使用多个不同的缓存存储:

cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80

我们可以将缓存名称传递给模块(例如 NewCache("cache1")),但这需要奇怪的连接来访问配置键,并且与全局配置的分离程度较低。

因此,我们可以将代表配置子集的Viper实例传递给构造函数:

cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // 如果找不到键,Sub返回nil
	panic("未找到缓存配置")
}

cache1 := NewCache(cache1Config)

注意: 始终检查 Sub 的返回值。如果找不到键,它返回 nil

在内部,NewCache 函数可以直接访问 max-itemsitem-size 键:

func NewCache(v *Viper) *Cache {
	return &Cache{
		MaxItems: v.GetInt("max-items"),
		ItemSize: v.GetInt("item-size"),
	}
}

生成的代码易于测试,因为它与主配置结构解耦,并且更容易重用(出于同样的原因)。

解析

你还可以选择将所有或特定值解析到结构体、映射等中。

有两种方法可以做到这一点:

  • Unmarshal(rawVal any) : error
  • UnmarshalKey(key string, rawVal any) : error

示例:

type config struct {
	Port int
	Name string
	PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
	t.Fatalf("无法解码到结构体,%v", err)
}

如果你想解析键本身包含点(默认键分隔符)的配置,你必须更改分隔符:

v := viper.NewWithOptions(viper.KeyDelimiter("::"))

v.SetDefault("chart::values", map[string]any{
	"ingress": map[string]any{
		"annotations": map[string]any{
			"traefik.frontend.rule.type":                 "PathPrefix",
			"traefik.ingress.kubernetes.io/ssl-redirect": "true",
		},
	},
})

type config struct {
	Chart struct{
		Values map[string]any
	}
}

var C config

v.Unmarshal(&C)

Viper也支持解析到嵌入式结构体:

/*
示例配置:
模块:
    启用:true
    令牌:89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
	Module struct {
		Enabled bool

		moduleConfig `mapstructure:",squash"`
	}
}

// moduleConfig 可以在特定模块包中定义
type moduleConfig struct {
	Token string
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
	t.Fatalf("无法解码到结构体,%v", err)
}

Viper 在底层使用 github.com/go-viper/mapstructure 进行值的解析,默认使用 mapstructure 标签。

解码自定义格式

Viper 的一个常见需求是添加更多的值格式和解码器。 例如,将字符(点、逗号、分号等)分隔的字符串解析为切片。

使用 mapstructure 解码钩子,Viper 已经支持这一功能。

这篇博客文章中可以了解更多详情。

序列化为字符串

你可能需要将 Viper 中保存的所有设置序列化为字符串,而不是将它们写入文件。 你可以使用你喜欢的格式的序列化器处理 AllSettings() 返回的配置。

import (
	yaml "gopkg.in/yaml.v2"
	// ...
)

func yamlStringSettings() string {
	c := viper.AllSettings()
	bs, err := yaml.Marshal(c)
	if err != nil {
		log.Fatalf("无法将配置序列化为YAML:%v", err)
	}
	return string(bs)
}

Viper 还是 Vipers?

Viper 默认提供了一个全局实例(单例)。

尽管它使配置设置变得简单,但通常不建议使用它,因为它会使测试变得更困难,并可能导致意外行为。

最佳实践是初始化一个 Viper 实例,并在需要时传递它。

全局实例在将来_可能_会被弃用。 详情请参阅 #1855

使用多个 Viper 实例

你还可以为你的应用程序创建多个不同的 Viper 实例。每个实例都有自己独特的配置和值集。每个实例可以从不同的配置文件、键值存储等读取。Viper 包支持的所有函数都在 Viper 实例上有对应的方法。

示例:

x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

在使用多个 Viper 实例时,用户需要自行跟踪不同的 Viper 实例。

问答

为什么叫"Viper"?

答:Viper 被设计为 Cobra伴侣。虽然两者都可以完全独立运行,但它们结合在一起形成了一个强大的组合,可以处理你的应用程序基础需求的大部分内容。

为什么叫"Cobra"?

还有比指挥官更好的名字吗?

Viper 是否支持区分大小写的键?

简而言之: 不支持。

Viper 合并来自各种来源的配置,其中许多要么不区分大小写,要么使用与其他来源不同的大小写(例如环境变量)。 为了在使用多个来源时提供最佳体验,我们决定使所有键不区分大小写。

曾经有几次尝试实现区分大小写的功能,但不幸的是,这并不那么简单。我们可能会在 Viper v2 中尝试实现它,但尽管最初有些声音,但似乎并没有太多人要求这个功能。

你可以通过填写此反馈表来为区分大小写投票:https://forms.gle/R6faU74qPRPAzchZ9

并发读写 Viper 是否安全?

不,你需要自己同步对 Viper 的访问(例如使用 sync 包)。并发读写可能会导致 panic。

故障排除

请参阅 TROUBLESHOOTING.md

开发

为了获得最佳的开发者体验,建议安装 Nixdirenv

或者,在你的计算机上安装 Go,然后运行 make deps 安装其余依赖项。

运行测试套件:

make test

运行代码检查器:

make lint # 使用 -j 选项并行运行

某些代码检查问题可以自动修复:

make fmt

许可证

该项目采用 MIT 许可证

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号