mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-21 05:38:21 +08:00
Compare commits
6 Commits
copilot/fi
...
v1.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe5bbb0c0 | ||
|
|
5ad2278a69 | ||
|
|
77763fe748 | ||
|
|
538c4fb5c7 | ||
|
|
315fb2fe0a | ||
|
|
e382887eb8 |
@@ -259,12 +259,34 @@ func (s *Redis) BitPosCtx(ctx context.Context, key string, bit, start, end int64
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Blpop uses passed in redis connection to execute blocking queries.
|
// Blpop uses passed in redis connection to execute blocking queries.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode to avoid
|
||||||
|
// exhausting the connection pool. Blocking commands hold connections for extended periods and should
|
||||||
|
// not share the regular connection pool.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// node, err := redis.CreateBlockingNode(rds)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// defer node.Close()
|
||||||
|
//
|
||||||
|
// value, err := rds.Blpop(node, "mylist")
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
// Doesn't benefit from pooling redis connections of blocking queries
|
// Doesn't benefit from pooling redis connections of blocking queries
|
||||||
func (s *Redis) Blpop(node RedisNode, key string) (string, error) {
|
func (s *Redis) Blpop(node RedisNode, key string) (string, error) {
|
||||||
return s.BlpopCtx(context.Background(), node, key)
|
return s.BlpopCtx(context.Background(), node, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlpopCtx uses passed in redis connection to execute blocking queries.
|
// BlpopCtx uses passed in redis connection to execute blocking queries.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode.
|
||||||
|
// See Blpop for usage examples.
|
||||||
|
//
|
||||||
// Doesn't benefit from pooling redis connections of blocking queries
|
// Doesn't benefit from pooling redis connections of blocking queries
|
||||||
func (s *Redis) BlpopCtx(ctx context.Context, node RedisNode, key string) (string, error) {
|
func (s *Redis) BlpopCtx(ctx context.Context, node RedisNode, key string) (string, error) {
|
||||||
return s.BlpopWithTimeoutCtx(ctx, node, blockingQueryTimeout, key)
|
return s.BlpopWithTimeoutCtx(ctx, node, blockingQueryTimeout, key)
|
||||||
@@ -272,12 +294,18 @@ func (s *Redis) BlpopCtx(ctx context.Context, node RedisNode, key string) (strin
|
|||||||
|
|
||||||
// BlpopEx uses passed in redis connection to execute blpop command.
|
// BlpopEx uses passed in redis connection to execute blpop command.
|
||||||
// The difference against Blpop is that this method returns a bool to indicate success.
|
// The difference against Blpop is that this method returns a bool to indicate success.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode.
|
||||||
|
// See Blpop for usage examples.
|
||||||
func (s *Redis) BlpopEx(node RedisNode, key string) (string, bool, error) {
|
func (s *Redis) BlpopEx(node RedisNode, key string) (string, bool, error) {
|
||||||
return s.BlpopExCtx(context.Background(), node, key)
|
return s.BlpopExCtx(context.Background(), node, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlpopExCtx uses passed in redis connection to execute blpop command.
|
// BlpopExCtx uses passed in redis connection to execute blpop command.
|
||||||
// The difference against Blpop is that this method returns a bool to indicate success.
|
// The difference against Blpop is that this method returns a bool to indicate success.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode.
|
||||||
|
// See Blpop for usage examples.
|
||||||
func (s *Redis) BlpopExCtx(ctx context.Context, node RedisNode, key string) (string, bool, error) {
|
func (s *Redis) BlpopExCtx(ctx context.Context, node RedisNode, key string) (string, bool, error) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return "", false, ErrNilNode
|
return "", false, ErrNilNode
|
||||||
@@ -297,12 +325,18 @@ func (s *Redis) BlpopExCtx(ctx context.Context, node RedisNode, key string) (str
|
|||||||
|
|
||||||
// BlpopWithTimeout uses passed in redis connection to execute blpop command.
|
// BlpopWithTimeout uses passed in redis connection to execute blpop command.
|
||||||
// Control blocking query timeout
|
// Control blocking query timeout
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode.
|
||||||
|
// See Blpop for usage examples.
|
||||||
func (s *Redis) BlpopWithTimeout(node RedisNode, timeout time.Duration, key string) (string, error) {
|
func (s *Redis) BlpopWithTimeout(node RedisNode, timeout time.Duration, key string) (string, error) {
|
||||||
return s.BlpopWithTimeoutCtx(context.Background(), node, timeout, key)
|
return s.BlpopWithTimeoutCtx(context.Background(), node, timeout, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlpopWithTimeoutCtx uses passed in redis connection to execute blpop command.
|
// BlpopWithTimeoutCtx uses passed in redis connection to execute blpop command.
|
||||||
// Control blocking query timeout
|
// Control blocking query timeout
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode.
|
||||||
|
// See Blpop for usage examples.
|
||||||
func (s *Redis) BlpopWithTimeoutCtx(ctx context.Context, node RedisNode, timeout time.Duration,
|
func (s *Redis) BlpopWithTimeoutCtx(ctx context.Context, node RedisNode, timeout time.Duration,
|
||||||
key string) (string, error) {
|
key string) (string, error) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
@@ -1840,6 +1874,29 @@ func (s *Redis) XInfoStreamCtx(ctx context.Context, stream string) (*red.XInfoSt
|
|||||||
|
|
||||||
// XReadGroup reads messages from Redis streams as part of a consumer group.
|
// XReadGroup reads messages from Redis streams as part of a consumer group.
|
||||||
// It allows for distributed processing of stream messages with automatic message delivery semantics.
|
// It allows for distributed processing of stream messages with automatic message delivery semantics.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode to avoid
|
||||||
|
// exhausting the connection pool. Blocking commands hold connections for extended periods and should
|
||||||
|
// not share the regular connection pool.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// node, err := redis.CreateBlockingNode(rds)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// defer node.Close()
|
||||||
|
//
|
||||||
|
// streams, err := rds.XReadGroup(
|
||||||
|
// node, // RedisNode created with CreateBlockingNode
|
||||||
|
// "mygroup", // consumer group name
|
||||||
|
// "consumer1", // consumer ID
|
||||||
|
// 10, // max number of messages to read
|
||||||
|
// 5*time.Second, // block duration
|
||||||
|
// false, // noAck flag
|
||||||
|
// "mystream", // stream name
|
||||||
|
// )
|
||||||
|
//
|
||||||
// Doesn't benefit from pooling redis connections of blocking queries.
|
// Doesn't benefit from pooling redis connections of blocking queries.
|
||||||
func (s *Redis) XReadGroup(node RedisNode, group string, consumerId string, count int64,
|
func (s *Redis) XReadGroup(node RedisNode, group string, consumerId string, count int64,
|
||||||
block time.Duration, noAck bool, streams ...string) ([]red.XStream, error) {
|
block time.Duration, noAck bool, streams ...string) ([]red.XStream, error) {
|
||||||
@@ -1847,6 +1904,10 @@ func (s *Redis) XReadGroup(node RedisNode, group string, consumerId string, coun
|
|||||||
}
|
}
|
||||||
|
|
||||||
// XReadGroupCtx is the context-aware version of XReadGroup.
|
// XReadGroupCtx is the context-aware version of XReadGroup.
|
||||||
|
//
|
||||||
|
// For blocking operations, you must create a dedicated RedisNode using CreateBlockingNode to avoid
|
||||||
|
// exhausting the connection pool. See XReadGroup for usage examples.
|
||||||
|
//
|
||||||
// Doesn't benefit from pooling redis connections of blocking queries.
|
// Doesn't benefit from pooling redis connections of blocking queries.
|
||||||
func (s *Redis) XReadGroupCtx(ctx context.Context, node RedisNode, group string, consumerId string,
|
func (s *Redis) XReadGroupCtx(ctx context.Context, node RedisNode, group string, consumerId string,
|
||||||
count int64, block time.Duration, noAck bool, streams ...string) ([]red.XStream, error) {
|
count int64, block time.Duration, noAck bool, streams ...string) ([]red.XStream, error) {
|
||||||
|
|||||||
@@ -13,7 +13,37 @@ type ClosableNode interface {
|
|||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBlockingNode returns a ClosableNode.
|
// CreateBlockingNode creates a dedicated RedisNode for blocking operations.
|
||||||
|
//
|
||||||
|
// Blocking Redis commands (like BLPOP, BRPOP, XREADGROUP with block parameter) hold connections
|
||||||
|
// for extended periods while waiting for data. Using them with the regular Redis connection pool
|
||||||
|
// can exhaust all available connections, causing other operations to fail or timeout.
|
||||||
|
//
|
||||||
|
// CreateBlockingNode creates a separate Redis client with a minimal connection pool (size 1) that
|
||||||
|
// is dedicated to blocking operations. This ensures blocking commands don't interfere with regular
|
||||||
|
// Redis operations.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// rds := redis.MustNewRedis(redis.RedisConf{
|
||||||
|
// Host: "localhost:6379",
|
||||||
|
// Type: redis.NodeType,
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// // Create a dedicated node for blocking operations
|
||||||
|
// node, err := redis.CreateBlockingNode(rds)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// defer node.Close() // Important: close the node when done
|
||||||
|
//
|
||||||
|
// // Use the node for blocking operations
|
||||||
|
// value, err := rds.Blpop(node, "mylist")
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The returned ClosableNode must be closed when no longer needed to release resources.
|
||||||
func CreateBlockingNode(r *Redis) (ClosableNode, error) {
|
func CreateBlockingNode(r *Redis) (ClosableNode, error) {
|
||||||
timeout := readWriteTimeout + blockingQueryTimeout
|
timeout := readWriteTimeout + blockingQueryTimeout
|
||||||
|
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -16,12 +16,12 @@ require (
|
|||||||
github.com/jhump/protoreflect v1.17.0
|
github.com/jhump/protoreflect v1.17.0
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/prometheus/client_golang v1.21.1
|
github.com/prometheus/client_golang v1.21.1
|
||||||
github.com/redis/go-redis/v9 v9.15.0
|
github.com/redis/go-redis/v9 v9.14.0
|
||||||
github.com/spaolacci/murmur3 v1.1.0
|
github.com/spaolacci/murmur3 v1.1.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15
|
go.etcd.io/etcd/api/v3 v3.5.15
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15
|
go.etcd.io/etcd/client/v3 v3.5.15
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.0
|
go.mongodb.org/mongo-driver/v2 v2.3.1
|
||||||
go.opentelemetry.io/otel v1.24.0
|
go.opentelemetry.io/otel v1.24.0
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -154,8 +154,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
|||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/redis/go-redis/v9 v9.15.0 h1:2jdes0xJxer4h3NUZrZ4OGSntGlXp4WbXju2nOTRXto=
|
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||||
github.com/redis/go-redis/v9 v9.15.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
@@ -197,8 +197,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5
|
|||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.0 h1:sh55yOXA2vUjW1QYw/2tRlHSQViwDyPnW61AwpZ4rtU=
|
go.mongodb.org/mongo-driver/v2 v2.3.1 h1:WrCgSzO7dh1/FrePud9dK5fKNZOE97q5EQimGkos7Wo=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.3.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
go.mongodb.org/mongo-driver/v2 v2.3.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
|||||||
|
|
||||||
* API 文档
|
* API 文档
|
||||||
|
|
||||||
[https://go-zero.dev/cn/](https://go-zero.dev/cn/)
|
[https://go-zero.dev](https://go-zero.dev)
|
||||||
|
|
||||||
* awesome 系列(更多文章见『微服务实践』公众号)
|
* awesome 系列(更多文章见『微服务实践』公众号)
|
||||||
|
|
||||||
@@ -305,6 +305,7 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>107. 深圳市聚货通信息科技有限公司
|
>107. 深圳市聚货通信息科技有限公司
|
||||||
>108. 浙江银盾云科技有限公司
|
>108. 浙江银盾云科技有限公司
|
||||||
>109. 南京造世网络科技有限公司
|
>109. 南京造世网络科技有限公司
|
||||||
|
>110. 温州飞儿云信息技术有限公司
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ func genHandler(dir, rootPkg, projectPkg string, cfg *config.Config, group spec.
|
|||||||
}
|
}
|
||||||
|
|
||||||
var builtinTemplate = handlerTemplate
|
var builtinTemplate = handlerTemplate
|
||||||
|
var templateFile = handlerTemplateFile
|
||||||
sse := group.GetAnnotation("sse")
|
sse := group.GetAnnotation("sse")
|
||||||
if sse == "true" {
|
if sse == "true" {
|
||||||
builtinTemplate = sseHandlerTemplate
|
builtinTemplate = sseHandlerTemplate
|
||||||
|
templateFile = sseHandlerTemplateFile
|
||||||
}
|
}
|
||||||
|
|
||||||
return genFile(fileGenConfig{
|
return genFile(fileGenConfig{
|
||||||
@@ -49,7 +51,7 @@ func genHandler(dir, rootPkg, projectPkg string, cfg *config.Config, group spec.
|
|||||||
filename: filename + ".go",
|
filename: filename + ".go",
|
||||||
templateName: "handlerTemplate",
|
templateName: "handlerTemplate",
|
||||||
category: category,
|
category: category,
|
||||||
templateFile: handlerTemplateFile,
|
templateFile: templateFile,
|
||||||
builtinTemplate: builtinTemplate,
|
builtinTemplate: builtinTemplate,
|
||||||
data: map[string]any{
|
data: map[string]any{
|
||||||
"PkgName": pkgName,
|
"PkgName": pkgName,
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ func genLogicByRoute(dir, rootPkg, projectPkg string, cfg *config.Config, group
|
|||||||
|
|
||||||
subDir := getLogicFolderPath(group, route)
|
subDir := getLogicFolderPath(group, route)
|
||||||
builtinTemplate := logicTemplate
|
builtinTemplate := logicTemplate
|
||||||
|
templateFile := logicTemplateFile
|
||||||
sse := group.GetAnnotation("sse")
|
sse := group.GetAnnotation("sse")
|
||||||
if sse == "true" {
|
if sse == "true" {
|
||||||
builtinTemplate = sseLogicTemplate
|
builtinTemplate = sseLogicTemplate
|
||||||
|
templateFile = sseLogicTemplateFile
|
||||||
responseString = "error"
|
responseString = "error"
|
||||||
returnString = "return nil"
|
returnString = "return nil"
|
||||||
resp := responseGoTypeName(route, typesPacket)
|
resp := responseGoTypeName(route, typesPacket)
|
||||||
@@ -80,7 +82,7 @@ func genLogicByRoute(dir, rootPkg, projectPkg string, cfg *config.Config, group
|
|||||||
filename: goFile + ".go",
|
filename: goFile + ".go",
|
||||||
templateName: "logicTemplate",
|
templateName: "logicTemplate",
|
||||||
category: category,
|
category: category,
|
||||||
templateFile: logicTemplateFile,
|
templateFile: templateFile,
|
||||||
builtinTemplate: builtinTemplate,
|
builtinTemplate: builtinTemplate,
|
||||||
data: map[string]any{
|
data: map[string]any{
|
||||||
"pkgName": subDir[strings.LastIndex(subDir, "/")+1:],
|
"pkgName": subDir[strings.LastIndex(subDir, "/")+1:],
|
||||||
|
|||||||
153
tools/goctl/api/gogen/gensse_test.go
Normal file
153
tools/goctl/api/gogen/gensse_test.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSEGeneration(t *testing.T) {
|
||||||
|
// Create a temporary directory for test
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
// Create a test API file with SSE annotation
|
||||||
|
apiContent := `syntax = "v1"
|
||||||
|
|
||||||
|
type SseReq {
|
||||||
|
Message string ` + "`json:\"message\"`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type SseResp {
|
||||||
|
Data string ` + "`json:\"data\"`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
sse: true
|
||||||
|
)
|
||||||
|
service Test {
|
||||||
|
@handler Sse
|
||||||
|
get /sse (SseReq) returns (SseResp)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
apiFile := filepath.Join(dir, "test.api")
|
||||||
|
err := os.WriteFile(apiFile, []byte(apiContent), 0644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Generate code
|
||||||
|
err = DoGenProject(apiFile, dir, "gozero", false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Read generated handler file
|
||||||
|
handlerPath := filepath.Join(dir, "internal/handler/ssehandler.go")
|
||||||
|
handlerContent, err := os.ReadFile(handlerPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Read generated logic file
|
||||||
|
logicPath := filepath.Join(dir, "internal/logic/sselogic.go")
|
||||||
|
logicContent, err := os.ReadFile(logicPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
handlerStr := string(handlerContent)
|
||||||
|
logicStr := string(logicContent)
|
||||||
|
|
||||||
|
// Verify SSE-specific patterns in handler
|
||||||
|
// Handler should call: err := l.Sse(&req, client)
|
||||||
|
assert.Contains(t, handlerStr, "err := l.Sse(&req, client)",
|
||||||
|
"Handler should call logic with client channel parameter")
|
||||||
|
|
||||||
|
// Handler should NOT have the regular pattern: resp, err := l.Sse(&req)
|
||||||
|
assert.NotContains(t, handlerStr, "resp, err := l.Sse(&req)",
|
||||||
|
"Handler should not use regular pattern with resp return")
|
||||||
|
|
||||||
|
// Handler should use threading.GoSafeCtx
|
||||||
|
assert.Contains(t, handlerStr, "threading.GoSafeCtx",
|
||||||
|
"Handler should use threading.GoSafeCtx for SSE")
|
||||||
|
|
||||||
|
// Handler should create client channel
|
||||||
|
assert.Contains(t, handlerStr, "client := make(chan",
|
||||||
|
"Handler should create client channel")
|
||||||
|
|
||||||
|
// Verify SSE-specific patterns in logic
|
||||||
|
// Logic should have signature: Sse(req *types.SseReq, client chan<- *types.SseResp) error
|
||||||
|
assert.Contains(t, logicStr, "func (l *SseLogic) Sse(req *types.SseReq, client chan<- *types.SseResp) error",
|
||||||
|
"Logic should have SSE signature with client channel parameter")
|
||||||
|
|
||||||
|
// Logic should NOT have regular signature: Sse(req *types.SseReq) (resp *types.SseResp, err error)
|
||||||
|
assert.NotContains(t, logicStr, "(resp *types.SseResp, err error)",
|
||||||
|
"Logic should not have regular signature with resp return")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonSSEGeneration(t *testing.T) {
|
||||||
|
// Create a temporary directory for test
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
// Create a test API file WITHOUT SSE annotation
|
||||||
|
apiContent := `syntax = "v1"
|
||||||
|
|
||||||
|
type SseReq {
|
||||||
|
Message string ` + "`json:\"message\"`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type SseResp {
|
||||||
|
Data string ` + "`json:\"data\"`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
service Test {
|
||||||
|
@handler Sse
|
||||||
|
get /sse (SseReq) returns (SseResp)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
apiFile := filepath.Join(dir, "test.api")
|
||||||
|
err := os.WriteFile(apiFile, []byte(apiContent), 0644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Generate code
|
||||||
|
err = DoGenProject(apiFile, dir, "gozero", false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Read generated handler file
|
||||||
|
handlerPath := filepath.Join(dir, "internal/handler/ssehandler.go")
|
||||||
|
handlerContent, err := os.ReadFile(handlerPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Read generated logic file
|
||||||
|
logicPath := filepath.Join(dir, "internal/logic/sselogic.go")
|
||||||
|
logicContent, err := os.ReadFile(logicPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
handlerStr := string(handlerContent)
|
||||||
|
logicStr := string(logicContent)
|
||||||
|
|
||||||
|
// Verify regular (non-SSE) patterns in handler
|
||||||
|
// Handler should call: resp, err := l.Sse(&req)
|
||||||
|
assert.Contains(t, handlerStr, "resp, err := l.Sse(&req)",
|
||||||
|
"Handler should use regular pattern with resp return")
|
||||||
|
|
||||||
|
// Handler should NOT have SSE pattern: err := l.Sse(&req, client)
|
||||||
|
assert.NotContains(t, handlerStr, "err := l.Sse(&req, client)",
|
||||||
|
"Handler should not use SSE pattern")
|
||||||
|
|
||||||
|
// Handler should NOT use threading.GoSafeCtx
|
||||||
|
assert.NotContains(t, handlerStr, "threading.GoSafeCtx",
|
||||||
|
"Handler should not use threading.GoSafeCtx for regular routes")
|
||||||
|
|
||||||
|
// Verify regular (non-SSE) patterns in logic
|
||||||
|
// Logic should have signature: Sse(req *types.SseReq) (resp *types.SseResp, err error)
|
||||||
|
assert.Contains(t, logicStr, "(resp *types.SseResp, err error)",
|
||||||
|
"Logic should have regular signature with resp return")
|
||||||
|
|
||||||
|
// Logic should NOT have SSE signature with client parameter
|
||||||
|
linesToCheck := strings.Split(logicStr, "\n")
|
||||||
|
hasSSESignature := false
|
||||||
|
for _, line := range linesToCheck {
|
||||||
|
if strings.Contains(line, "func (l *SseLogic) Sse") && strings.Contains(line, "client chan<-") {
|
||||||
|
hasSSESignature = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.False(t, hasSSESignature,
|
||||||
|
"Logic should not have SSE signature with client channel parameter")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user