Files
fun/ctx.go
T
chiyi 41df889a1a v2.0.0: error-channel APIs, correctness fixes, and server hardening
BREAKING CHANGES:
- Guard interface: Guard(ctx Ctx) error; returning error short-circuits
  (subsequent guards and the method no longer execute)
- Wired[T]() (*T, error); New() may return error; failed wiring is sticky
- BindService/BindGuard/BindRoute return error; routes accept guards
  (guards receive merged query/form params as Ctx.State)
- registration panics after Start; Ctx.Ip honors X-Forwarded-For/X-Real-IP
- internal errors sanitized to fixed client messages

FIXES:
- int64 precision loss: /cell data decoded from raw JSON bytes and
  responses serialized with json.Number (no float64 round-trip)
- enum values range-checked before uint8 conversion (256 no longer
  truncates to 0 and slips through)
- logger: files failing name parsing are no longer deleted; log channel
  never blocks request goroutines; ConfigLogger is race-free;
  logWriterWorker unlock bug fixed
- stream writer panics recovered (process no longer crashes);
  streamDone closed exactly once
- generated Go client emits definitions for pointer-to-enum/struct fields
- anonymous structs rejected at registration; isPrivate safe on empty names
- removed dead Result.Id and RequestInfo.Type

ADDITIONS:
- graceful shutdown (Shutdown), StartOn, server timeouts by default
  (Read 60s/Idle 120s/Write off), SetTimeouts/SetMaxConcurrency
- big-int-safe JSON in the generated TS client (>2^53 as BigInt)
- example/ demo services and cmd/genexample artifact generator
2026-09-03 15:32:07 +08:00

96 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package fun
import (
"bytes"
"encoding/json"
"net"
"reflect"
"github.com/valyala/fasthttp"
)
// Ctx 请求上下文。
// 命名持有 *fasthttp.RequestCtx(非嵌入),避免其方法集提升到服务上,
// 辅助方法全部小写,保证服务方法集只含业务方法。
type Ctx struct {
Ip string
State map[string]string
MethodName string
ServiceName string
Data *map[string]any
RequestCtx *fasthttp.RequestCtx
// rawData 请求 data 字段的原始 JSON 字节(HTTP 路径填充)。
// 业务 DTO 解码优先用它:不经 map[string]any 的 float64 往返,大整数无精度丢失
rawData []byte
}
var ctxType = reflect.TypeFor[Ctx]()
func (c *Ctx) path() string { return string(c.RequestCtx.Path()) }
func (c *Ctx) isPost() bool { return c.RequestCtx.IsPost() }
func (c *Ctx) postBody() []byte { return c.RequestCtx.PostBody() }
func (c *Ctx) remoteIP() net.IP { return c.RequestCtx.RemoteIP() }
func (c *Ctx) setStatusCode(code int) { c.RequestCtx.SetStatusCode(code) }
func (c *Ctx) write(p []byte) (int, error) { return c.RequestCtx.Write(p) }
// send 写回响应,嵌套对象键统一转小写(兼容 TS/大小写敏感客户端)
func (c *Ctx) send(result Result[any]) {
data, err := json.Marshal(result)
if err != nil {
c.sendError(err)
return
}
raw, err := lowerKeysFromJSON(data)
if err != nil {
_, _ = c.write(data)
return
}
out, err := json.Marshal(raw)
if err != nil {
_, _ = c.write(data)
return
}
_, _ = c.write(out)
}
// lowerKeysFromJSON 解析 JSON 后递归把所有对象键转为首字母小写。
// 数字以 json.Number 原文保留,不落入 float64——否则响应侧 int64
// 超过 2^53 会丢精度(9007199254740993 → ...992
func lowerKeysFromJSON(data []byte) (any, error) {
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
var raw any
if err := dec.Decode(&raw); err != nil {
return nil, err
}
return lowerKeys(raw), nil
}
func lowerKeys(obj any) any {
switch v := obj.(type) {
case map[string]any:
m := make(map[string]any, len(v))
for k, val := range v {
m[firstLetterToLower(k)] = lowerKeys(val)
}
return m
case []any:
for i := range v {
v[i] = lowerKeys(v[i])
}
return v
}
return obj
}
// sendError 写回错误响应
// 业务 Error() 构造的 Result[any] 原样透传保留 Code/Msg/Status,普通 error 包成错误响应
func (c *Ctx) sendError(err error) {
if result, ok := err.(Result[any]); ok {
c.send(result)
return
}
c.send(callError(err))
}