konfig

2025-12-11 0 918

konfig

可以进行组合,可观察和性能的配置处理。为较大的分布式系统编写,您可能具有大量的配置源 – 它允许您从多个源中创建带有重新加载钩的配置,从而使构建在高度动态环境中的应用程序易于构建。

这个名字怎么了?

该名称为“ config”。我们在拉拉莫夫(Lalamove)这里有很多国籍,为了庆祝文化多样性,我们大多数开源套餐都会带有一个来自非英语语言的名称,这可能至少有一位员工(?)。

为什么另一个配置软件包

Golang的大多数配置软件包不是很可扩展,并且很少露出接口。这使得构建可以动态且难以嘲笑的应用程序变得复杂。诸如Vault,ETCD和多个编码格式之类的来源仍然更少。简而言之,我们没有找到一个启动时满足所有要求的软件包。

konfig围绕4个小界面建造:

  • 加载程序
  • 观察者
  • 解析器
  • 靠近

konfig功能包括:

  • 动态配置加载
  • 来自多个来源的可组合加载配置,例如保险库,文件等
  • 来自多种格式的Polyglot负载配置。 konfig支持JSON,YAML,TOML,键=值
  • 快速,无锁,线程安全读取读数的速度比Viper快10倍
  • 可观察的配置 – 热加载机制和管理状态的工具
  • 键入读取从配置或绑定结构的键入值
  • 指标暴露了Prometheus指标告诉您,如果失败了,将配置重新加载了多少次,以及重新加载需要多长时间

开始

go get github.com/lalamove/ konfig

加载并观看JSON格式化的配置文件。

konfig .DefaultConfig())
}

func main() {
// load from json file
konfig .RegisterLoaderWatcher(
klfile.New(&klfile.Config{
Files: configFiles,
Watch: true,
}),
// optionally you can pass config hooks to run when a file is changed
func(c konfig .Store) error {
return nil
},
)

if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}

// retrieve value from config file
konfig .Bool(\”debug\”)
}\”>

 var configFiles = []klfile. File {
	{
		Path :   \"./config.json\" ,
		Parser : kpjson . Parser ,
	},
}

func init () {
	konfig . Init ( konfig . DefaultConfig ())
}

func main () {
	// load from json file
	konfig . RegisterLoaderWatcher (
		klfile . New ( & klfile. Config {
			Files : configFiles ,
			Watch : true ,
		}),
		// optionally you can pass config hooks to run when a file is changed
		func ( c konfig . Store ) error {
			return nil
		},
	)

	if err := konfig . LoadWatch (); err != nil {
		log . Fatal ( err )
	}

	// retrieve value from config file
	konfig . Bool ( \"debug\" )
}

店铺

该商店是配置软件包的基础。它保存并允许访问密钥存储的值。

创建商店

您可以通过调用konfig .Init(* konfig .Config)来创建一个全局商店:

konfig .DefaultConfig())\”>

 konfig . Init ( konfig . DefaultConfig ())

全球商店可直接从包装访问:

 konfig . Get ( \"foo\" ) // calls store.Get(\"foo\")

您可以通过调用konfig .New(* konfig .Config)来创建新商店:

konfig .DefaultConfig())\”>

 s := konfig . New ( konfig . DefaultConfig ())

加载和看商店

konfig .Store中注册装载机和观察者后,您必须加载并观看商店。

您可以通过调用LoadWatch来做:

konfig.LoadWatch(); err != nil {
log.Fatal(err)
}\”>

 if err := konfig . LoadWatch (); err != nil {
	log . Fatal ( err )
}

您只能调用Load ,它将加载所有加载程序并返回:

konfig.Load(); err != nil {
log.Fatal(err)
}\”>

 if err := konfig . Load (); err != nil {
	log . Fatal ( err )
}

最后,您只能致电Watch ,它将启动所有观察者并返回:

konfig.Watch(); err != nil {
log.Fatal(err)
}\”>

 if err := konfig . Watch (); err != nil {
	log . Fatal ( err )
}

装载机

加载程序将配置值加载到商店。加载程序是加载程序接口的实现。

 type Loader interface {
	// Name return the name of the load, it is used to create labeled vectors metrics per loader
	Name () string
	// StopOnFailure indicates if a failure of the loader should trigger a stop
	StopOnFailure () bool
	// Loads the config and add it to the Store
	Load ( Store ) error
	// MaxRetry returns the maximum number of times to allow retrying on load failure
	MaxRetry () int
	// RetryDelay returns the delay to wait before retrying
	RetryDelay () time. Duration
}

您可以单独或观察者注册配置中的加载程序。

