精简低代码 API 调用结果 (by AI)
This commit is contained in:
parent
d10e722665
commit
6d70396e30
@ -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 验证、超时、配置覆盖与敏感/控制字段覆盖保护。
|
||||
|
||||
@ -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)。
|
||||
|
||||
6
TEST.md
6
TEST.md
@ -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**。
|
||||
该指标证明引擎的参数合并、注入及校验流程具有极高的运行效率和极小的内存逃逸。
|
||||
|
||||
## 🚀 运行测试
|
||||
|
||||
31
js_export.go
31
js_export.go
@ -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})
|
||||
|
||||
24
js_export_internal_test.go
Normal file
24
js_export_internal_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user