不同服务注册不同组件模块

This commit is contained in:
Cold
2025-12-09 17:55:08 +08:00
committed by 张斌
parent 87b3ac9878
commit 43a8834c5a
7 changed files with 347 additions and 45 deletions

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
"github.com/gogf/gf/contrib/registry/consul/v2" "github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@@ -13,14 +14,30 @@ import (
"github.com/gogf/gf/v2/util/grand" "github.com/gogf/gf/v2/util/grand"
) )
func init() { var initOnce sync.Once
// Init 初始化 Consul 注册中心(延迟初始化,首次调用时执行)
func Init() {
initOnce.Do(func() {
consulAddr := g.Cfg().MustGet(context.Background(), "consul.address").String() consulAddr := g.Cfg().MustGet(context.Background(), "consul.address").String()
if consulAddr == "" {
g.Log().Warning(context.Background(), "⚠️ Consul 配置未找到,跳过初始化")
return
}
registry, err := consul.New(consul.WithAddress(consulAddr)) registry, err := consul.New(consul.WithAddress(consulAddr))
if err != nil { if err != nil {
panic(err) g.Log().Errorf(context.Background(), "Consul 初始化失败: %v", err)
return
} }
gsvc.SetRegistry(registry) gsvc.SetRegistry(registry)
gsel.SetBuilder(gsel.NewBuilderRoundRobin()) gsel.SetBuilder(gsel.NewBuilderRoundRobin())
g.Log().Infof(context.Background(), "✅ Consul 初始化成功: %s", consulAddr)
})
}
func init() {
// 默认自动初始化(保持向后兼容)
Init()
} }
func getLocalIP() (string, error) { func getLocalIP() (string, error) {
// 获取本机所有网络接口 // 获取本机所有网络接口

116
elasticsearch/client.go Normal file
View File

@@ -0,0 +1,116 @@
package elasticsearch
import (
"context"
"sync"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/olivere/elastic/v7"
)
var (
client *elastic.Client
clientOnce sync.Once
)
// Config ES 配置
type Config struct {
Addresses []string // ES 地址列表
Username string // 用户名
Password string // 密码
}
// Init 初始化 ES 客户端(单例)
func Init(ctx context.Context) (err error) {
clientOnce.Do(func() {
addresses := g.Cfg().MustGet(ctx, "elasticsearch.addresses", []string{"http://localhost:9200"}).Strings()
username := g.Cfg().MustGet(ctx, "elasticsearch.username", "").String()
password := g.Cfg().MustGet(ctx, "elasticsearch.password", "").String()
options := []elastic.ClientOptionFunc{
elastic.SetURL(addresses...),
elastic.SetSniff(false), // 禁用嗅探,避免容器环境问题
}
if username != "" && password != "" {
options = append(options, elastic.SetBasicAuth(username, password))
}
client, err = elastic.NewClient(options...)
if err != nil {
glog.Errorf(ctx, "ES 客户端初始化失败: %v", err)
return
}
// 测试连接
info, code, err := client.Ping(addresses[0]).Do(ctx)
if err != nil {
glog.Errorf(ctx, "ES 连接测试失败: %v", err)
return
}
glog.Infof(ctx, "ES 连接成功 - 版本: %s, 状态码: %d", info.Version.Number, code)
})
return
}
// GetClient 获取 ES 客户端
func GetClient() *elastic.Client {
return client
}
// BulkIndex 批量写入文档
func BulkIndex(ctx context.Context, indexName string, docs []interface{}) (err error) {
if client == nil {
return gerror.New("ES 客户端未初始化")
}
bulk := client.Bulk().Index(indexName)
for _, doc := range docs {
bulk.Add(elastic.NewBulkIndexRequest().Doc(doc))
}
resp, err := bulk.Do(ctx)
if err != nil {
return
}
if resp.Errors {
for _, item := range resp.Failed() {
glog.Errorf(ctx, "ES 写入失败 - Index: %s, Error: %s", item.Index, item.Error.Reason)
}
}
glog.Infof(ctx, "ES 批量写入完成 - 索引: %s, 成功: %d, 失败: %d",
indexName, len(resp.Succeeded()), len(resp.Failed()))
return
}
// CreateIndexIfNotExists 创建索引(如果不存在)
func CreateIndexIfNotExists(ctx context.Context, indexName, mapping string) (err error) {
if client == nil {
return gerror.New("ES 客户端未初始化")
}
exists, err := client.IndexExists(indexName).Do(ctx)
if err != nil {
return
}
if !exists {
_, err = client.CreateIndex(indexName).BodyString(mapping).Do(ctx)
if err != nil {
return
}
glog.Infof(ctx, "ES 索引创建成功: %s", indexName)
}
return
}
// Close 关闭客户端
func Close() {
if client != nil {
client.Stop()
}
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/gogf/gf/contrib/trace/otlphttp/v2" "github.com/gogf/gf/contrib/trace/otlphttp/v2"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
@@ -13,16 +14,38 @@ import (
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
) )
var ShutDown func(ctx context.Context) var (
ShutDown func(ctx context.Context)
initOnce sync.Once
)
// Init 初始化 Jaeger 链路追踪(延迟初始化,首次调用时执行)
func Init() {
initOnce.Do(func() {
ctx := context.Background()
jaegerAgent := g.Cfg().MustGet(ctx, "jaeger.addr").String()
serverName := g.Cfg().MustGet(ctx, "server.name").String()
if jaegerAgent == "" {
g.Log().Warning(ctx, "⚠️ Jaeger 配置未找到,跳过初始化")
ShutDown = func(ctx context.Context) {} // 空函数,避免 nil panic
return
}
func init() {
jaegerAgent := g.Cfg().MustGet(context.Background(), "jaeger.addr").String()
serverName := g.Cfg().MustGet(context.Background(), "server.name").String()
shutdown, err := otlphttp.Init(serverName, jaegerAgent, "/v1/traces") shutdown, err := otlphttp.Init(serverName, jaegerAgent, "/v1/traces")
if err != nil { if err != nil {
panic(err) g.Log().Errorf(ctx, "Jaeger 初始化失败: %v", err)
ShutDown = func(ctx context.Context) {}
return
} }
ShutDown = shutdown ShutDown = shutdown
g.Log().Infof(ctx, "✅ Jaeger 初始化成功: %s", jaegerAgent)
})
}
func init() {
// 默认自动初始化(保持向后兼容)
Init()
} }
func NewTracer(r *ghttp.Request) { func NewTracer(r *ghttp.Request) {
_, span := gtrace.NewSpan(r.Context(), r.GetServeHandler().GetMetaTag("summary")) _, span := gtrace.NewSpan(r.Context(), r.GetServeHandler().GetMetaTag("summary"))

View File

@@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync"
"time" "time"
"github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/encoding/gjson"
@@ -14,12 +15,14 @@ import (
) )
var ( var (
// globalClient 全局 RAGFlow 客户端(单例,自动初始化) // globalClient 全局 RAGFlow 客户端(单例,延迟初始化)
globalClient *Client globalClient *Client
clientOnce sync.Once
) )
// init 包初始化时自动创建全局客户端 // initClient 延迟初始化客户端
func init() { func initClient() {
clientOnce.Do(func() {
ctx := context.Background() ctx := context.Background()
// 读取配置 // 读取配置
@@ -35,7 +38,7 @@ func init() {
httpClient := gclient.New() httpClient := gclient.New()
httpClient.SetHeader("Authorization", "Bearer "+apiKey) httpClient.SetHeader("Authorization", "Bearer "+apiKey)
httpClient.SetHeader("Content-Type", "application/json") httpClient.SetHeader("Content-Type", "application/json")
httpClient.SetTimeout(60 * time.Second) // RAGFlow AI 推理需要较长时间 httpClient.SetTimeout(180 * time.Second) // RAGFlow AI 推理需要较长时间
globalClient = &Client{ globalClient = &Client{
BaseURL: strings.TrimSuffix(baseURL, "/"), BaseURL: strings.TrimSuffix(baseURL, "/"),
@@ -44,6 +47,7 @@ func init() {
} }
g.Log().Infof(ctx, "✅ RAGFlow 全局客户端初始化成功: baseURL=%s", baseURL) g.Log().Infof(ctx, "✅ RAGFlow 全局客户端初始化成功: baseURL=%s", baseURL)
})
} }
// loadConfig 从配置文件加载 RAGFlow 配置 // loadConfig 从配置文件加载 RAGFlow 配置
@@ -54,9 +58,10 @@ func loadConfig(ctx context.Context) (baseURL, apiKey string) {
return return
} }
// GetGlobalClient 获取全局客户端 // GetGlobalClient 获取全局客户端(延迟初始化)
// 使用示例client := ragflow.GetGlobalClient() // 使用示例client := ragflow.GetGlobalClient()
func GetGlobalClient() *Client { func GetGlobalClient() *Client {
initClient()
return globalClient return globalClient
} }

View File

@@ -66,6 +66,7 @@ type ChatCompletionReq struct {
// ChatCompletionRes 对话响应 (非流式) // ChatCompletionRes 对话响应 (非流式)
type ChatCompletionRes struct { type ChatCompletionRes struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` // 错误信息
Data struct { Data struct {
Answer string `json:"answer"` Answer string `json:"answer"`
Reference interface{} `json:"reference"` Reference interface{} `json:"reference"`
@@ -163,7 +164,7 @@ func (c *Client) ChatCompletion(ctx context.Context, chatId string, req *ChatCom
return nil, err return nil, err
} }
if res.Code != 0 { if res.Code != 0 {
return nil, gerror.Newf("chat completion failed: code=%d", res.Code) return nil, gerror.Newf("chat completion failed: code=%d, message=%s", res.Code, res.Message)
} }
return &res, nil return &res, nil
} }

View File

@@ -3,18 +3,36 @@ package redis
import ( import (
"context" "context"
"strings" "strings"
"sync"
"github.com/gogf/gf/v2/database/gredis"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/gconv"
) )
// redisClient 内部使用的 Redis 客户端g.Redis() 是原型模式,需要变量引用) var (
var redisClient = g.Redis() // redisClient 内部使用的 Redis 客户端(单例模式)
redisClient *gredis.Redis
redisOnce sync.Once
)
// RedisClient 导出的 Redis 客户端(供 mongo.go 使用 // getClient 获取 Redis 客户端(延迟初始化
var RedisClient = redisClient func getClient() *gredis.Redis {
redisOnce.Do(func() {
redisClient = g.Redis()
})
return redisClient
}
// GetRedisClient 获取 Redis 客户端(供外部使用)
func GetRedisClient() *gredis.Redis {
return getClient()
}
// RedisClient 导出的 Redis 客户端(供 mongo.go 使用,兼容旧代码)
var RedisClient = getClient()
// Stream 和消费者组常量 // Stream 和消费者组常量
const ( const (

122
startup/startup.go Normal file
View File

@@ -0,0 +1,122 @@
// Package startup 提供服务启动时的组件初始化控制
// 各服务可以按需初始化所需组件,避免不必要的资源占用
package startup
import (
"context"
"sync"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
// Components 组件配置
type Components struct {
Consul bool // Consul 服务注册发现(所有服务都需要)
Jaeger bool // Jaeger 链路追踪(所有服务都需要)
Redis bool // Redis 缓存
RabbitMQ bool // RabbitMQ 消息队列
MongoDB bool // MongoDB 数据库
RAGFlow bool // RAGFlow AI 客户端
ES bool // Elasticsearch
}
var (
initialized bool
initOnce sync.Once
components *Components
)
// Init 初始化指定的组件
// 示例:
//
// bootstrap.Init(ctx, &bootstrap.Components{
// Consul: true,
// Jaeger: true,
// Redis: true,
// RabbitMQ: true,
// })
func Init(ctx context.Context, c *Components) {
initOnce.Do(func() {
components = c
initialized = true
glog.Infof(ctx, "Bootstrap 初始化完成: %+v", c)
})
}
// IsInitialized 检查是否已初始化
func IsInitialized() bool {
return initialized
}
// GetComponents 获取组件配置
func GetComponents() *Components {
if components == nil {
// 默认配置:从配置文件读取
return loadFromConfig()
}
return components
}
// NeedRedis 是否需要 Redis
func NeedRedis() bool {
c := GetComponents()
return c != nil && c.Redis
}
// NeedRabbitMQ 是否需要 RabbitMQ
func NeedRabbitMQ() bool {
c := GetComponents()
return c != nil && c.RabbitMQ
}
// NeedMongoDB 是否需要 MongoDB
func NeedMongoDB() bool {
c := GetComponents()
return c != nil && c.MongoDB
}
// NeedRAGFlow 是否需要 RAGFlow
func NeedRAGFlow() bool {
c := GetComponents()
return c != nil && c.RAGFlow
}
// NeedES 是否需要 Elasticsearch
func NeedES() bool {
c := GetComponents()
return c != nil && c.ES
}
// loadFromConfig 从配置文件加载组件配置
// 如果配置文件中没有 startup 配置,则默认全部启动
func loadFromConfig() *Components {
ctx := context.Background()
// 检查是否有 startup 配置节
startupCfg := g.Cfg().MustGet(ctx, "startup")
if startupCfg.IsEmpty() {
// 没有配置 startup默认全部启动
glog.Debug(ctx, "未找到 startup 配置,默认启动所有组件")
return &Components{
Consul: true,
Jaeger: true,
Redis: true,
RabbitMQ: true,
MongoDB: true,
RAGFlow: true,
ES: true,
}
}
// 有配置则按配置来,未配置的项默认 true
return &Components{
Consul: g.Cfg().MustGet(ctx, "startup.consul", true).Bool(),
Jaeger: g.Cfg().MustGet(ctx, "startup.jaeger", true).Bool(),
Redis: g.Cfg().MustGet(ctx, "startup.redis", true).Bool(),
RabbitMQ: g.Cfg().MustGet(ctx, "startup.rabbitmq", true).Bool(),
MongoDB: g.Cfg().MustGet(ctx, "startup.mongodb", true).Bool(),
RAGFlow: g.Cfg().MustGet(ctx, "startup.ragflow", true).Bool(),
ES: g.Cfg().MustGet(ctx, "startup.es", true).Bool(),
}
}