单独注册装载机:

konfig.RegisterLoader(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./ konfig .json\”,
},
},
},
),
)\”>

 configLoader := konfig . RegisterLoader (
	klfile . New (
		& klfile. Config {
			Files : []klfile. File {
				{
					Parser : kpjson . Parser ,
					Path :   \"./ konfig .json\" ,
				},
			},
		},
	),
)

向观察者注册装载器:

要一起注册加载程序和一个观察者,您必须注册一个LoaderWatcher ,该接口同时实现了LoaderWatcher接口。

konfig.RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./ konfig .json\”,
},
},
Watch: true,
},
),
)\”>

 configLoader := konfig . RegisterLoaderWatcher (
	klfile . New (
		& klfile. Config {
			Files : []klfile. File {
				{
					Parser : kpjson . Parser ,
					Path :   \"./ konfig .json\" ,
				},
			},
			Watch : true ,
		},
	),
)

您还可以撰写一个加载程序和一个观察者来创建一个LoaderWatcher

konfig.RegisterLoaderWatcher(
// it creates a LoaderWatcher from a loader and a watcher
konfig .NewLoaderWatcher(
someLoader,
someWatcher,
),
)\”>

 configLoader := konfig . RegisterLoaderWatcher (
	// it creates a LoaderWatcher from a loader and a watcher
	konfig . NewLoaderWatcher (
		someLoader ,
		someWatcher ,
	),
)

内置装载机

konfig已经有以下装载机,他们都有一个内置的观察者:

  • 文件加载程序

从可以观看的文件中加载配置。文件可以具有不同的解析器来加载不同的格式。它具有内置文件观察器,该文件观察器会在修改文件时触发配置重新加载(运行挂钩)。

  • 金库加载程序

从保险库秘密加载配置。它具有内置的民意调查观察者,该观察器在秘密之前触发配置重新加载(运行钩子),而来自Auth Provider的令牌到期。

  • HTTP加载器

从HTTP来源加载配置。来源可以具有不同的解析器来加载不同的格式。它具有内置的民意调查DIFF观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。

  • ETCD加载程序

从ETCD键加载配置。密钥可以具有不同的解析器来加载不同的格式。它具有内置的民意调查DIFF观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。

  • 领事加载器

从领事KV加载配置。密钥可以具有不同的解析器来加载不同的格式。它构建了Poll Diff观察器,如果数据不同,它会触发配置重新加载(运行挂钩)。

  • env加载器

从环境变量加载配置。

  • 标志加载程序

从命令行标志加载配置。

  • io.Reader加载器

从io.reader加载配置。

解析器

解析器将io.Reader解析为konfig .Store 。某些加载程序使用这些用来将其获取的数据解析到配置存储中。文件加载程序等,ETCD加载程序和HTTP加载程序使用解析器。

配置已经具有以下解析器:

  • JSON PARSER
  • Toml Parser
  • YAML解析器
  • KV解析器
  • 地图解析器

观察者

观察者在事件上触发加载程序的呼叫。观察者是Watcher接口的实现。

 type Watcher interface {
	// Start starts the watcher, it must not be blocking.
	Start () error
	// Done indicate whether the watcher is done or not
	Done () <- chan struct {}
	// Watch should block until an event unlocks it
	Watch () <- chan struct {}
	// Close closes the watcher, it returns a non nil error if it is already closed
	// or something prevents it from closing properly.
	Close () error
	// Err returns the error attached to the watcher
	Err () error
}

内置观察者

konfig已经有以下观察者:

  • 文件观察者

观看文件以进行更改。

  • 民意调查员

以给定的速率或启用diff发送事件。它需要一个获取器,并以给定的速率获取数据。如果数据不同,它将发送一个事件。

钩子

挂钩是成功加载器Load()调用后运行的功能。它们用于在配置更改上重新加载应用程序的状态。

用一些钩子注册装载机

您可以注册带有钩子的装载机或装载机观察器。

konfig.RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpyaml.Parser,
Path: \”./ konfig .yaml\”,
},
},
Watch: true,
},
),
func(s konfig .Store) error {
// Here you should reload the state of your app
return nil
},
)\”>

 configLoader := konfig . RegisterLoaderWatcher (
	klfile . New (
		& klfile. Config {
			Files : []klfile. File {
				{
					Parser : kpyaml . Parser ,
					Path :   \"./ konfig .yaml\" ,
				},
			},
			Watch : true ,
		},
	),
	func ( s konfig . Store ) error {
		// Here you should reload the state of your app
		return nil
	},
)

将钩子添加到现有加载器

