mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
Compare commits
10 Commits
5d3964d81a
...
v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0841d7be5 | ||
|
|
b3fd282b50 | ||
|
|
a1cebec9f2 | ||
|
|
88cf1600e8 | ||
|
|
0851b68b83 | ||
|
|
5c13fd4f2f | ||
|
|
889d0cc3d6 | ||
|
|
aa05027cab | ||
|
|
fabc3483ed | ||
|
|
30363242bb |
150
README.md
150
README.md
@@ -84,6 +84,8 @@ func main() {
|
||||
- [Cryptor](#user-content-cryptor)
|
||||
- [Datetime](#user-content-datetime)
|
||||
- [Datastructure](#user-content-datastructure)
|
||||
- [EventBus](#user-content-eventbus)
|
||||
- [Enum](#user-content-enum)
|
||||
- [Fileutil](#user-content-fileutil)
|
||||
- [Formatter](#user-content-formatter)
|
||||
- [Function](#user-content-function)
|
||||
@@ -313,6 +315,15 @@ import "github.com/duke-git/lancet/v2/convertor"
|
||||
- **<big>ToPointer</big>** : return a pointer of passed value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToPointer)]
|
||||
[[play](https://go.dev/play/p/ASf_etHNlw1)]
|
||||
- **<big>ToPointers</big>** : convert a slice of values to a slice of pointers.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToPointers)]
|
||||
[[play](https://go.dev/play/p/ZUoXd2i5ZkV)]
|
||||
- **<big>FromPointer</big>** : returns the value pointed to by the pointer.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#FromPointer)]
|
||||
[[play](https://go.dev/play/p/wAp90V7Zu6g)]
|
||||
- **<big>FromPointers</big>** : convert a slice of pointers to a slice of values.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#FromPointers)]
|
||||
[[play](https://go.dev/play/p/qIPsyYtNy3Q)]
|
||||
- **<big>ToString</big>** : convert value to string.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToString)]
|
||||
[[play](https://go.dev/play/p/nF1zOOslpQq)]
|
||||
@@ -512,6 +523,30 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>RsaVerifySign</big>** : verifies the signature of the data with RSA.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaVerifySign)]
|
||||
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
|
||||
- **<big>GenerateSm2Key</big>** : generate SM2 private and public key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#GenerateSm2Key)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm2Encrypt</big>** : encrypt data with SM2 public key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm2Encrypt)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm2Decrypt</big>** : decrypt data with SM2 private key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm2Decrypt)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm3</big>** : return the SM3 hash value (256-bit) of data.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm3)]
|
||||
[[play](https://go.dev/play/p/zDAQpteAiOc)]
|
||||
- **<big>Sm4EcbEncrypt</big>** : encrypt data with SM4 ECB mode.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4EcbEncrypt)]
|
||||
[[play](https://go.dev/play/p/l5IQxYuuaED)]
|
||||
- **<big>Sm4EcbDecrypt</big>** : decrypt data with SM4 ECB mode.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4EcbDecrypt)]
|
||||
[[play](https://go.dev/play/p/l5IQxYuuaED)]
|
||||
- **<big>Sm4CbcEncrypt</big>** : encrypt data with SM4 CBC mode.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4CbcEncrypt)]
|
||||
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
|
||||
- **<big>Sm4CbcDecrypt</big>** : decrypt data with SM4 CBC mode.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4CbcDecrypt)]
|
||||
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
|
||||
|
||||
<h3 id="datetime"> 7. Datetime package supports date and time format and compare. <a href="#index">index</a></h3>
|
||||
|
||||
@@ -724,7 +759,7 @@ import optional "github.com/duke-git/lancet/v2/datastructure/optional"
|
||||
import "github.com/duke-git/lancet/v2/eventbus"
|
||||
```
|
||||
|
||||
#### 函数列表:
|
||||
#### Function list:
|
||||
|
||||
- **<big>NewEventBus</big>** : Create an EventBus instance.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#NewEventBus)]
|
||||
@@ -757,7 +792,70 @@ import "github.com/duke-git/lancet/v2/eventbus"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#SetErrorHandler)]
|
||||
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
|
||||
|
||||
<h3 id="fileutil"> 9. Fileutil package implements some basic functions for file operations. <a href="#index">index</a></h3>
|
||||
<h3 id="enum"> 10. Package enum provides a simple enum implementation. <a href="#index">Index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/enum"
|
||||
```
|
||||
|
||||
#### Function list:
|
||||
|
||||
- **<big>NewItem</big>** : Creates a new enum item.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewEventBus)]
|
||||
[[play](https://go.dev/play/p/8qNsLw01HD5)]
|
||||
- **<big>NewItemsFromPairs</big>** : Creates enum items from a slice of Pair structs.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewItemsFromPairs)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Value</big>** : Returns the value of the enum item.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Value)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Name</big>** : Returns the name of the enum item.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Name)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Valid</big>** : Checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Valid)]
|
||||
[[play](https://go.dev/play/p/pA3lYY2VSm3)]
|
||||
- **<big>MarshalJSON</big>** : Implementation of json.Marshaler interface.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#MarshalJSON)]
|
||||
[[play](https://go.dev/play/p/zIZEdAnneB5)]
|
||||
- **<big>NewRegistry</big>** : Creates a new enum registry.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewRegistry)]
|
||||
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
|
||||
- **<big>Add</big>** : Adds enum items to the registry.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Add)]
|
||||
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
|
||||
- **<big>Remove</big>** : Removes an enum item from the registry by its value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Remove)]
|
||||
[[play](https://go.dev/play/p/dSG84wQ3TuC)]
|
||||
- **<big>Update</big>** : Updates the name of an enum item in the registry by its value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Update)]
|
||||
[[play](https://go.dev/play/p/Ol0moT1J9Xl)]
|
||||
- **<big>GetByValue</big>** : Retrieves an enum item by its value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#GetByValue)]
|
||||
[[play](https://go.dev/play/p/niJ1U2KlE_m)]
|
||||
- **<big>GetByName</big>** : Retrieves an enum item by its name.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#GetByName)]
|
||||
[[play](https://go.dev/play/p/49ie_gpqH0m)]
|
||||
- **<big>Items</big>** : Returns a slice of all enum items in the registry.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Items)]
|
||||
[[play](https://go.dev/play/p/lAJFAradbvQ)]
|
||||
- **<big>Contains</big>** : Checks if an enum item with the given value exists in the registry.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Contains)]
|
||||
[[play](https://go.dev/play/p/_T-lPYkZn2j)]
|
||||
- **<big>Size</big>** : Returns the number of enum items in the registry.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Size)]
|
||||
[[play](https://go.dev/play/p/TeDArWhlQe2)]
|
||||
- **<big>Range</big>** : Iterates over all enum items in the registry and applies the given function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Range)]
|
||||
[[play](https://go.dev/play/p/GPsZbQbefWN)]
|
||||
- **<big>SortedItems</big>** : Returns a slice of all enum items sorted by the given less function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#SortedItems)]
|
||||
[[play](https://go.dev/play/p/tN9RE_m_WEI)]
|
||||
- **<big>Filter</big>** : Returns a slice of enum items that satisfy the given predicate function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Filter)]
|
||||
[[play](https://go.dev/play/p/uTUpTdcyoCU)]
|
||||
|
||||
<h3 id="fileutil"> 11. Fileutil package implements some basic functions for file operations. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/fileutil"
|
||||
@@ -859,7 +957,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#GetExeOrDllVersion)]
|
||||
[[play](https://go.dev/play/p/iLRrDBhE38E)]
|
||||
|
||||
<h3 id="formatter"> 10. Formatter contains some functions for data formatting. <a href="#index">index</a></h3>
|
||||
<h3 id="formatter"> 12. Formatter contains some functions for data formatting. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/formatter"
|
||||
@@ -888,8 +986,14 @@ import "github.com/duke-git/lancet/v2/formatter"
|
||||
- **<big>ParseBinaryBytes</big>** : return the human readable bytes size string into the amount it represents(base 1024).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParseBinaryBytes)]
|
||||
[[play](https://go.dev/play/p/69v1tTT62x8)]
|
||||
- **<big>ParseCNAddress</big>** : parses a Chinese address string intelligently and extracts structured information (province, city, district, street, name, phone, etc.). Supports various address formats including county-level cities.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParseCNAddress)]
|
||||
[[play](https://go.dev/play/p/o5l09hQopEV)]
|
||||
- **<big>ParsePersonInfo</big>** : extracts user information (name, phone, ID card, postal code) from an address string and separates it from the location address.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParsePersonInfo)]
|
||||
[[play](https://go.dev/play/p/JO-uTlJlTy7)]
|
||||
|
||||
<h3 id="function"> 11. Function package can control the flow of function execution and support part of functional programming. <a href="#index">index</a></h3>
|
||||
<h3 id="function"> 13. Function package can control the flow of function execution and support part of functional programming. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/function"
|
||||
@@ -952,7 +1056,7 @@ import "github.com/duke-git/lancet/v2/function"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
|
||||
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
|
||||
|
||||
<h3 id="maputil"> 12. Maputil package includes some functions to manipulate map. <a href="#index">index</a></h3>
|
||||
<h3 id="maputil"> 14. Maputil package includes some functions to manipulate map. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/maputil"
|
||||
@@ -1127,9 +1231,9 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
[[play](https://go.dev/play/p/bvNwNBZDm6v)]
|
||||
- **<big>ToMarkdownTable</big>** : Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToMarkdownTable)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/w_pSLfeyEB5)]
|
||||
|
||||
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
||||
<h3 id="mathutil"> 15. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/mathutil"
|
||||
@@ -1240,7 +1344,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Combination)]
|
||||
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
|
||||
|
||||
<h3 id="netutil"> 14. Netutil package contains functions to get net information and send http request. <a href="#index">index</a></h3>
|
||||
<h3 id="netutil"> 16. Netutil package contains functions to get net information and send http request. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/netutil"
|
||||
@@ -1319,7 +1423,7 @@ import "github.com/duke-git/lancet/v2/netutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#AddQueryParams)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
|
||||
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. <a href="#index">index</a></h3>
|
||||
<h3 id="pointer"> 17. Pointer package contains some util functions to operate go pointer. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/pointer"
|
||||
@@ -1343,7 +1447,7 @@ import "github.com/duke-git/lancet/v2/pointer"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#UnwrapOrDefault)]
|
||||
[[play](https://go.dev/play/p/ZnGIHf8_o4E)]
|
||||
|
||||
<h3 id="random"> 16. Random package implements some basic functions to generate random int and string. <a href="#index">index</a></h3>
|
||||
<h3 id="random"> 18. Random package implements some basic functions to generate random int and string. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/random"
|
||||
@@ -1409,7 +1513,7 @@ import "github.com/duke-git/lancet/v2/random"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandNumberOfLength)]
|
||||
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
|
||||
|
||||
<h3 id="retry"> 17. Retry package is for executing a function repeatedly until it was successful or canceled by the context. <a href="#index">index</a></h3>
|
||||
<h3 id="retry"> 19. Retry package is for executing a function repeatedly until it was successful or canceled by the context. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/retry"
|
||||
@@ -1444,7 +1548,7 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
||||
|
||||
<h3 id="slice"> 18. Slice contains some functions to manipulate slice. <a href="#index">index</a></h3>
|
||||
<h3 id="slice"> 20. Slice contains some functions to manipulate slice. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/slice"
|
||||
@@ -1716,7 +1820,7 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ConcatBy)]
|
||||
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
|
||||
|
||||
<h3 id="stream"> 19. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. <a href="#index">index</a></h3>
|
||||
<h3 id="stream"> 21. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/stream"
|
||||
@@ -1815,7 +1919,7 @@ import "github.com/duke-git/lancet/v2/stream"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#LastIndexOf)]
|
||||
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
|
||||
|
||||
<h3 id="structs"> 20. Structs package provides several high level functions to manipulate struct, tag, and field. <a href="#index">index</a></h3>
|
||||
<h3 id="structs"> 22. Structs package provides several high level functions to manipulate struct, tag, and field. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/structs"
|
||||
@@ -1867,9 +1971,9 @@ import "github.com/duke-git/lancet/v2/structs"
|
||||
[[play](https://go.dev/play/p/Ig75P-agN39)]
|
||||
- **<big>TypeName</big>** : Return struct type name.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#TypeName)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/SWLWd0XBaBb)]
|
||||
|
||||
<h3 id="strutil"> 21. Strutil package contains some functions to manipulate string. <a href="#index">index</a></h3>
|
||||
<h3 id="strutil"> 23. Strutil package contains some functions to manipulate string. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/strutil"
|
||||
@@ -2021,7 +2125,7 @@ import "github.com/duke-git/lancet/v2/strutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#FindAllOccurrences)]
|
||||
[[play](https://go.dev/play/p/uvyA6azGLB1)]
|
||||
|
||||
<h3 id="system"> 22. System package contain some functions about os, runtime, shell command. <a href="#index">index</a></h3>
|
||||
<h3 id="system"> 24. System package contain some functions about os, runtime, shell command. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/system"
|
||||
@@ -2069,7 +2173,7 @@ import "github.com/duke-git/lancet/v2/system"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
|
||||
[[play](https://go.dev/play/p/NQDVywEYYx7)]
|
||||
|
||||
<h3 id="tuple"> 23. Tuple package implements tuple data type and some operations on it. <a href="#index">index</a></h3>
|
||||
<h3 id="tuple"> 25. Tuple package implements tuple data type and some operations on it. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/tuple"
|
||||
@@ -2186,7 +2290,7 @@ import "github.com/duke-git/lancet/v2/tuple"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/tuple.md#Unzip10)]
|
||||
[[play](https://go.dev/play/p/-taQB6Wfre_z)]
|
||||
|
||||
<h3 id="validator"> 24. Validator package contains some functions for data validation. <a href="#index">index</a></h3>
|
||||
<h3 id="validator"> 26. Validator package contains some functions for data validation. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/validator"
|
||||
@@ -2235,7 +2339,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/jlYApVLLGTZ)]
|
||||
- **<big>IsEmail</big>** : check if the string is a email address.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmail)]
|
||||
[[play](https://go.dev/play/p/Os9VaFlT33G)]
|
||||
[[play](https://go.dev/play/p/HVQ5LAe-vFz)]
|
||||
- **<big>IsEmptyString</big>** : check if the string is empty.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmptyString)]
|
||||
[[play](https://go.dev/play/p/dpzgUjFnBCX)]
|
||||
@@ -2328,12 +2432,12 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/yafpdxLiymu)]
|
||||
- **<big>IsPassport</big>** : Passport validation(using regex).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsPassport)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/dvOiV2BW7Aw)]
|
||||
- **<big>IsChineseHMPassport</big>** : Mainland travel permit for Hong Kong, Macao validation (using regex).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChineseHMPassport)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/xKG6spQTcY0)]
|
||||
|
||||
<h3 id="xerror"> 25. Xerror package implements helpers for errors. <a href="#index">index</a></h3>
|
||||
<h3 id="xerror"> 27. Xerror package implements helpers for errors. <a href="#index">index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/xerror"
|
||||
|
||||
151
README_zh-CN.md
151
README_zh-CN.md
@@ -83,6 +83,8 @@ func main() {
|
||||
- [Cryptor](#user-content-cryptor)
|
||||
- [Datetime](#user-content-datetime)
|
||||
- [Datastructure](#user-content-datastructure)
|
||||
- [EventBus](#user-content-eventbus)
|
||||
- [Enum](#user-content-enum)
|
||||
- [Fileutil](#user-content-fileutil)
|
||||
- [Formatter](#user-content-formatter)
|
||||
- [Function](#user-content-function)
|
||||
@@ -312,6 +314,15 @@ import "github.com/duke-git/lancet/v2/convertor"
|
||||
- **<big>ToPointer</big>** : 返回传入值的指针。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToPointer)]
|
||||
[[play](https://go.dev/play/p/ASf_etHNlw1)]
|
||||
- **<big>ToPointers</big>** : 将值的切片转换为指针的切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToPointers)]
|
||||
[[play](https://go.dev/play/p/ZUoXd2i5ZkV)]
|
||||
- **<big>FromPointer</big>** : 返回指针所指向的值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#FromPointer)]
|
||||
[[play](https://go.dev/play/p/wAp90V7Zu6g)]
|
||||
- **<big>FromPointers</big>** : 将指针的切片转换为值的切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#FromPointers)]
|
||||
[[play](https://go.dev/play/p/qIPsyYtNy3Q)]
|
||||
- **<big>ToString</big>** : 将值转换为字符串,对于数字、字符串、[]byte,将转换为字符串。 对于其他类型(切片、映射、数组、结构)将调用 json.Marshal。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToString)]
|
||||
[[play](https://go.dev/play/p/nF1zOOslpQq)]
|
||||
@@ -524,6 +535,30 @@ import "github.com/duke-git/lancet/v2/cryptor"
|
||||
- **<big>RsaVerifySign</big>** : 验证数据的签名是否符合 RSA 算法。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaVerifySign)]
|
||||
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
|
||||
- **<big>GenerateSm2Key</big>** : 生成 SM2 公钥和私钥。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#GenerateSm2Key)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm2Encrypt</big>** : 使用 SM2 公钥加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm2Encrypt)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm2Decrypt</big>** : 使用 SM2 私钥解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm2Decrypt)]
|
||||
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
|
||||
- **<big>Sm3</big>** : 返回数据的 SM3 哈希值(256 位)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm3)]
|
||||
[[play](https://go.dev/play/p/zDAQpteAiOc)]
|
||||
- **<big>Sm4EcbEncrypt</big>** : 使用 SM4 ECB 模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4EcbEncrypt)]
|
||||
[[play](https://go.dev/play/p/l5IQxYuuaED)]
|
||||
- **<big>Sm4EcbDecrypt</big>** : 使用 SM4 ECB 模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4EcbDecrypt)]
|
||||
[[play](https://go.dev/play/p/l5IQxYuuaED)]
|
||||
- **<big>Sm4CbcEncrypt</big>** : 使用 SM4 CBC 模式加密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4CbcEncrypt)]
|
||||
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
|
||||
- **<big>Sm4CbcDecrypt</big>** : 使用 SM4 CBC 模式解密数据。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4CbcDecrypt)]
|
||||
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
|
||||
|
||||
<h3 id="datetime"> 7. datetime日期时间处理包,格式化日期,比较日期。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
@@ -767,7 +802,70 @@ import "github.com/duke-git/lancet/v2/eventbus"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#SetErrorHandler)]
|
||||
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
|
||||
|
||||
<h3 id="fileutil"> 10. fileutil 包含文件基本操作。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="enum"> 10. Enum实现一个简单枚举工具包。. <a href="#index">Index</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/enum"
|
||||
```
|
||||
|
||||
#### Function list:
|
||||
|
||||
- **<big>NewItem</big>** : 创建枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewItem)]
|
||||
[[play](https://go.dev/play/p/8qNsLw01HD5)]
|
||||
- **<big>NewItemsFromPairs</big>** : 从 Pair 结构体的切片创建枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewItemsFromPairs)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Value</big>** : 返回枚举项的值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Value)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Name</big>** : 返回枚举项的名称。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Name)]
|
||||
[[play](https://go.dev/play/p/xKnoGa7gnev)]
|
||||
- **<big>Valid</big>** : 检查枚举项是否有效。如果提供了自定义检查函数,将使用该函数验证值。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Valid)]
|
||||
[[play](https://go.dev/play/p/pA3lYY2VSm3)]
|
||||
- **<big>MarshalJSON</big>** : 枚举项实现 json.Marshaler 接口。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#MarshalJSON)]
|
||||
[[play](https://go.dev/play/p/zIZEdAnneB5)]
|
||||
- **<big>NewRegistry</big>** : Registry 定义了一个通用的枚举注册表结构体。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewRegistry)]
|
||||
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
|
||||
- **<big>Add</big>** : 向枚举注册表添加枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Add)]
|
||||
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
|
||||
- **<big>Remove</big>** : 在枚举注册表中删除枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Remove)]
|
||||
[[play](https://go.dev/play/p/dSG84wQ3TuC)]
|
||||
- **<big>Update</big>** : 在枚举注册表中更新枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Update)]
|
||||
[[play](https://go.dev/play/p/Ol0moT1J9Xl)]
|
||||
- **<big>GetByValue</big>** : 在枚举注册表中通过值获取枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#GetByValue)]
|
||||
[[play](https://go.dev/play/p/niJ1U2KlE_m)]
|
||||
- **<big>GetByName</big>** : 在枚举注册表中通过名称获取枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#GetByName)]
|
||||
[[play](https://go.dev/play/p/49ie_gpqH0m)]
|
||||
- **<big>Items</big>** : 返回枚举注册表中的枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Items)]
|
||||
[[play](https://go.dev/play/p/lAJFAradbvQ)]
|
||||
- **<big>Contains</big>** : 检查注册表中是否存在具有给定值的枚举项。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Contains)]
|
||||
[[play](https://go.dev/play/p/_T-lPYkZn2j)]
|
||||
- **<big>Size</big>** : 返回注册表中枚举项的数目。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Size)]
|
||||
[[play](https://go.dev/play/p/TeDArWhlQe2)]
|
||||
- **<big>Range</big>** : 遍历注册表中的所有枚举项,并应用给定的函数。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Range)]
|
||||
[[play](https://go.dev/play/p/GPsZbQbefWN)]
|
||||
- **<big>SortedItems</big>** : 返回按给定比较函数排序的所有枚举项的切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#SortedItems)]
|
||||
[[play](https://go.dev/play/p/tN9RE_m_WEI)]
|
||||
- **<big>Filter</big>** : 返回满足给定谓词函数的枚举项切片。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Filter)]
|
||||
[[play](https://go.dev/play/p/uTUpTdcyoCU)]
|
||||
|
||||
<h3 id="fileutil"> 11. fileutil 包含文件基本操作。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/fileutil"
|
||||
@@ -869,7 +967,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#GetExeOrDllVersion)]
|
||||
[[play](https://go.dev/play/p/iLRrDBhE38E)]
|
||||
|
||||
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="formatter"> 12. formatter 格式化器包含一些数据格式化处理方法。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/formatter"
|
||||
@@ -898,8 +996,14 @@ import "github.com/duke-git/lancet/v2/formatter"
|
||||
- **<big>ParseBinaryBytes</big>** : 将字节单位字符串转换成其所表示的字节数(以 1024 为基数)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParseBinaryBytes)]
|
||||
[[play](https://go.dev/play/p/69v1tTT62x8)]
|
||||
- **<big>ParseCNAddress</big>** : 智能解析中国地址字符串并提取结构化信息(省、市、区、街道、姓名、电话等)。支持多种地址格式,包括县级市。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParseCNAddress)]
|
||||
[[play](https://go.dev/play/p/o5l09hQopEV)]
|
||||
- **<big>ParsePersonInfo</big>** : 从地址字符串中提取用户信息(姓名、电话、身份证、邮编)并将其与位置地址分离。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParsePersonInfo)]
|
||||
[[play](https://go.dev/play/p/JO-uTlJlTy7)]
|
||||
|
||||
<h3 id="function"> 12. function 函数包控制函数执行流程,包含部分函数式编程。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="function"> 13. function 函数包控制函数执行流程,包含部分函数式编程。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/function"
|
||||
@@ -962,7 +1066,7 @@ import "github.com/duke-git/lancet/v2/function"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
|
||||
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
|
||||
|
||||
<h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="maputil"> 14. maputil 包括一些操作 map 的函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/maputil"
|
||||
@@ -1137,9 +1241,9 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
[[play](https://go.dev/play/p/bvNwNBZDm6v)]
|
||||
- **<big>ToMarkdownTable</big>** : 将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToMarkdownTable)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/w_pSLfeyEB5)]
|
||||
|
||||
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="mathutil"> 15. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/mathutil"
|
||||
@@ -1250,7 +1354,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Combination)]
|
||||
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
|
||||
|
||||
<h3 id="netutil"> 15. netutil 网络包支持获取 ip 地址,发送 http 请求。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="netutil"> 16. netutil 网络包支持获取 ip 地址,发送 http 请求。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/netutil"
|
||||
@@ -1329,7 +1433,7 @@ import "github.com/duke-git/lancet/v2/netutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#AddQueryParams)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
|
||||
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="pointer"> 17. pointer 包支持一些指针类型的操作。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/pointer"
|
||||
@@ -1353,7 +1457,7 @@ import "github.com/duke-git/lancet/v2/pointer"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/pointer.md#UnwrapOrDefault)]
|
||||
[[play](https://go.dev/play/p/ZnGIHf8_o4E)]
|
||||
|
||||
<h3 id="random"> 17. random 随机数生成器包,可以生成随机[]bytes, int, string。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="random"> 18. random 随机数生成器包,可以生成随机[]bytes, int, string。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/random"
|
||||
@@ -1419,7 +1523,7 @@ import "github.com/duke-git/lancet/v2/random"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandNumberOfLength)]
|
||||
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
|
||||
|
||||
<h3 id="retry"> 18. retry 重试执行函数直到函数运行成功或被 context cancel。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="retry"> 19. retry 重试执行函数直到函数运行成功或被 context cancel。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/retry"
|
||||
@@ -1451,7 +1555,7 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
||||
|
||||
<h3 id="slice"> 19. slice 包含操作切片的方法集合。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="slice"> 20. slice 包含操作切片的方法集合。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/slice"
|
||||
@@ -1725,7 +1829,7 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ConcatBy)]
|
||||
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
|
||||
|
||||
<h3 id="stream"> 20. stream 流,该包仅验证简单的 stream 实现,功能有限。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="stream"> 21. stream 流,该包仅验证简单的 stream 实现,功能有限。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/stream"
|
||||
@@ -1821,7 +1925,7 @@ import "github.com/duke-git/lancet/v2/stream"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#LastIndexOf)]
|
||||
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
|
||||
|
||||
<h3 id="structs"> 21. structs 提供操作 struct, tag, field 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="structs"> 22. structs 提供操作 struct, tag, field 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/structs"
|
||||
@@ -1873,9 +1977,9 @@ import "github.com/duke-git/lancet/v2/structs"
|
||||
[[play](https://go.dev/play/p/Ig75P-agN39)]
|
||||
- **<big>TypeName</big>** : 获取结构体类型名。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#TypeName)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/SWLWd0XBaBb)]
|
||||
|
||||
<h3 id="strutil"> 22. strutil 包含字符串处理的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="strutil"> 23. strutil 包含字符串处理的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/strutil"
|
||||
@@ -2028,7 +2132,7 @@ import "github.com/duke-git/lancet/v2/strutil"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
|
||||
[[play](https://go.dev/play/p/uvyA6azGLB1)]
|
||||
|
||||
<h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="system"> 24. system 包含 os, runtime, shell command 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/system"
|
||||
@@ -2076,7 +2180,7 @@ import "github.com/duke-git/lancet/v2/system"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
|
||||
[[play](https://go.dev/play/p/NQDVywEYYx7)]
|
||||
|
||||
<h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="tuple"> 25. Tuple 包实现一个元组数据类型。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/tuple"
|
||||
@@ -2193,7 +2297,7 @@ import "github.com/duke-git/lancet/v2/tuple"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/tuple.md#Unzip10)]
|
||||
[[play](https://go.dev/play/p/-taQB6Wfre_z)]
|
||||
|
||||
<h3 id="validator"> 25. validator 验证器包,包含常用字符串格式验证函数。 <a href="#index">回到目录</a></h3>
|
||||
<h3 id="validator"> 26. validator 验证器包,包含常用字符串格式验证函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/validator"
|
||||
@@ -2242,7 +2346,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/jlYApVLLGTZ)]
|
||||
- **<big>IsEmail</big>** : 验证字符串是否是有效电子邮件地址。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmail)]
|
||||
[[play](https://go.dev/play/p/Os9VaFlT33G)]
|
||||
[[play](https://go.dev/play/p/HVQ5LAe-vFz)]
|
||||
- **<big>IsEmptyString</big>** : 验证字符串是否是空字符串。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmptyString)]
|
||||
[[play](https://go.dev/play/p/dpzgUjFnBCX)]
|
||||
@@ -2335,11 +2439,12 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/yafpdxLiymu)]
|
||||
- **<big>IsPassport</big>** : 判断护照(正则判断)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsPassport)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/dvOiV2BW7Aw)]
|
||||
- **<big>IsChineseHMPassport</big>** : 判断港澳台通行证(正则判断)。
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChineseHMPassport)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
<h3 id="xerror"> 26. xerror 包实现一些错误处理函数。 <a href="#index">回到目录</a></h3>
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChineseHMPassport)]
|
||||
[[play](https://go.dev/play/p/xKG6spQTcY0)]
|
||||
|
||||
<h3 id="xerror"> 27. xerror 包实现一些错误处理函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/xerror"
|
||||
|
||||
@@ -229,7 +229,7 @@ func ToPointer[T any](value T) *T {
|
||||
}
|
||||
|
||||
// ToPointers convert a slice of values to a slice of pointers.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/ZUoXd2i5ZkV
|
||||
func ToPointers[T any](values []T) []*T {
|
||||
result := make([]*T, len(values))
|
||||
for i := range values {
|
||||
@@ -239,7 +239,7 @@ func ToPointers[T any](values []T) []*T {
|
||||
}
|
||||
|
||||
// FromPointer returns the value pointed to by the pointer.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/wAp90V7Zu6g
|
||||
func FromPointer[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
@@ -250,7 +250,7 @@ func FromPointer[T any](ptr *T) T {
|
||||
}
|
||||
|
||||
// FromPointers convert a slice of pointers to a slice of values.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/qIPsyYtNy3Q
|
||||
func FromPointers[T any](pointers []*T) []T {
|
||||
result := make([]T, len(pointers))
|
||||
for i, ptr := range pointers {
|
||||
|
||||
@@ -169,6 +169,45 @@ func ExampleToPointer() {
|
||||
// 123
|
||||
}
|
||||
|
||||
func ExampleToPointers() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
pointerStrs := ToPointers(strs)
|
||||
fmt.Println(*pointerStrs[0])
|
||||
fmt.Println(*pointerStrs[1])
|
||||
fmt.Println(*pointerStrs[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
|
||||
func ExampleFromPointer() {
|
||||
str := "abc"
|
||||
strPtr := &str
|
||||
result := FromPointer(strPtr)
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// abc
|
||||
}
|
||||
|
||||
func ExampleFromPointers() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
|
||||
|
||||
result := FromPointers(strPtr)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
fmt.Println(result[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
|
||||
func ExampleToMap() {
|
||||
type Message struct {
|
||||
name string
|
||||
|
||||
71
cryptor/gm_example_test.go
Normal file
71
cryptor/gm_example_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package cryptor_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func ExampleSm3() {
|
||||
data := []byte("hello world")
|
||||
hash := cryptor.Sm3(data)
|
||||
|
||||
fmt.Println(hex.EncodeToString(hash))
|
||||
|
||||
// Output:
|
||||
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
|
||||
}
|
||||
|
||||
func ExampleSm4EcbEncrypt() {
|
||||
key := []byte("1234567890abcdef") // 16 bytes key
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
|
||||
func ExampleSm4CbcEncrypt() {
|
||||
key := []byte("1234567890abcdef") // 16 bytes key
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
|
||||
func ExampleGenerateSm2Key() {
|
||||
// Generate SM2 key pair
|
||||
privateKey, err := cryptor.GenerateSm2Key()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
// Encrypt with public key
|
||||
ciphertext, err := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt with private key
|
||||
decrypted, err := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
251
cryptor/gm_sm2.go
Normal file
251
cryptor/gm_sm2.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// SM2 implements the Chinese SM2 elliptic curve public key algorithm.
|
||||
// SM2 is based on elliptic curve cryptography and provides encryption, decryption, signing and verification.
|
||||
//
|
||||
// Note: This implementation uses crypto/elliptic package methods (GenerateKey, ScalarBaseMult, ScalarMult, IsOnCurve)
|
||||
// which are marked as deprecated in Go 1.20+. These methods still work correctly and are widely used.
|
||||
// The //nolint:staticcheck directive suppresses deprecation warnings.
|
||||
// A future version may replace these with a custom elliptic curve implementation.
|
||||
|
||||
var (
|
||||
sm2P256 *sm2Curve
|
||||
sm2P256Params = &elliptic.CurveParams{Name: "sm2p256v1"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// SM2 curve parameters
|
||||
sm2P256Params.P, _ = new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
|
||||
sm2P256Params.N, _ = new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
|
||||
sm2P256Params.B, _ = new(big.Int).SetString("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
|
||||
sm2P256Params.Gx, _ = new(big.Int).SetString("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
|
||||
sm2P256Params.Gy, _ = new(big.Int).SetString("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)
|
||||
sm2P256Params.BitSize = 256
|
||||
|
||||
sm2P256 = &sm2Curve{sm2P256Params}
|
||||
}
|
||||
|
||||
type sm2Curve struct {
|
||||
*elliptic.CurveParams
|
||||
}
|
||||
|
||||
// Sm2PrivateKey represents an SM2 private key.
|
||||
type Sm2PrivateKey struct {
|
||||
D *big.Int
|
||||
PublicKey Sm2PublicKey
|
||||
}
|
||||
|
||||
// Sm2PublicKey represents an SM2 public key.
|
||||
type Sm2PublicKey struct {
|
||||
X, Y *big.Int
|
||||
}
|
||||
|
||||
// GenerateSm2Key generates a new SM2 private/public key pair.
|
||||
// Play: https://go.dev/play/p/bKYMqRLvIx3
|
||||
func GenerateSm2Key() (*Sm2PrivateKey, error) {
|
||||
priv, x, y, err := elliptic.GenerateKey(sm2P256, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privateKey := &Sm2PrivateKey{
|
||||
D: new(big.Int).SetBytes(priv),
|
||||
PublicKey: Sm2PublicKey{
|
||||
X: x,
|
||||
Y: y,
|
||||
},
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// Sm2Encrypt encrypts plaintext using SM2 public key.
|
||||
// Returns ciphertext in the format: C1 || C3 || C2
|
||||
// C1 = kG (65 bytes in uncompressed format)
|
||||
// C3 = Hash(x2 || M || y2) (32 bytes for SM3)
|
||||
// C2 = M xor t (same length as plaintext)
|
||||
// Play: https://go.dev/play/p/bKYMqRLvIx3
|
||||
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error) {
|
||||
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||
return nil, errors.New("sm2: invalid public key")
|
||||
}
|
||||
|
||||
for {
|
||||
// Generate random k
|
||||
k, err := randFieldElement(sm2P256, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// C1 = kG
|
||||
c1x, c1y := sm2P256.ScalarBaseMult(k.Bytes())
|
||||
|
||||
// kP = (x2, y2)
|
||||
x2, y2 := sm2P256.ScalarMult(pub.X, pub.Y, k.Bytes())
|
||||
|
||||
// Derive key using KDF
|
||||
kdfLen := len(plaintext)
|
||||
t := sm2KDF(append(toBytes(sm2P256, x2), toBytes(sm2P256, y2)...), kdfLen)
|
||||
|
||||
// Check if t is all zeros
|
||||
allZero := true
|
||||
for _, b := range t {
|
||||
if b != 0 {
|
||||
allZero = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allZero {
|
||||
continue
|
||||
}
|
||||
|
||||
// C2 = M xor t
|
||||
c2 := make([]byte, len(plaintext))
|
||||
for i := 0; i < len(plaintext); i++ {
|
||||
c2[i] = plaintext[i] ^ t[i]
|
||||
}
|
||||
|
||||
// C3 = Hash(x2 || M || y2)
|
||||
c3Input := append(toBytes(sm2P256, x2), plaintext...)
|
||||
c3Input = append(c3Input, toBytes(sm2P256, y2)...)
|
||||
c3 := Sm3(c3Input)
|
||||
|
||||
// Return C1 || C3 || C2
|
||||
c1 := sm2MarshalUncompressed(sm2P256, c1x, c1y)
|
||||
result := append(c1, c3...)
|
||||
result = append(result, c2...)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Sm2Decrypt decrypts ciphertext using SM2 private key.
|
||||
// Expects ciphertext in the format: C1 || C3 || C2
|
||||
// Play: https://go.dev/play/p/bKYMqRLvIx3
|
||||
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
if priv == nil || priv.D == nil {
|
||||
return nil, errors.New("sm2: invalid private key")
|
||||
}
|
||||
|
||||
// Parse C1 (65 bytes), C3 (32 bytes), C2 (remaining)
|
||||
if len(ciphertext) < 97 {
|
||||
return nil, errors.New("sm2: ciphertext too short")
|
||||
}
|
||||
|
||||
c1 := ciphertext[:65]
|
||||
c3 := ciphertext[65:97]
|
||||
c2 := ciphertext[97:]
|
||||
|
||||
// Parse C1
|
||||
c1x, c1y := sm2UnmarshalUncompressed(sm2P256, c1)
|
||||
if c1x == nil {
|
||||
return nil, errors.New("sm2: invalid C1 point")
|
||||
}
|
||||
|
||||
// Verify C1 is on curve
|
||||
if !sm2P256.IsOnCurve(c1x, c1y) {
|
||||
return nil, errors.New("sm2: C1 not on curve")
|
||||
}
|
||||
|
||||
// dC1 = (x2, y2)
|
||||
x2, y2 := sm2P256.ScalarMult(c1x, c1y, priv.D.Bytes())
|
||||
|
||||
// Derive key using KDF
|
||||
kdfLen := len(c2)
|
||||
t := sm2KDF(append(toBytes(sm2P256, x2), toBytes(sm2P256, y2)...), kdfLen)
|
||||
|
||||
// M = C2 xor t
|
||||
plaintext := make([]byte, len(c2))
|
||||
for i := 0; i < len(c2); i++ {
|
||||
plaintext[i] = c2[i] ^ t[i]
|
||||
}
|
||||
|
||||
// Verify C3 = Hash(x2 || M || y2)
|
||||
u := append(toBytes(sm2P256, x2), plaintext...)
|
||||
u = append(u, toBytes(sm2P256, y2)...)
|
||||
hash := Sm3(u)
|
||||
|
||||
for i := 0; i < len(c3); i++ {
|
||||
if c3[i] != hash[i] {
|
||||
return nil, errors.New("sm2: hash verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// SM2 KDF (Key Derivation Function)
|
||||
func sm2KDF(z []byte, klen int) []byte {
|
||||
limit := (klen + 31) / 32
|
||||
result := make([]byte, 0, limit*32)
|
||||
|
||||
for i := 1; i <= limit; i++ {
|
||||
counter := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(counter, uint32(i))
|
||||
hash := Sm3(append(z, counter...))
|
||||
result = append(result, hash...)
|
||||
}
|
||||
|
||||
return result[:klen]
|
||||
}
|
||||
|
||||
func toBytes(curve elliptic.Curve, value *big.Int) []byte {
|
||||
byteLen := (curve.Params().BitSize + 7) / 8
|
||||
buf := make([]byte, byteLen)
|
||||
b := value.Bytes()
|
||||
copy(buf[byteLen-len(b):], b)
|
||||
return buf
|
||||
}
|
||||
|
||||
func sm2MarshalUncompressed(curve *sm2Curve, x, y *big.Int) []byte {
|
||||
byteLen := (curve.BitSize + 7) / 8
|
||||
ret := make([]byte, 1+2*byteLen)
|
||||
ret[0] = 4 // uncompressed point
|
||||
|
||||
xBytes := x.Bytes()
|
||||
copy(ret[1+byteLen-len(xBytes):], xBytes)
|
||||
yBytes := y.Bytes()
|
||||
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func sm2UnmarshalUncompressed(curve *sm2Curve, data []byte) (*big.Int, *big.Int) {
|
||||
byteLen := (curve.BitSize + 7) / 8
|
||||
if len(data) != 1+2*byteLen {
|
||||
return nil, nil
|
||||
}
|
||||
if data[0] != 4 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
x := new(big.Int).SetBytes(data[1 : 1+byteLen])
|
||||
y := new(big.Int).SetBytes(data[1+byteLen:])
|
||||
|
||||
return x, y
|
||||
}
|
||||
|
||||
func randFieldElement(c elliptic.Curve, rand io.Reader) (*big.Int, error) {
|
||||
params := c.Params()
|
||||
b := make([]byte, params.BitSize/8+8)
|
||||
_, err := io.ReadFull(rand, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := new(big.Int).SetBytes(b)
|
||||
n := new(big.Int).Sub(params.N, big.NewInt(1))
|
||||
k.Mod(k, n)
|
||||
k.Add(k, big.NewInt(1))
|
||||
|
||||
return k, nil
|
||||
}
|
||||
211
cryptor/gm_sm3.go
Normal file
211
cryptor/gm_sm3.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// SM3 implements the Chinese SM3 cryptographic hash algorithm.
|
||||
// SM3 produces a 256-bit (32-byte) hash value.
|
||||
|
||||
const (
|
||||
sm3BlockSize = 64
|
||||
sm3Size = 32
|
||||
sm3T1 = 0x79cc4519
|
||||
sm3T2 = 0x7a879d8a
|
||||
)
|
||||
|
||||
var sm3IV = [8]uint32{
|
||||
0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600,
|
||||
0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e,
|
||||
}
|
||||
|
||||
type sm3Digest struct {
|
||||
h [8]uint32
|
||||
x [sm3BlockSize]byte
|
||||
nx int
|
||||
len uint64
|
||||
}
|
||||
|
||||
// Sm3 returns a new hash.Hash computing the SM3 checksum.
|
||||
// Play: https://go.dev/play/p/zDAQpteAiOc
|
||||
func Sm3(data []byte) []byte {
|
||||
h := newSm3()
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func newSm3() hash.Hash {
|
||||
d := new(sm3Digest)
|
||||
d.Reset()
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *sm3Digest) Reset() {
|
||||
d.h = sm3IV
|
||||
d.nx = 0
|
||||
d.len = 0
|
||||
}
|
||||
|
||||
func (d *sm3Digest) Size() int {
|
||||
return sm3Size
|
||||
}
|
||||
|
||||
func (d *sm3Digest) BlockSize() int {
|
||||
return sm3BlockSize
|
||||
}
|
||||
|
||||
func (d *sm3Digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.len += uint64(nn)
|
||||
|
||||
if d.nx > 0 {
|
||||
n := copy(d.x[d.nx:], p)
|
||||
d.nx += n
|
||||
if d.nx == sm3BlockSize {
|
||||
sm3Block(d, d.x[:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
|
||||
if len(p) >= sm3BlockSize {
|
||||
n := len(p) &^ (sm3BlockSize - 1)
|
||||
sm3Block(d, p[:n])
|
||||
p = p[n:]
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *sm3Digest) Sum(in []byte) []byte {
|
||||
d0 := *d
|
||||
hash := d0.checkSum()
|
||||
return append(in, hash[:]...)
|
||||
}
|
||||
|
||||
func (d *sm3Digest) checkSum() [sm3Size]byte {
|
||||
len := d.len
|
||||
var tmp [64]byte
|
||||
tmp[0] = 0x80
|
||||
|
||||
if len%64 < 56 {
|
||||
d.Write(tmp[0 : 56-len%64])
|
||||
} else {
|
||||
d.Write(tmp[0 : 64+56-len%64])
|
||||
}
|
||||
|
||||
len <<= 3
|
||||
binary.BigEndian.PutUint64(tmp[:], len)
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
var digest [sm3Size]byte
|
||||
for i := 0; i < 8; i++ {
|
||||
binary.BigEndian.PutUint32(digest[i*4:], d.h[i])
|
||||
}
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
func sm3Block(dig *sm3Digest, p []byte) {
|
||||
var w [68]uint32
|
||||
var w1 [64]uint32
|
||||
|
||||
h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7]
|
||||
|
||||
for len(p) >= sm3BlockSize {
|
||||
for i := 0; i < 16; i++ {
|
||||
j := i * 4
|
||||
w[i] = binary.BigEndian.Uint32(p[j : j+4])
|
||||
}
|
||||
|
||||
for i := 16; i < 68; i++ {
|
||||
w[i] = sm3P1(w[i-16]^w[i-9]^sm3RotateLeft(w[i-3], 15)) ^ sm3RotateLeft(w[i-13], 7) ^ w[i-6]
|
||||
}
|
||||
|
||||
for i := 0; i < 64; i++ {
|
||||
w1[i] = w[i] ^ w[i+4]
|
||||
}
|
||||
|
||||
A, B, C, D, E, F, G, H := h0, h1, h2, h3, h4, h5, h6, h7
|
||||
|
||||
for j := 0; j < 64; j++ {
|
||||
var ss1, ss2, tt1, tt2, t uint32
|
||||
|
||||
if j < 16 {
|
||||
t = sm3T1
|
||||
} else {
|
||||
t = sm3T2
|
||||
}
|
||||
|
||||
ss1 = sm3RotateLeft(sm3RotateLeft(A, 12)+E+sm3RotateLeft(t, uint32(j%32)), 7)
|
||||
ss2 = ss1 ^ sm3RotateLeft(A, 12)
|
||||
|
||||
if j < 16 {
|
||||
tt1 = sm3FF0(A, B, C) + D + ss2 + w1[j]
|
||||
tt2 = sm3GG0(E, F, G) + H + ss1 + w[j]
|
||||
} else {
|
||||
tt1 = sm3FF1(A, B, C) + D + ss2 + w1[j]
|
||||
tt2 = sm3GG1(E, F, G) + H + ss1 + w[j]
|
||||
}
|
||||
|
||||
D = C
|
||||
C = sm3RotateLeft(B, 9)
|
||||
B = A
|
||||
A = tt1
|
||||
H = G
|
||||
G = sm3RotateLeft(F, 19)
|
||||
F = E
|
||||
E = sm3P0(tt2)
|
||||
}
|
||||
|
||||
h0 ^= A
|
||||
h1 ^= B
|
||||
h2 ^= C
|
||||
h3 ^= D
|
||||
h4 ^= E
|
||||
h5 ^= F
|
||||
h6 ^= G
|
||||
h7 ^= H
|
||||
|
||||
p = p[sm3BlockSize:]
|
||||
}
|
||||
|
||||
dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7
|
||||
}
|
||||
|
||||
func sm3RotateLeft(x, n uint32) uint32 {
|
||||
return (x << n) | (x >> (32 - n))
|
||||
}
|
||||
|
||||
func sm3P0(x uint32) uint32 {
|
||||
return x ^ sm3RotateLeft(x, 9) ^ sm3RotateLeft(x, 17)
|
||||
}
|
||||
|
||||
func sm3P1(x uint32) uint32 {
|
||||
return x ^ sm3RotateLeft(x, 15) ^ sm3RotateLeft(x, 23)
|
||||
}
|
||||
|
||||
func sm3FF0(x, y, z uint32) uint32 {
|
||||
return x ^ y ^ z
|
||||
}
|
||||
|
||||
func sm3FF1(x, y, z uint32) uint32 {
|
||||
return (x & y) | (x & z) | (y & z)
|
||||
}
|
||||
|
||||
func sm3GG0(x, y, z uint32) uint32 {
|
||||
return x ^ y ^ z
|
||||
}
|
||||
|
||||
func sm3GG1(x, y, z uint32) uint32 {
|
||||
return (x & y) | (^x & z)
|
||||
}
|
||||
270
cryptor/gm_sm4.go
Normal file
270
cryptor/gm_sm4.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// SM4 implements the Chinese SM4 block cipher.
|
||||
// SM4 is a 128-bit block cipher with 128-bit keys.
|
||||
// This implementation uses pre-computed lookup tables for optimal performance.
|
||||
|
||||
const sm4BlockSize = 16
|
||||
|
||||
// Pre-computed T-transformation lookup tables for performance optimization
|
||||
var sm4T1Table [256][4]uint32 // S-box + L1 transformation
|
||||
var sm4T2Table [256][4]uint32 // S-box + L2 transformation
|
||||
|
||||
var sm4Sbox = [256]byte{
|
||||
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
|
||||
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
|
||||
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
|
||||
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
|
||||
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
|
||||
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
|
||||
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
|
||||
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
|
||||
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
|
||||
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
|
||||
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
|
||||
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
|
||||
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
|
||||
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
|
||||
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
|
||||
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
|
||||
}
|
||||
|
||||
var sm4FK = [4]uint32{0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc}
|
||||
|
||||
var sm4CK = [32]uint32{
|
||||
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
|
||||
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
|
||||
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
|
||||
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
|
||||
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
|
||||
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
|
||||
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
|
||||
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
|
||||
}
|
||||
|
||||
// 初始化预计算查找表
|
||||
func init() {
|
||||
// Pre-compute all possible T1 and T2 transformations
|
||||
for pos := 0; pos < 4; pos++ {
|
||||
for i := 0; i < 256; i++ {
|
||||
// S-box 替换
|
||||
sboxVal := sm4Sbox[i]
|
||||
|
||||
// 根据字节位置计算偏移
|
||||
shift := uint32((3 - pos) * 8)
|
||||
b := uint32(sboxVal) << shift
|
||||
|
||||
// L1 变换:b ^ ROL(b,2) ^ ROL(b,10) ^ ROL(b,18) ^ ROL(b,24)
|
||||
sm4T1Table[i][pos] = b ^ sm4RotateLeft(b, 2) ^ sm4RotateLeft(b, 10) ^ sm4RotateLeft(b, 18) ^ sm4RotateLeft(b, 24)
|
||||
|
||||
// L2 变换:b ^ ROL(b,13) ^ ROL(b,23)
|
||||
sm4T2Table[i][pos] = b ^ sm4RotateLeft(b, 13) ^ sm4RotateLeft(b, 23)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sm4Cipher struct {
|
||||
enc [32]uint32
|
||||
dec [32]uint32
|
||||
}
|
||||
|
||||
// Sm4EcbEncrypt encrypts data using SM4 in ECB mode.
|
||||
// key must be 16 bytes.
|
||||
// Play: https://go.dev/play/p/l5IQxYuuaED
|
||||
func Sm4EcbEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 16 {
|
||||
panic("sm4: key length must be 16 bytes")
|
||||
}
|
||||
|
||||
c := newSm4Cipher(key)
|
||||
padded := pkcs7Padding(data, sm4BlockSize)
|
||||
encrypted := make([]byte, len(padded))
|
||||
|
||||
for i := 0; i < len(padded); i += sm4BlockSize {
|
||||
c.Encrypt(encrypted[i:i+sm4BlockSize], padded[i:i+sm4BlockSize])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// Sm4EcbDecrypt decrypts data using SM4 in ECB mode.
|
||||
// key must be 16 bytes.
|
||||
// Play: https://go.dev/play/p/l5IQxYuuaED
|
||||
func Sm4EcbDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 16 {
|
||||
panic("sm4: key length must be 16 bytes")
|
||||
}
|
||||
|
||||
if len(encrypted)%sm4BlockSize != 0 {
|
||||
panic("sm4: encrypted data length must be multiple of block size")
|
||||
}
|
||||
|
||||
c := newSm4Cipher(key)
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
|
||||
for i := 0; i < len(encrypted); i += sm4BlockSize {
|
||||
c.Decrypt(decrypted[i:i+sm4BlockSize], encrypted[i:i+sm4BlockSize])
|
||||
}
|
||||
|
||||
return pkcs7UnPadding(decrypted)
|
||||
}
|
||||
|
||||
// Sm4CbcEncrypt encrypts data using SM4 in CBC mode.
|
||||
// key must be 16 bytes.
|
||||
// Play: https://go.dev/play/p/65Q6iYhLRTa
|
||||
func Sm4CbcEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 16 {
|
||||
panic("sm4: key length must be 16 bytes")
|
||||
}
|
||||
|
||||
c := newSm4Cipher(key)
|
||||
padded := pkcs7Padding(data, sm4BlockSize)
|
||||
|
||||
iv := make([]byte, sm4BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("sm4: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(padded))
|
||||
mode := cipher.NewCBCEncrypter(c, iv)
|
||||
mode.CryptBlocks(encrypted, padded)
|
||||
|
||||
return append(iv, encrypted...)
|
||||
}
|
||||
|
||||
// Sm4CbcDecrypt decrypts data using SM4 in CBC mode.
|
||||
// key must be 16 bytes.
|
||||
// Play: https://go.dev/play/p/65Q6iYhLRTa
|
||||
func Sm4CbcDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 16 {
|
||||
panic("sm4: key length must be 16 bytes")
|
||||
}
|
||||
|
||||
if len(encrypted) < sm4BlockSize {
|
||||
panic("sm4: encrypted data too short")
|
||||
}
|
||||
|
||||
if len(encrypted)%sm4BlockSize != 0 {
|
||||
panic("sm4: encrypted data length must be multiple of block size")
|
||||
}
|
||||
|
||||
c := newSm4Cipher(key)
|
||||
iv := encrypted[:sm4BlockSize]
|
||||
ciphertext := encrypted[sm4BlockSize:]
|
||||
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
mode := cipher.NewCBCDecrypter(c, iv)
|
||||
mode.CryptBlocks(decrypted, ciphertext)
|
||||
|
||||
return pkcs7UnPadding(decrypted)
|
||||
}
|
||||
|
||||
func newSm4Cipher(key []byte) *sm4Cipher {
|
||||
c := &sm4Cipher{}
|
||||
|
||||
var mk [4]uint32
|
||||
for i := 0; i < 4; i++ {
|
||||
mk[i] = binary.BigEndian.Uint32(key[i*4 : (i+1)*4])
|
||||
}
|
||||
|
||||
var k [36]uint32
|
||||
k[0] = mk[0] ^ sm4FK[0]
|
||||
k[1] = mk[1] ^ sm4FK[1]
|
||||
k[2] = mk[2] ^ sm4FK[2]
|
||||
k[3] = mk[3] ^ sm4FK[3]
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
k[i+4] = k[i] ^ sm4T2Fast(k[i+1]^k[i+2]^k[i+3]^sm4CK[i])
|
||||
c.enc[i] = k[i+4]
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
c.dec[i] = c.enc[31-i]
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *sm4Cipher) BlockSize() int {
|
||||
return sm4BlockSize
|
||||
}
|
||||
|
||||
func (c *sm4Cipher) Encrypt(dst, src []byte) {
|
||||
if len(src) < sm4BlockSize {
|
||||
panic("sm4: input not full block")
|
||||
}
|
||||
if len(dst) < sm4BlockSize {
|
||||
panic("sm4: output not full block")
|
||||
}
|
||||
|
||||
// 使用局部变量避免数组分配,提升性能
|
||||
x0 := binary.BigEndian.Uint32(src[0:4])
|
||||
x1 := binary.BigEndian.Uint32(src[4:8])
|
||||
x2 := binary.BigEndian.Uint32(src[8:12])
|
||||
x3 := binary.BigEndian.Uint32(src[12:16])
|
||||
|
||||
// 32 轮加密
|
||||
for i := 0; i < 32; i++ {
|
||||
t := x1 ^ x2 ^ x3 ^ c.enc[i]
|
||||
x0 ^= sm4T1Fast(t)
|
||||
x0, x1, x2, x3 = x1, x2, x3, x0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(dst[0:4], x3)
|
||||
binary.BigEndian.PutUint32(dst[4:8], x2)
|
||||
binary.BigEndian.PutUint32(dst[8:12], x1)
|
||||
binary.BigEndian.PutUint32(dst[12:16], x0)
|
||||
}
|
||||
|
||||
func (c *sm4Cipher) Decrypt(dst, src []byte) {
|
||||
if len(src) < sm4BlockSize {
|
||||
panic("sm4: input not full block")
|
||||
}
|
||||
if len(dst) < sm4BlockSize {
|
||||
panic("sm4: output not full block")
|
||||
}
|
||||
|
||||
x0 := binary.BigEndian.Uint32(src[0:4])
|
||||
x1 := binary.BigEndian.Uint32(src[4:8])
|
||||
x2 := binary.BigEndian.Uint32(src[8:12])
|
||||
x3 := binary.BigEndian.Uint32(src[12:16])
|
||||
|
||||
// 32 轮解密
|
||||
for i := 0; i < 32; i++ {
|
||||
t := x1 ^ x2 ^ x3 ^ c.dec[i]
|
||||
x0 ^= sm4T1Fast(t)
|
||||
x0, x1, x2, x3 = x1, x2, x3, x0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(dst[0:4], x3)
|
||||
binary.BigEndian.PutUint32(dst[4:8], x2)
|
||||
binary.BigEndian.PutUint32(dst[8:12], x1)
|
||||
binary.BigEndian.PutUint32(dst[12:16], x0)
|
||||
}
|
||||
|
||||
// 使用预计算查找表的快速 T1 变换(用于加密轮函数)
|
||||
func sm4T1Fast(a uint32) uint32 {
|
||||
return sm4T1Table[byte(a>>24)][0] ^
|
||||
sm4T1Table[byte(a>>16)][1] ^
|
||||
sm4T1Table[byte(a>>8)][2] ^
|
||||
sm4T1Table[byte(a)][3]
|
||||
}
|
||||
|
||||
// 使用预计算查找表的快速 T2 变换(用于密钥扩展)
|
||||
func sm4T2Fast(a uint32) uint32 {
|
||||
return sm4T2Table[byte(a>>24)][0] ^
|
||||
sm4T2Table[byte(a>>16)][1] ^
|
||||
sm4T2Table[byte(a>>8)][2] ^
|
||||
sm4T2Table[byte(a)][3]
|
||||
}
|
||||
|
||||
func sm4RotateLeft(x uint32, n uint32) uint32 {
|
||||
return (x << n) | (x >> (32 - n))
|
||||
}
|
||||
163
cryptor/gm_test.go
Normal file
163
cryptor/gm_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestSm3(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm3")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "abc",
|
||||
expected: "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0",
|
||||
},
|
||||
{
|
||||
input: "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
|
||||
expected: "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: "1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := Sm3([]byte(tt.input))
|
||||
resultHex := hex.EncodeToString(result)
|
||||
assert.Equal(tt.expected, resultHex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSm4EcbEncryptDecrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm4EcbEncryptDecrypt")
|
||||
|
||||
key := []byte("1234567890abcdef") // 16 bytes
|
||||
plaintext := []byte("Hello, SM4!")
|
||||
|
||||
// Encrypt
|
||||
encrypted := Sm4EcbEncrypt(plaintext, key)
|
||||
assert.IsNotNil(encrypted)
|
||||
|
||||
// Decrypt
|
||||
decrypted := Sm4EcbDecrypt(encrypted, key)
|
||||
assert.Equal(plaintext, decrypted)
|
||||
}
|
||||
|
||||
func TestSm4CbcEncryptDecrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm4CbcEncryptDecrypt")
|
||||
|
||||
key := []byte("1234567890abcdef") // 16 bytes
|
||||
plaintext := []byte("Hello, SM4 CBC mode!")
|
||||
|
||||
// Encrypt
|
||||
encrypted := Sm4CbcEncrypt(plaintext, key)
|
||||
assert.IsNotNil(encrypted)
|
||||
|
||||
// Decrypt
|
||||
decrypted := Sm4CbcDecrypt(encrypted, key)
|
||||
assert.Equal(plaintext, decrypted)
|
||||
}
|
||||
|
||||
func TestSm4EcbWithLongData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm4EcbWithLongData")
|
||||
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("This is a longer message that spans multiple blocks for SM4 encryption testing.")
|
||||
|
||||
encrypted := Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
assert.Equal(plaintext, decrypted)
|
||||
}
|
||||
|
||||
func TestSm2EncryptDecrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm2EncryptDecrypt")
|
||||
|
||||
// Generate key pair
|
||||
privateKey, err := GenerateSm2Key()
|
||||
assert.IsNil(err)
|
||||
assert.IsNotNil(privateKey)
|
||||
|
||||
plaintext := []byte("Hello, SM2!")
|
||||
|
||||
// Encrypt with public key
|
||||
ciphertext, err := Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
assert.IsNil(err)
|
||||
assert.IsNotNil(ciphertext)
|
||||
|
||||
// Decrypt with private key
|
||||
decrypted, err := Sm2Decrypt(privateKey, ciphertext)
|
||||
assert.IsNil(err)
|
||||
assert.Equal(plaintext, decrypted)
|
||||
}
|
||||
|
||||
func TestSm2WithLongData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm2WithLongData")
|
||||
|
||||
privateKey, err := GenerateSm2Key()
|
||||
assert.IsNil(err)
|
||||
|
||||
plaintext := []byte("This is a longer message for SM2 encryption testing. " +
|
||||
"SM2 is an elliptic curve public key cryptography algorithm.")
|
||||
|
||||
ciphertext, err := Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
assert.IsNil(err)
|
||||
|
||||
decrypted, err := Sm2Decrypt(privateKey, ciphertext)
|
||||
assert.IsNil(err)
|
||||
assert.Equal(plaintext, decrypted)
|
||||
}
|
||||
|
||||
func TestSm4InvalidKeyLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm4InvalidKeyLength")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
assert.IsNotNil(r)
|
||||
}
|
||||
}()
|
||||
|
||||
key := []byte("short")
|
||||
plaintext := []byte("test")
|
||||
Sm4EcbEncrypt(plaintext, key) // Should panic
|
||||
}
|
||||
|
||||
func TestSm2InvalidInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestSm2InvalidInput")
|
||||
|
||||
// Test with nil public key
|
||||
_, err := Sm2Encrypt(nil, []byte("test"))
|
||||
assert.IsNotNil(err)
|
||||
|
||||
// Test with nil private key
|
||||
_, err = Sm2Decrypt(nil, []byte("test"))
|
||||
assert.IsNotNil(err)
|
||||
|
||||
// Test with invalid ciphertext
|
||||
privateKey, _ := GenerateSm2Key()
|
||||
_, err = Sm2Decrypt(privateKey, []byte("short"))
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
@@ -33,6 +33,9 @@ import (
|
||||
- [ToJson](#ToJson)
|
||||
- [ToMap](#ToMap)
|
||||
- [ToPointer](#ToPointer)
|
||||
- [ToPointers](#ToPointers)
|
||||
- [FromPointer](#FromPointer)
|
||||
- [FromPointers](#FromPointers)
|
||||
- [ToString](#ToString)
|
||||
- [StructToMap](#StructToMap)
|
||||
- [MapToSlice](#MapToSlice)
|
||||
@@ -456,6 +459,108 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToPointers">ToPointers</span>
|
||||
|
||||
<p>将值的切片转换为指针的切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ToPointers[T any](values []T) []*T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZUoXd2i5ZkV)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
pointerStrs := convertor.ToPointers(strs)
|
||||
fmt.Println(*pointerStrs[0])
|
||||
fmt.Println(*pointerStrs[1])
|
||||
fmt.Println(*pointerStrs[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FromPointer">FromPointer</span>
|
||||
|
||||
<p>返回指针所指向的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func FromPointer[T any](ptr *T) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wAp90V7Zu6g)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := "abc"
|
||||
strPtr := &str
|
||||
result := convertor.FromPointer(strPtr)
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// abc
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FromPointers">FromPointers</span>
|
||||
|
||||
<p>将指针的切片转换为值的切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func FromPointers[T any](pointers []*T) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qIPsyYtNy3Q)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
|
||||
|
||||
result := convertor.FromPointers(strPtr)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
fmt.Println(result[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToString">ToString</span>
|
||||
|
||||
<p>将值转换为字符串,对于数字、字符串、[]byte,将转换为字符串。 对于其他类型(切片、映射、数组、结构体)将调用 json.Marshal</p>
|
||||
@@ -1179,4 +1284,4 @@ func main() {
|
||||
// Output:
|
||||
// 9876543210
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cryptor
|
||||
|
||||
cryptor 包包含数据加密和解密功能。支持 base64, md5, hmac, hash, aes, des, rsa。
|
||||
cryptor 包包含数据加密和解密功能。支持 base64, md5, hmac, hash, aes, des, rsa, sm2, sm3, sm4。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -74,6 +74,14 @@ import (
|
||||
- [RsaDecryptOAEP](#RsaDecryptOAEP)
|
||||
- [RsaSign](#RsaSign)
|
||||
- [RsaVerifySign](#RsaVerifySign)
|
||||
- [Sm3](#Sm3)
|
||||
- [Sm4EcbEncrypt](#Sm4EcbEncrypt)
|
||||
- [Sm4EcbDecrypt](#Sm4EcbDecrypt)
|
||||
- [Sm4CbcEncrypt](#Sm4CbcEncrypt)
|
||||
- [Sm4CbcDecrypt](#Sm4CbcDecrypt)
|
||||
- [GenerateSm2Key](#GenerateSm2Key)
|
||||
- [Sm2Encrypt](#Sm2Encrypt)
|
||||
- [Sm2Decrypt](#Sm2Decrypt)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1829,3 +1837,279 @@ func main() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="Sm3">Sm3</span>
|
||||
|
||||
<p>计算 SM3 哈希值(国密SM3密码杂凑算法)。SM3 是中国国家密码管理局发布的密码杂凑算法,用于替代 MD5/SHA-1/SHA-2 等国际算法。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm3(data []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zDAQpteAiOc)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := []byte("hello world")
|
||||
hash := cryptor.Sm3(data)
|
||||
|
||||
fmt.Println(hex.EncodeToString(hash))
|
||||
|
||||
// Output:
|
||||
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4EcbEncrypt">Sm4EcbEncrypt</span>
|
||||
|
||||
<p>使用 SM4 ECB 模式加密数据(国密SM4分组密码算法)。密钥长度必须为 16 字节。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm4EcbEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/l5IQxYuuaED)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef") // 16 bytes key
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4EcbDecrypt">Sm4EcbDecrypt</span>
|
||||
|
||||
<p>使用 SM4 ECB 模式解密数据。密钥长度必须为 16 字节。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm4EcbDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/l5IQxYuuaED)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4CbcEncrypt">Sm4CbcEncrypt</span>
|
||||
|
||||
<p>使用 SM4 CBC 模式加密数据。密钥长度必须为 16 字节。返回的密文包含 IV(前 16 字节)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm4CbcEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4CbcDecrypt">Sm4CbcDecrypt</span>
|
||||
|
||||
<p>使用 SM4 CBC 模式解密数据。密钥长度必须为 16 字节。密文应包含 IV(前 16 字节)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm4CbcDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GenerateSm2Key">GenerateSm2Key</span>
|
||||
|
||||
<p>生成 SM2 密钥对(国密SM2椭圆曲线公钥密码算法)。SM2 是基于椭圆曲线的非对称加密算法。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GenerateSm2Key() (*Sm2PrivateKey, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, err := cryptor.GenerateSm2Key()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm2Encrypt">Sm2Encrypt</span>
|
||||
|
||||
<p>使用 SM2 公钥加密数据。返回的密文格式为:C1(65字节) || C3(32字节) || C2(明文长度)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, _ := cryptor.GenerateSm2Key()
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm2Decrypt">Sm2Decrypt</span>
|
||||
|
||||
<p>使用 SM2 私钥解密数据。密文格式应为:C1(65字节) || C3(32字节) || C2(明文长度)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, _ := cryptor.GenerateSm2Key()
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Enum
|
||||
|
||||
Enum实现一个简单枚举工具包。
|
||||
Enum 实现一个简单枚举工具包。
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -55,7 +55,7 @@ import (
|
||||
func NewItem[T comparable](value T, name string) *Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/8qNsLw01HD5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -96,7 +96,7 @@ func main() {
|
||||
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -139,7 +139,7 @@ func main() {
|
||||
func (it *Item[T]) Value() T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -182,7 +182,7 @@ func main() {
|
||||
func (it *Item[T]) Name() string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -225,7 +225,7 @@ func main() {
|
||||
func (it *Item[T]) Valid(checker ...func(T) bool) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pA3lYY2VSm3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -258,7 +258,7 @@ func main() {
|
||||
|
||||
### <span id="MarshalJSON">MarshalJSON</span>
|
||||
|
||||
<p>枚举项实现json.Marshaler 接口。</p>
|
||||
<p>枚举项实现json.Marshaler接口。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
@@ -267,7 +267,7 @@ func (it *Item[T]) MarshalJSON() ([]byte, error)
|
||||
func (it *Item[T]) UnmarshalJSON(data []byte) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zIZEdAnneB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -290,7 +290,7 @@ func main() {
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
var unmarshaledItem enum.Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
@@ -310,7 +310,7 @@ func main() {
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -359,7 +359,7 @@ func main() {
|
||||
func (r *Registry[T]) Add(items ...*Item[T])
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -408,7 +408,7 @@ func main() {
|
||||
func (r *Registry[T]) Remove(value T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -454,7 +454,7 @@ func main() {
|
||||
func (r *Registry[T]) Update(value T, newName string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ol0moT1J9Xl)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -500,7 +500,7 @@ func main() {
|
||||
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/niJ1U2KlE_m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -520,9 +520,9 @@ const (
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found name by value:", item.Name())
|
||||
@@ -543,7 +543,7 @@ func main() {
|
||||
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/49ie_gpqH0m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -586,7 +586,7 @@ func main() {
|
||||
func (r *Registry[T]) Items() []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/lAJFAradbvQ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -631,7 +631,7 @@ func main() {
|
||||
func (r *Registry[T]) Contains(value T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -673,7 +673,7 @@ func main() {
|
||||
func (r *Registry[T]) Size() int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/TeDArWhlQe2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -721,7 +721,7 @@ func main() {
|
||||
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GPsZbQbefWN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -745,7 +745,7 @@ func main() {
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
registry.Range(func(item *enum.Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
@@ -766,7 +766,7 @@ func main() {
|
||||
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/tN9RE_m_WEI)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -790,8 +790,8 @@ func main() {
|
||||
item2 := enum.NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
|
||||
return i1.Value() < i2.Value()
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
@@ -812,7 +812,7 @@ func main() {
|
||||
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uTUpTdcyoCU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -836,7 +836,7 @@ func main() {
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
@@ -847,4 +847,4 @@ func main() {
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ formatter 格式化器包含一些数据格式化处理方法。
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/formatter.go](https://github.com/duke-git/lancet/blob/main/formatter/formatter.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/byte.go](https://github.com/duke-git/lancet/blob/main/formatter/byte.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/address.go](https://github.com/duke-git/lancet/blob/main/formatter/address.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -30,6 +31,8 @@ import (
|
||||
- [BinaryBytes](#BinaryBytes)
|
||||
- [ParseDecimalBytes](#ParseDecimalBytes)
|
||||
- [ParseBinaryBytes](#ParseBinaryBytes)
|
||||
- [ParseCNAddress](#ParseCNAddress)
|
||||
- [ParsePersonInfo](#ParsePersonInfo)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -308,3 +311,134 @@ func main() {
|
||||
// 12492
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ParseCNAddress">ParseCNAddress</span>
|
||||
|
||||
<p>智能解析中国地址字符串并提取结构化信息。可以解析带或不带用户信息(姓名、电话、身份证等)的地址。当 withUser 为 true 时,从地址字符串中提取用户信息。当 withUser 为 false 时,仅解析位置信息。支持多种地址格式:标准格式、紧凑格式、带关键词格式、县级市格式等。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ParseCNAddress(str string, withUser bool) *AddressInfo
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/o5l09hQopEV)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/formatter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 解析包含用户信息的完整地址
|
||||
result1 := formatter.ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
|
||||
jsonData1, _ := json.MarshalIndent(result1, "", " ")
|
||||
fmt.Println("示例 1 - 带用户信息:")
|
||||
fmt.Println(string(jsonData1))
|
||||
|
||||
// 仅解析地址,不提取用户信息
|
||||
result2 := formatter.ParseCNAddress("北京市海淀区中关村大街1号", false)
|
||||
fmt.Printf("\n示例 2 - 仅地址:\n")
|
||||
fmt.Printf("省: %s, 市: %s, 区: %s, 街道: %s\n",
|
||||
result2.Province, result2.City, result2.Region, result2.Street)
|
||||
|
||||
// 解析县级市地址
|
||||
result3 := formatter.ParseCNAddress("河北省石家庄市新乐市经济开发区兴工街10号", false)
|
||||
fmt.Printf("\n示例 3 - 县级市:\n")
|
||||
fmt.Printf("省: %s, 市: %s, 区/县: %s, 街道: %s\n",
|
||||
result3.Province, result3.City, result3.Region, result3.Street)
|
||||
|
||||
// 紧凑格式
|
||||
result4 := formatter.ParseCNAddress("马云13593464918陕西省西安市雁塔区丈八沟街道", true)
|
||||
fmt.Printf("\n示例 4 - 紧凑格式:\n")
|
||||
fmt.Printf("姓名: %s, 电话: %s, 地址: %s%s%s%s\n",
|
||||
result4.Name, result4.Mobile, result4.Province, result4.City, result4.Region, result4.Street)
|
||||
|
||||
// Output:
|
||||
// 示例 1 - 带用户信息:
|
||||
// {
|
||||
// "name": "张三",
|
||||
// "mobile": "13800138000",
|
||||
// "idn": "",
|
||||
// "postcode": "",
|
||||
// "province": "北京",
|
||||
// "city": "北京市",
|
||||
// "region": "朝阳区",
|
||||
// "street": "建国路1号",
|
||||
// "addr": "北京市朝阳区建国路1号"
|
||||
// }
|
||||
//
|
||||
// 示例 2 - 仅地址:
|
||||
// 省: 北京, 市: 北京市, 区: 海淀区, 街道: 中关村大街1号
|
||||
//
|
||||
// 示例 3 - 县级市:
|
||||
// 省: 河北省, 市: 石家庄市, 区/县: 新乐市, 街道: 经济开发区兴工街10号
|
||||
//
|
||||
// 示例 4 - 紧凑格式:
|
||||
// 姓名: 马云, 电话: 13593464918, 地址: 陕西省西安市雁塔区丈八沟街道
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ParsePersonInfo">ParsePersonInfo</span>
|
||||
|
||||
<p>从地址字符串中提取用户信息(姓名、电话、身份证、邮编)。将个人信息与地址分离,支持带标签格式、紧凑格式、带分隔符格式。返回包含提取的用户信息和清理后地址字符串的 AddressInfo。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ParsePersonInfo(str string) *AddressInfo
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JO-uTlJlTy7)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/formatter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 提取姓名和手机号
|
||||
result1 := formatter.ParsePersonInfo("张三 13800138000 北京市朝阳区")
|
||||
fmt.Println("示例 1 - 姓名和手机号:")
|
||||
fmt.Printf("姓名: %s, 手机: %s, 地址: %s\n", result1.Name, result1.Mobile, result1.Addr)
|
||||
|
||||
// 提取身份证号
|
||||
result2 := formatter.ParsePersonInfo("李四 110101199001011234 上海市")
|
||||
fmt.Println("\n示例 2 - 身份证号:")
|
||||
fmt.Printf("姓名: %s, 身份证: %s, 地址: %s\n", result2.Name, result2.IDN, result2.Addr)
|
||||
|
||||
// 带标签格式
|
||||
result3 := formatter.ParsePersonInfo("收货人:王五 电话:13900139000 收货地址:天津市河西区友谊路20号")
|
||||
jsonData3, _ := json.MarshalIndent(result3, "", " ")
|
||||
fmt.Println("\n示例 3 - 带标签格式:")
|
||||
fmt.Println(string(jsonData3))
|
||||
|
||||
// Output:
|
||||
// 示例 1 - 姓名和手机号:
|
||||
// 姓名: 张三, 手机: 13800138000, 地址: 北京市朝阳区
|
||||
//
|
||||
// 示例 2 - 身份证号:
|
||||
// 姓名: 李四, 身份证: 110101199001011234, 地址: 上海市
|
||||
//
|
||||
// 示例 3 - 带标签格式:
|
||||
// {
|
||||
// "name": "王五",
|
||||
// "mobile": "13900139000",
|
||||
// "idn": "",
|
||||
// "postcode": "",
|
||||
// "province": "",
|
||||
// "city": "",
|
||||
// "region": "",
|
||||
// "street": "",
|
||||
// "addr": "天津市河西区友谊路20号"
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2357,8 +2357,8 @@ func main() {
|
||||
编辑
|
||||
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
|
||||
```
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/w_pSLfeyEB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -2410,4 +2410,4 @@ func main() {
|
||||
// |50000|Alice|
|
||||
// |60000|Bob|
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -69,7 +69,7 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
|
||||
|
||||
fmt.Println(s.ToMap())
|
||||
|
||||
// Output:
|
||||
@@ -261,7 +261,7 @@ func main() {
|
||||
tag := n.Tag()
|
||||
|
||||
fmt.Println(tag.Name)
|
||||
|
||||
|
||||
// Output:
|
||||
// name
|
||||
}
|
||||
@@ -295,10 +295,10 @@ func main() {
|
||||
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
|
||||
|
||||
fmt.Println(n.Value())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// 111
|
||||
}
|
||||
```
|
||||
@@ -338,11 +338,11 @@ func main() {
|
||||
s := structs.New(c1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("Age")
|
||||
|
||||
|
||||
fmt.Println(n.IsEmbedded())
|
||||
fmt.Println(a.IsEmbedded())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
@@ -377,11 +377,11 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("age")
|
||||
|
||||
|
||||
fmt.Println(n.IsExported())
|
||||
fmt.Println(a.IsExported())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
@@ -416,11 +416,11 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("Age")
|
||||
|
||||
|
||||
fmt.Println(n.IsZero())
|
||||
fmt.Println(a.IsZero())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
@@ -455,11 +455,11 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("Age")
|
||||
|
||||
|
||||
fmt.Println(n.Name())
|
||||
fmt.Println(a.Name())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// Name
|
||||
// Age
|
||||
}
|
||||
@@ -494,11 +494,11 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("Age")
|
||||
|
||||
|
||||
fmt.Println(n.Kind())
|
||||
fmt.Println(a.Kind())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// string
|
||||
// int
|
||||
}
|
||||
@@ -514,7 +514,7 @@ func main() {
|
||||
func (s *Struct) TypeName() string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -529,13 +529,13 @@ func main() {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
|
||||
p := &Parent{Age: 11}
|
||||
s := structs.New(p1)
|
||||
s := structs.New(p)
|
||||
|
||||
fmt.Println(s.TypeName())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// Parent
|
||||
}
|
||||
```
|
||||
@@ -569,10 +569,10 @@ func main() {
|
||||
p1 := &Parent{arr: []int{1, 2, 3}}
|
||||
s := structs.New(p1)
|
||||
a, _ := s.Field("arr")
|
||||
|
||||
|
||||
fmt.Println(a.IsSlice())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
```
|
||||
@@ -608,12 +608,12 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("arr")
|
||||
|
||||
|
||||
fmt.Println(n.IsTargetType(reflect.String))
|
||||
fmt.Println(a.IsTargetType(reflect.Slice))
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -549,7 +549,7 @@ func main() {
|
||||
func IsEmail(email string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/Os9VaFlT33G)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/HVQ5LAe-vFz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -559,13 +559,28 @@ import (
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsEmail("abc@xyz.com")
|
||||
result2 := validator.IsEmail("a.b@@com")
|
||||
result2 := validator.IsEmail("user@domain.co")
|
||||
result3 := validator.IsEmail("test.user@example.org")
|
||||
result4 := validator.IsEmail("@abc@xyz.com")
|
||||
result5 := validator.IsEmail("a.b@@com")
|
||||
result6 := validator.IsEmail("a.b@com")
|
||||
result7 := validator.IsEmail("test@example")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
@@ -1552,6 +1567,8 @@ func IsChinaUnionPay(v string) bool
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/yafpdxLiymu)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
@@ -1580,9 +1597,11 @@ func main() {
|
||||
func IsPassport(passport, country string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/dvOiV2BW7Aw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
@@ -1617,7 +1636,7 @@ func main() {
|
||||
func IsChineseHMPassport(hmPassport string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xKG6spQTcY0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1645,4 +1664,4 @@ func main() {
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -33,6 +33,9 @@ import (
|
||||
- [ToJson](#ToJson)
|
||||
- [ToMap](#ToMap)
|
||||
- [ToPointer](#ToPointer)
|
||||
- [ToPointers](#ToPointers)
|
||||
- [FromPointer](#FromPointer)
|
||||
- [FromPointers](#FromPointers)
|
||||
- [ToString](#ToString)
|
||||
- [StructToMap](#StructToMap)
|
||||
- [MapToSlice](#MapToSlice)
|
||||
@@ -456,6 +459,108 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToPointers">ToPointers</span>
|
||||
|
||||
<p>Convert a slice of values to a slice of pointers.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ToPointers[T any](values []T) []*T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZUoXd2i5ZkV)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
pointerStrs := convertor.ToPointers(strs)
|
||||
fmt.Println(*pointerStrs[0])
|
||||
fmt.Println(*pointerStrs[1])
|
||||
fmt.Println(*pointerStrs[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FromPointer">FromPointer</span>
|
||||
|
||||
<p>Returns the value pointed to by the pointer.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func FromPointer[T any](ptr *T) T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wAp90V7Zu6g)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := "abc"
|
||||
strPtr := &str
|
||||
result := convertor.FromPointer(strPtr)
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// abc
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="FromPointers">FromPointers</span>
|
||||
|
||||
<p>Convert a slice of pointers to a slice of values.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func FromPointers[T any](pointers []*T) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qIPsyYtNy3Q)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
strs := []string{"a", "b", "c"}
|
||||
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
|
||||
|
||||
result := convertor.FromPointers(strPtr)
|
||||
|
||||
fmt.Println(result[0])
|
||||
fmt.Println(result[1])
|
||||
fmt.Println(result[2])
|
||||
|
||||
// Output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToString">ToString</span>
|
||||
|
||||
<p>ToString convert value to string, for number, string, []byte, will convert to string. For other type (slice, map, array, struct) will call json.Marshal</p>
|
||||
@@ -572,7 +677,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="EncodeByte">EncodeByte</span>
|
||||
|
||||
<p>Encode data to byte slice.</p>
|
||||
@@ -638,7 +742,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="CopyProperties">CopyProperties</span>
|
||||
|
||||
<p>Copies each field from the source struct into the destination struct. Use json.Marshal/Unmarshal, so json tag should be set for fields of dst and src struct.</p>
|
||||
@@ -748,7 +851,7 @@ func main() {
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -781,7 +884,7 @@ func main() {
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// hello
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
@@ -857,8 +960,6 @@ func main() {
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### <span id="ToUrlBase64">ToUrlBase64</span>
|
||||
|
||||
<p>Convert a value to a string encoded in url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
|
||||
@@ -1147,4 +1248,4 @@ func main() {
|
||||
// Output:
|
||||
// 9876543210
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cryptor
|
||||
|
||||
Package cryptor contains some functions for data encryption and decryption. Support base64, md5, hmac, aes, des, rsa.
|
||||
Package cryptor contains some functions for data encryption and decryption. Support base64, md5, hmac, aes, des, rsa, sm2, sm3, sm4.
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -76,6 +76,14 @@ import (
|
||||
- [RsaDecryptOAEP](#RsaDecryptOAEP)
|
||||
- [RsaSign](#RsaSign)
|
||||
- [RsaVerifySign](#RsaVerifySign)
|
||||
- [Sm3](#Sm3)
|
||||
- [Sm4EcbEncrypt](#Sm4EcbEncrypt)
|
||||
- [Sm4EcbDecrypt](#Sm4EcbDecrypt)
|
||||
- [Sm4CbcEncrypt](#Sm4CbcEncrypt)
|
||||
- [Sm4CbcDecrypt](#Sm4CbcDecrypt)
|
||||
- [GenerateSm2Key](#GenerateSm2Key)
|
||||
- [Sm2Encrypt](#Sm2Encrypt)
|
||||
- [Sm2Decrypt](#Sm2Decrypt)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1831,3 +1839,278 @@ func main() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="Sm3">Sm3</span>
|
||||
|
||||
<p>Calculate SM3 hash (Chinese National Cryptography SM3 Hash Algorithm). SM3 is a cryptographic hash algorithm published by the Chinese State Cryptography Administration, designed to replace MD5/SHA-1/SHA-2.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm3(data []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zDAQpteAiOc)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := []byte("hello world")
|
||||
hash := cryptor.Sm3(data)
|
||||
|
||||
fmt.Println(hex.EncodeToString(hash))
|
||||
|
||||
// Output:
|
||||
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4EcbEncrypt">Sm4EcbEncrypt</span>
|
||||
|
||||
<p>Encrypt data using SM4 ECB mode (Chinese National Cryptography SM4 Block Cipher Algorithm). Key length must be 16 bytes.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm4EcbEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/l5IQxYuuaED)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef") // 16 bytes key
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4EcbDecrypt">Sm4EcbDecrypt</span>
|
||||
|
||||
<p>Decrypt data using SM4 ECB mode. Key length must be 16 bytes.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm4EcbDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/l5IQxYuuaED)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4CbcEncrypt">Sm4CbcEncrypt</span>
|
||||
|
||||
<p>Encrypt data using SM4 CBC mode. Key length must be 16 bytes. The returned ciphertext contains IV (first 16 bytes).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm4CbcEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm4CbcDecrypt">Sm4CbcDecrypt</span>
|
||||
|
||||
<p>Decrypt data using SM4 CBC mode. Key length must be 16 bytes. The ciphertext should contain IV (first 16 bytes).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm4CbcDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key := []byte("1234567890abcdef")
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
|
||||
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GenerateSm2Key">GenerateSm2Key</span>
|
||||
|
||||
<p>Generate SM2 key pair (Chinese National Cryptography SM2 Elliptic Curve Public Key Algorithm). SM2 is an asymmetric encryption algorithm based on elliptic curve cryptography.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func GenerateSm2Key() (*Sm2PrivateKey, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, err := cryptor.GenerateSm2Key()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm2Encrypt">Sm2Encrypt</span>
|
||||
|
||||
<p>Encrypt data using SM2 public key. The returned ciphertext format is: C1(65 bytes) || C3(32 bytes) || C2(plaintext length).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, _ := cryptor.GenerateSm2Key()
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Sm2Decrypt">Sm2Decrypt</span>
|
||||
|
||||
<p>Decrypt data using SM2 private key. The ciphertext format should be: C1(65 bytes) || C3(32 bytes) || C2(plaintext length).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
privateKey, _ := cryptor.GenerateSm2Key()
|
||||
plaintext := []byte("hello world")
|
||||
|
||||
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
|
||||
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello world
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ Package enum provides a simple enum implementation.
|
||||
|
||||
## Source:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -55,7 +55,7 @@ import (
|
||||
func NewItem[T comparable](value T, name string) *Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/8qNsLw01HD5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -96,7 +96,7 @@ func main() {
|
||||
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -139,7 +139,7 @@ func main() {
|
||||
func (it *Item[T]) Value() T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -182,7 +182,7 @@ func main() {
|
||||
func (it *Item[T]) Name() string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -225,7 +225,7 @@ func main() {
|
||||
func (it *Item[T]) Valid(checker ...func(T) bool) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/pA3lYY2VSm3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -267,7 +267,7 @@ func (it *Item[T]) MarshalJSON() ([]byte, error)
|
||||
func (it *Item[T]) UnmarshalJSON(data []byte) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zIZEdAnneB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -290,7 +290,7 @@ func main() {
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
var unmarshaledItem enum.Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
@@ -310,7 +310,7 @@ func main() {
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -359,7 +359,7 @@ func main() {
|
||||
func (r *Registry[T]) Add(items ...*Item[T])
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -408,7 +408,7 @@ func main() {
|
||||
func (r *Registry[T]) Remove(value T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -454,7 +454,7 @@ func main() {
|
||||
func (r *Registry[T]) Update(value T, newName string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Ol0moT1J9Xl)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -500,7 +500,7 @@ func main() {
|
||||
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/niJ1U2KlE_m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -520,9 +520,9 @@ const (
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found name by value:", item.Name())
|
||||
@@ -543,7 +543,7 @@ func main() {
|
||||
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/49ie_gpqH0m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -586,7 +586,7 @@ func main() {
|
||||
func (r *Registry[T]) Items() []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/lAJFAradbvQ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -631,7 +631,7 @@ func main() {
|
||||
func (r *Registry[T]) Contains(value T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -673,7 +673,7 @@ func main() {
|
||||
func (r *Registry[T]) Size() int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/TeDArWhlQe2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -721,7 +721,7 @@ func main() {
|
||||
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GPsZbQbefWN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -745,7 +745,7 @@ func main() {
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
registry.Range(func(item *enum.Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
@@ -766,7 +766,7 @@ func main() {
|
||||
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/tN9RE_m_WEI)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -790,8 +790,8 @@ func main() {
|
||||
item2 := enum.NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
|
||||
return i1.Value() < i2.Value()
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
@@ -812,7 +812,7 @@ func main() {
|
||||
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uTUpTdcyoCU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -836,7 +836,7 @@ func main() {
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
@@ -847,4 +847,4 @@ func main() {
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ formatter contains some functions for data formatting.
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/formatter.go](https://github.com/duke-git/lancet/blob/main/formatter/formatter.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/byte.go](https://github.com/duke-git/lancet/blob/main/formatter/byte.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/formatter/address.go](https://github.com/duke-git/lancet/blob/main/formatter/address.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -30,6 +31,8 @@ import (
|
||||
- [BinaryBytes](#BinaryBytes)
|
||||
- [ParseDecimalBytes](#ParseDecimalBytes)
|
||||
- [ParseBinaryBytes](#ParseBinaryBytes)
|
||||
- [ParseCNAddress](#ParseCNAddress)
|
||||
- [ParsePersonInfo](#ParsePersonInfo)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -308,3 +311,134 @@ func main() {
|
||||
// 12492
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ParseCNAddress">ParseCNAddress</span>
|
||||
|
||||
<p>Parses a Chinese address string intelligently and extracts structured information. It can parse addresses with or without user information (name, phone, ID card, etc.). When withUser is true, it extracts user information from the address string. When withUser is false, it only parses the location information. Supports various address formats: standard format, compact format, labeled format, county-level cities format, etc.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ParseCNAddress(str string, withUser bool) *AddressInfo
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/o5l09hQopEV)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/formatter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse complete address with user information
|
||||
result1 := formatter.ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
|
||||
jsonData1, _ := json.MarshalIndent(result1, "", " ")
|
||||
fmt.Println("Example 1 - With user info:")
|
||||
fmt.Println(string(jsonData1))
|
||||
|
||||
// Parse address only, without extracting user information
|
||||
result2 := formatter.ParseCNAddress("北京市海淀区中关村大街1号", false)
|
||||
fmt.Printf("\nExample 2 - Address only:\n")
|
||||
fmt.Printf("Province: %s, City: %s, Region: %s, Street: %s\n",
|
||||
result2.Province, result2.City, result2.Region, result2.Street)
|
||||
|
||||
// Parse county-level city address
|
||||
result3 := formatter.ParseCNAddress("河北省石家庄市新乐市经济开发区兴工街10号", false)
|
||||
fmt.Printf("\nExample 3 - County-level city:\n")
|
||||
fmt.Printf("Province: %s, City: %s, Region: %s, Street: %s\n",
|
||||
result3.Province, result3.City, result3.Region, result3.Street)
|
||||
|
||||
// Compact format
|
||||
result4 := formatter.ParseCNAddress("马云13593464918陕西省西安市雁塔区丈八沟街道", true)
|
||||
fmt.Printf("\nExample 4 - Compact format:\n")
|
||||
fmt.Printf("Name: %s, Phone: %s, Address: %s%s%s%s\n",
|
||||
result4.Name, result4.Mobile, result4.Province, result4.City, result4.Region, result4.Street)
|
||||
|
||||
// Output:
|
||||
// Example 1 - With user info:
|
||||
// {
|
||||
// "name": "张三",
|
||||
// "mobile": "13800138000",
|
||||
// "idn": "",
|
||||
// "postcode": "",
|
||||
// "province": "北京",
|
||||
// "city": "北京市",
|
||||
// "region": "朝阳区",
|
||||
// "street": "建国路1号",
|
||||
// "addr": "北京市朝阳区建国路1号"
|
||||
// }
|
||||
//
|
||||
// Example 2 - Address only:
|
||||
// Province: 北京, City: 北京市, Region: 海淀区, Street: 中关村大街1号
|
||||
//
|
||||
// Example 3 - County-level city:
|
||||
// Province: 河北省, City: 石家庄市, Region: 新乐市, Street: 经济开发区兴工街10号
|
||||
//
|
||||
// Example 4 - Compact format:
|
||||
// Name: 马云, Phone: 13593464918, Address: 陕西省西安市雁塔区丈八沟街道
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ParsePersonInfo">ParsePersonInfo</span>
|
||||
|
||||
<p>Extracts user information (name, phone, ID card, postal code) from an address string. It separates personal information from the address, supporting labeled format, compact format, and formats with separators. Returns an AddressInfo with extracted user information and cleaned address string.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ParsePersonInfo(str string) *AddressInfo
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JO-uTlJlTy7)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/formatter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Extract name and phone
|
||||
result1 := formatter.ParsePersonInfo("张三 13800138000 北京市朝阳区")
|
||||
fmt.Println("Example 1 - Name and phone:")
|
||||
fmt.Printf("Name: %s, Phone: %s, Address: %s\n", result1.Name, result1.Mobile, result1.Addr)
|
||||
|
||||
// Extract ID card number
|
||||
result2 := formatter.ParsePersonInfo("李四 110101199001011234 上海市")
|
||||
fmt.Println("\nExample 2 - ID card number:")
|
||||
fmt.Printf("Name: %s, ID Card: %s, Address: %s\n", result2.Name, result2.IDN, result2.Addr)
|
||||
|
||||
// Labeled format
|
||||
result3 := formatter.ParsePersonInfo("收货人:王五 电话:13900139000 收货地址:天津市河西区友谊路20号")
|
||||
jsonData3, _ := json.MarshalIndent(result3, "", " ")
|
||||
fmt.Println("\nExample 3 - Labeled format:")
|
||||
fmt.Println(string(jsonData3))
|
||||
|
||||
// Output:
|
||||
// Example 1 - Name and phone:
|
||||
// Name: 张三, Phone: 13800138000, Address: 北京市朝阳区
|
||||
//
|
||||
// Example 2 - ID card number:
|
||||
// Name: 李四, ID Card: 110101199001011234, Address: 上海市
|
||||
//
|
||||
// Example 3 - Labeled format:
|
||||
// {
|
||||
// "name": "王五",
|
||||
// "mobile": "13900139000",
|
||||
// "idn": "",
|
||||
// "postcode": "",
|
||||
// "province": "",
|
||||
// "city": "",
|
||||
// "region": "",
|
||||
// "street": "",
|
||||
// "addr": "天津市河西区友谊路20号"
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -81,7 +81,6 @@ import (
|
||||
- [FindValuesBy](#FindValuesBy)
|
||||
- [ToMarkdownTable](#ToMarkdownTable)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Documentation
|
||||
@@ -2376,8 +2375,8 @@ func main() {
|
||||
编辑
|
||||
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
|
||||
```
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/w_pSLfeyEB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -2424,10 +2423,10 @@ func main() {
|
||||
result = maputil.ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// output:
|
||||
// |salary|name|
|
||||
// |---|---|
|
||||
// |50000|Alice|
|
||||
// |60000|Bob|
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -70,7 +70,7 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
|
||||
|
||||
fmt.Println(s.ToMap())
|
||||
|
||||
// Output:
|
||||
@@ -516,7 +516,7 @@ func main() {
|
||||
func (s *Struct) TypeName() string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -533,11 +533,11 @@ func main() {
|
||||
}
|
||||
|
||||
p := &Parent{Age: 11}
|
||||
s := structs.New(p1)
|
||||
s := structs.New(p)
|
||||
|
||||
fmt.Println(s.TypeName())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// Parent
|
||||
}
|
||||
```
|
||||
@@ -610,12 +610,12 @@ func main() {
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
a, _ := s.Field("arr")
|
||||
|
||||
|
||||
fmt.Println(n.IsTargetType(reflect.String))
|
||||
fmt.Println(a.IsTargetType(reflect.Slice))
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -551,7 +551,7 @@ func main() {
|
||||
func IsEmail(email string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/Os9VaFlT33G)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/HVQ5LAe-vFz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -561,13 +561,28 @@ import (
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsEmail("abc@xyz.com")
|
||||
result2 := validator.IsEmail("a.b@@com")
|
||||
result2 := validator.IsEmail("user@domain.co")
|
||||
result3 := validator.IsEmail("test.user@example.org")
|
||||
result4 := validator.IsEmail("@abc@xyz.com")
|
||||
result5 := validator.IsEmail("a.b@@com")
|
||||
result6 := validator.IsEmail("a.b@com")
|
||||
result7 := validator.IsEmail("test@example")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
fmt.Println(result6)
|
||||
fmt.Println(result7)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
@@ -1582,7 +1597,7 @@ func main() {
|
||||
func IsPassport(passport, country string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/dvOiV2BW7Aw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1619,7 +1634,7 @@ func main() {
|
||||
func IsChineseHMPassport(hmPassport string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/xKG6spQTcY0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1647,4 +1662,4 @@ func main() {
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
19
enum/enum.go
19
enum/enum.go
@@ -27,6 +27,7 @@ type Item[T comparable] struct {
|
||||
}
|
||||
|
||||
// NewItem creates a new enum item.
|
||||
// Play: https://go.dev/play/p/8qNsLw01HD5
|
||||
func NewItem[T comparable](value T, name string) *Item[T] {
|
||||
return &Item[T]{value: value, name: name}
|
||||
}
|
||||
@@ -38,6 +39,7 @@ type Pair[T comparable] struct {
|
||||
}
|
||||
|
||||
// NewItemsFromPairs creates enum items from a slice of Pair structs.
|
||||
// Play: https://go.dev/play/p/xKnoGa7gnev
|
||||
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T] {
|
||||
if len(pairs) == 0 {
|
||||
return []*Item[T]{}
|
||||
@@ -52,11 +54,13 @@ func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T] {
|
||||
}
|
||||
|
||||
// Value returns the value of the enum item.
|
||||
// Play: https://go.dev/play/p/xKnoGa7gnev
|
||||
func (it *Item[T]) Value() T {
|
||||
return it.value
|
||||
}
|
||||
|
||||
// Name returns the name of the enum item.
|
||||
// Play: https://go.dev/play/p/xKnoGa7gnev
|
||||
func (it *Item[T]) Name() string {
|
||||
return it.name
|
||||
}
|
||||
@@ -67,6 +71,7 @@ func (it *Item[T]) String() string {
|
||||
}
|
||||
|
||||
// Valid checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
|
||||
// Play: https://go.dev/play/p/pA3lYY2VSm3
|
||||
func (it *Item[T]) Valid(checker ...func(T) bool) bool {
|
||||
if len(checker) > 0 {
|
||||
return checker[0](it.value) && it.name != ""
|
||||
@@ -76,6 +81,7 @@ func (it *Item[T]) Valid(checker ...func(T) bool) bool {
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
// Play: https://go.dev/play/p/zIZEdAnneB5
|
||||
func (it *Item[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"value": it.value,
|
||||
@@ -84,6 +90,7 @@ func (it *Item[T]) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
// Play: https://go.dev/play/p/zIZEdAnneB5
|
||||
func (it *Item[T]) UnmarshalJSON(data []byte) error {
|
||||
type alias struct {
|
||||
Value any `json:"value"`
|
||||
@@ -156,6 +163,7 @@ type Registry[T comparable] struct {
|
||||
}
|
||||
|
||||
// NewRegistry creates a new enum registry.
|
||||
// Play: https://go.dev/play/p/ABEXsYfJKMo
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
|
||||
r := &Registry[T]{
|
||||
values: make(map[T]*Item[T]),
|
||||
@@ -169,6 +177,7 @@ func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
|
||||
}
|
||||
|
||||
// Add adds enum items to the registry.
|
||||
// Play: https://go.dev/play/p/ABEXsYfJKMo
|
||||
func (r *Registry[T]) Add(items ...*Item[T]) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -188,6 +197,7 @@ func (r *Registry[T]) Add(items ...*Item[T]) {
|
||||
}
|
||||
|
||||
// Remove removes an enum item from the registry by its value.
|
||||
// Play: https://go.dev/play/p/dSG84wQ3TuC
|
||||
func (r *Registry[T]) Remove(value T) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -207,6 +217,7 @@ func (r *Registry[T]) Remove(value T) bool {
|
||||
}
|
||||
|
||||
// Update updates the name of an enum item in the registry by its value.
|
||||
// Play: https://go.dev/play/p/Ol0moT1J9Xl
|
||||
func (r *Registry[T]) Update(value T, newName string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -221,6 +232,7 @@ func (r *Registry[T]) Update(value T, newName string) bool {
|
||||
}
|
||||
|
||||
// GetByValue retrieves an enum item by its value.
|
||||
// Play: https://go.dev/play/p/niJ1U2KlE_m
|
||||
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -230,6 +242,7 @@ func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
|
||||
}
|
||||
|
||||
// GetByName retrieves an enum item by its name.
|
||||
// Play: https://go.dev/play/p/49ie_gpqH0m
|
||||
func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -239,6 +252,7 @@ func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
|
||||
}
|
||||
|
||||
// Items returns a slice of all enum items in the registry.
|
||||
// Play: https://go.dev/play/p/lAJFAradbvQ
|
||||
func (r *Registry[T]) Items() []*Item[T] {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -249,12 +263,14 @@ func (r *Registry[T]) Items() []*Item[T] {
|
||||
}
|
||||
|
||||
// Contains checks if an enum item with the given value exists in the registry.
|
||||
// Play: https://go.dev/play/p/_T-lPYkZn2j
|
||||
func (r *Registry[T]) Contains(value T) bool {
|
||||
_, ok := r.GetByValue(value)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns the number of enum items in the registry.
|
||||
// Play: https://go.dev/play/p/TeDArWhlQe2
|
||||
func (r *Registry[T]) Size() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -263,6 +279,7 @@ func (r *Registry[T]) Size() int {
|
||||
}
|
||||
|
||||
// Range iterates over all enum items in the registry and applies the given function.
|
||||
// Play: https://go.dev/play/p/GPsZbQbefWN
|
||||
func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -275,6 +292,7 @@ func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
|
||||
}
|
||||
|
||||
// SortedItems returns a slice of all enum items sorted by the given less function.
|
||||
// Play: https://go.dev/play/p/tN9RE_m_WEI
|
||||
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T] {
|
||||
items := r.Items()
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
@@ -284,6 +302,7 @@ func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
|
||||
}
|
||||
|
||||
// Filter returns a slice of enum items that satisfy the given predicate function.
|
||||
// Play: https://go.dev/play/p/uTUpTdcyoCU
|
||||
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T] {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
@@ -24,9 +24,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
// FileReader is a reader supporting offset seeking and reading one
|
||||
@@ -283,21 +284,18 @@ func ReadFileByLine(path string) ([]string, error) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
result := make([]string, 0)
|
||||
buf := bufio.NewReader(f)
|
||||
|
||||
for {
|
||||
line, _, err := buf.ReadLine()
|
||||
l := string(line)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for scanner.Scan() {
|
||||
l := scanner.Text()
|
||||
result = append(result, l)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
494
formatter/address.go
Normal file
494
formatter/address.go
Normal file
@@ -0,0 +1,494 @@
|
||||
// Package formatter implements some functions to format string, struct.
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// AddressInfo represents the parsed address information including user details and location.
|
||||
// AddressInfo 表示解析后的地址信息,包括用户详细信息和位置信息
|
||||
type AddressInfo struct {
|
||||
Name string `json:"name"` // Name of the recipient / 姓名
|
||||
Mobile string `json:"mobile"` // Mobile phone number or landline / 手机号或座机
|
||||
IDN string `json:"idn"` // ID card number / 身份证号
|
||||
Postcode string `json:"postcode"` // Postal code / 邮编
|
||||
Province string `json:"province"` // Province / 省
|
||||
City string `json:"city"` // City / 市
|
||||
Region string `json:"region"` // District or county / 区/县
|
||||
Street string `json:"street"` // Street address / 街道详细地址
|
||||
Addr string `json:"addr"` // Original address string / 原始地址字符串
|
||||
}
|
||||
|
||||
// fuzzyResult represents the result of fuzzy address parsing.
|
||||
// fuzzyResult 表示模糊地址解析的结果
|
||||
type fuzzyResult struct {
|
||||
A1 string // Province level / 省级
|
||||
A2 string // City level / 市级
|
||||
A3 string // District/County level / 区/县级
|
||||
Street string // Street address / 街道地址
|
||||
}
|
||||
|
||||
// ParseCNAddress parses a Chinese address string intelligently and extracts structured information.
|
||||
// It can parse addresses with or without user information (name, phone, ID card, etc.).
|
||||
// When withUser is true, it extracts user information from the address string.
|
||||
// When withUser is false, it only parses the location information.
|
||||
// The function handles various address formats including:
|
||||
// - Standard format: "Province City District Street"
|
||||
// - Compact format: "Name Phone Province City District Street"
|
||||
// - With keywords: "Name: xxx Phone: xxx Address: xxx"
|
||||
// - County-level cities: "Province City CountyCity District" (e.g., "河北省石家庄市新乐市")
|
||||
// ParseCNAddress 智能解析中国地址字符串并提取结构化信息。
|
||||
// 可以解析带或不带用户信息(姓名、电话、身份证等)的地址。
|
||||
// 当 withUser 为 true 时,从地址字符串中提取用户信息。
|
||||
// 当 withUser 为 false 时,仅解析位置信息。
|
||||
// 该函数处理多种地址格式,包括:
|
||||
// - 标准格式:"省 市 区 街道"
|
||||
// - 紧凑格式:"姓名 电话 省 市 区 街道"
|
||||
// - 带关键词:"姓名:xxx 电话:xxx 地址:xxx"
|
||||
// - 县级市:"省 市 县级市 区"(如"河北省石家庄市新乐市")
|
||||
func ParseCNAddress(str string, withUser bool) *AddressInfo {
|
||||
result := &AddressInfo{}
|
||||
|
||||
if withUser {
|
||||
ParsePersonInfo := ParsePersonInfo(str)
|
||||
result = ParsePersonInfo
|
||||
} else {
|
||||
result.Addr = str
|
||||
}
|
||||
|
||||
fuzz := fuzz(result.Addr)
|
||||
parse := parse(fuzz.A1, fuzz.A2, fuzz.A3)
|
||||
|
||||
result.Province = parse.Province
|
||||
result.City = parse.City
|
||||
result.Region = parse.Region
|
||||
|
||||
// 提取街道地址:从原始地址中找到区/县的位置,提取后面的内容
|
||||
if result.Region != "" && result.Addr != "" {
|
||||
// 在原始地址中查找区/县的位置(转换为rune数组以正确处理中文)
|
||||
addrRunes := []rune(result.Addr)
|
||||
regionRunes := []rune(result.Region)
|
||||
regionPos := mbStrpos(result.Addr, result.Region)
|
||||
|
||||
if regionPos != -1 {
|
||||
// 提取区/县后面的内容作为街道地址
|
||||
streetStart := regionPos + len(regionRunes)
|
||||
if streetStart < len(addrRunes) {
|
||||
result.Street = string(addrRunes[streetStart:])
|
||||
}
|
||||
} else if fuzz.Street != "" {
|
||||
// 如果没找到区/县,使用fuzz返回的街道
|
||||
result.Street = fuzz.Street
|
||||
}
|
||||
} else if fuzz.Street != "" {
|
||||
result.Street = fuzz.Street
|
||||
}
|
||||
|
||||
// 清理街道地址中的重复省市区信息(可能存在部分匹配的残留)
|
||||
result.Street = strings.ReplaceAll(result.Street, result.Region, "")
|
||||
result.Street = strings.ReplaceAll(result.Street, result.City, "")
|
||||
result.Street = strings.ReplaceAll(result.Street, result.Province, "")
|
||||
// 清理街道地址中的残留片段(如"自治区直辖县级市"被替换后的残留)
|
||||
result.Street = strings.ReplaceAll(result.Street, "自治区直辖县级市", "")
|
||||
result.Street = strings.ReplaceAll(result.Street, "直辖县级市", "")
|
||||
result.Street = strings.TrimSpace(result.Street)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ParsePersonInfo extracts user information (name, phone, ID card, postal code) from an address string.
|
||||
// It separates personal information from the address, supporting various formats:
|
||||
// - Labeled format: "Name: xxx Phone: xxx Address: xxx"
|
||||
// - Compact format: "Name Phone Address" (e.g., "张三13800138000北京市朝阳区")
|
||||
// - With separators: using colons, commas, newlines as delimiters
|
||||
// Returns an AddressInfo with extracted user information and cleaned address string in Addr field.
|
||||
// ParsePersonInfo 从地址字符串中提取用户信息(姓名、电话、身份证、邮编)。
|
||||
// 将个人信息与地址分离,支持多种格式:
|
||||
// - 带标签格式:"姓名:xxx 电话:xxx 地址:xxx"
|
||||
// - 紧凑格式:"姓名 电话 地址"(如"张三13800138000北京市朝阳区")
|
||||
// - 带分隔符:使用冒号、逗号、换行符作为分隔符
|
||||
// 返回包含提取的用户信息和清理后地址字符串(在 Addr 字段中)的 AddressInfo。
|
||||
func ParsePersonInfo(str string) *AddressInfo {
|
||||
compose := &AddressInfo{}
|
||||
|
||||
// 先尝试提取带标签的信息
|
||||
// 提取姓名 (支持: 姓名:xxx, 收货人:xxx, 收件人:xxx)
|
||||
nameRe := regexp.MustCompile(`(?:姓名|收货人|收件人)[::]\s*([^\s\d\n]+)`)
|
||||
if match := nameRe.FindStringSubmatch(str); len(match) > 1 {
|
||||
compose.Name = strings.TrimSpace(match[1])
|
||||
str = nameRe.ReplaceAllString(str, " ")
|
||||
}
|
||||
|
||||
// 提取手机号或座机号 (支持: 电话:xxx, 手机:xxx, 联系电话:xxx)
|
||||
phoneRe := regexp.MustCompile(`(?:电话|手机号码|手机|联系电话)[::]\s*([\d\-]+)`)
|
||||
if match := phoneRe.FindStringSubmatch(str); len(match) > 1 {
|
||||
compose.Mobile = strings.TrimSpace(match[1])
|
||||
str = phoneRe.ReplaceAllString(str, " ")
|
||||
}
|
||||
|
||||
// 提取所在地区 (支持: 所在地区:xxx)
|
||||
regionRe := regexp.MustCompile(`所在地区[::]\s*([^\n]+)`)
|
||||
if match := regionRe.FindStringSubmatch(str); len(match) > 1 {
|
||||
// 将所在地区保留在字符串中,不删除
|
||||
// str 保持不变,让后续的地址解析处理
|
||||
}
|
||||
|
||||
// 提取详细地址 (支持: 详细地址:xxx, 收货地址:xxx, 地址:xxx)
|
||||
addrRe := regexp.MustCompile(`(?:详细地址|收货地址|地址)[::]\s*([^\n]+)`)
|
||||
if match := addrRe.FindStringSubmatch(str); len(match) > 1 {
|
||||
// 保留详细地址在字符串中
|
||||
str = addrRe.ReplaceAllString(str, " "+match[1])
|
||||
}
|
||||
|
||||
// 如果还没有提取到姓名和手机号,尝试识别紧凑格式 (如: 马云13593464918陕西省...)
|
||||
if compose.Name == "" && compose.Mobile == "" {
|
||||
// 匹配: 2-4个汉字 + 7-12位数字 + 剩余内容
|
||||
compactRe := regexp.MustCompile(`^([\x{4e00}-\x{9fa5}]{2,4})(\d{7,12})(.*)$`)
|
||||
if match := compactRe.FindStringSubmatch(str); len(match) > 3 {
|
||||
compose.Name = match[1]
|
||||
compose.Mobile = match[2]
|
||||
str = match[3] // 保留剩余的地址部分
|
||||
}
|
||||
}
|
||||
|
||||
// 替换常见的地址关键词为空格
|
||||
replacements := map[string]string{
|
||||
"收货地址": " ", "详细地址": " ", "地址": " ", "收货人": " ",
|
||||
"收件人": " ", "收货": " ", "所在地区": " ", "邮编": " ",
|
||||
"电话": " ", "手机号码": " ", "身份证号码": " ", "身份证号": " ",
|
||||
"身份证": " ", "姓名": " ", "联系电话": " ", "手机": " ",
|
||||
":": " ", ":": " ", ";": " ", ";": " ",
|
||||
",": " ", ",": " ", "。": " ", "\n": " ", "\r": " ",
|
||||
}
|
||||
|
||||
for old, new := range replacements {
|
||||
str = strings.ReplaceAll(str, old, new)
|
||||
}
|
||||
|
||||
// 将多个空格合并为一个
|
||||
spaceRe := regexp.MustCompile(`\s{1,}`)
|
||||
str = spaceRe.ReplaceAllString(str, " ")
|
||||
|
||||
// 处理座机号格式 (如: 800-8585222)
|
||||
telRe := regexp.MustCompile(`(\d{3,4})-(\d{6,8})`)
|
||||
str = telRe.ReplaceAllString(str, "$1$2")
|
||||
|
||||
// 提取身份证号 (18位或17位+X)
|
||||
idnRe := regexp.MustCompile(`\d{18}|\d{17}[Xx]`)
|
||||
if match := idnRe.FindString(str); match != "" {
|
||||
compose.IDN = strings.ToUpper(match)
|
||||
str = strings.ReplaceAll(str, match, "")
|
||||
}
|
||||
|
||||
// 如果之前没有提取到手机号,现在提取
|
||||
if compose.Mobile == "" {
|
||||
mobileRe := regexp.MustCompile(`\d{7,12}`)
|
||||
if match := mobileRe.FindString(str); match != "" {
|
||||
compose.Mobile = match
|
||||
str = strings.ReplaceAll(str, match, "")
|
||||
}
|
||||
} else {
|
||||
// 已经提取过手机号,从字符串中删除
|
||||
str = strings.ReplaceAll(str, compose.Mobile, "")
|
||||
}
|
||||
|
||||
// 提取邮编
|
||||
postcodeRe := regexp.MustCompile(`\d{6}`)
|
||||
if match := postcodeRe.FindString(str); match != "" {
|
||||
compose.Postcode = match
|
||||
str = strings.ReplaceAll(str, match, "")
|
||||
}
|
||||
|
||||
// 清理多余空格
|
||||
str = strings.TrimSpace(spaceRe.ReplaceAllString(str, " "))
|
||||
|
||||
// 如果之前没有提取到姓名,现在提取
|
||||
if compose.Name == "" {
|
||||
// 提取姓名(取最短的词作为姓名,排除空字符串)
|
||||
splitArr := strings.Split(str, " ")
|
||||
if len(splitArr) > 0 {
|
||||
for _, value := range splitArr {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if compose.Name == "" {
|
||||
compose.Name = value
|
||||
} else if utf8.RuneCountInString(value) < utf8.RuneCountInString(compose.Name) && utf8.RuneCountInString(value) >= 2 {
|
||||
compose.Name = value
|
||||
}
|
||||
}
|
||||
if compose.Name != "" {
|
||||
str = strings.TrimSpace(strings.ReplaceAll(str, compose.Name, ""))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 已经提取过姓名,从字符串中删除
|
||||
str = strings.TrimSpace(strings.ReplaceAll(str, compose.Name, ""))
|
||||
}
|
||||
|
||||
compose.Addr = str
|
||||
return compose
|
||||
}
|
||||
|
||||
// fuzz 根据统计规律分析出二三级地址
|
||||
func fuzz(addr string) *fuzzyResult {
|
||||
addrOrigin := addr
|
||||
addr = strings.ReplaceAll(addr, " ", "")
|
||||
addr = strings.ReplaceAll(addr, ",", "")
|
||||
// 先替换"自治区直辖县级市"为"市",避免后续"自治区"替换时产生问题
|
||||
addr = strings.ReplaceAll(addr, "自治区直辖县级市", "市")
|
||||
addr = strings.ReplaceAll(addr, "自治区", "省")
|
||||
addr = strings.ReplaceAll(addr, "自治州", "州")
|
||||
addr = strings.ReplaceAll(addr, "小区", "")
|
||||
addr = strings.ReplaceAll(addr, "校区", "")
|
||||
// 过滤"市辖区" - 这是一个行政术语占位符,不是真正的区名
|
||||
addr = strings.ReplaceAll(addr, "市辖区", "")
|
||||
|
||||
a1 := ""
|
||||
a2 := ""
|
||||
a3 := ""
|
||||
street := ""
|
||||
|
||||
deep3KeywordPos := -1
|
||||
|
||||
// 判断是否包含县/区/旗
|
||||
countyPos := mbStrpos(addr, "县")
|
||||
districtPos := mbStrpos(addr, "区")
|
||||
bannerPos := mbStrpos(addr, "旗")
|
||||
|
||||
// 只要存在这些关键词就处理,不再限制位置
|
||||
hasEarlyCounty := countyPos != -1
|
||||
hasEarlyDistrict := districtPos != -1
|
||||
hasEarlyBanner := bannerPos != -1
|
||||
|
||||
if hasEarlyCounty || hasEarlyDistrict || hasEarlyBanner {
|
||||
// 优先检查是否存在县级市(如"新乐市")
|
||||
// 如果同时存在"XX市"和"XX区"/"XX县",优先处理"市"
|
||||
hasCountyLevelCity := false
|
||||
if mbStrstr(addr, "市") {
|
||||
// 查找所有"市"的位置
|
||||
cityCount := mbSubstrCount(addr, "市")
|
||||
if cityCount >= 2 {
|
||||
// 找到第二个"市"的位置(可能是县级市)
|
||||
firstCityPos := mbStrpos(addr, "市")
|
||||
// 从第一个"市"之后继续查找
|
||||
addrAfterFirstCity := mbSubstr(addr, firstCityPos+1, utf8.RuneCountInString(addr)-firstCityPos-1)
|
||||
secondCityPos := mbStrpos(addrAfterFirstCity, "市")
|
||||
if secondCityPos != -1 {
|
||||
secondCityAbsPos := firstCityPos + 1 + secondCityPos
|
||||
// 检查第二个"市"后面是否存在"区"或"县"
|
||||
addrAfterSecondCity := mbSubstr(addr, secondCityAbsPos+1, utf8.RuneCountInString(addr)-secondCityAbsPos-1)
|
||||
if mbStrstr(addrAfterSecondCity, "区") || mbStrstr(addrAfterSecondCity, "县") {
|
||||
// 提取两个"市"之间的内容
|
||||
betweenCities := mbSubstr(addr, firstCityPos+1, secondCityAbsPos-firstCityPos)
|
||||
// 检查是否是重复的地名(如"北京市北京市"或"杭州市西湖区杭州市")
|
||||
// 如果两个"市"之间包含"区"或"县",说明不是县级市,而是重复地名
|
||||
if !mbStrstr(betweenCities, "区") && !mbStrstr(betweenCities, "县") {
|
||||
// 第一个"市"及之前的内容
|
||||
firstCityFull := mbSubstr(addr, 0, firstCityPos+1)
|
||||
if betweenCities != firstCityFull {
|
||||
// 不是重复地名,这是县级市
|
||||
a3 = betweenCities
|
||||
deep3KeywordPos = secondCityAbsPos
|
||||
hasCountyLevelCity = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasCountyLevelCity {
|
||||
// 处理旗
|
||||
if mbStrstr(addr, "旗") {
|
||||
deep3KeywordPos = mbStrpos(addr, "旗")
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-1, 2)
|
||||
}
|
||||
|
||||
// 处理区
|
||||
if mbStrstr(addr, "区") {
|
||||
// 使用第一个"区"(避免重复地名干扰,如"西湖区杭州市西湖区")
|
||||
deep3KeywordPos = mbStrpos(addr, "区")
|
||||
|
||||
if mbStrstr(addr, "市") {
|
||||
// 策略:找到"区"之前的最后一个"市"
|
||||
// 这样可以避免详细地址中的"市"字干扰(如"农贸市场")
|
||||
zonePos := deep3KeywordPos
|
||||
// 从开头到"区"的子串中,查找最后一个"市"
|
||||
addrBeforeZone := mbSubstr(addr, 0, zonePos)
|
||||
cityPos := mbStrripos(addrBeforeZone, "市")
|
||||
if cityPos != -1 {
|
||||
a3 = mbSubstr(addr, cityPos+1, zonePos-cityPos)
|
||||
} else {
|
||||
// 没有找到"市",使用默认逻辑
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
}
|
||||
} else {
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理县
|
||||
if mbStrstr(addr, "县") {
|
||||
// 使用第一个"县"(避免重复地名干扰)
|
||||
deep3KeywordPos = mbStrpos(addr, "县")
|
||||
if mbStrstr(addr, "市") {
|
||||
// 从开头到"县"的子串中,查找最后一个"市"
|
||||
addrBeforeCounty := mbSubstr(addr, 0, deep3KeywordPos)
|
||||
cityPos := mbStrripos(addrBeforeCounty, "市")
|
||||
if cityPos != -1 {
|
||||
a3 = mbSubstr(addr, cityPos+1, deep3KeywordPos-cityPos)
|
||||
} else {
|
||||
if mbStrstr(addr, "自治县") {
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-6, 7)
|
||||
firstChar := mbSubstr(a3, 0, 1)
|
||||
if firstChar == "省" || firstChar == "市" || firstChar == "州" {
|
||||
a3 = mbSubstr(a3, 1, utf8.RuneCountInString(a3)-1)
|
||||
}
|
||||
} else {
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if mbStrstr(addr, "自治县") {
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-6, 7)
|
||||
firstChar := mbSubstr(a3, 0, 1)
|
||||
if firstChar == "省" || firstChar == "市" || firstChar == "州" {
|
||||
a3 = mbSubstr(a3, 1, utf8.RuneCountInString(a3)-1)
|
||||
}
|
||||
} else {
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if deep3KeywordPos != -1 {
|
||||
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
|
||||
}
|
||||
} else {
|
||||
// 处理市
|
||||
if mbStrripos(addr, "市") != -1 {
|
||||
cityCount := mbSubstrCount(addr, "市")
|
||||
if cityCount == 1 {
|
||||
deep3KeywordPos = mbStrripos(addr, "市")
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
|
||||
} else if cityCount >= 2 {
|
||||
deep3KeywordPos = mbStrripos(addr, "市")
|
||||
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
|
||||
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
|
||||
}
|
||||
} else {
|
||||
a3 = ""
|
||||
street = addr
|
||||
}
|
||||
}
|
||||
|
||||
// 提取市级地址
|
||||
if mbStrpos(addr, "市") != -1 || mbStrstr(addr, "盟") || mbStrstr(addr, "州") {
|
||||
tmpPos := -1
|
||||
if tmpPos = mbStrpos(addr, "市"); tmpPos != -1 {
|
||||
// 使用第一个"市"(避免重复地名干扰,如"杭州市西湖区杭州市")
|
||||
// 向前查找省的位置,如果有省就从省后开始,否则从开头开始
|
||||
addrBeforeCity := mbSubstr(addr, 0, tmpPos)
|
||||
provincePos := mbStrripos(addrBeforeCity, "省")
|
||||
startPos := 0
|
||||
if provincePos != -1 {
|
||||
startPos = provincePos + 1
|
||||
}
|
||||
a2 = mbSubstr(addr, startPos, tmpPos-startPos+1)
|
||||
} else if tmpPos = mbStrpos(addr, "盟"); tmpPos != -1 {
|
||||
a2 = mbSubstr(addr, tmpPos-2, 3)
|
||||
} else if mbStrpos(addr, "州") != -1 {
|
||||
if tmpPos = mbStrpos(addr, "自治州"); tmpPos != -1 {
|
||||
a2 = mbSubstr(addr, tmpPos-4, 5)
|
||||
} else {
|
||||
tmpPos = mbStrpos(addr, "州")
|
||||
a2 = mbSubstr(addr, tmpPos-2, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &fuzzyResult{
|
||||
A1: a1,
|
||||
A2: a2,
|
||||
A3: a3,
|
||||
Street: street,
|
||||
}
|
||||
}
|
||||
|
||||
// parse 智能解析出省市区
|
||||
func parse(a1, a2, a3 string) *AddressInfo {
|
||||
r := &AddressInfo{}
|
||||
|
||||
if a3 == "" {
|
||||
return r
|
||||
}
|
||||
|
||||
// 在三级地址数据中查找匹配
|
||||
area3Matches := make(map[int]*Region)
|
||||
for id, v := range A3Data {
|
||||
if mbStrpos(v.Name, a3) != -1 {
|
||||
area3Matches[id] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 多个匹配项,需要通过二级地址筛选
|
||||
if len(area3Matches) > 1 {
|
||||
if a2 != "" {
|
||||
area2Matches := make(map[int]*Region)
|
||||
for id, v := range A2Data {
|
||||
if mbStrpos(v.Name, a2) != -1 {
|
||||
area2Matches[id] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(area2Matches) > 0 {
|
||||
for _, v := range area3Matches {
|
||||
if city, ok := area2Matches[v.PID]; ok {
|
||||
r.City = city.Name
|
||||
r.Region = v.Name
|
||||
if province, ok := A1Data[city.PID]; ok {
|
||||
r.Province = province.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.Province = ""
|
||||
r.City = ""
|
||||
r.Region = a3
|
||||
}
|
||||
} else if len(area3Matches) == 1 {
|
||||
// 唯一匹配
|
||||
for _, v := range area3Matches {
|
||||
r.Region = v.Name
|
||||
if city, ok := A2Data[v.PID]; ok {
|
||||
r.City = city.Name
|
||||
if province, ok := A1Data[city.PID]; ok {
|
||||
r.Province = province.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(area3Matches) == 0 && a2 == a3 {
|
||||
// 没有匹配到三级地址,但二级地址等于三级地址,可能是直辖市
|
||||
shengID := 0
|
||||
for _, v := range A2Data {
|
||||
if mbStrpos(v.Name, a2) != -1 {
|
||||
r.City = v.Name
|
||||
shengID = v.PID
|
||||
break
|
||||
}
|
||||
}
|
||||
if province, ok := A1Data[shengID]; ok {
|
||||
r.Province = province.Name
|
||||
}
|
||||
r.Region = ""
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
4470
formatter/address_data.go
Normal file
4470
formatter/address_data.go
Normal file
File diff suppressed because it is too large
Load Diff
75
formatter/address_helper.go
Normal file
75
formatter/address_helper.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// mbStrpos 返回字符串首次出现的位置(UTF-8字符计数)
|
||||
func mbStrpos(haystack, needle string) int {
|
||||
if needle == "" {
|
||||
return 0
|
||||
}
|
||||
idx := strings.Index(haystack, needle)
|
||||
if idx == -1 {
|
||||
return -1
|
||||
}
|
||||
return utf8.RuneCountInString(haystack[:idx])
|
||||
}
|
||||
|
||||
// mbStrripos 返回字符串最后出现的位置(UTF-8字符计数)
|
||||
func mbStrripos(haystack, needle string) int {
|
||||
if needle == "" {
|
||||
return utf8.RuneCountInString(haystack)
|
||||
}
|
||||
idx := strings.LastIndex(haystack, needle)
|
||||
if idx == -1 {
|
||||
return -1
|
||||
}
|
||||
return utf8.RuneCountInString(haystack[:idx])
|
||||
}
|
||||
|
||||
// mbStrstr 检查字符串是否包含子串
|
||||
func mbStrstr(haystack, needle string) bool {
|
||||
return strings.Contains(haystack, needle)
|
||||
}
|
||||
|
||||
// mbSubstr 截取字符串(UTF-8字符计数)
|
||||
// start: 起始位置(从0开始)
|
||||
// length: 截取长度(字符数)
|
||||
func mbSubstr(str string, start, length int) string {
|
||||
runes := []rune(str)
|
||||
strLen := len(runes)
|
||||
|
||||
// 处理负数起始位置
|
||||
if start < 0 {
|
||||
start = strLen + start
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 起始位置超出字符串长度
|
||||
if start >= strLen {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 计算结束位置
|
||||
end := start + length
|
||||
if end > strLen {
|
||||
end = strLen
|
||||
}
|
||||
if end < start {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(runes[start:end])
|
||||
}
|
||||
|
||||
// mbSubstrCount 统计子串出现次数
|
||||
func mbSubstrCount(haystack, needle string) int {
|
||||
if needle == "" {
|
||||
return 0
|
||||
}
|
||||
return strings.Count(haystack, needle)
|
||||
}
|
||||
360
formatter/address_test.go
Normal file
360
formatter/address_test.go
Normal file
@@ -0,0 +1,360 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCNAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
withUser bool
|
||||
want *AddressInfo
|
||||
}{
|
||||
{
|
||||
name: "完整地址信息",
|
||||
input: "张三 13800138000 北京市朝阳区建国路1号",
|
||||
withUser: true,
|
||||
want: &AddressInfo{
|
||||
Name: "张三",
|
||||
Mobile: "13800138000",
|
||||
Province: "北京",
|
||||
City: "北京市",
|
||||
Region: "朝阳区",
|
||||
Street: "建国路1号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "带身份证和邮编",
|
||||
input: "李四 18612345678 110101199001011234 100000 上海市浦东新区世纪大道100号",
|
||||
withUser: true,
|
||||
want: &AddressInfo{
|
||||
Name: "李四",
|
||||
Mobile: "18612345678",
|
||||
IDN: "110101199001011234",
|
||||
Postcode: "100000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "仅地址不含用户信息",
|
||||
input: "北京市海淀区中关村大街1号",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "北京",
|
||||
City: "北京市",
|
||||
Region: "海淀区",
|
||||
Street: "中关村大街1号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "带收货关键词",
|
||||
input: "收货人:王五 电话:13900139000 收货地址:天津市河西区友谊路20号",
|
||||
withUser: true,
|
||||
want: &AddressInfo{
|
||||
Name: "王五",
|
||||
Mobile: "13900139000",
|
||||
Province: "天津",
|
||||
City: "天津市",
|
||||
Region: "河西区",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "紧凑格式地址",
|
||||
input: "马云13593464918陕西省西安市雁塔区丈八沟街道高新四路南江国际",
|
||||
withUser: true,
|
||||
want: &AddressInfo{
|
||||
Name: "马云",
|
||||
Mobile: "13593464918",
|
||||
Province: "陕西省",
|
||||
City: "西安市",
|
||||
Region: "雁塔区",
|
||||
Street: "丈八沟街道高新四路南江国际",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "带座机号格式",
|
||||
input: "姓名:马云\n联系电话:800-8585222\n所在地区:河北省石家庄市新华区\n详细地址:中华北大街68号鹿城商务中心6号楼1413室",
|
||||
withUser: true,
|
||||
want: &AddressInfo{
|
||||
Name: "马云",
|
||||
Mobile: "800-8585222",
|
||||
Province: "河北省",
|
||||
City: "石家庄市",
|
||||
Region: "新华区",
|
||||
Street: "中华北大街68号鹿城商务中心6号楼1413室",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "北京市重复格式",
|
||||
input: "北京市北京市市辖区东城区",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "北京",
|
||||
City: "北京市",
|
||||
Region: "东城区",
|
||||
Street: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "河北省新乐市地址",
|
||||
input: "河北省石家庄市新乐市经济开发区兴工街10号来优品仓库",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "河北省",
|
||||
City: "石家庄市",
|
||||
Region: "新乐市",
|
||||
Street: "经济开发区兴工街10号来优品仓库",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "江苏仪征市地址",
|
||||
input: "江苏省扬州市仪征市真州镇解放东路99号",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "江苏省",
|
||||
City: "扬州市",
|
||||
Region: "仪征市",
|
||||
Street: "真州镇解放东路99号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "新疆石河子市地址",
|
||||
input: "新疆石河子市北三路25小区",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "新疆维吾尔自治区",
|
||||
City: "自治区直辖县级市",
|
||||
Region: "石河子市",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "新疆石河子市-简化格式省+县级市",
|
||||
input: "新疆维吾尔自治区石河子市",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "新疆维吾尔自治区",
|
||||
City: "自治区直辖县级市",
|
||||
Region: "石河子市",
|
||||
Street: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "新疆石河子市-完整行政区划表述",
|
||||
input: "新疆维吾尔自治区自治区直辖县级市石河子市",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "新疆维吾尔自治区",
|
||||
City: "自治区直辖县级市",
|
||||
Region: "石河子市",
|
||||
Street: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "浙江杭州西湖区重复地址",
|
||||
input: "浙江省杭州市西湖区杭州市西湖区人民政府109号",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "浙江省",
|
||||
City: "杭州市",
|
||||
Region: "西湖区",
|
||||
Street: "人民政府109号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "湖南长沙市重复地址",
|
||||
input: "湖南省长沙市岳麓区银盆岭街道长沙市人民政府长沙市政府大楼",
|
||||
withUser: false,
|
||||
want: &AddressInfo{
|
||||
Province: "湖南省",
|
||||
City: "长沙市",
|
||||
Region: "岳麓区",
|
||||
Street: "银盆岭街道人民政府政府大楼",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseCNAddress(tt.input, tt.withUser)
|
||||
|
||||
// 打印结果便于调试
|
||||
jsonData, _ := json.MarshalIndent(got, "", " ")
|
||||
t.Logf("Result: %s", jsonData)
|
||||
|
||||
// 验证主要字段
|
||||
if tt.want.Name != "" && got.Name != tt.want.Name {
|
||||
t.Errorf("Name = %v, want %v", got.Name, tt.want.Name)
|
||||
}
|
||||
if tt.want.Mobile != "" && got.Mobile != tt.want.Mobile {
|
||||
t.Errorf("Mobile = %v, want %v", got.Mobile, tt.want.Mobile)
|
||||
}
|
||||
if tt.want.Province != "" && got.Province != tt.want.Province {
|
||||
t.Errorf("Province = %v, want %v", got.Province, tt.want.Province)
|
||||
}
|
||||
if tt.want.City != "" && got.City != tt.want.City {
|
||||
t.Errorf("City = %v, want %v", got.City, tt.want.City)
|
||||
}
|
||||
if tt.want.Region != "" && got.Region != tt.want.Region {
|
||||
t.Errorf("Region = %v, want %v", got.Region, tt.want.Region)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePersonInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
verify func(*testing.T, *AddressInfo)
|
||||
}{
|
||||
{
|
||||
name: "提取姓名和手机号",
|
||||
input: "张三 13800138000 北京市朝阳区",
|
||||
verify: func(t *testing.T, got *AddressInfo) {
|
||||
if got.Name != "张三" {
|
||||
t.Errorf("Name = %v, want 张三", got.Name)
|
||||
}
|
||||
if got.Mobile != "13800138000" {
|
||||
t.Errorf("Mobile = %v, want 13800138000", got.Mobile)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "提取身份证号",
|
||||
input: "李四 110101199001011234 上海市",
|
||||
verify: func(t *testing.T, got *AddressInfo) {
|
||||
if got.Name != "李四" {
|
||||
t.Errorf("Name = %v, want 李四", got.Name)
|
||||
}
|
||||
if got.IDN != "110101199001011234" {
|
||||
t.Errorf("IDN = %v, want 110101199001011234", got.IDN)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "提取邮编",
|
||||
input: "王五 100000 天津市",
|
||||
verify: func(t *testing.T, got *AddressInfo) {
|
||||
if got.Name != "王五" {
|
||||
t.Errorf("Name = %v, want 王五", got.Name)
|
||||
}
|
||||
if got.Postcode != "100000" {
|
||||
t.Errorf("Postcode = %v, want 100000", got.Postcode)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParsePersonInfo(tt.input)
|
||||
jsonData, _ := json.MarshalIndent(got, "", " ")
|
||||
t.Logf("Result: %s", jsonData)
|
||||
tt.verify(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzz(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want *fuzzyResult
|
||||
}{
|
||||
{
|
||||
name: "包含区",
|
||||
input: "北京市朝阳区建国路1号",
|
||||
want: &fuzzyResult{
|
||||
A2: "北京市",
|
||||
A3: "朝阳区",
|
||||
Street: "建国路1号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "包含县",
|
||||
input: "河北省石家庄市正定县",
|
||||
want: &fuzzyResult{
|
||||
A2: "石家庄市",
|
||||
A3: "正定县",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "复杂街道地址",
|
||||
input: "浙江省杭州市拱墅区武林街道杭州锦麟宾馆中河片区",
|
||||
want: &fuzzyResult{
|
||||
A2: "杭州市",
|
||||
A3: "拱墅区",
|
||||
Street: "武林街道杭州锦麟宾馆中河片区",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "北京市重复格式",
|
||||
input: "北京市北京市市辖区东城区",
|
||||
want: &fuzzyResult{
|
||||
A2: "北京市",
|
||||
A3: "东城区",
|
||||
Street: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "详细地址包含市字",
|
||||
input: "北京市朝阳区建外大街1号国贸商城",
|
||||
want: &fuzzyResult{
|
||||
A2: "北京市",
|
||||
A3: "朝阳区",
|
||||
Street: "建外大街1号国贸商城",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "详细地址真的包含市字",
|
||||
input: "北京市朝阳区农贸市场路1号",
|
||||
want: &fuzzyResult{
|
||||
A2: "北京市",
|
||||
A3: "朝阳区",
|
||||
Street: "农贸市场路1号",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "河北省新乐市地址",
|
||||
input: "河北省石家庄市新乐市经济开发区兴工街10号来优品仓库",
|
||||
want: &fuzzyResult{
|
||||
A2: "石家庄市",
|
||||
A3: "新乐市",
|
||||
Street: "经济开发区兴工街10号来优品仓库",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := fuzz(tt.input)
|
||||
jsonData, _ := json.MarshalIndent(got, "", " ")
|
||||
t.Logf("Result: %s", jsonData)
|
||||
|
||||
if got.A2 != tt.want.A2 {
|
||||
t.Errorf("A2 = %v, want %v", got.A2, tt.want.A2)
|
||||
}
|
||||
if got.A3 != tt.want.A3 {
|
||||
t.Errorf("A3 = %v, want %v", got.A3, tt.want.A3)
|
||||
}
|
||||
if tt.want.Street != "" && got.Street != tt.want.Street {
|
||||
t.Errorf("Street = %v, want %v", got.Street, tt.want.Street)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleParseCNAddress() {
|
||||
// 解析包含用户信息的完整地址
|
||||
result := ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
|
||||
jsonData, _ := json.MarshalIndent(result, "", " ")
|
||||
println(string(jsonData))
|
||||
}
|
||||
|
||||
func ExampleParsePersonInfo() {
|
||||
// 分离用户信息
|
||||
result := ParsePersonInfo("收货人:李四 电话:18612345678 地址:上海市浦东新区世纪大道100号")
|
||||
jsonData, _ := json.MarshalIndent(result, "", " ")
|
||||
println(string(jsonData))
|
||||
}
|
||||
@@ -683,7 +683,7 @@ func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V)
|
||||
}
|
||||
|
||||
// ToMarkdownTable converts a slice of maps to a Markdown table.
|
||||
// Play: ttps://go.dev/play/p/todo
|
||||
// Play: ttps://go.dev/play/p/w_pSLfeyEB5
|
||||
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string {
|
||||
if len(data) == 0 {
|
||||
return "| |\n|---|\n"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -27,7 +27,14 @@ const (
|
||||
AllChars = Numeral + LowwerLetters + UpperLetters + SymbolChars
|
||||
)
|
||||
|
||||
var rn = rand.NewSource(time.Now().UnixNano())
|
||||
// var rn = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
// 每个 goroutine 独立的 rand.Rand,避免并发问题
|
||||
var randPool = sync.Pool{
|
||||
New: func() any {
|
||||
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
@@ -286,10 +293,9 @@ func nearestPowerOfTwo(cap int) int {
|
||||
|
||||
// random generate a random string based on given string range.
|
||||
func random(s string, length int) string {
|
||||
// 确保随机数生成器的种子是动态的
|
||||
pid := os.Getpid()
|
||||
timestamp := time.Now().UnixNano()
|
||||
rand.Seed(int64(pid) + timestamp)
|
||||
// 从 pool 中获取 rand.Rand(替代全局 rand)
|
||||
rn := randPool.Get().(*rand.Rand)
|
||||
defer randPool.Put(rn)
|
||||
|
||||
// 仿照strings.Builder
|
||||
// 创建一个长度为 length 的字节切片
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
@@ -376,3 +377,31 @@ func TestRandNumberOfLength(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestRandNumberOfLength")
|
||||
assert.Equal(6, len(strconv.Itoa(randi)))
|
||||
}
|
||||
|
||||
// TestRandStringConcurrent verifies RandString is safe under high concurrency.
|
||||
// Before the fix, this test may panic or trigger data races.
|
||||
// After the fix, it should always pass.
|
||||
func TestRandStringConcurrent(t *testing.T) {
|
||||
const (
|
||||
goroutines = 100
|
||||
iterations = 1000
|
||||
length = 32
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for g := 0; g < goroutines; g++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
s := RandString(length)
|
||||
if len(s) != length {
|
||||
t.Fatalf("unexpected string length: got %d, want %d", len(s), length)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func ToMap(v any) (map[string]any, error) {
|
||||
}
|
||||
|
||||
// TypeName return struct type name
|
||||
// Play: https://go.dev/play/p/todo
|
||||
// Play: https://go.dev/play/p/SWLWd0XBaBb
|
||||
func (s *Struct) TypeName() string {
|
||||
return s.rtype.Name()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -26,6 +25,7 @@ var (
|
||||
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||
// dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*(?:xn--[a-zA-Z0-9\-]{1,59}|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)$`)
|
||||
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
|
||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||
@@ -321,10 +321,9 @@ func IsDns(dns string) bool {
|
||||
}
|
||||
|
||||
// IsEmail check if the string is a email address.
|
||||
// Play: https://go.dev/play/p/Os9VaFlT33G
|
||||
// Play: https://go.dev/play/p/HVQ5LAe-vFz
|
||||
func IsEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
return emailMatcher.MatchString(strings.ToLower(email))
|
||||
}
|
||||
|
||||
// IsChineseMobile check if the string is chinese mobile number.
|
||||
@@ -634,7 +633,7 @@ func IsChinaUnionPay(cardNo string) bool {
|
||||
|
||||
// IsPassport checks if the passport number is valid for a given country.
|
||||
// country is a two-letter country code (ISO 3166-1 alpha-2).
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/dvOiV2BW7Aw
|
||||
func IsPassport(passport, country string) bool {
|
||||
if matcher, ok := passportMatcher[country]; ok {
|
||||
return matcher.MatchString(passport)
|
||||
@@ -645,7 +644,7 @@ func IsPassport(passport, country string) bool {
|
||||
|
||||
// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number.
|
||||
// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678).
|
||||
// Play: https://go.dev/play/p/TODO
|
||||
// Play: https://go.dev/play/p/xKG6spQTcY0
|
||||
func IsChineseHMPassport(hmPassport string) bool {
|
||||
return chineseHMPassportMatcher.MatchString(hmPassport)
|
||||
}
|
||||
|
||||
@@ -494,8 +494,12 @@ func TestIsEmail(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestIsEmail")
|
||||
|
||||
assert.Equal(true, IsEmail("abc@xyz.com"))
|
||||
assert.Equal(true, IsEmail("user@domain.co"))
|
||||
assert.Equal(true, IsEmail("test.user@example.org"))
|
||||
assert.Equal(false, IsEmail("@abc@xyz.com"))
|
||||
assert.Equal(false, IsEmail("a.b@@com"))
|
||||
assert.Equal(false, IsEmail("a.b@com"))
|
||||
assert.Equal(false, IsEmail("test@example"))
|
||||
}
|
||||
|
||||
func TestContainChinese(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user