v1.4.0: generic Stream[T] — typed stream messages, drop (T, *Stream, error)
- Stream[T]: Send(v T) type-safe; message type recovered from the instantiated Send signature (reflect has no type-args API) - method signatures reduced to (error), (T, error), (*Stream[T], error); the (T, *Stream, error) first-message form is gone — send it via Send - return T and stream message T now run checkType at bind time (Stream[any] remains the explicit untyped escape hatch) - generated clients: TS onMessage: (data: T); Go <-chan T - fix vacuous panic assertions (fmt.Sprint(nil) -> "<nil>") in hardening/route test catch helpers Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -13,19 +13,19 @@ import (
|
||||
|
||||
type Fun struct {
|
||||
methods map[string]methodInfo
|
||||
routes map[string]boundRoute // 自定义路由:"GET /path" → 绑定的处理器与 Guard(精确匹配)
|
||||
routes map[string]boundRoute // 自定义路由:"GET /path" → 绑定的处理器与 Guard(精确匹配)
|
||||
wildcardRoutes map[string][]wildcardRoute // 通配路由,按 HTTP 方法
|
||||
boxes *sync.Map // 依赖容器:reflect.Type → boxEntry(单例或粘性错误)
|
||||
guards []*any // 全局 Guard
|
||||
serviceGuards map[string][]*any // 服务级 Guard,按服务名
|
||||
bodyLimit int // 请求体上限(字节);0 = fasthttp 默认 4MB
|
||||
boxes *sync.Map // 依赖容器:reflect.Type → boxEntry(单例或粘性错误)
|
||||
guards []*any // 全局 Guard
|
||||
serviceGuards map[string][]*any // 服务级 Guard,按服务名
|
||||
bodyLimit int // 请求体上限(字节);0 = fasthttp 默认 4MB
|
||||
|
||||
readTimeout time.Duration // 读超时,默认 60s(slowloris 防线)
|
||||
writeTimeout time.Duration // 写超时,默认 0 不限制(避免掐断长流式响应)
|
||||
idleTimeout time.Duration // keep-alive 空闲超时,默认 120s
|
||||
maxConcurrency int // 最大并发连接数;0 = 不限制
|
||||
|
||||
corsOrigins map[string]struct{} // CORS 来源白名单(小写比较);nil/空表示未开启
|
||||
corsOrigins map[string]struct{} // CORS 来源白名单(小写比较);nil/空表示未开启
|
||||
|
||||
server atomic.Pointer[fasthttp.Server]
|
||||
started atomic.Bool
|
||||
@@ -69,8 +69,7 @@ type wildcardRoute struct {
|
||||
}
|
||||
|
||||
var (
|
||||
errorType = reflect.TypeFor[error]()
|
||||
streamType = reflect.TypeFor[*Stream]()
|
||||
errorType = reflect.TypeFor[error]()
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -83,7 +82,7 @@ type methodInfo struct {
|
||||
serviceType reflect.Type // 服务值类型(非指针),每请求新建实例
|
||||
methodIndex int // 方法在实例上的反射索引
|
||||
dtoType reflect.Type // DTO 参数类型,无参数时为 nil
|
||||
isStream bool // 返回签名带 *Stream,响应走 NDJSON 流式
|
||||
isStream bool // 返回签名带 *Stream[T],响应走 NDJSON 流式
|
||||
}
|
||||
|
||||
func newFun() *Fun {
|
||||
@@ -122,7 +121,7 @@ func GetFun() *Fun {
|
||||
// BindService 注册服务,要求传入指向结构体的指针
|
||||
// 方法签名约束:
|
||||
// - 参数:最多一个,且必须是 struct(作为 DTO)
|
||||
// - 返回值:只支持四种签名——(error)、(T, error)、(stream, error)、(T, stream, error)
|
||||
// - 返回值:只支持三种签名——(error)、(T, error)、(*Stream[T], error)
|
||||
//
|
||||
// guardList 为该服务绑定的 Guard,方法调用前按注册顺序执行。
|
||||
// 依赖装配失败(New() 返回 error)以 error 返回,由调用方决定退出或降级;
|
||||
@@ -188,31 +187,34 @@ func (f *Fun) bindServiceMethods(t reflect.Type, name string) {
|
||||
checkType(dtoType)
|
||||
}
|
||||
|
||||
// 返回值只支持四种签名:error / (T, error) / (stream, error) / (T, stream, error)
|
||||
// 返回值只支持三种签名:error / (T, error) / (*Stream[T], error)
|
||||
isStream := false
|
||||
switch mt.NumOut() {
|
||||
case 1:
|
||||
// 情况 1:func(...) error
|
||||
if mt.Out(0) != errorType {
|
||||
panic(fmt.Sprintf("fun: method %s must return (error), (T, error), (stream, error) or (T, stream, error)", m.Name))
|
||||
panic(fmt.Sprintf("fun: method %s must return (error), (T, error) or (*Stream[T], error)", m.Name))
|
||||
}
|
||||
case 2:
|
||||
// 情况 2:func(...) (T, error) 或 func(...) (*Stream, error)
|
||||
// 情况 2:func(...) (T, error) 或 func(...) (*Stream[T], error)
|
||||
if mt.Out(1) != errorType {
|
||||
panic(fmt.Sprintf("fun: method %s last return value must be error", m.Name))
|
||||
}
|
||||
isStream = mt.Out(0) == streamType
|
||||
case 3:
|
||||
// 情况 3:func(...) (T, *Stream, error)
|
||||
if mt.Out(2) != errorType {
|
||||
panic(fmt.Sprintf("fun: method %s last return value must be error", m.Name))
|
||||
}
|
||||
if mt.Out(1) != streamType {
|
||||
panic(fmt.Sprintf("fun: method %s second return value must be *Stream", m.Name))
|
||||
}
|
||||
isStream = true
|
||||
isStream = streamMsgType(mt.Out(0)) != nil
|
||||
default:
|
||||
panic(fmt.Sprintf("fun: method %s must return (error), (T, error), (stream, error) or (T, stream, error)", m.Name))
|
||||
panic(fmt.Sprintf("fun: method %s must return (error), (T, error) or (*Stream[T], error)", m.Name))
|
||||
}
|
||||
|
||||
// 数据返回 T 与 DTO 同受类型系统约束;流式校验消息类型 T
|
||||
// (Stream[any] 是显式的任意消息流,客户端生成 data: any)
|
||||
if mt.NumOut() == 2 {
|
||||
if msgT := streamMsgType(mt.Out(0)); msgT != nil {
|
||||
if msgT.Kind() != reflect.Interface {
|
||||
checkType(msgT)
|
||||
}
|
||||
} else {
|
||||
checkType(mt.Out(0))
|
||||
}
|
||||
}
|
||||
|
||||
// 注册到 "ServiceName.MethodName"
|
||||
|
||||
Reference in New Issue
Block a user