您可以注册带有钩子的加载程序LoaderWatcher

konfig.Store) error {
// Here you should reload the state of your app
return nil
},
func(s konfig .Store) error {
// Here you should reload the state of your app
return nil
},
)\”>

 configLoader . AddHooks (
	func ( s konfig . Store ) error {
		// Here you should reload the state of your app
		return nil
	},
	func ( s konfig . Store ) error {
		// Here you should reload the state of your app
		return nil
	},
)

在钥匙上添加钩子

另外,您可以在键上添加钩子。键上的挂钩将匹配前缀,以便在更新带有给定前缀的任何键时运行钩子。即使多个键匹配该钩子,每个加载事件也只能运行一次。

konfig.RegisterKeyHook(
\”db.\”,
func(s konfig .Store) error {
return nil
},
)\”>

 konfig . RegisterKeyHook (
	\"db.\" ,
	func ( s konfig . Store ) error {
		return nil
	},
)

关闭者

可以将关闭器添加到konfig ,以便如果konfig无法加载,它将在注册关闭器上执行Close()

 type Closer interface {
	Close () error
}

近距离注册

 konfig . RegisterCloser ( closer )

配置组

您可以使用配置组命名配置。

konfig.Group(\”db\”).RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpyaml.Parser,
Path: \”./db.yaml\”,
},
},
Watch: true,
},
),
)

// accessing grouped config
dbHost := konfig .Group(\”db\”).MustString(\”credentials.host\”)\”>

 konfig . Group ( \"db\" ). RegisterLoaderWatcher (
	klfile . New (
		& klfile. Config {
			Files : []klfile. File {
				{
					Parser : kpyaml . Parser ,
					Path :   \"./db.yaml\" ,
				},
			},
			Watch : true ,
		},
	),
)

// accessing grouped config
dbHost := konfig . Group ( \"db\" ). MustString ( \"credentials.host\" )

将类型绑定到商店

如果您希望将配置值未贴到structmap [string]接口{} {} ,则可以将类型绑定到konfig Store。然后,您可以以线程安全的方式访问该类型的实例(为了安全的动态配置更新安全)。

让我们以JSON配置文件的示例来看看:

{
    \"addr\" : \" :8080 \" ,
    \"debug\" : true ,
    \"db\" : {
        \"username\" : \" foo \"
    },
    \"redis\" : {
        \"host\" : \" 127.0.0.1 \"
    }
}

konfig:\”db\”`
RedisHost string ` konfig :\”redis.host\”`
}

// we init the root konfig store
konfig .Init( konfig .DefaultConfig())

// we bind the Config struct to the konfig .Store
konfig .Bind(Config{})

// we register our config file
konfig .RegisterLoaderWatcher(
klfile.New(
&klfile.Config{
Files: []klfile.File{
{
Parser: kpjson.Parser,
Path: \”./config.json\”,
},
},
Watch: true,
},
),
)

// we load our config and start watching
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}

// Get our config value
c := konfig .Value().(Config)

fmt.Println(c.Addr) // :8080\”>

 type DBConfig struct {
	Username string
}
type Config struct {
	Addr      string
	Debug     string
	DB        DBConfig ` konfig :\"db\"`
	RedisHost string   ` konfig :\"redis.host\"`
}

// we init the root konfig store
konfig . Init ( konfig . DefaultConfig ())

// we bind the Config struct to the konfig .Store
konfig . Bind ( Config {})

// we register our config file
konfig . RegisterLoaderWatcher (
	klfile . New (
		& klfile. Config {
			Files : []klfile. File {
				{
					Parser : kpjson . Parser ,
					Path :   \"./config.json\" ,
				},
			},
			Watch : true ,
		},
	),
)

// we load our config and start watching
if err := konfig . LoadWatch (); err != nil {
	log . Fatal ( err )
}

// Get our config value
c := konfig . Value ().( Config )

fmt . Println ( c . Addr ) // :8080

请注意,您可以构成配置源。例如,让您的凭据来自保险库,并经常续订,并从文件中加载其余的配置并在文件更改时更新。

重要的是要了解konfig如何将您的配置值列入结构。当加载程序调用konfig .set()时,如果konfig存储具有与之绑定的值,它将尝试将密钥删除到界值。

  • 首先,它将在struct中寻找字段标签,如果标签与键完全匹配,它将删除键字段的键。
  • 然后,它将在字段名称和键上做一个相同的折叠率,如果它们匹配,它将删除结构字段的键。
  • 然后,如果键具有点,它将检查标签或字段名称(小写字母)是钥匙的前缀,如果是的,它将检查字段的类型是否是指针的结构,如果是的,它将使用前缀后的键作为钥匙检查结构。

