服务与服务注册
服务是 fun 的业务单元:一个 Go 结构体 + 一组导出方法,就是一个 RPC 服务。本章讲清结构体怎么写、方法签名有哪些、请求上下文里有什么。
服务结构体
go
type UserSvc struct {
fun.Ctx // 必须嵌入:请求上下文
Pg *platform.Postgres // 依赖字段:指针结构体自动从容器装配
Redis *platform.Redis
}两条规则:
- 嵌入
fun.Ctx:拿到请求上下文(State、Ip 等)。Ctx命名持有*fasthttp.RequestCtx(非嵌入),辅助方法全部小写,因此不会污染服务方法集——导出方法就是且仅是你的业务端点。 - 依赖字段用指针结构体:注册时自动从 DI 容器解析,容器没有就递归创建(详见依赖注入),不需要任何标签。
每请求新建实例
框架每个请求都会新建服务实例并注入依赖,因此服务结构体里不要放共享状态(计数器、缓存 map、配置快照都不行)。需要共享的资源放进依赖(单例 Box)里。
注册
go
f := fun.GetFun()
f.BindService(&UserSvc{}) // 无 Guard
f.BindService(&AdminSvc{}, &AuthGuard{}) // 服务级 Guard(可变参数,按顺序执行)
f.BindGuard(&LogGuard{}) // 全局 Guard:对所有服务生效- 只有导出方法(首字母大写)会成为 RPC 端点;
- 端点注册名固定为
服务名.方法名,如UserSvc.Get; - 客户端传
serviceName/methodName时首字母大小写不敏感(框架统一首字母大写再匹配),"userSvc"+"get"也能命中; - 所有注册(
BindService/BindGuard/BindRoute)必须在Start之前完成,之后调用会 panic。
方法签名
参数最多 1 个且必须是 struct DTO(也允许零参);返回值只允许以下四种:
| 签名 | 用途 |
|---|---|
func (s *Svc) M() error | 无返回数据(删除、确认类操作) |
func (s *Svc) M(dto Dto) (T, error) | 普通请求/响应 |
func (s *Svc) M(dto Dto) (*fun.Stream, error) | 纯流式(NDJSON 逐行推送) |
func (s *Svc) M(dto Dto) (T, *fun.Stream, error) | 首条消息为 T,后续走流 |
违反签名在注册期直接 panic,例如:
go
func (s *UserSvc) Bad(a, b int) error // panic: more than one parameter
func (s *UserSvc) Bad2(dto Dto) (T1, T2, error) // panic: 只允许上述四种返回形状一个服务里混用多种签名完全合法,按方法各自判断。
请求上下文 Ctx
go
type Ctx struct {
Ip string // 已解析的客户端 IP
State map[string]string // 请求往返透传的字符串字典
MethodName string
ServiceName string
Data *map[string]any // 请求 data 字段(一般用不到,优先用 DTO 解码)
RequestCtx *fasthttp.RequestCtx // 原生 fasthttp 上下文,需要底层能力时用
}常用姿势:
go
func (s *OrderSvc) Cancel(dto CancelDto) error {
token := s.Ctx.State["token"] // Guard 已校验过的会话标识
ip := s.Ctx.Ip // 客户端 IP(日志/风控)
_ = token
_ = ip
return nil
}State由客户端setState()或每调用options.state传入,原样回传给响应拦截器,常用于放 token、请求标识、会话纪元;Data是data字段的map[string]any视图——注意它经历过 JSON 数字到float64的往返,大整数会丢精度;业务解码框架内部走原始字节,DTO 字段不受影响。
组合示例
go
type OrderSvc struct {
fun.Ctx
Pg *platform.Postgres
}
type CreateOrderDto struct {
Sku string
Count int64
Note *string
}
type OrderDto struct {
Id int64
Amount string
}
func (s *OrderSvc) Create(dto CreateOrderDto) (OrderDto, error) {
return OrderDto{Id: 9007199254740993, Amount: "199.00"}, nil
}
func (s *OrderSvc) Ping() error { return nil } // 零参 + error-only,合法下一步
- DTO 与类型规则:字段怎么定义才能通过注册期检查
- Guard 拦截器:在方法执行前做鉴权
- NDJSON 流式响应:
(T, *Stream, error)签名的用法