RenderAPI 现已实现以下重要更新:
钩子系统重构:将钩子实现分离到独立文件中,提高可维护性
custom_hook.go- 包含自定义钩子实现js_hook.go- JavaScript钩子实现cmd_hook.go- 命令行钩子实现
异步钩子支持:所有钩子现在都支持同步和异步执行模式
前置和后置钩子分离:模板定义中钩子分为
beforeHooks和afterHooks两部分改进的缓存系统:支持配置TTL和自定义缓存键模式
重试机制增强:完善的重试策略,支持指数退避
RenderAPI 是一个强大的 Go 语言 HTTP 客户端库,专为模板驱动的 API 请求设计。它允许用户通过 JSON 模板定义 HTTP 请求,支持动态数据插入和请求转换。
- 灵活的 HTTP 客户端,支持所有标准 HTTP 方法
- 强大的 JSON 模板引擎,支持动态数据注入
- 可扩展的钩子系统,用于请求/响应拦截和修改
- 内置丰富的模板函数库,增强模板处理能力
- 缓存机制提高性能
- 简单直观的 API 设计
go get github.com/birdmichael/RenderAPIpackage main
import (
"fmt""github.com/birdmichael/RenderAPI/pkg/client""github.com/birdmichael/RenderAPI/pkg/hooks"
)
funcmain() {
// 创建 HTTP 客户端c:=client.NewClient("https://api.example.com", 10)
// 添加认证令牌c.SetHeader("Authorization", "Bearer your-token")
// 添加请求日志钩子c.AddBeforeRequestHook(hooks.LoggingHook)
// 发送 GET 请求resp, err:=c.Get("/users")
iferr!=nil {
fmt.Printf("请求失败: %v\n", err)
return
}
// 读取响应body, err:=client.ReadResponseBody(resp)
iferr!=nil {
fmt.Printf("读取响应失败: %v\n", err)
return
}
fmt.Println(string(body))
}package main
import (
"fmt""github.com/birdmichael/RenderAPI/pkg/client"
)
funcmain() {
c:=client.NewClient("https://api.example.com", 10)
// 定义 JSON 模板template:=`{ "user": { "name": "{{.name}}", "age": {{.age}}, "email": "{{.email}}" } }`// 准备数据data:=map[string]interface{}{
"name": "张三",
"age": 30,
"email": "zhangsan@example.com",
}
// 发送带有模板的 POST 请求resp, err:=c.PostWithTemplate("/users", template, data)
iferr!=nil {
fmt.Printf("请求失败: %v\n", err)
return
}
// 处理响应...
}package main
import (
"fmt""github.com/birdmichael/RenderAPI/pkg/client"
)
funcmain() {
c:=client.NewClient("https://api.example.com", 10)
// 使用文件模板和数据文件发送请求resp, err:=c.PostWithTemplateFile("/users", "templates/user.json", "data/user_data.json")
iferr!=nil {
fmt.Printf("请求失败: %v\n", err)
return
}
// 处理响应...
}RenderAPI 的模板引擎内置了丰富的函数库,使模板操作更加灵活强大。以下是可用的内置函数分类:
| 函数名 | 说明 | 示例 |
|---|---|---|
toUpper | 转换为大写 | {{ toUpper "hello" }} => "HELLO" |
toLower | 转换为小写 | {{ toLower "HELLO" }} => "hello" |
title | 首字母大写 | {{ title "hello world" }} => "Hello World" |
trim | 去除前后空格 | {{ trim " hello " }} => "hello" |
trimPrefix | 去除前缀 | {{ trimPrefix "hello world" "hello " }} => "world" |
trimSuffix | 去除后缀 | {{ trimSuffix "hello world" " world" }} => "hello" |
replace | 替换字符串(有次数限制) | {{ replace "hello" "l" "L" 1 }} => "heLlo" |
replaceAll | 替换全部 | {{ replaceAll "hello" "l" "L" }} => "heLLo" |
split | 分割字符串 | {{ split "a,b,c" "," }} => ["a", "b", "c"] |
join | 连接字符串 | {{ join (split "a,b,c" ",") "-" }} => "a-b-c" |
contains | 是否包含子串 | {{ contains "hello" "ll" }} => true |
hasPrefix | 是否有前缀 | {{ hasPrefix "hello" "he" }} => true |
hasSuffix | 是否有后缀 | {{ hasSuffix "hello" "lo" }} => true |
length | 字符串长度 | {{ length "hello" }} => 5 |
regexMatch | 正则匹配 | {{ regexMatch "^h.*o$" "hello" }} => true |
regexReplace | 正则替换 | {{ regexReplace "[aeiou]" "*" "hello" }} => "h*ll*" |
urlEncode | URL编码 | {{ urlEncode "hello world" }} => "hello+world" |
urlDecode | URL解码 | {{ urlDecode "hello+world" }} => "hello world" |
htmlEscape | HTML转义 | {{ htmlEscape "<div>" }} => "<div>" |
htmlUnescape | HTML反转义 | {{ htmlUnescape "<div>" }} => "<div>" |
substr | 子字符串 | {{ substr "hello" 1 2 }} => "el" |
repeat | 重复字符串 | {{ repeat "ab" 3 }} => "ababab" |
| 函数名 | 说明 | 示例 |
|---|---|---|
now | 当前时间 | {{ now }} => 当前时间对象 |
formatTime | 格式化时间 | {{ formatTime (now) "2006-01-02" }} => 如 "2023-01-01" |
formatDate | 格式化日期 | {{ formatDate (now) }} => 如 "2023-01-01" |
formatDateTime | 格式化日期时间 | {{ formatDateTime (now) }} => 如 "2023-01-01 12:34:56" |
parseTime | 解析时间字符串 | {{ parseTime "2006-01-02" "2023-01-01" }} => 时间对象 |
addDate | 添加天数 | {{ addDate (now) 7 }} => 一周后的时间 |
addHours | 添加小时 | {{ addHours (now) 2 }} => 两小时后的时间 |
addMinutes | 添加分钟 | {{ addMinutes (now) 30 }} => 30分钟后的时间 |
since | 计算时间差(自某时刻起) | {{ since (parseTime "2006-01-02" "2023-01-01") }} => 时间差 |
until | 计算时间差(距某时刻) | {{ until (parseTime "2006-01-02" "2023-01-01") }} => 时间差 |
isAfter | 是否在之后 | {{ isAfter (now) (parseTime "2006-01-02" "2000-01-01") }} => true |
isBefore | 是否在之前 | {{ isBefore (now) (parseTime "2006-01-02" "2030-01-01") }} => true |
year | 获取年份 | {{ year (now) }} => 如 2023 |
month | 获取月份 | {{ month (now) }} => 如 "January" |
day | 获取日 | {{ day (now) }} => 如 1 |
weekday | 获取星期几 | {{ weekday (now) }} => 如 "Monday" |
unixTime | 获取Unix时间戳 | {{ unixTime (now) }} => 如 1672531200 |
fromUnixTime | 从Unix时间戳创建时间 | {{ fromUnixTime 1672531200 }} => 对应的时间对象 |
| 函数名 | 说明 | 示例 |
|---|---|---|
add | 加法 | {{ add 1 2 }} => 3 |
sub | 减法 | {{ sub 5 2 }} => 3 |
mul | 乘法 | {{ mul 2 3 }} => 6 |
div | 除法 | {{ div 6 2 }} => 3 |
mod | 取模 | {{ mod 7 3 }} => 1 |
ceil | 向上取整 | {{ ceil 3.2 }} => 4 |
floor | 向下取整 | {{ floor 3.8 }} => 3 |
round | 四舍五入 | {{ round 3.5 }} => 4 |
max | 最大值 | {{ max 1 5 }} => 5 |
min | 最小值 | {{ min 1 5 }} => 1 |
abs | 绝对值 | {{ abs -5 }} => 5 |
pow | 幂运算 | {{ pow 2 3 }} => 8 |
sqrt | 平方根 | {{ sqrt 16 }} => 4 |
rand | 随机数(0-1) | {{ rand }} => 随机小数 |
randInt | 随机整数 | {{ randInt 1 10 }} => 1到10间的随机整数 |
| 函数名 | 说明 | 示例 |
|---|---|---|
toString | 转换为字符串 | {{ toString 123 }} => "123" |
toInt | 转换为整数 | {{ toInt "123" }} => 123 |
toFloat | 转换为浮点数 | {{ toFloat "3.14" }} => 3.14 |
toBool | 转换为布尔值 | {{ toBool "true" }} => true |
jsonEncode | JSON编码 | {{ jsonEncode (dict "name" "张三") }} => {"name":"张三"} |
jsonDecode | JSON解码 | {{ (jsonDecode "{\"name\":\"张三\"}").name }} => "张三" |
prettifyJSON | 美化JSON | {{ prettifyJSON "{\"name\":\"张三\"}" }} => 格式化后的JSON |
| 函数名 | 说明 | 示例 |
|---|---|---|
first | 获取第一个元素 | {{ first .items }} => 集合第一个元素 |
last | 获取最后一个元素 | {{ last .items }} => 集合最后一个元素 |
slice | 切片 | {{ slice .items 1 3 }} => 索引1到3的元素 |
append | 追加元素 | {{ append .items "new" }} => 添加元素后的集合 |
indexOf | 查找索引 | {{ indexOf .items "item" }} => 元素在集合中的索引 |
reverse | 反转集合 | {{ reverse .items }} => 反转后的集合 |
keys | 获取Map的键 | {{ keys .dict }} => 所有键的切片 |
values | 获取Map的值 | {{ values .dict }} => 所有值的切片 |
hasKey | 是否有键 | {{ hasKey .dict "name" }} => 是否包含指定键 |
sum | 求和 | {{ sum .numbers }} => 数组所有元素的和 |
avg | 求平均值 | {{ avg .numbers }} => 数组元素的平均值 |
| 函数名 | 说明 | 示例 |
|---|---|---|
ternary | 三元操作符 | {{ ternary true "真" "假" }} => "真" |
defaultValue | 默认值 | {{ defaultValue .name "默认名称" }} => 当name为nil时返回默认值 |
coalesce | 返回第一个非空值 | {{ coalesce .name .nickname "匿名" }} => 第一个非nil非空字符串值 |
and | 逻辑与 | {{ and true false }} => false |
or | 逻辑或 | {{ or true false }} => true |
not | 逻辑非 | {{ not true }} => false |
eq | 相等 | {{ eq 5 5 }} => true |
ne | 不等 | {{ ne 5 6 }} => true |
lt | 小于 | {{ lt 5 10 }} => true |
le | 小于等于 | {{ le 5 5 }} => true |
gt | 大于 | {{ gt 10 5 }} => true |
ge | 大于等于 | {{ ge 5 5 }} => true |
| 函数名 | 说明 | 示例 |
|---|---|---|
md5 | MD5哈希 | {{ md5 "hello" }} => MD5哈希字符串 |
sha1 | SHA1哈希 | {{ sha1 "hello" }} => SHA1哈希字符串 |
sha256 | SHA256哈希 | {{ sha256 "hello" }} => SHA256哈希字符串 |
base64Encode | Base64编码 | {{ base64Encode "hello" }} => "aGVsbG8=" |
base64Decode | Base64解码 | {{ base64Decode "aGVsbG8=" }} => "hello" |
hexEncode | 十六进制编码 | {{ hexEncode "hello" }} => "68656c6c6f" |
hexDecode | 十六进制解码 | {{ hexDecode "68656c6c6f" }} => "hello" |
以下是使用内置函数的模板示例:
{
"user": {
"name": "{{ toUpper .name }}",
"email": "{{ toLower .email }}",
"registered_at": "{{ formatDateTime (now) }}",
"is_active": {{ toBool .status }},
"subscription": {
"type": "{{ defaultValue .plan "free" }}","expires_in_days": {{ div (toFloat .expires_seconds) 86400 }}
}
},"security": {
"token_hash": "{{ sha256 .token }}",
"login_ip": "{{ regexReplace "(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)" "$1.$2.XXX.XXX" .ip }}"}}{
"order": {
"id": "{{ .order_id }}",
"status": "{{ .status }}",
"total": {{ .total }},
"discount": {{ ternary (gt (toFloat .total) 1000) 0.15 0.05 }},
"final_price": {{ sub (toFloat .total) (mul (toFloat .total) (ternary (gt (toFloat .total) 1000) 0.15 0.05)) }},
"shipping": {
"method": "{{ coalesce .shipping_method .default_shipping "standard" }}","estimate_days": {{ ternary (eq .shipping_method "express") 1 (ternary (eq .shipping_method "priority") 3 7) }}
}
}}{
"analytics": {
"total_items": {{ length .items }},
"categories": {{ keys .categories }},
"most_expensive": {{ last (slice (sort .prices) 0 (len .prices)) }},
"average_price": {{ avg .prices }},
"price_summary": {
"min": {{ min (index .prices 0) (index .prices 1) }},
"max": {{ max (index .prices 0) (index .prices 1) }}
}
},
"highlighted_item": {{ jsonEncode (first .featured_items) }}
}RenderAPI提供了强大的钩子系统,支持前置钩子(BeforeHooks)和后置钩子(AfterHooks),允许你在请求前后进行拦截和修改。现在钩子系统还支持异步执行:
// 添加请求日志钩子client.AddBeforeHook(&hooks.LoggingHook{})
// 添加认证钩子client.AddBeforeHook(hooks.NewAuthHook("your-token"))
// 添加响应日志钩子client.AddAfterHook(&hooks.ResponseLogHook{})
// 添加字段转换钩子transformMap:=map[string]string{
"user": "phone"// 将 user 字段转换为 phone 字段
}
client.AddBeforeHook(hooks.NewFieldTransformHook(transformMap))
// 添加自定义钩子client.AddBeforeHook(&hooks.CustomFunctionHook{
BeforeFn: func(req*http.Request) (*http.Request, error) {
req.Header.Set("X-Custom-Header", "value")
returnreq, nil
},
})你可以使用JavaScript脚本来动态修改请求和响应:
// 从文件加载JavaScript脚本钩子iferr:=client.AddJSHookFromFile("scripts/transform_request.js", false, 30); err!=nil {
log.Fatalf("加载脚本失败: %v", err)
}
// 从字符串加载JavaScript脚本钩子scriptContent:=`function processRequest(request) { // 修改请求体 var body = JSON.parse(request.body); body.timestamp = new Date().toISOString(); request.body = JSON.stringify(body); return request;}`iferr:=client.AddJSHookFromString(scriptContent, false, 30); err!=nil {
log.Fatalf("添加脚本钩子失败: %v", err)
}JavaScript脚本示例:
// scripts/transform_request.jsfunctionprocessRequest(request){// 读取请求体varbody=JSON.parse(request.body);// 修改请求体if(body.user){body.user.name=body.user.name.toUpperCase();body.user.created_at=newDate().toISOString();// 使用内置的RSA加密函数加密敏感信息if(body.user.password){body.user.password=rsaEncryptGo(body.user.password,publicKey);}}// 返回修改后的请求体request.body=JSON.stringify(body);returnrequest;}你可以使用命令行脚本处理请求和响应:
// 添加命令行钩子(非异步,30秒超时)client.AddCommandHook("jq '.user.name = .user.name | ascii_upcase'", false, 30)在模板定义文件中,你可以指定前置钩子和后置钩子:
{
"request": {
"method": "POST",
"baseURL": "https://api.example.com",
"path": "/users",
"headers": {
"Content-Type": "application/json"
}
},
"beforeHooks": [
{
"type": "js",
"name": "authHook",
"script": "function processRequest(request) { console.log('Auth处理请求...'); return request; }",
"async": false,
"timeout": 10
},
{
"type": "command",
"name": "timestamp",
"command": "jq '.body.timestamp = now'",
"async": false,
"timeout": 3
}
],
"afterHooks": [
{
"type": "js",
"name": "postProcess",
"script": "function processResponse(response) { console.log('后置处理响应...'); return response; }",
"async": false,
"timeout": 5
}
],
"body": {
"user": {
"name": "{{.name}}",
"email": "{{.email}}"
}
}
}RenderAPI 提供了内置的缓存系统,可以提高性能并减少重复请求。在模板定义中配置缓存:
{
"request": {
"method": "GET",
"path": "/users/{{.user_id}}"
},
"caching": {
"enabled": true,
"ttl": 300,
"keyPattern": "users-{{.user_id}}"
}
}缓存配置说明:
enabled: 是否启用缓存ttl: 缓存的生存时间(秒)keyPattern: 可选的缓存键模式,支持模板语法。如果未指定,将使用请求URL和请求体的哈希作为键
对于不稳定的API,RenderAPI提供了内置的重试机制:
{
"request": {
"method": "POST",
"path": "/process"
},
"retry": {
"enabled": true,
"maxAttempts": 3,
"initialDelay": 1000,
"backoffFactor": 2
}
}重试配置说明:
enabled: 是否启用重试maxAttempts: 最大尝试次数initialDelay: 首次重试前的延迟(毫秒)backoffFactor: 退避因子,用于计算后续重试的延迟时间
RenderAPI/
├── cmd/ # 命令行工具
│ └── httpclient/ # HTTP客户端命令行工具
├── pkg/ # 核心包
│ ├── client/ # HTTP客户端实现
│ ├── template/ # 模板引擎
│ ├── hooks/ # 请求/响应钩子
│ │ ├── hooks.go # 钩子接口和通用功能
│ │ ├── custom_hook.go # 自定义钩子实现
│ │ ├── js_hook.go # JavaScript钩子实现
│ │ └── cmd_hook.go # 命令行钩子实现
│ └── config/ # 配置管理
├── examples/ # 使用示例
│ ├── basic/ # 基本使用示例
│ ├── advanced/ # 高级功能示例
│ └── template_file/ # 模板文件示例
├── testdata/ # 测试数据
└── internal/ # 内部工具和辅助函数
└── utils/ # 工具函数
RenderAPI 包含详尽的单元测试和集成测试。运行以下命令来执行测试:
# 运行所有测试
make test# 生成测试覆盖率报告
make test-coverage
# 运行基准测试
make benchRenderAPI 特别适用于以下场景:
- API 客户端开发:轻松构建与复杂 API 交互的客户端
- 自动化测试:通过模板定义请求,简化 API 测试
- API 代理:动态转换和处理 API 请求
- 数据采集:从多个 API 源收集和处理数据
欢迎提交Issues和Pull Requests!
本项目基于 MIT 许可证发布 - 详情请查看 LICENSE 文件。