从配置中读取

除了从绑定的配置值读取外, konfig还提供了几种读取值的方法。

检索配置值的每种方法都有2种口味:

  • 在给定键处读取值。如果不存在键,它将返回类型的零值。
  • Mustget在给定密钥处读取一个值。如果不存在钥匙,那就恐慌了。

从商店读取值的所有方法:

 // Exists checks whether the key k is set in the store.
Exists ( k string ) bool

// Get gets the value with the key k from the store. If the key is not set, Get returns nil. To check whether a value is really set, use Exists.
Get ( k string ) interface {}
// MustGet tries to get the value with the key k from the store. If the key k does not exist in the store, MustGet panics.
MustGet ( k string ) interface {}

// MustString tries to get the value with the key k from the store and casts it to a string. If the key k does not exist in the store, MustString panics.
MustString ( k string ) string
// String tries to get the value with the key k from the store and casts it to a string. If the key k does not exist it returns the Zero value.
String ( k string ) string

// MustInt tries to get the value with the key k from the store and casts it to a int. If the key k does not exist in the store, MustInt panics.
MustInt ( k string ) int
// Int tries to get the value with the key k from the store and casts it to a int. If the key k does not exist it returns the Zero value.
Int ( k string ) int

// MustFloat tries to get the value with the key k from the store and casts it to a float. If the key k does not exist in the store, MustFloat panics.
MustFloat ( k string ) float64
// Float tries to get the value with the key k from the store and casts it to a float. If the key k does not exist it returns the Zero value.
Float ( k string ) float64

// MustBool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist in the store, MustBool panics.
MustBool ( k string ) bool
// Bool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist it returns the Zero value.
Bool ( k string ) bool

// MustDuration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist in the store, MustDuration panics.
MustDuration ( k string ) time . Duration
// Duration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist it returns the Zero value.
Duration ( k string ) time . Duration

// MustTime tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist in the store, MustTime panics.
MustTime ( k string ) time . Time
// Time tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist it returns the Zero value.
Time ( k string ) time . Time

// MustStringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist in the store, MustStringSlice panics.
MustStringSlice ( k string ) [] string
// StringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist it returns the Zero value.
StringSlice ( k string ) [] string

// MustIntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist in the store, MustIntSlice panics.
MustIntSlice ( k string ) [] int
// IntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist it returns the Zero value.
IntSlice ( k string ) [] int

// MustStringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist in the store, MustStringMap panics.
MustStringMap ( k string ) map [ string ] interface {}
// StringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist it returns the Zero value.
StringMap ( k string ) map [ string ] interface {}

// MustStringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist in the store, MustStringMapString panics.
MustStringMapString ( k string ) map [ string ] string
// StringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist it returns the Zero value.
StringMapString ( k string ) map [ string ] string

严格的钥匙

您可以通过调用Strict方法来定义konfig .Store上所需的键。当调用严格的方法时, konfig将在商店中设置所需的键,在商店的第一个Load调用期间,将检查键是否存在,如果不存在,则负载将返回非零错误。然后,在加载程序上的每个Load后, konfig将再次检查键是否仍然存在,如果不是, Load被视为失败。

用法:

konfig store
konfig .Init( konfig .DefaultConfig()).Strict(\”debug\”, \”username\”)

// Register our loaders

// We load our config and start watching.
// If strict keys are not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}\”>

 // We init the root konfig store
konfig .Init( konfig .DefaultConfig()).Strict(\"debug\", \"username\")

// Register our loaders
...

// We load our config and start watching.
// If strict keys are not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
	log.Fatal(err)
}

另外, BindStructStrict可用于严格绑定配置。用法:

konfig:\”-\”` // this key will be non-strict
DB DBConfig ` konfig :\”db\”`
RedisHost string ` konfig :\”redis.host\”`
}

// we init the root konfig store
konfig .Init( konfig .DefaultConfig())

// we bind the Config struct to the konfig .Store
konfig .BindStructStrict(Config{})

// Register our loaders

// We load our config and start watching.
// If any strict key is not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
log.Fatal(err)
}\”>

 type DBConfig struct {
	Username string
}
type Config struct {
	Addr      string    ` konfig :\"-\"` // this key will be non-strict
	DB        DBConfig  ` konfig :\"db\"`
	RedisHost string    ` konfig :\"redis.host\"`
}

// we init the root konfig store
konfig .Init( konfig .DefaultConfig())

// we bind the Config struct to the konfig .Store
konfig .BindStructStrict(Config{})

