refactor: 重构trustlog-sdk目录结构到trustlog/go-trustlog
- 将所有trustlog-sdk文件移动到trustlog/go-trustlog/目录 - 更新README中所有import路径从trustlog-sdk改为go-trustlog - 更新cookiecutter配置文件中的项目名称 - 更新根目录.lefthook.yml以引用新位置的配置 - 添加go.sum文件到版本控制 - 删除过时的示例文件 这次重构与trustlog-server保持一致的目录结构, 为未来支持多语言SDK(Python、Java等)预留空间。
This commit is contained in:
30
internal/grpcclient/config.go
Normal file
30
internal/grpcclient/config.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package grpcclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Config 客户端配置.
|
||||
type Config struct {
|
||||
// ServerAddrs gRPC服务器地址列表,格式: "host:port"
|
||||
// 支持多个地址,客户端将使用轮询负载均衡
|
||||
ServerAddrs []string
|
||||
// ServerAddr 单个服务器地址(向后兼容),如果设置了此字段,将忽略ServerAddrs
|
||||
ServerAddr string
|
||||
// DialOptions 额外的gRPC拨号选项
|
||||
DialOptions []grpc.DialOption
|
||||
}
|
||||
|
||||
// GetAddrs 获取服务器地址列表.
|
||||
func (c *Config) GetAddrs() ([]string, error) {
|
||||
switch {
|
||||
case len(c.ServerAddrs) > 0:
|
||||
return c.ServerAddrs, nil
|
||||
case c.ServerAddr != "":
|
||||
return []string{c.ServerAddr}, nil
|
||||
default:
|
||||
return nil, errors.New("at least one server address is required")
|
||||
}
|
||||
}
|
||||
119
internal/grpcclient/config_test.go
Normal file
119
internal/grpcclient/config_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package grpcclient_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/internal/grpcclient"
|
||||
)
|
||||
|
||||
func TestConfig_GetAddrs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config grpcclient.Config
|
||||
wantAddrs []string
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "ServerAddrs优先级高于ServerAddr",
|
||||
config: grpcclient.Config{
|
||||
ServerAddrs: []string{"server1:9090", "server2:9090"},
|
||||
ServerAddr: "server3:9090",
|
||||
},
|
||||
wantAddrs: []string{"server1:9090", "server2:9090"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "只有ServerAddrs",
|
||||
config: grpcclient.Config{
|
||||
ServerAddrs: []string{"server1:9090", "server2:9090", "server3:9090"},
|
||||
},
|
||||
wantAddrs: []string{"server1:9090", "server2:9090", "server3:9090"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "只有ServerAddr",
|
||||
config: grpcclient.Config{
|
||||
ServerAddr: "server1:9090",
|
||||
},
|
||||
wantAddrs: []string{"server1:9090"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ServerAddrs为空,使用ServerAddr",
|
||||
config: grpcclient.Config{
|
||||
ServerAddrs: []string{},
|
||||
ServerAddr: "server1:9090",
|
||||
},
|
||||
wantAddrs: []string{"server1:9090"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "没有任何地址应该返回错误",
|
||||
config: grpcclient.Config{},
|
||||
wantAddrs: nil,
|
||||
wantErr: true,
|
||||
errMsg: "at least one server address is required",
|
||||
},
|
||||
{
|
||||
name: "ServerAddrs为空且ServerAddr为空",
|
||||
config: grpcclient.Config{
|
||||
ServerAddrs: []string{},
|
||||
ServerAddr: "",
|
||||
},
|
||||
wantAddrs: nil,
|
||||
wantErr: true,
|
||||
errMsg: "at least one server address is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
addrs, err := tt.config.GetAddrs()
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
if tt.errMsg != "" {
|
||||
assert.Contains(t, err.Error(), tt.errMsg)
|
||||
}
|
||||
assert.Nil(t, addrs)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantAddrs, addrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_EmptyServerAddrs(t *testing.T) {
|
||||
// 测试空的 ServerAddrs 切片
|
||||
config := grpcclient.Config{
|
||||
ServerAddrs: []string{},
|
||||
ServerAddr: "fallback:9090",
|
||||
}
|
||||
|
||||
addrs, err := config.GetAddrs()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"fallback:9090"}, addrs)
|
||||
}
|
||||
|
||||
func TestConfig_MultipleServerAddrs(t *testing.T) {
|
||||
// 测试多个服务器地址
|
||||
config := grpcclient.Config{
|
||||
ServerAddrs: []string{
|
||||
"server1:9090",
|
||||
"server2:9091",
|
||||
"server3:9092",
|
||||
"server4:9093",
|
||||
},
|
||||
}
|
||||
|
||||
addrs, err := config.GetAddrs()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, addrs, 4)
|
||||
assert.Equal(t, "server1:9090", addrs[0])
|
||||
assert.Equal(t, "server4:9093", addrs[3])
|
||||
}
|
||||
113
internal/grpcclient/loadbalancer.go
Normal file
113
internal/grpcclient/loadbalancer.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package grpcclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
// ClientFactory 客户端工厂函数类型.
|
||||
type ClientFactory[T any] func(grpc.ClientConnInterface) T
|
||||
|
||||
// ServerClient 封装单个服务器的连接.
|
||||
type ServerClient[T any] struct {
|
||||
addr string
|
||||
conn *grpc.ClientConn
|
||||
client T
|
||||
}
|
||||
|
||||
// LoadBalancer 轮询负载均衡器(泛型版本).
|
||||
type LoadBalancer[T any] struct {
|
||||
servers []*ServerClient[T]
|
||||
counter atomic.Uint64
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewLoadBalancer 创建新的负载均衡器.
|
||||
func NewLoadBalancer[T any](
|
||||
addrs []string,
|
||||
dialOpts []grpc.DialOption,
|
||||
factory ClientFactory[T],
|
||||
) (*LoadBalancer[T], error) {
|
||||
if len(addrs) == 0 {
|
||||
return nil, errors.New("at least one server address is required")
|
||||
}
|
||||
|
||||
lb := &LoadBalancer[T]{
|
||||
servers: make([]*ServerClient[T], 0, len(addrs)),
|
||||
}
|
||||
|
||||
// 默认使用不安全的连接(生产环境应使用TLS)
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
}
|
||||
opts = append(opts, dialOpts...)
|
||||
|
||||
// 连接所有服务器
|
||||
for _, addr := range addrs {
|
||||
conn, err := grpc.NewClient(addr, opts...)
|
||||
if err != nil {
|
||||
// 关闭已创建的连接
|
||||
_ = lb.Close()
|
||||
return nil, fmt.Errorf("failed to connect to server %s: %w", addr, err)
|
||||
}
|
||||
|
||||
client := factory(conn)
|
||||
lb.servers = append(lb.servers, &ServerClient[T]{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
client: client,
|
||||
})
|
||||
}
|
||||
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
// Next 使用轮询算法获取下一个客户端.
|
||||
func (lb *LoadBalancer[T]) Next() T {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
|
||||
if len(lb.servers) == 0 || lb.closed {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
// 原子递增计数器并取模
|
||||
idx := lb.counter.Add(1) % uint64(len(lb.servers))
|
||||
return lb.servers[idx].client
|
||||
}
|
||||
|
||||
// Close 关闭所有连接.
|
||||
func (lb *LoadBalancer[T]) Close() error {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
|
||||
// 如果已经关闭,直接返回
|
||||
if lb.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, server := range lb.servers {
|
||||
if err := server.conn.Close(); err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// 标记为已关闭
|
||||
lb.closed = true
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// ServerCount 返回服务器数量.
|
||||
func (lb *LoadBalancer[T]) ServerCount() int {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
return len(lb.servers)
|
||||
}
|
||||
186
internal/grpcclient/loadbalancer_test.go
Normal file
186
internal/grpcclient/loadbalancer_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package grpcclient_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/internal/grpcclient"
|
||||
)
|
||||
|
||||
// mockClient 用于测试的模拟客户端.
|
||||
type mockClient struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func TestNewLoadBalancer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []string
|
||||
dialOpts []grpc.DialOption
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "成功创建负载均衡器",
|
||||
addrs: []string{
|
||||
"localhost:9090",
|
||||
"localhost:9091",
|
||||
},
|
||||
dialOpts: []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "没有地址应该失败",
|
||||
addrs: []string{},
|
||||
dialOpts: nil,
|
||||
wantErr: true,
|
||||
errMsg: "at least one server address is required",
|
||||
},
|
||||
{
|
||||
name: "nil地址列表应该失败",
|
||||
addrs: nil,
|
||||
dialOpts: nil,
|
||||
wantErr: true,
|
||||
errMsg: "at least one server address is required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lb, err := grpcclient.NewLoadBalancer(
|
||||
tt.addrs,
|
||||
tt.dialOpts,
|
||||
func(_ grpc.ClientConnInterface) *mockClient {
|
||||
return &mockClient{ID: "test"}
|
||||
},
|
||||
)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
if tt.errMsg != "" {
|
||||
assert.Contains(t, err.Error(), tt.errMsg)
|
||||
}
|
||||
assert.Nil(t, lb)
|
||||
} else {
|
||||
// 注意:这里会实际尝试连接,在测试环境下可能失败
|
||||
// 实际使用时应该使用 mock 或 bufconn
|
||||
if err != nil {
|
||||
t.Skipf("Skipping test - cannot connect to servers: %v", err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lb)
|
||||
assert.Equal(t, len(tt.addrs), lb.ServerCount())
|
||||
// 清理
|
||||
_ = lb.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBalancer_Next(t *testing.T) {
|
||||
// 创建一个模拟的负载均衡器,不需要真实连接
|
||||
t.Run("轮询算法测试", func(t *testing.T) {
|
||||
// 这个测试需要使用 bufconn 或其他 mock 方式
|
||||
// 暂时跳过需要真实连接的测试
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping test that requires network connection")
|
||||
}
|
||||
|
||||
addrs := []string{"localhost:9090", "localhost:9091", "localhost:9092"}
|
||||
lb, err := grpcclient.NewLoadBalancer(
|
||||
addrs,
|
||||
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
|
||||
func(_ grpc.ClientConnInterface) *mockClient {
|
||||
return &mockClient{ID: "test"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Skipf("Cannot create load balancer: %v", err)
|
||||
return
|
||||
}
|
||||
defer lb.Close()
|
||||
|
||||
// 测试轮询:调用 Next() 多次应该轮询返回不同的客户端
|
||||
clients := make([]*mockClient, 6)
|
||||
for i := range 6 {
|
||||
clients[i] = lb.Next()
|
||||
assert.NotNil(t, clients[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadBalancer_Close(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping test that requires network connection")
|
||||
}
|
||||
|
||||
addrs := []string{"localhost:9090"}
|
||||
lb, err := grpcclient.NewLoadBalancer(
|
||||
addrs,
|
||||
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
|
||||
func(_ grpc.ClientConnInterface) *mockClient {
|
||||
return &mockClient{ID: "test"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Skipf("Cannot create load balancer: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 第一次关闭
|
||||
err = lb.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// 再次关闭应该也不会报错
|
||||
err = lb.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLoadBalancer_ServerCount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping test that requires network connection")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []string
|
||||
wantCount int
|
||||
}{
|
||||
{
|
||||
name: "单服务器",
|
||||
addrs: []string{"localhost:9090"},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "多服务器",
|
||||
addrs: []string{"localhost:9090", "localhost:9091", "localhost:9092"},
|
||||
wantCount: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lb, err := grpcclient.NewLoadBalancer(
|
||||
tt.addrs,
|
||||
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
|
||||
func(_ grpc.ClientConnInterface) *mockClient {
|
||||
return &mockClient{ID: "test"}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Skipf("Cannot create load balancer: %v", err)
|
||||
return
|
||||
}
|
||||
defer lb.Close()
|
||||
|
||||
assert.Equal(t, tt.wantCount, lb.ServerCount())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user