Skip to content

Commit ac3efe5

Browse files
LanceAddhailazgithub-actions[bot]joy999Copilot
authored
feat(os/gcfg): Add file watcher with custom callback support (gogf#4446)
为`gcfg`添加配置文件变更自定义回调,实现了`WatcherAdapter`接口,以下是`AdapterFile`的用法 test.yaml ``` b: "b" ``` ``` package main import ( "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/gctx" ) func main() { ctx := gctx.New() file, _ := gcfg.NewAdapterFile("test.yaml") file.Data(ctx) file.AddWatcher("test", func() { value := file.MustGet(ctx, "b") fmt.Println(value.String()) }) server := g.Server() server.Run() } ``` 使用`g`和默认配置文件 ``` file := g.Cfg().GetAdapter().(*gcfg.AdapterFile) file.AddWatcher("test", func() { }) file := g.Cfg().GetAdapter().(*gcfg.AdapterFile) file.RemoveWatcher("test") ``` 注意:由于`gf`的`AdapterFile`使用的监听到文件变化删除缓存下一次重新初始化的懒加载方案,所有除了默认加载的`config.xxx`文件外,自定义的配置文件像`test.yaml`之类的都需要在`AddWatcher`前主动读取一次数据进行初始化监听( `g.Cfg("test").Data(ctx)`) --------- Co-authored-by: hailaz <739476267@qq.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Hunk Zhu <hunk@joy999.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2744fe2 commit ac3efe5

22 files changed

Lines changed: 1673 additions & 53 deletions

contrib/config/apollo/apollo.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import (
2222
"github.com/gogf/gf/v2/util/gconv"
2323
)
2424

25+
var (
26+
// Compile-time checking for interface implementation.
27+
_ gcfg.Adapter = (*Client)(nil)
28+
_ gcfg.WatcherAdapter = (*Client)(nil)
29+
)
30+
2531
// Config is the configuration object for apollo client.
2632
type Config struct {
2733
AppID string `v:"required"` // See apolloConfig.Config.
@@ -38,9 +44,10 @@ type Config struct {
3844

3945
// Client implements gcfg.Adapter implementing using apollo service.
4046
type Client struct {
41-
config Config // Config object when created.
42-
client agollo.Client // Apollo client.
43-
value *g.Var // Configmap content cached. It is `*gjson.Json` value internally.
47+
config Config // Config object when created.
48+
client agollo.Client // Apollo client.
49+
value *g.Var // Configmap content cached. It is `*gjson.Json` value internally.
50+
watchers *gcfg.WatcherRegistry // Watchers for watching file changes.
4451
}
4552

4653
// New creates and returns gcfg.Adapter implementing using apollo service.
@@ -54,8 +61,9 @@ func New(ctx context.Context, config Config) (adapter gcfg.Adapter, err error) {
5461
config.NamespaceName = storage.GetDefaultNamespace()
5562
}
5663
client := &Client{
57-
config: config,
58-
value: g.NewVar(nil, true),
64+
config: config,
65+
value: g.NewVar(nil, true),
66+
watchers: gcfg.NewWatcherRegistry(),
5967
}
6068
// Apollo client.
6169
client.client, err = agollo.StartWithConfig(func() (*apolloConfig.AppConfig, error) {
@@ -89,7 +97,7 @@ func (c *Client) Available(ctx context.Context, resource ...string) (ok bool) {
8997
if len(resource) == 0 && !c.value.IsNil() {
9098
return true
9199
}
92-
var namespace = c.config.NamespaceName
100+
namespace := c.config.NamespaceName
93101
if len(resource) > 0 {
94102
namespace = resource[0]
95103
}
@@ -132,18 +140,46 @@ func (c *Client) OnNewestChange(event *storage.FullChangeEvent) {
132140
}
133141

134142
func (c *Client) updateLocalValue(ctx context.Context) (err error) {
135-
var j = gjson.New(nil)
143+
j := gjson.New(nil)
144+
content := gjson.New(nil, true)
136145
cache := c.client.GetConfigCache(c.config.NamespaceName)
137146
cache.Range(func(key, value any) bool {
138147
err = j.Set(gconv.String(key), value)
139148
if err != nil {
140149
return false
141150
}
151+
err = content.Set(gconv.String(key), value)
152+
if err != nil {
153+
return false
154+
}
142155
return true
143156
})
144157
cache.Clear()
145158
if err == nil {
146159
c.value.Set(j)
160+
adapterCtx := NewAdapterCtx(ctx).WithOperation(gcfg.OperationUpdate).WithNamespace(c.config.NamespaceName).
161+
WithAppId(c.config.AppID).WithCluster(c.config.Cluster).WithContent(content)
162+
c.notifyWatchers(adapterCtx.Ctx)
147163
}
148164
return
149165
}
166+
167+
// AddWatcher adds a watcher for the specified configuration file.
168+
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
169+
c.watchers.Add(name, f)
170+
}
171+
172+
// RemoveWatcher removes the watcher for the specified configuration file.
173+
func (c *Client) RemoveWatcher(name string) {
174+
c.watchers.Remove(name)
175+
}
176+
177+
// GetWatcherNames returns all watcher names.
178+
func (c *Client) GetWatcherNames() []string {
179+
return c.watchers.GetNames()
180+
}
181+
182+
// notifyWatchers notifies all watchers.
183+
func (c *Client) notifyWatchers(ctx context.Context) {
184+
c.watchers.Notify(ctx)
185+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
// Package apollo implements gcfg.Adapter using apollo service.
8+
package apollo
9+
10+
import (
11+
"context"
12+
13+
"github.com/gogf/gf/v2/encoding/gjson"
14+
"github.com/gogf/gf/v2/os/gcfg"
15+
"github.com/gogf/gf/v2/os/gctx"
16+
)
17+
18+
const (
19+
// ContextKeyNamespace is the context key for namespace
20+
ContextKeyNamespace gctx.StrKey = "namespace"
21+
// ContextKeyAppId is the context key for appId
22+
ContextKeyAppId gctx.StrKey = "appId"
23+
// ContextKeyCluster is the context key for cluster
24+
ContextKeyCluster gctx.StrKey = "cluster"
25+
)
26+
27+
// ApolloAdapterCtx is the context adapter for Apollo configuration
28+
type ApolloAdapterCtx struct {
29+
Ctx context.Context
30+
}
31+
32+
// NewAdapterCtxWithCtx creates and returns a new ApolloAdapterCtx with the given context.
33+
func NewAdapterCtxWithCtx(ctx context.Context) *ApolloAdapterCtx {
34+
if ctx == nil {
35+
ctx = context.Background()
36+
}
37+
return &ApolloAdapterCtx{Ctx: ctx}
38+
}
39+
40+
// NewAdapterCtx creates and returns a new ApolloAdapterCtx.
41+
// If context is provided, it will be used; otherwise, a background context is created.
42+
func NewAdapterCtx(ctx ...context.Context) *ApolloAdapterCtx {
43+
if len(ctx) > 0 {
44+
return NewAdapterCtxWithCtx(ctx[0])
45+
}
46+
return NewAdapterCtxWithCtx(context.Background())
47+
}
48+
49+
// GetAdapterCtx creates a new ApolloAdapterCtx with the given context
50+
func GetAdapterCtx(ctx context.Context) *ApolloAdapterCtx {
51+
return NewAdapterCtxWithCtx(ctx)
52+
}
53+
54+
// WithOperation sets the operation in the context
55+
func (a *ApolloAdapterCtx) WithOperation(operation gcfg.OperationType) *ApolloAdapterCtx {
56+
a.Ctx = context.WithValue(a.Ctx, gcfg.ContextKeyOperation, operation)
57+
return a
58+
}
59+
60+
// WithNamespace sets the namespace in the context
61+
func (a *ApolloAdapterCtx) WithNamespace(namespace string) *ApolloAdapterCtx {
62+
a.Ctx = context.WithValue(a.Ctx, ContextKeyNamespace, namespace)
63+
return a
64+
}
65+
66+
// WithAppId sets the appId in the context
67+
func (a *ApolloAdapterCtx) WithAppId(appId string) *ApolloAdapterCtx {
68+
a.Ctx = context.WithValue(a.Ctx, ContextKeyAppId, appId)
69+
return a
70+
}
71+
72+
// WithCluster sets the cluster in the context
73+
func (a *ApolloAdapterCtx) WithCluster(cluster string) *ApolloAdapterCtx {
74+
a.Ctx = context.WithValue(a.Ctx, ContextKeyCluster, cluster)
75+
return a
76+
}
77+
78+
// WithContent sets the content in the context
79+
func (a *ApolloAdapterCtx) WithContent(content *gjson.Json) *ApolloAdapterCtx {
80+
a.Ctx = context.WithValue(a.Ctx, gcfg.ContextKeyContent, content)
81+
return a
82+
}
83+
84+
// GetNamespace retrieves the namespace from the context
85+
func (a *ApolloAdapterCtx) GetNamespace() string {
86+
if v := a.Ctx.Value(ContextKeyNamespace); v != nil {
87+
if s, ok := v.(string); ok {
88+
return s
89+
}
90+
}
91+
return ""
92+
}
93+
94+
// GetAppId retrieves the appId from the context
95+
func (a *ApolloAdapterCtx) GetAppId() string {
96+
if v := a.Ctx.Value(ContextKeyAppId); v != nil {
97+
if s, ok := v.(string); ok {
98+
return s
99+
}
100+
}
101+
return ""
102+
}
103+
104+
// GetCluster retrieves the cluster from the context
105+
func (a *ApolloAdapterCtx) GetCluster() string {
106+
if v := a.Ctx.Value(ContextKeyCluster); v != nil {
107+
if s, ok := v.(string); ok {
108+
return s
109+
}
110+
}
111+
return ""
112+
}
113+
114+
// GetContent retrieves the content from the context
115+
func (a *ApolloAdapterCtx) GetContent() *gjson.Json {
116+
if v := a.Ctx.Value(gcfg.ContextKeyContent); v != nil {
117+
if s, ok := v.(*gjson.Json); ok {
118+
return s
119+
}
120+
}
121+
return gjson.New(nil)
122+
}
123+
124+
// GetOperation retrieves the operation from the context
125+
func (a *ApolloAdapterCtx) GetOperation() gcfg.OperationType {
126+
if v := a.Ctx.Value(gcfg.ContextKeyOperation); v != nil {
127+
if s, ok := v.(gcfg.OperationType); ok {
128+
return s
129+
}
130+
}
131+
return ""
132+
}

contrib/config/consul/consul.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import (
2121
"github.com/gogf/gf/v2/os/glog"
2222
)
2323

24+
var (
25+
// Compile-time checking for interface implementation.
26+
_ gcfg.Adapter = (*Client)(nil)
27+
_ gcfg.WatcherAdapter = (*Client)(nil)
28+
)
29+
2430
// Config is the configuration object for consul client.
2531
type Config struct {
2632
// api.Config in consul package
@@ -41,6 +47,8 @@ type Client struct {
4147
client *api.Client
4248
// Configmap content cached. It is `*gjson.Json` value internally.
4349
value *g.Var
50+
// Watchers for watching file changes.
51+
watchers *gcfg.WatcherRegistry
4452
}
4553

4654
// New creates and returns gcfg.Adapter implementing using consul service.
@@ -55,8 +63,9 @@ func New(ctx context.Context, config Config) (adapter gcfg.Adapter, err error) {
5563
}
5664

5765
client := &Client{
58-
config: config,
59-
value: g.NewVar(nil, true),
66+
config: config,
67+
value: g.NewVar(nil, true),
68+
watchers: gcfg.NewWatcherRegistry(),
6069
}
6170

6271
client.client, err = api.NewClient(&config.ConsulConfig)
@@ -156,13 +165,26 @@ func (c *Client) addWatcher() (err error) {
156165
if v, ok = raw.(*api.KVPair); !ok {
157166
return
158167
}
159-
160-
if err = c.doUpdate(v.Value); err != nil {
168+
err = c.doUpdate(v.Value)
169+
if err != nil {
161170
c.config.Logger.Errorf(
162171
context.Background(),
163172
"watch config from consul path %+v update failed: %s",
164173
c.config.Path, err,
165174
)
175+
} else {
176+
var m *gjson.Json
177+
m, err = gjson.LoadContent(v.Value, true)
178+
if err != nil {
179+
c.config.Logger.Errorf(
180+
context.Background(),
181+
"watch config from consul path %+v parse failed: %s",
182+
c.config.Path, err,
183+
)
184+
} else {
185+
adapterCtx := NewAdapterCtx().WithOperation(gcfg.OperationUpdate).WithPath(c.config.Path).WithContent(m)
186+
c.notifyWatchers(adapterCtx.Ctx)
187+
}
166188
}
167189
}
168190

@@ -173,6 +195,7 @@ func (c *Client) addWatcher() (err error) {
173195
return nil
174196
}
175197

198+
// startAsynchronousWatch starts the asynchronous watch.
176199
func (c *Client) startAsynchronousWatch(plan *watch.Plan) {
177200
if err := plan.Run(c.config.ConsulConfig.Address); err != nil {
178201
c.config.Logger.Errorf(
@@ -182,3 +205,23 @@ func (c *Client) startAsynchronousWatch(plan *watch.Plan) {
182205
)
183206
}
184207
}
208+
209+
// AddWatcher adds a watcher for the specified configuration file.
210+
func (c *Client) AddWatcher(name string, f func(ctx context.Context)) {
211+
c.watchers.Add(name, f)
212+
}
213+
214+
// RemoveWatcher removes the watcher for the specified configuration file.
215+
func (c *Client) RemoveWatcher(name string) {
216+
c.watchers.Remove(name)
217+
}
218+
219+
// GetWatcherNames returns all watcher names.
220+
func (c *Client) GetWatcherNames() []string {
221+
return c.watchers.GetNames()
222+
}
223+
224+
// notifyWatchers notifies all watchers.
225+
func (c *Client) notifyWatchers(ctx context.Context) {
226+
c.watchers.Notify(ctx)
227+
}

0 commit comments

Comments
 (0)