// Register our loaders
...

// We load our config and start watching.
// If any strict key is not found after the load operation, LoadWatch will return a non nil error.
if err := konfig .LoadWatch(); err != nil {
	log.Fatal(err)
}

Getter

为了轻松构建可以使用动态加载配置的服务,您可以为特定密钥创建Getters。 getter实现了ngetter.GetterTyped从nui软件包中。在较大的分布式环境中构建应用程序时,这很有用。

带有调试键的配置值集的示例:

konfig.Getter(\”debug\”)

debug.Bool() // true\”>

 debug := konfig . Getter ( \"debug\" )

debug . Bool () // true

指标

konfig配备了普罗米修斯指标。

暴露了两个指标:

  • 带有标签的配置重新加载反向向量
  • 配置重新加载持续时间摘要向量带有标签

指标的示例:

konfig_loader_reload Number of config loader reload
# TYPE konfig _loader_reload counter
konfig _loader_reload{loader=\”config-files\”,result=\”failure\”,store=\”root\”} 0.0
konfig _loader_reload{loader=\”config-files\”,result=\”success\”,store=\”root\”} 1.0

# HELP konfig _loader_reload_duration Histogram for the config reload duration
# TYPE konfig _loader_reload_duration summary
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.5\”} 0.001227641
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.9\”} 0.001227641
konfig _loader_reload_duration{loader=\”config-files\”,store=\”root\”,quantile=\”0.99\”} 0.001227641
konfig _loader_reload_duration_sum{loader=\”config-files\”,store=\”\”} 0.001227641
konfig _loader_reload_duration_count{loader=\”config-files\”,store=\”\”} 1.0\”>

 # HELP konfig _loader_reload Number of config loader reload
# TYPE konfig _loader_reload counter
konfig _loader_reload{loader=\"config-files\",result=\"failure\",store=\"root\"} 0.0
konfig _loader_reload{loader=\"config-files\",result=\"success\",store=\"root\"} 1.0

# HELP konfig _loader_reload_duration Histogram for the config reload duration
# TYPE konfig _loader_reload_duration summary
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.5\"} 0.001227641
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.9\"} 0.001227641
konfig _loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.99\"} 0.001227641
konfig _loader_reload_duration_sum{loader=\"config-files\",store=\"\"} 0.001227641
konfig _loader_reload_duration_count{loader=\"config-files\",store=\"\"} 1.0

要启用指标,您必须在创建配置存储时传递自定义配置:

konfig .Config{
Metrics: true,
Name: \”root\”,
})\”>

 konfig . Init ( & konfig . Config {
	Metrics : true ,
	Name : \"root\" ,
})

基准

基准在vipergo-configkonfig上运行。基准是在阅读操作时完成的,并证明konfig在阅读和衬里的3倍上比Viper快3倍:

konfig/benchmarks
BenchmarkGet konfig -4 200000000 7.75 ns/op 0 B/op 0 allocs/op
BenchmarkString konfig -4 30000000 49.9 ns/op 0 B/op 0 allocs/op
BenchmarkGetViper-4 20000000 101 ns/op 32 B/op 2 allocs/op
BenchmarkStringViper-4 10000000 152 ns/op 32 B/op 2 allocs/op
BenchmarkGetGoConfig-4 10000000 118 ns/op 40 B/op 3 allocs/op
BenchmarkStringGoConfig-4 10000000 125 ns/op 40 B/op 3 allocs/op
PASS\”>

 cd benchmarks && go test -bench . && cd ../
goos: linux
goarch: amd64
pkg: github.com/lalamove/ konfig /benchmarks
BenchmarkGet konfig -4            200000000                7.75 ns/op            0 B/op          0 allocs/op
BenchmarkString konfig -4         30000000                49.9 ns/op             0 B/op          0 allocs/op
BenchmarkGetViper-4             20000000               101 ns/op              32 B/op          2 allocs/op
BenchmarkStringViper-4          10000000               152 ns/op              32 B/op          2 allocs/op
BenchmarkGetGoConfig-4          10000000               118 ns/op              40 B/op          3 allocs/op
BenchmarkStringGoConfig-4       10000000               125 ns/op              40 B/op          3 allocs/op
PASS

贡献

欢迎捐款。为了做出贡献,将存储库分叉,创建一个分支并向主分支提交拉动请求。

下载源码

通过命令行克隆项目:

git clone https://github.com/lalamove/konfig.git

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 konfig https://www.zuozi.net/34097.html

wren
上一篇: wren
kaitai_struct
下一篇: kaitai_struct
常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务