Skip to content

快速开始

本章从零搭建一个可运行的 fun 服务:安装、写服务、启动、用 curl 调用、生成 TS 客户端、前端联调。

环境要求

工具版本说明
Go≥ 1.26框架模块声明 go 1.26.1
Node.js≥ 18仅前端 / 生成产物消费方需要
fasthttpv1.73.0go get 自动带入

安装

bash
mkdir hello-fun && cd hello-fun
go mod init hello-fun
go get github.com/cyi-cc/fun@latest

最小可运行示例

新建 main.go

go
package main

import (
    "log"

    "github.com/cyi-cc/fun"
)

// UserSvc 用户服务:嵌入 fun.Ctx,导出方法即 RPC 端点
type UserSvc struct {
    fun.Ctx
}

type GetUserDto struct {
    Id int64 // 数值一律定宽整型
}

type UserDto struct {
    Id   int64
    Name string
}

// Get 导出方法 → 端点 UserSvc.Get
func (s *UserSvc) Get(dto GetUserDto) (UserDto, error) {
    return UserDto{Id: dto.Id, Name: "阿宝"}, nil
}

func main() {
    f := fun.GetFun()
    if err := f.BindService(&UserSvc{}); err != nil {
        log.Fatal(err)
    }
    go f.Start(8080) // 监听 :8080,业务只响应 POST /cell
    select {}        // 阻塞主 goroutine
}

运行:

bash
go run main.go

用 curl 调用

所有业务请求都是 POST /cell,body 指定服务与方法:

bash
curl -X POST http://localhost:8080/cell \
  -H 'Content-Type: application/json' \
  -d '{"serviceName":"UserSvc","methodName":"get","data":{"Id":1}}'

响应是统一的 Result所有键递归转首字母小写

json
{"status":0,"data":{"id":1,"name":"阿宝"}}

字段名大小写不敏感:请求里写 "id":1"Id":1 都能匹配到 GetUserDto.Id

完整示例:仓库自带的 demo

example/demo 演示了全部核心特性——定宽整型与可空指针字段、枚举、业务错误码、两种流式签名:

go
// OrderStatus 订单状态枚举:uint8 底层 + Names/DisplayNames
type OrderStatus uint8

func (OrderStatus) Names() []string        { return []string{"Pending", "Paid", "Shipped"} }
func (OrderStatus) DisplayNames() []string { return []string{"待支付", "已支付", "已发货"} }

type CreateOrderDto struct {
    Sku    string        // 必传
    Count  int64         // 必传
    Note   *string       // 可空
    Status *OrderStatus  // 可空,缺省 Pending
    Tags   []string      // 可省略
}

type OrderSvc struct {
    fun.Ctx
}

func (s *OrderSvc) Create(dto CreateOrderDto) (OrderDto, error) {
    return OrderDto{Id: 9007199254740993, Status: OrderStatus(0), Amount: "199.00"}, nil
}

// Cancel error-only 签名:无返回数据
func (s *OrderSvc) Cancel(dto CancelDto) error {
    if dto.Reason == nil {
        return fun.Error(4004, "必须填写取消原因") // status=2,code/msg 原样透传
    }
    return nil
}

生成对应客户端产物的命令在 cmd/genexample,产物见 example/gen

生成 TypeScript 客户端

推荐单独建一个 cmd/genBindServiceForGen 代替 BindService——只登记类型元信息,不装配任何基础设施(数据库、Redis 无需运行):

go
// cmd/gen/main.go
package main

import (
    "fmt"

    "github.com/cyi-cc/fun"
    "hello-fun" // 引入你的服务定义
)

func main() {
    f := fun.New()
    f.BindServiceForGen(&UserSvc{})
    fun.SetOutput("./frontend/src/api") // ⚠️ 会清空整个输出目录
    fun.GenCode(fun.GenTs{})            // 也可加 fun.GenGo{} 同时生成 Go 客户端
    fmt.Println("生成完成")
}
bash
go run ./cmd/gen

产物落在 <out>/ts/ 子目录,fun.ts 是聚合入口:

ts
import { api } from './api/ts/fun'

const c = api.create('/api')
c.setState({ token: 'xxx' })        // 每个请求自动带上 state

const r = await c.userSvc.get({ id: 1 })
if (r.status === 0) {
  console.log(r.data!.name)         // 响应键已转小写,类型直达
}

前端联调(Vite 代理)

后端只有 /cell 一个路径,vite 代理需要把前缀重写掉:

js
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/api/, ''),
      },
    },
  },
}

客户端 api.create('/api'),实际请求 POST /api/cell → 代理转给后端 /cell

浏览器直连(无代理)场景改用服务端 CORS 白名单

常见的「第一步」错误

现象原因
启动即 panic:Unsupported types intDTO 用了普通 int/uint、float、map 等非法类型,见 DTO 规则
运行期报 must be a pointer or have a corresponding field非指针、非 slice 的 DTO 字段漏传或传了 null
客户端报 method not found方法名首字母小写(未导出),不会注册为端点
前端取不到字段响应键已全部转首字母小写,按 camelCase 取值

更多见常见问题

下一步

基于 MIT 许可发布