From 8875212dba9db7e0a1adcde041f68ddd5fbb07fd Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Fri, 8 May 2026 22:29:43 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=20SortJoin=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=B9=B6=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E8=87=B3=20v1.1.1=20(by=20AI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ README.md | 5 +++++ TEST.md | 3 ++- encoding.go | 42 ++++++++++++++++++++++++++++++++++++++++++ encoding_test.go | 30 ++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8350343..d1b9507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog: @go/encoding +## [v1.1.1] - 2026-05-08 + +### Added +- **文档补全**:在 README 中补充了 `SortJoin` 接口的详细说明,该接口用于 Map 或 Struct 的排序拼接(签名场景)。 + ## [v1.1.0] - 2026-05-06 ### Added diff --git a/README.md b/README.md index 317f504..023f41a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ - `func UnUrlEncode(data string) ([]byte, error)` - `func HtmlEscape(data []byte) string` - `func HtmlUnescape(data string) string` +- `func Utf8Valid(data []byte) bool` + +### 签名工具 (Signature Utils) +- `func SortJoin(v any, separator, connector string, urlEncode bool) string` + 将 Map 或 Struct 转换为排序并拼接后的字符串。常用于生成签名原串。支持自动 URL 编码。 ### 整数与自定义进制 (IntEncoder) - `func EncodeInt(u uint64) []byte` diff --git a/TEST.md b/TEST.md index 2ccdba7..5cfcb7a 100644 --- a/TEST.md +++ b/TEST.md @@ -1,7 +1,7 @@ # Test Report: @go/encoding ## 📋 测试概览 -- **测试时间**: 2026-05-06 +- **测试时间**: 2026-05-08 - **测试环境**: darwin/amd64 - **Go 版本**: 1.25.0 @@ -11,6 +11,7 @@ | `TestHex` | PASS | Hex 编解码往返一致性,配合 `cast.As` 消除摩擦测试。 | | `TestBase64` | PASS | Standard/URL Base64 编解码一致性,`Un...FromString` 补全测试。 | | `TestWebEncoding` | PASS | URL 编解码、HTML 转义反转义往返测试。 | +| `TestSortJoin` | PASS | Map 与 Struct 的排序拼接测试,验证签名场景。 | | `TestUtf8` | PASS | UTF-8 校验逻辑测试。 | | `TestIntEncoder` | PASS | 包含正常编解码、FillInt 补齐、ExchangeInt 置换、HashInt 确定性测试。 | diff --git a/encoding.go b/encoding.go index dacae84..26cb588 100644 --- a/encoding.go +++ b/encoding.go @@ -5,7 +5,11 @@ import ( "encoding/hex" "html" "net/url" + "sort" + "strings" "unicode/utf8" + + "apigo.cc/go/cast" ) // Hex 将数据转换为 Hex 编码的字节切片 @@ -148,3 +152,41 @@ func HtmlUnescape(data string) string { func Utf8Valid(data []byte) bool { return utf8.Valid(data) } + +// SortJoin 将 Map 或 Struct 转换为排序并拼接后的字符串 (常用于签名) +func SortJoin(v any, separator, connector string, urlEncode bool) string { + var m map[string]any + if vm, ok := v.(map[string]any); ok { + m = vm + } else if v != nil { + cast.Convert(&m, v) + } + + if len(m) == 0 { + return "" + } + + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + var builder strings.Builder + for i, k := range keys { + if i > 0 { + builder.WriteString(separator) + } + val := cast.String(m[k]) + if urlEncode { + builder.WriteString(url.QueryEscape(k)) + builder.WriteString(connector) + builder.WriteString(url.QueryEscape(val)) + } else { + builder.WriteString(k) + builder.WriteString(connector) + builder.WriteString(val) + } + } + return builder.String() +} diff --git a/encoding_test.go b/encoding_test.go index 9353882..c581948 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -103,3 +103,33 @@ func TestUtf8(t *testing.T) { t.Error("Invalid UTF-8 should fail") } } + +// --- SortJoin --- +func TestSortJoin(t *testing.T) { + m := map[string]any{ + "B": 2, + "A": 1, + "C": "hello world", + } + + // Test Map + res := encoding.SortJoin(m, "&", "=", true) + expected := "A=1&B=2&C=hello+world" + if res != expected { + t.Errorf("SortJoin Map failed: expected %s, got %s", expected, res) + } + + // Test Struct + type User struct { + Name string `json:"name"` + Age int `json:"age"` + ID string + } + u := User{Name: "Alice", Age: 30, ID: "123"} + resStruct := encoding.SortJoin(u, ";", ":", false) + // Keys: age, name, ID. Sorted: ID, age, name + expectedStruct := "ID:123;age:30;name:Alice" + if resStruct != expectedStruct { + t.Errorf("SortJoin Struct failed: expected %s, got %s", expectedStruct, resStruct) + } +}