精简低代码 API 调用结果 (by AI)

This commit is contained in:
Star 2026-08-17 14:12:30 +08:00
parent d10e722665
commit 6d70396e30
5 changed files with 67 additions and 1 deletions

View File

@ -1,5 +1,10 @@
# CHANGELOG
## v1.5.7 (2026-08-17)
- **低代码结果投影**: 动态 Action 可通过 `resultMode: data` 让 JavaScript `api.Call` 直接返回业务数据,并补齐 `ok/code/error`
- **继承一致性**: 结果投影读取完整的 Action 继承配置,子 Action 无需重复声明。
- **兼容边界**: Go `Call`/`CallBy` 与未配置该选项的 JavaScript Action 均保持原有完整 `Result` 行为。
## v1.5.6 (2026-08-17)
- **统一调用结果**: `Call``CallBy` 返回 `ok/statusCode/headers/data/code/error`,支持响应错误字段和成功码规则。
- **调用选项**: 新增 Token 验证、超时、配置覆盖与敏感/控制字段覆盖保护。

View File

@ -80,5 +80,7 @@ Action 只使用 `filters: ["name"]` 这一种形式。每个 Filter 都会收
动态 Action 可通过 `extends` 继承另一个 Action父配置先合并子配置深度覆盖。继承在每次调用时解析因此父 Action 热更新会立即作用于子 Action。循环继承或不存在的父 Action 会直接返回错误。
面向 JavaScript 低代码调用时Action 可配置 `resultMode: data`,使 `api.Call` 直接返回业务 `data`,并保留顶层 `ok/code/error`,避免 HTTP 状态和响应头干扰应用逻辑。该配置支持 `extends` 继承,只影响 JavaScript 导出Go 的 `Call``CallBy` 始终返回完整 `Result`
---
更多详情请参阅 [TEST.md](./TEST.md) 和 [CHANGELOG.md](./CHANGELOG.md)。

View File

@ -35,11 +35,15 @@
验证 `extends` 深度继承、父 Action 热解析、循环和缺失父项报错,以及 `filters: []` 同时收到请求与响应事件。JavaScript Filter 的中文流片段按 UTF-8 字符串传递。
### 8. JavaScript 业务结果投影 (`TestProjectDataResult`, `TestActionUsesInheritedDataResult`)
验证 `resultMode: data` 仅向 JavaScript 返回业务数据与 `ok/code/error`,不会泄露 HTTP Headers同时验证子 Action 能继承父 Action 的投影策略。
## ⏱ 性能基准测试 (Benchmark)
使用 `go test -bench=. ./...` 评估框架调用阶段的开销。
> **基准**: Darwin / Apple M3 Max
* `BenchmarkCallEngineLogic-16`:约 **118.9 ns/op**, **2 allocs/op**
* `BenchmarkCallEngineLogic-16`:约 **115.4 ns/op**, **80 B/op**, **2 allocs/op**
该指标证明引擎的参数合并、注入及校验流程具有极高的运行效率和极小的内存逃逸。
## 🚀 运行测试

View File

@ -3,6 +3,7 @@ package api
import (
"context"
"apigo.cc/go/cast"
"apigo.cc/go/jsmod"
)
@ -22,6 +23,9 @@ func call(ctx context.Context, name string, payload any, options ...*CallOptions
opts := firstOptions(options)
opts.Context = ctx
res, err := CallBy(name, payload, opts)
if actionUsesDataResult(name) {
return projectDataResult(res), nil
}
if err != nil {
if res != nil {
return res, nil
@ -31,6 +35,33 @@ func call(ctx context.Context, name string, payload any, options ...*CallOptions
return res, nil
}
func actionUsesDataResult(name string) bool {
config, err := resolveGenericActionConfig(name, map[string]bool{})
return err == nil && cast.String(config["resultMode"]) == "data"
}
func projectDataResult(result *Result) any {
if result == nil {
return map[string]any{"ok": false, "code": "", "error": "empty API result"}
}
if data, ok := result.Data.(map[string]any); ok {
output := cloneMap(data)
if _, exists := output["ok"]; !exists {
output["ok"] = result.Ok
}
if _, exists := output["code"]; !exists {
output["code"] = result.Code
}
if _, exists := output["error"]; !exists {
output["error"] = result.Error
}
return output
}
return map[string]any{
"ok": result.Ok, "code": result.Code, "error": result.Error, "result": result.Data,
}
}
// registerSigner 允许从 JS 注册动态签名逻辑
func registerSigner(name string, code string) {
RegisterSigner(name, &jsSigner{code: code})

View File

@ -0,0 +1,24 @@
package api
import "testing"
func TestProjectDataResult(t *testing.T) {
result := &Result{Ok: true, StatusCode: 200, Headers: map[string]string{"X-Test": "ignored"}, Data: map[string]any{"result": "hello"}}
projected := projectDataResult(result).(map[string]any)
if projected["ok"] != true || projected["result"] != "hello" || projected["code"] != "" || projected["error"] != "" {
t.Fatalf("unexpected projected result: %#v", projected)
}
if _, exists := projected["headers"]; exists {
t.Fatalf("transport headers leaked into projected result: %#v", projected)
}
}
func TestActionUsesInheritedDataResult(t *testing.T) {
parent, child := t.Name()+".parent", t.Name()+".child"
RegisterAction(parent, map[string]any{"abstract": true, "resultMode": "data"})
RegisterAction(child, map[string]any{"extends": parent, "url": "http://127.0.0.1"})
t.Cleanup(func() { RemoveAction(child); RemoveAction(parent) })
if !actionUsesDataResult(child) {
t.Fatal("child Action did not inherit resultMode=data")
}
}