Skip to content

服务与服务注册

服务是 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、请求标识、会话纪元;
  • Datadata 字段的 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,合法

下一步

基于 MIT 许可发布