DTO 与类型规则
DTO(数据传输对象)是 fun 的参数契约。框架在注册期检查类型合法性(违反直接 panic),在运行期校验必传字段与枚举值域。规则的设计目标是:类型即文档、错配在最早的时机暴露。
允许与禁止
| ✅ 允许 | ❌ 禁止(注册期 panic) |
|---|---|
定宽整型:int8/16/32/64、uint8/16/32/64 | 普通 int / uint |
string | float32 / float64(小数用字符串传) |
bool | map、any / interface{} |
| 具名 struct(嵌套 DTO) | 匿名结构体 |
slice([]T,T 合法即可) | 空结构体、私有字段 |
指针 *T(T 合法即可) |
为什么禁止普通 int 和 float:
int平台相关宽度,跨端(Go ↔ JS)无法稳定契约,TS 侧需要明确的位宽;- JSON 数字在 JS 里是 double,小数传丢失精度路径太多,小数一律用字符串(如
"199.00"),由业务两端自行解析。
必传与可空
go
type SaveSettingsDto struct {
SiteName string // 非指针、非 slice → 必传且非 null
Rate *string // 指针 → 可省略(nil)
Count *int64 // 可空数值用指针
Tags []string // slice → 可省略(nil)
}- 非指针且非 slice 的字段必传:请求里漏传或显式传
null,运行期返回must be a pointer or have a corresponding field; - 可空字段一律用
*T:省略时保持 nil,业务里if dto.Note != nil判断; - 请求字段名大小写不敏感:
siteName/SiteName/SITENAME均可匹配(转换是 JSON marshal/unmarshal 往返)。
大整数与 BigInt
雪花 ID 等超过 2^53 - 1 的整数全链路 int64 精度:
- 服务端:DTO 解码直接走原始字节,不经
float64往返,无精度丢失; - TS 客户端:生成的
client.ts内置无损 JSON 解析,超出Number.MAX_SAFE_INTEGER的整数字面量自动解析为BigInt,序列化时还原为数字字面量。
ts
const r = await c.orderSvc.create({ sku: 'A', count: 1 })
r.data!.id // 9007199254740993 → BigInt,不是被舍入的 Number枚举
uint8 底层 + 实现 Names() []string 即成为枚举:
go
type OrderStatus uint8
func (OrderStatus) Names() []string { return []string{"Pending", "Paid", "Shipped"} }
// 可选:展示名(给前端下拉框用),长度必须与 Names 一致
func (OrderStatus) DisplayNames() []string { return []string{"待支付", "已支付", "已发货"} }- 值域:
0 ~ len(Names())-1,运行期传越界值(如3)报错; - TS 侧生成对应的 enum/类型定义,
DisplayNames一并下发。
指针枚举 *OrderStatus = 可空枚举,缺省时业务自行给默认值(如 Pending)。
响应侧规则
- 返回值
T同样受类型检查(具名 struct / 定宽整型 / string / bool / slice / 指针); - 所有响应键递归转首字母小写(
SiteName→siteName),前端直接按 camelCase 取值,无需任何配置; - 成功时空 slice 序列化为
[]而非null;data为 nil 时整个字段省略。
注册期 panic 速查
| panic 信息 | 原因 |
|---|---|
Unsupported types int | DTO 字段用了 int/uint/float/map/any/匿名结构体/私有字段 |
method X has more than one parameter | 方法参数超过 1 个 |
method X parameter must be a struct | 参数不是 struct(如传了 int64) |
method X must return (error), (T, error)... | 返回值不符合四种签名 |