mirror of
https://github.com/duke-git/lancet.git
synced 2026-03-01 00:35:28 +08:00
Compare commits
91 Commits
v2.3.5
...
c0841d7be5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0841d7be5 | ||
|
|
b3fd282b50 | ||
|
|
a1cebec9f2 | ||
|
|
88cf1600e8 | ||
|
|
0851b68b83 | ||
|
|
5c13fd4f2f | ||
|
|
889d0cc3d6 | ||
|
|
aa05027cab | ||
|
|
fabc3483ed | ||
|
|
30363242bb | ||
|
|
5d3964d81a | ||
|
|
6c3dc3e7d6 | ||
|
|
2a2e1ca551 | ||
|
|
e1821eed2c | ||
|
|
62f0a96d91 | ||
|
|
cbdc3971dd | ||
|
|
350450bb67 | ||
|
|
f407e51b24 | ||
|
|
3c6c3a14cf | ||
|
|
41bafdef92 | ||
|
|
fc624195c7 | ||
|
|
5b3a59e785 | ||
|
|
74abb2d3f1 | ||
|
|
3f12b34eea | ||
|
|
cd43004a91 | ||
|
|
3ac9461c00 | ||
|
|
309b07ae8a | ||
|
|
8fe56b6dc7 | ||
|
|
15a0dad0d8 | ||
|
|
93c777a418 | ||
|
|
5ff1c6578f | ||
|
|
7d4b9510a2 | ||
|
|
9f0ad2354a | ||
|
|
55b66dee99 | ||
|
|
4c64a16204 | ||
|
|
385e64cc52 | ||
|
|
be45a259db | ||
|
|
6f703fe577 | ||
|
|
cb8d93c499 | ||
|
|
ae1014c572 | ||
|
|
d5b9e67330 | ||
|
|
a81403766f | ||
|
|
6307d624cb | ||
|
|
2f9f8b3f3d | ||
|
|
db5d9407bb | ||
|
|
83c069e234 | ||
|
|
55ee000684 | ||
|
|
fc7f2509ca | ||
|
|
a97d27c32e | ||
|
|
c176ba378e | ||
|
|
a3a24fc381 | ||
|
|
9caf2ffb1c | ||
|
|
1a5c31fd02 | ||
|
|
c175b202de | ||
|
|
d818219672 | ||
|
|
539078e6b8 | ||
|
|
cdefbde9f5 | ||
|
|
1e57a743af | ||
|
|
7d4184a365 | ||
|
|
1b73483945 | ||
|
|
622aacaf44 | ||
|
|
e78ac65605 | ||
|
|
03f0d4d905 | ||
|
|
b2ae71c983 | ||
|
|
093f4a2286 | ||
|
|
f7ada6093c | ||
|
|
47e82aad39 | ||
|
|
9e813d236b | ||
|
|
72a23d2cb9 | ||
|
|
27667f8b3a | ||
|
|
0b5dc86d70 | ||
|
|
d88bba07dd | ||
|
|
d7f3354b98 | ||
|
|
f4dee28ebb | ||
|
|
c841a5b88c | ||
|
|
333038634b | ||
|
|
ef0fed23b2 | ||
|
|
1e0ee1fac1 | ||
|
|
4947327ed6 | ||
|
|
ceb706b874 | ||
|
|
3849337919 | ||
|
|
f4427b9fbc | ||
|
|
0ef45b533b | ||
|
|
169f280c9c | ||
|
|
e74126a0bd | ||
|
|
f7be4190f1 | ||
|
|
8089b71bfd | ||
|
|
5b9543255a | ||
|
|
bf859387f4 | ||
|
|
a8a92844f3 | ||
|
|
6dfdadd34e |
270
README.md
270
README.md
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
@@ -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)
|
||||
@@ -214,6 +216,30 @@ import "github.com/duke-git/lancet/v2/concurrency"
|
||||
- **<big>Tee</big>** : split one chanel into two channels, until cancel the context.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Tee)]
|
||||
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
||||
- **<big>NewKeyedLocker</big>** : KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>Do</big>** :acquires a lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Do)]
|
||||
[[play](https://go.dev/play/p/GzeyC33T5rw)]
|
||||
- **<big>NewRWKeyedLocker</big>** :RRWKeyedLocker is a read-write version of KeyedLocker.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewRWKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>RLock</big>** : acquires a read lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#RLock)]
|
||||
[[play](https://go.dev/play/p/ZrCr8sMo77T)]
|
||||
- **<big>Lock</big>** : acquires a write lock for the specified key and executes the provided function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Lock)]
|
||||
[[play](https://go.dev/play/p/WgAcXbOPKGk)]
|
||||
- **<big>NewTryKeyedLocker</big>** : TryKeyedLocker is a non-blocking version of KeyedLocker.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#NewTryKeyedLocker)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>TryLock</big>** : TryLock tries to acquire a lock for the specified key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#TryLock)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
- **<big>Unlock</big>** : Unlock releases the lock for the specified key.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Unlock)]
|
||||
[[play](https://go.dev/play/p/VG9qLvyetE2)]
|
||||
|
||||
<h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator... <a href="#index">index</a> </h3>
|
||||
|
||||
@@ -289,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)]
|
||||
@@ -331,7 +366,7 @@ import "github.com/duke-git/lancet/v2/convertor"
|
||||
- **<big>ToRawUrlBase64</big>** : converts a value to a string encoded in raw url Base64.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToRawUrlBase64)]
|
||||
[[play](https://go.dev/play/p/HwdDPFcza1O)]
|
||||
- **<big>ToBigInt</big>** : converts an integer of any supported type (int, int64, uint64, etc.) to *big.Int.
|
||||
- **<big>ToBigInt</big>** : converts an integer of any supported type (int, int64, uint64, etc.) to \*big.Int.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToBigInt)]
|
||||
[[play](https://go.dev/play/p/X3itkCxwB_x)]
|
||||
|
||||
@@ -488,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>
|
||||
|
||||
@@ -508,22 +567,22 @@ import "github.com/duke-git/lancet/v2/datetime"
|
||||
[[play](https://go.dev/play/p/nT1heB1KUUK)]
|
||||
- **<big>AddWeek</big>** : add or sub week to time.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddWeek)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/M9TqdMiaA2p)]
|
||||
- **<big>AddMonth</big>** : add or sub months to time.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonth)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/DLoiOnpLvsN)]
|
||||
- **<big>AddYear</big>** : add or sub year to the time.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYear)]
|
||||
[[play](https://go.dev/play/p/MqW2ujnBx10)]
|
||||
- **<big>AddDaySafe</big>** : add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddDaySafe)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/JTohZFpoDJ3)]
|
||||
- **<big>AddMonthSafe</big>** : add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddMonthSafe)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/KLw0lo6mbVW)]
|
||||
- **<big>AddYearSafe</big>** : Add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#AddYearSafe)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/KVGXWZZ54ZH)]
|
||||
- **<big>BeginOfMinute</big>** : return the date time at the begin of minute of specific date.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMinute)]
|
||||
[[play](https://go.dev/play/p/ieOLVJ9CiFT)]
|
||||
@@ -535,7 +594,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
||||
[[play](https://go.dev/play/p/94m_UT6cWs9)]
|
||||
- **<big>BeginOfWeek</big>** : return the date time at the begin of week of specific date.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfWeek)]
|
||||
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
|
||||
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
|
||||
- **<big>BeginOfMonth</big>** : return the date time at the begin of month of specific date.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMonth)]
|
||||
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
|
||||
@@ -553,7 +612,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
||||
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
|
||||
- **<big>EndOfWeek</big>** : return the date time at the end of week of specific date.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfWeek)]
|
||||
[[play](https://go.dev/play/p/i08qKXD9flf)]
|
||||
[[play](https://go.dev/play/p/mGSA162YgX9)]
|
||||
- **<big>EndOfMonth</big>** : return the date time at the end of month of specific date.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfMonth)]
|
||||
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
|
||||
@@ -693,7 +752,6 @@ import optional "github.com/duke-git/lancet/v2/datastructure/optional"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/hashmap.md)]
|
||||
- **<big>Optional</big>** : Optional container.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/optional.md)]
|
||||
|
||||
|
||||
<h3 id="eventbus"> 9. EventBus is an event bus used for handling events within an application. <a href="#index">Index</a></h3>
|
||||
|
||||
@@ -701,40 +759,103 @@ 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)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/gHbOPV_NUOJ)]
|
||||
- **<big>Subscribe</big>** : subscribes to an event with a specific event topic and listener function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Subscribe)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/EYGf_8cHei-)]
|
||||
- **<big>Unsubscribe</big>** : unsubscribes from an event with a specific event topic and listener function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Unsubscribe)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/Tmh7Ttfvprf)]
|
||||
- **<big>Publish</big>** : publishes an event with a specific event topic and data payload.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#Publish)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/gHTtVexFSH9)]
|
||||
- **<big>ClearListeners</big>** : clears all the listeners.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListeners)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/KBfBYlKPgqD)]
|
||||
- **<big>ClearListenersByTopic</big>** : clears all the listeners by topic.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#ClearListenersByTopic)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/gvMljmJOZmU)]
|
||||
- **<big>GetListenersCount</big>** : returns the number of listeners for a specific event topic.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetListenersCount)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/8VPJsMQgStM)]
|
||||
- **<big>GetAllListenersCount</big>** : returns the total number of all listeners.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetAllListenersCount)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/PUlr0xcpEOz)]
|
||||
- **<big>GetEvents</big>** : returns all the events topics.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#GetEvents)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[play](https://go.dev/play/p/etgjjcOtAjX)]
|
||||
- **<big>SetErrorHandler</big>** : sets the error handler function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#SetErrorHandler)]
|
||||
[[play](https://go.dev/play/p/todo)]
|
||||
[[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"
|
||||
@@ -778,6 +899,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
||||
- **<big>RemoveFile</big>** : remove file, param should be file path.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#RemoveFile)]
|
||||
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
||||
- **<big>RemoveDir</big>** : delete directory.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#RemoveDir)]
|
||||
[[play](https://go.dev/play/p/Oa6KnPek2uy)]
|
||||
- **<big>ReadFileToString</big>** : return string of file content.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ReadFileToString)]
|
||||
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
||||
@@ -833,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"
|
||||
@@ -862,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"
|
||||
@@ -926,8 +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"
|
||||
@@ -1097,9 +1226,14 @@ import "github.com/duke-git/lancet/v2/maputil"
|
||||
- **<big>GetOrDefault</big>** : returns the value of the given key or a default value if the key is not present.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrDefault)]
|
||||
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
||||
- **<big>FindValuesBy</big>** : returns a slice of values from the map that satisfy the given predicate function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#FindValuesBy)]
|
||||
[[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/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"
|
||||
@@ -1210,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"
|
||||
@@ -1282,8 +1416,14 @@ import "github.com/duke-git/lancet/v2/netutil"
|
||||
- **<big>IsTelnetConnected</big>** : checks if can if can telnet the specified host or not.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#IsTelnetConnected)]
|
||||
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
||||
- **<big>BuildUrl</big>** : builds a URL from the given params.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#BuildUrl)]
|
||||
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
|
||||
- **<big>AddQueryParams</big>** : adds query parameters to the given URL.
|
||||
[[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"
|
||||
@@ -1307,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"
|
||||
@@ -1373,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"
|
||||
@@ -1407,9 +1547,8 @@ import "github.com/duke-git/lancet/v2/retry"
|
||||
- **<big>RetryWithExponentialWithJitterBackoff</big>** : set exponential strategy backoff.
|
||||
[[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"
|
||||
@@ -1429,6 +1568,9 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>ContainSubSlice</big>** : check if the slice contain a given subslice or not.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainSubSlice)]
|
||||
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
|
||||
- **<big>ContainAny</big>** : check if the slice contains any element from the targets slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainAny)]
|
||||
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
|
||||
- **<big>Chunk</big>** : creates a slice of elements split into groups the length of size.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Chunk)]
|
||||
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
|
||||
@@ -1479,7 +1621,7 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[play](https://go.dev/play/p/b9iygtgsHI1)]
|
||||
- **<big>EqualUnordered</big>** : Checks if two slices are equal: the same length and all elements value are equal (unordered).
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#EqualUnordered)]
|
||||
[[play](todo)]
|
||||
[[play](https://go.dev/play/p/n8fSc2w8ZgX)]
|
||||
- **<big>Every</big>** : return true if all of the values in the slice pass the predicate function.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Every)]
|
||||
[[play](https://go.dev/play/p/R8U6Sl-j8cD)]
|
||||
@@ -1581,7 +1723,7 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
[[play](https://go.dev/play/p/YHvhnWGU3Ge)]
|
||||
- **<big>ShuffleCopy</big>** : return a new slice with elements shuffled.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ShuffleCopy)]
|
||||
[[play](todo)]
|
||||
[[play](https://go.dev/play/p/vqDa-Gs1vT0)]
|
||||
- **<big>IsAscending</big>** : Checks if a slice is ascending order.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#IsAscending)]
|
||||
[[play](https://go.dev/play/p/9CtsFjet4SH)]
|
||||
@@ -1654,7 +1796,7 @@ import "github.com/duke-git/lancet/v2/slice"
|
||||
- **<big>Partition</big>** : partition all slice elements with the evaluation of the given predicate functions.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Partition)]
|
||||
[[play](https://go.dev/play/p/lkQ3Ri2NQhV)]
|
||||
- **<big>Random</big>** : get a random item of slice, return its index, when slice is empty, return -1.
|
||||
- **<big>Random</big>** : get a random item of slice, return its index, when slice is empty, return -1.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Random)]
|
||||
[[play](https://go.dev/play/p/UzpGQptWppw)]
|
||||
- **<big>SetToDefaultIf</big>** : set elements to their default value if they match the given predicate.
|
||||
@@ -1678,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"
|
||||
@@ -1730,7 +1872,7 @@ import "github.com/duke-git/lancet/v2/stream"
|
||||
[[play](https://go.dev/play/p/A8_zkJnLHm4)]
|
||||
- **<big>ReverseCopy</big>** : returns a new slice of element order is reversed to the given slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#ReverseCopy)]
|
||||
[[play](todo)]
|
||||
[[play](https://go.dev/play/p/c9arEaP7Cg-)]
|
||||
- **<big>Range</big>** : returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#Range)]
|
||||
[[play](https://go.dev/play/p/indZY5V2f4j)]
|
||||
@@ -1777,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"
|
||||
@@ -1787,32 +1929,51 @@ import "github.com/duke-git/lancet/v2/structs"
|
||||
|
||||
- **<big>New</big>** : creates a `Struct` instance.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#New)]
|
||||
[[play](https://go.dev/play/p/O29l8kk-Z17)]
|
||||
- **<big>ToMap</big>** : converts a valid struct to a map.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#ToMap)]
|
||||
[[play](https://go.dev/play/p/qQbLySBgerZ)]
|
||||
- **<big>Fields</big>** : get all fields of a given struct, that the fields are abstract struct field.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Fields)]
|
||||
[[play](https://go.dev/play/p/w3Kk_CyVY7D)]
|
||||
- **<big>Field</big>** : get an abstract field of a struct by given field name
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Field)]
|
||||
[[play](https://go.dev/play/p/KocZMSYarza)]
|
||||
- **<big>IsStruct</big>** : check if the struct is valid.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsStruct)]
|
||||
[[play](https://go.dev/play/p/bU2FSdkbK1C)]
|
||||
- **<big>Tag</big>** : get a `Tag` of the `Field`, `Tag` is a abstract struct field tag.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Tag)]
|
||||
[[play](https://go.dev/play/p/DVrx5HvvUJr)]
|
||||
- **<big>Name</big>** : get the field name.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Name)]
|
||||
[[play](https://go.dev/play/p/zfIGlqsatee)]
|
||||
- **<big>Value</big>** : get the `Field` underlying value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Value)]
|
||||
[[play](https://go.dev/play/p/qufYEU2o4Oi)]
|
||||
- **<big>Kind</big>** : get the field's kind.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Kind)]
|
||||
[[play](https://go.dev/play/p/wg4NlcUNG5o)]
|
||||
- **<big>IsEmbedded</big>** : check if the field is an embedded field.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsEmbedded)]
|
||||
[[play](https://go.dev/play/p/wV2PrbYm3Ec)]
|
||||
- **<big>IsExported</big>** : check if the field is exported.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsExported)]
|
||||
[[play](https://go.dev/play/p/csK4AXYaNbJ)]
|
||||
- **<big>IsZero</big>** : check if the field is zero value.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsZero)]
|
||||
[[play](https://go.dev/play/p/RzqpGISf87r)]
|
||||
- **<big>IsSlice</big>** : check if the field is a slice.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsSlice)]
|
||||
[[play](https://go.dev/play/p/MKz4CgBIUrU)]
|
||||
- **<big>IsTargetType</big>** : check if the field is target type.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsTargetType)]
|
||||
[[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/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"
|
||||
@@ -1962,9 +2123,9 @@ import "github.com/duke-git/lancet/v2/strutil"
|
||||
[[play](https://go.dev/play/p/Ay9UIk7Rum9)]
|
||||
- **<big>FindAllOccurrences</big>** : Returns the positions of all occurrences of a substring in a string.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#FindAllOccurrences)]
|
||||
[[play](todo)]
|
||||
[[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"
|
||||
@@ -2012,8 +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"
|
||||
@@ -2130,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"
|
||||
@@ -2179,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)]
|
||||
@@ -2195,6 +2355,9 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
- **<big>IsNumberStr</big>** : check if the string can convert to a number.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsNumberStr)]
|
||||
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
||||
- **<big>IsAlphaNumeric</big>** : check if the string is alphanumeric.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsAlphaNumeric)]
|
||||
[[play](https://go.dev/play/p/RHeESLrLg9c)]
|
||||
- **<big>IsJSON</big>** : check if the string is valid JSON.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsJSON)]
|
||||
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
||||
@@ -2218,7 +2381,7 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
[[play](https://go.dev/play/p/AHA0r0AzIdC)]
|
||||
- **<big>IsIpPort</big>** : check if the string is ip:port.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsIpPort)]
|
||||
[[play](todo)]
|
||||
[[play](https://go.dev/play/p/xUmls_b9L29)]
|
||||
- **<big>IsStrongPassword</big>** : check if the string is strong password.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsStrongPassword)]
|
||||
[[play](https://go.dev/play/p/QHdVcSQ3uDg)]
|
||||
@@ -2267,8 +2430,14 @@ import "github.com/duke-git/lancet/v2/validator"
|
||||
- **<big>IsChinaUnionPay</big>** : check if a give string is a valid china union pay number or not.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChinaUnionPay)]
|
||||
[[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/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/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"
|
||||
@@ -2315,7 +2484,7 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
- **<big>TryUnwrap</big>** : check if err is nil then it returns a valid value. If err is not nil, TryUnwrap panics with err.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/xerror.md#TryUnwrap)]
|
||||
[[play](https://go.dev/play/p/acyZVkNZEeW)]
|
||||
- **<big>TryCatch</big>** : simple simulation of Java-style try-catch.
|
||||
- **<big>TryCatch</big>** : simple simulation of Java-style try-catch.
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/xerror.md#TryCatch)]
|
||||
[[play](https://go.dev/play/p/D5Mdb0mRj0P)]
|
||||
|
||||
@@ -2324,6 +2493,7 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
#### [Contribution Guide](./CONTRIBUTION.md)
|
||||
|
||||
## Contributors
|
||||
|
||||
Thank you to all the people who contributed to lancet!
|
||||
|
||||
<a href="https://github.com/duke-git/lancet/graphs/contributors">
|
||||
|
||||
444
README_zh-CN.md
444
README_zh-CN.md
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ package concurrency
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -199,3 +200,159 @@ func ExampleChannel_Bridge() {
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleKeyedLocker_Do() {
|
||||
locker := NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
|
||||
func ExampleRWKeyedLocker_Lock() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
|
||||
func ExampleRWKeyedLocker_RLock() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.RLock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
|
||||
func ExampleTryKeyedLocker() {
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
|
||||
func ExampleTryKeyedLocker_TryLock() {
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
if locker.TryLock(key) {
|
||||
time.Sleep(2 * time.Second)
|
||||
locker.Unlock(key)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
locker.Unlock(key)
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
// wait for the goroutine to finish
|
||||
<-done
|
||||
|
||||
fmt.Println("Retrying...")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Lock failed
|
||||
// Retrying...
|
||||
// Lock acquired
|
||||
// Lock released
|
||||
}
|
||||
|
||||
257
concurrency/keyed_locker.go
Normal file
257
concurrency/keyed_locker.go
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights reserved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel, locker.
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.
|
||||
type KeyedLocker[K comparable] struct {
|
||||
locks sync.Map
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type lockEntry struct {
|
||||
mu sync.Mutex
|
||||
ref int32
|
||||
timer atomic.Pointer[time.Timer]
|
||||
}
|
||||
|
||||
// NewKeyedLocker creates a new KeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K] {
|
||||
return &KeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// Do acquires a lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/GzeyC33T5rw
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(key, entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// acquire tries to acquire a lock for the specified key.
|
||||
func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
|
||||
lock, _ := l.locks.LoadOrStore(key, &lockEntry{})
|
||||
entry := lock.(*lockEntry)
|
||||
|
||||
atomic.AddInt32(&entry.ref, 1)
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// release releases the lock for the specified key.
|
||||
func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
|
||||
if atomic.AddInt32(&entry.ref, -1) == 0 {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
if entry.ref == 0 {
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
l.locks.Delete(rawKey)
|
||||
} else {
|
||||
if entry.timer.Load() == nil {
|
||||
t := time.AfterFunc(l.ttl, func() {
|
||||
l.release(key, entry, rawKey)
|
||||
})
|
||||
entry.timer.Store(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RWKeyedLocker is a read-write version of KeyedLocker.
|
||||
type RWKeyedLocker[K comparable] struct {
|
||||
locks sync.Map
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type rwLockEntry struct {
|
||||
mu sync.RWMutex
|
||||
ref int32
|
||||
timer atomic.Pointer[time.Timer]
|
||||
}
|
||||
|
||||
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
|
||||
// The TTL is used to automatically release locks that are no longer held.
|
||||
// Play: https://go.dev/play/p/CkaJWWwZm9
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K] {
|
||||
return &RWKeyedLocker[K]{ttl: ttl}
|
||||
}
|
||||
|
||||
// RLock acquires a read lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/ZrCr8sMo77T
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.RLock()
|
||||
defer entry.mu.RUnlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lock acquires a write lock for the specified key and executes the provided function.
|
||||
// It returns an error if the context is canceled before the function completes.
|
||||
// Play: https://go.dev/play/p/WgAcXbOPKGk
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
|
||||
entry := l.acquire(key)
|
||||
defer l.release(entry, key)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
entry.mu.Lock()
|
||||
defer entry.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
fn()
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// acquire tries to acquire a read lock for the specified key.
|
||||
func (l *RWKeyedLocker[K]) acquire(key K) *rwLockEntry {
|
||||
actual, _ := l.locks.LoadOrStore(key, &rwLockEntry{})
|
||||
entry := actual.(*rwLockEntry)
|
||||
atomic.AddInt32(&entry.ref, 1)
|
||||
|
||||
if t := entry.timer.Swap(nil); t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// release releases the lock for the specified key.
|
||||
func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
|
||||
if atomic.AddInt32(&entry.ref, -1) == 0 {
|
||||
timer := time.AfterFunc(l.ttl, func() {
|
||||
if atomic.LoadInt32(&entry.ref) == 0 {
|
||||
l.locks.Delete(rawKey)
|
||||
}
|
||||
})
|
||||
entry.timer.Store(timer)
|
||||
}
|
||||
}
|
||||
|
||||
// TryKeyedLocker is a non-blocking version of KeyedLocker.
|
||||
// It allows for trying to acquire a lock without blocking if the lock is already held.
|
||||
type TryKeyedLocker[K comparable] struct {
|
||||
mu sync.Mutex
|
||||
locks map[K]*casMutex
|
||||
}
|
||||
|
||||
// NewTryKeyedLocker creates a new TryKeyedLocker.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K] {
|
||||
return &TryKeyedLocker[K]{locks: make(map[K]*casMutex)}
|
||||
}
|
||||
|
||||
// TryLock tries to acquire a lock for the specified key.
|
||||
// It returns true if the lock was acquired, false otherwise.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool {
|
||||
l.mu.Lock()
|
||||
|
||||
lock, ok := l.locks[key]
|
||||
if !ok {
|
||||
lock = &casMutex{}
|
||||
l.locks[key] = lock
|
||||
}
|
||||
l.mu.Unlock()
|
||||
|
||||
return lock.TryLock()
|
||||
}
|
||||
|
||||
// Unlock releases the lock for the specified key.
|
||||
// Play: https://go.dev/play/p/VG9qLvyetE2
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
lock, ok := l.locks[key]
|
||||
if ok {
|
||||
lock.Unlock()
|
||||
if lock.lock == 0 {
|
||||
delete(l.locks, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// casMutex is a simple mutex that uses atomic operations to provide a non-blocking lock.
|
||||
type casMutex struct {
|
||||
lock int32
|
||||
}
|
||||
|
||||
// TryLock tries to acquire the lock without blocking.
|
||||
// It returns true if the lock was acquired, false otherwise.
|
||||
func (m *casMutex) TryLock() bool {
|
||||
return atomic.CompareAndSwapInt32(&m.lock, 0, 1)
|
||||
}
|
||||
|
||||
// Unlock releases the lock.
|
||||
func (m *casMutex) Unlock() {
|
||||
atomic.StoreInt32(&m.lock, 0)
|
||||
}
|
||||
230
concurrency/keyed_locker_test.go
Normal file
230
concurrency/keyed_locker_test.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package concurrency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestKeyedLocker_SerialExecutionSameKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_SerialExecutionSameKey")
|
||||
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
var result []int
|
||||
var mu sync.Mutex
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
err := locker.Do(context.Background(), "key1", func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
result = append(result, i)
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, len(result))
|
||||
}
|
||||
|
||||
func TestKeyedLocker_ParallelExecutionDifferentKeys(t *testing.T) {
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
start := time.Now()
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
key := "key" + strconv.Itoa(i)
|
||||
locker.Do(context.Background(), key, func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
})
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if elapsed > 100*time.Millisecond {
|
||||
t.Errorf("parallel execution took too long: %s", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyedLocker_ContextTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_ContextTimeout")
|
||||
|
||||
locker := NewKeyedLocker[string](100 * time.Millisecond)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Lock key before calling
|
||||
go func() {
|
||||
_ = locker.Do(context.Background(), "key-timeout", func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
})
|
||||
}()
|
||||
|
||||
time.Sleep(1 * time.Millisecond) // ensure lock is acquired first
|
||||
|
||||
err := locker.Do(ctx, "key-timeout", func() {
|
||||
t.Error("should not execute")
|
||||
})
|
||||
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestKeyedLocker_LockReleaseAfterTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewKeyedLocker[string](50 * time.Millisecond)
|
||||
|
||||
err := locker.Do(context.Background(), "ttl-key", func() {})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Wait for TTL to pass
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = locker.Do(context.Background(), "ttl-key", func() {})
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_LockAndUnlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewRWKeyedLocker[string](500 * time.Millisecond)
|
||||
|
||||
var locked bool
|
||||
err := locker.Lock(context.Background(), "key1", func() {
|
||||
locked = true
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
assert.Equal(true, locked)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_RLockParallel(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
|
||||
|
||||
locker := NewRWKeyedLocker[string](1 * time.Second)
|
||||
|
||||
var mu sync.Mutex
|
||||
var count int
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := locker.RLock(context.Background(), "shared-key", func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
mu.Lock()
|
||||
count++
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
assert.IsNil(err)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, count)
|
||||
}
|
||||
|
||||
func TestRWKeyedLocker_LockTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRWKeyedLocker_LockTimeout")
|
||||
|
||||
locker := NewRWKeyedLocker[string](1 * time.Second)
|
||||
|
||||
start := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
locker.Lock(context.Background(), "key-timeout", func() {
|
||||
close(start)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
})
|
||||
}()
|
||||
|
||||
<-start
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := locker.Lock(ctx, "key-timeout", func() {
|
||||
t.Error("should not reach here")
|
||||
})
|
||||
|
||||
assert.IsNotNil(err)
|
||||
}
|
||||
|
||||
func TestTryKeyedLocker_SimpleLockUnlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTryKeyedLocker_SimpleLockUnlock")
|
||||
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
ok := locker.TryLock("key1")
|
||||
assert.Equal(true, ok)
|
||||
|
||||
ok = locker.TryLock("key1")
|
||||
assert.Equal(false, ok)
|
||||
|
||||
locker.Unlock("key1")
|
||||
|
||||
ok = locker.TryLock("key1")
|
||||
assert.Equal(true, ok)
|
||||
|
||||
locker.Unlock("key1")
|
||||
}
|
||||
|
||||
func TestTryKeyedLocker_ParallelTry(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestTryKeyedLocker_ParallelTry")
|
||||
|
||||
locker := NewTryKeyedLocker[string]()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var count int
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
ok := locker.TryLock("key" + strconv.Itoa(i))
|
||||
mu.Lock()
|
||||
if ok {
|
||||
count++
|
||||
}
|
||||
mu.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if ok {
|
||||
locker.Unlock("key" + strconv.Itoa(i))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(5, count)
|
||||
assert.Equal(0, len(locker.locks))
|
||||
}
|
||||
@@ -228,6 +228,42 @@ func ToPointer[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
// ToPointers convert a slice of values to a slice of pointers.
|
||||
// Play: https://go.dev/play/p/ZUoXd2i5ZkV
|
||||
func ToPointers[T any](values []T) []*T {
|
||||
result := make([]*T, len(values))
|
||||
for i := range values {
|
||||
result[i] = &values[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromPointer returns the value pointed to by the pointer.
|
||||
// Play: https://go.dev/play/p/wAp90V7Zu6g
|
||||
func FromPointer[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
return zeroValue
|
||||
}
|
||||
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// FromPointers convert a slice of pointers to a slice of values.
|
||||
// Play: https://go.dev/play/p/qIPsyYtNy3Q
|
||||
func FromPointers[T any](pointers []*T) []T {
|
||||
result := make([]T, len(pointers))
|
||||
for i, ptr := range pointers {
|
||||
if ptr == nil {
|
||||
var zeroValue T
|
||||
result[i] = zeroValue
|
||||
} else {
|
||||
result[i] = *ptr
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMap convert a slice of structs to a map based on iteratee function.
|
||||
// Play: https://go.dev/play/p/tVFy7E-t24l
|
||||
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V {
|
||||
@@ -406,15 +442,15 @@ func ToStdBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.StdEncoding.EncodeToString(value.([]byte))
|
||||
return base64.StdEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.StdEncoding.EncodeToString([]byte(value.(string)))
|
||||
return base64.StdEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.StdEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
return base64.StdEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -428,15 +464,15 @@ func ToUrlBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.URLEncoding.EncodeToString(value.([]byte))
|
||||
return base64.URLEncoding.EncodeToString(v)
|
||||
case string:
|
||||
return base64.URLEncoding.EncodeToString([]byte(value.(string)))
|
||||
return base64.URLEncoding.EncodeToString([]byte(v))
|
||||
case error:
|
||||
return base64.URLEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
return base64.URLEncoding.EncodeToString([]byte(v.Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -450,7 +486,7 @@ func ToRawStdBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawStdEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
@@ -458,7 +494,7 @@ func ToRawStdBase64(value any) string {
|
||||
case error:
|
||||
return base64.RawStdEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@@ -472,7 +508,7 @@ func ToRawUrlBase64(value any) string {
|
||||
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
|
||||
return ""
|
||||
}
|
||||
switch value.(type) {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return base64.RawURLEncoding.EncodeToString(value.([]byte))
|
||||
case string:
|
||||
@@ -480,7 +516,7 @@ func ToRawUrlBase64(value any) string {
|
||||
case error:
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value.(error).Error()))
|
||||
default:
|
||||
marshal, err := json.Marshal(value)
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -302,6 +302,75 @@ func TestToPointer(t *testing.T) {
|
||||
assert.Equal(*result, 123)
|
||||
}
|
||||
|
||||
func TestToPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestToPointers")
|
||||
|
||||
intVals := []int{1, 2, 3}
|
||||
result := ToPointers(intVals)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, *result[0])
|
||||
assert.Equal(2, *result[1])
|
||||
assert.Equal(3, *result[2])
|
||||
|
||||
stringVals := []string{"a", "b", "c"}
|
||||
resultStr := ToPointers(stringVals)
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", *resultStr[0])
|
||||
assert.Equal("b", *resultStr[1])
|
||||
assert.Equal("c", *resultStr[2])
|
||||
}
|
||||
|
||||
func TestFromPointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointer")
|
||||
|
||||
intVal := 123
|
||||
pointer := &intVal
|
||||
result := FromPointer(pointer)
|
||||
|
||||
assert.Equal(123, result)
|
||||
|
||||
stringVal := "abc"
|
||||
stringPointer := &stringVal
|
||||
resultStr := FromPointer(stringPointer)
|
||||
|
||||
assert.Equal("abc", resultStr)
|
||||
}
|
||||
|
||||
func TestFromPointers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFromPointers")
|
||||
|
||||
intPointers := []*int{new(int), new(int), new(int)}
|
||||
*intPointers[0] = 1
|
||||
*intPointers[1] = 2
|
||||
*intPointers[2] = 3
|
||||
|
||||
result := FromPointers(intPointers)
|
||||
|
||||
assert.Equal(3, len(result))
|
||||
assert.Equal(1, result[0])
|
||||
assert.Equal(2, result[1])
|
||||
assert.Equal(3, result[2])
|
||||
|
||||
stringPointers := []*string{new(string), new(string), new(string)}
|
||||
*stringPointers[0] = "a"
|
||||
*stringPointers[1] = "b"
|
||||
*stringPointers[2] = "c"
|
||||
|
||||
resultStr := FromPointers(stringPointers)
|
||||
|
||||
assert.Equal(3, len(resultStr))
|
||||
assert.Equal("a", resultStr[0])
|
||||
assert.Equal("b", resultStr[1])
|
||||
assert.Equal("c", resultStr[2])
|
||||
}
|
||||
|
||||
func TestEncodeByte(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package cryptor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
@@ -66,34 +65,37 @@ func Md5ByteWithBase64(data []byte) string {
|
||||
|
||||
// Md5File return the md5 value of file.
|
||||
func Md5File(filename string) (string, error) {
|
||||
if fileInfo, err := os.Stat(filename); err != nil {
|
||||
return "", err
|
||||
} else if fileInfo.IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := md5.New()
|
||||
|
||||
chunkSize := 65536
|
||||
for buf, reader := make([]byte, chunkSize), bufio.NewReader(file); ; {
|
||||
n, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
hash.Write(buf[:n])
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
checksum := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
return checksum, nil
|
||||
hash := md5.New()
|
||||
buf := make([]byte, 65536) // 64KB
|
||||
|
||||
for {
|
||||
n, err := file.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
if n > 0 {
|
||||
hash.Write(buf[:n])
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// HmacMd5 return the hmac hash of string use md5.
|
||||
|
||||
@@ -15,39 +15,40 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||
func AesEcbEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
length := (len(data) + aes.BlockSize) / aes.BlockSize
|
||||
plain := make([]byte, length*aes.BlockSize)
|
||||
blockSize := aes.BlockSize
|
||||
dataLen := len(data)
|
||||
padding := blockSize - (dataLen % blockSize)
|
||||
paddedLen := dataLen + padding
|
||||
|
||||
copy(plain, data)
|
||||
paddedData := make([]byte, paddedLen)
|
||||
copy(paddedData, data)
|
||||
|
||||
pad := byte(len(plain) - len(data))
|
||||
for i := len(data); i < len(plain); i++ {
|
||||
plain[i] = pad
|
||||
for i := dataLen; i < paddedLen; i++ {
|
||||
paddedData[i] = byte(padding)
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(plain))
|
||||
cipher, _ := aes.NewCipher(generateAesKey(key, size))
|
||||
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||
encrypted := make([]byte, paddedLen)
|
||||
for bs := 0; bs < paddedLen; bs += blockSize {
|
||||
cipher.Encrypt(encrypted[bs:], paddedData[bs:])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
@@ -57,77 +58,107 @@ func AesEcbEncrypt(data, key []byte) []byte {
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
cipher, _ := aes.NewCipher(generateAesKey(key, size))
|
||||
|
||||
blockSize := aes.BlockSize
|
||||
if len(encrypted)%blockSize != 0 {
|
||||
panic("aes: encrypted data length is not a multiple of block size")
|
||||
}
|
||||
|
||||
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
|
||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||
for i := 0; i < len(encrypted); i += blockSize {
|
||||
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||
}
|
||||
|
||||
trim := 0
|
||||
if len(decrypted) > 0 {
|
||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
||||
if len(decrypted) == 0 {
|
||||
return nil
|
||||
}
|
||||
padding := int(decrypted[len(decrypted)-1])
|
||||
if padding == 0 || padding > blockSize {
|
||||
panic("aes: invalid PKCS#7 padding")
|
||||
}
|
||||
for i := len(decrypted) - padding; i < len(decrypted); i++ {
|
||||
if decrypted[i] != byte(padding) {
|
||||
panic("aes: invalid PKCS#7 padding content")
|
||||
}
|
||||
}
|
||||
|
||||
return decrypted[:trim]
|
||||
return decrypted[:len(decrypted)-padding]
|
||||
}
|
||||
|
||||
// AesCbcEncrypt encrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||
func AesCbcEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, _ := aes.NewCipher(key)
|
||||
data = pkcs7Padding(data, block.BlockSize())
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
padding := aes.BlockSize - len(data)%aes.BlockSize
|
||||
padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...)
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(padded))
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted[aes.BlockSize:], data)
|
||||
mode.CryptBlocks(encrypted, padded)
|
||||
|
||||
return encrypted
|
||||
return append(iv, encrypted...)
|
||||
}
|
||||
|
||||
// AesCbcDecrypt decrypt data with key use AES CBC algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||
func AesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, _ := aes.NewCipher(key)
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("aes: ciphertext too short")
|
||||
}
|
||||
|
||||
if len(encrypted)%aes.BlockSize != 0 {
|
||||
panic("aes: ciphertext is not a multiple of the block size")
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
encrypted = encrypted[aes.BlockSize:]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted, encrypted)
|
||||
mode.CryptBlocks(decrypted, ciphertext)
|
||||
|
||||
decrypted := pkcs7UnPadding(encrypted)
|
||||
return decrypted
|
||||
return pkcs7UnPadding(decrypted)
|
||||
}
|
||||
|
||||
// AesCtrCrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/SpaZO0-5Nsp
|
||||
// deprecated: use AesCtrEncrypt and AesCtrDecrypt instead.
|
||||
func AesCtrCrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, _ := aes.NewCipher(key)
|
||||
@@ -141,158 +172,214 @@ func AesCtrCrypt(data, key []byte) []byte {
|
||||
return dst
|
||||
}
|
||||
|
||||
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
|
||||
// AesCtrEncrypt encrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||
func AesCfbEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return encrypted
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesCtrDecrypt decrypt data with key use AES CTR algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/x6pjPAvThRz
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("aes: invalid ciphertext length")
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesCfbEncrypt encrypt data with key use AES CFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||
func AesCfbEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesCfbDecrypt decrypt data with key use AES CFB algorithm
|
||||
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||
func AesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
if len(encrypted) < aes.BlockSize {
|
||||
panic("encrypted data is too short")
|
||||
panic("aes: encrypted data too short")
|
||||
}
|
||||
|
||||
block, _ := aes.NewCipher(key)
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
encrypted = encrypted[aes.BlockSize:]
|
||||
ciphertext := encrypted[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
stream.XORKeyStream(encrypted, encrypted)
|
||||
|
||||
return encrypted
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||
func AesOfbEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
data = pkcs7Padding(data, aes.BlockSize)
|
||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(data))
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
|
||||
stream.XORKeyStream(ciphertext, data)
|
||||
|
||||
return encrypted
|
||||
return append(iv, ciphertext...)
|
||||
}
|
||||
|
||||
// AesOfbDecrypt decrypt data with key use AES OFB algorithm
|
||||
// len(key) should be 16, 24 or 32.
|
||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||
func AesOfbDecrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 16 && size != 24 && size != 32 {
|
||||
panic("key length shoud be 16 or 24 or 32")
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if len(data) < aes.BlockSize {
|
||||
panic("aes: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := data[:aes.BlockSize]
|
||||
data = data[aes.BlockSize:]
|
||||
if len(data)%aes.BlockSize != 0 {
|
||||
return nil
|
||||
ciphertext := data[aes.BlockSize:]
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(data))
|
||||
mode := cipher.NewOFB(block, iv)
|
||||
mode.XORKeyStream(decrypted, data)
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
|
||||
return decrypted
|
||||
return plaintext
|
||||
}
|
||||
|
||||
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
|
||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||
func AesGcmEncrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create GCM: " + err.Error())
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
panic(err)
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
panic("aes: failed to generate nonce: " + err.Error())
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||
ciphertext := gcm.Seal(nil, nonce, data, nil)
|
||||
|
||||
return ciphertext
|
||||
return append(nonce, ciphertext...)
|
||||
}
|
||||
|
||||
// AesGcmDecrypt decrypt data with key use AES GCM algorithm
|
||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||
func AesGcmDecrypt(data, key []byte) []byte {
|
||||
if !isAesKeyLengthValid(len(key)) {
|
||||
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: failed to create GCM: " + err.Error())
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
panic("ciphertext too short")
|
||||
panic("aes: ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("aes: decryption failed: " + err.Error())
|
||||
}
|
||||
|
||||
return plaintext
|
||||
@@ -302,20 +389,17 @@ func AesGcmDecrypt(data, key []byte) []byte {
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||
func DesEcbEncrypt(data, key []byte) []byte {
|
||||
length := (len(data) + des.BlockSize) / des.BlockSize
|
||||
plain := make([]byte, length*des.BlockSize)
|
||||
copy(plain, data)
|
||||
|
||||
pad := byte(len(plain) - len(data))
|
||||
for i := len(data); i < len(plain); i++ {
|
||||
plain[i] = pad
|
||||
cipher, err := des.NewCipher(generateDesKey(key))
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(plain))
|
||||
cipher, _ := des.NewCipher(generateDesKey(key))
|
||||
blockSize := cipher.BlockSize()
|
||||
padded := pkcs5Padding(data, blockSize)
|
||||
encrypted := make([]byte, len(padded))
|
||||
|
||||
for bs, be := 0, cipher.BlockSize(); bs <= len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||
for i := 0; i < len(padded); i += blockSize {
|
||||
cipher.Encrypt(encrypted[i:], padded[i:])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
@@ -325,42 +409,50 @@ func DesEcbEncrypt(data, key []byte) []byte {
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||
func DesEcbDecrypt(encrypted, key []byte) []byte {
|
||||
cipher, _ := des.NewCipher(generateDesKey(key))
|
||||
cipher, err := des.NewCipher(generateDesKey(key))
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := cipher.BlockSize()
|
||||
if len(encrypted)%blockSize != 0 {
|
||||
panic("des: invalid encrypted data length")
|
||||
}
|
||||
|
||||
decrypted := make([]byte, len(encrypted))
|
||||
|
||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||
for i := 0; i < len(encrypted); i += blockSize {
|
||||
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||
}
|
||||
|
||||
trim := 0
|
||||
if len(decrypted) > 0 {
|
||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
||||
}
|
||||
|
||||
return decrypted[:trim]
|
||||
// Remove padding
|
||||
return pkcs5UnPadding(decrypted)
|
||||
}
|
||||
|
||||
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||
func DesCbcEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, _ := des.NewCipher(key)
|
||||
data = pkcs7Padding(data, block.BlockSize())
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
iv := encrypted[:des.BlockSize]
|
||||
blockSize := block.BlockSize()
|
||||
data = pkcs7Padding(data, blockSize)
|
||||
|
||||
encrypted := make([]byte, blockSize+len(data))
|
||||
iv := encrypted[:blockSize]
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted[des.BlockSize:], data)
|
||||
mode.CryptBlocks(encrypted[blockSize:], data)
|
||||
|
||||
return encrypted
|
||||
}
|
||||
@@ -369,26 +461,33 @@ func DesCbcEncrypt(data, key []byte) []byte {
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||
func DesCbcDecrypt(encrypted, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, _ := des.NewCipher(key)
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := encrypted[:des.BlockSize]
|
||||
encrypted = encrypted[des.BlockSize:]
|
||||
blockSize := block.BlockSize()
|
||||
if len(encrypted) < blockSize || len(encrypted)%blockSize != 0 {
|
||||
panic("des: invalid encrypted data length")
|
||||
}
|
||||
|
||||
iv := encrypted[:blockSize]
|
||||
ciphertext := encrypted[blockSize:]
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(encrypted, encrypted)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
decrypted := pkcs7UnPadding(encrypted)
|
||||
return decrypted
|
||||
return pkcs7UnPadding(ciphertext)
|
||||
}
|
||||
|
||||
// DesCtrCrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/9-T6OjKpcdw
|
||||
// deprecated: use DesCtrEncrypt and DesCtrDecrypt instead.
|
||||
func DesCtrCrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
@@ -406,25 +505,83 @@ func DesCtrCrypt(data, key []byte) []byte {
|
||||
return dst
|
||||
}
|
||||
|
||||
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
|
||||
// DesCtrEncrypt encrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||
func DesCfbEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, block.BlockSize())
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
encrypted := make([]byte, len(data))
|
||||
stream.XORKeyStream(encrypted, data)
|
||||
|
||||
// 返回前缀包含 IV,便于解密
|
||||
return append(iv, encrypted...)
|
||||
}
|
||||
|
||||
// DesCtrDecrypt decrypt data with key use DES CTR algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/S6p_WHCgH1d
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
if len(encrypted) < blockSize {
|
||||
panic("des: ciphertext too short")
|
||||
}
|
||||
|
||||
iv := encrypted[:blockSize]
|
||||
ciphertext := encrypted[blockSize:]
|
||||
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(decrypted, ciphertext)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
// DesCfbEncrypt encrypt data with key use DES CFB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||
func DesCfbEncrypt(data, key []byte) []byte {
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
iv := make([]byte, des.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
iv := encrypted[:des.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
copy(encrypted[:des.BlockSize], iv)
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
@@ -436,44 +593,51 @@ func DesCfbEncrypt(data, key []byte) []byte {
|
||||
// len(encrypted) should be great than 16, len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||
func DesCfbDecrypt(encrypted, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, _ := des.NewCipher(key)
|
||||
if len(encrypted) < des.BlockSize {
|
||||
panic("encrypted data is too short")
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
if len(encrypted) < des.BlockSize {
|
||||
panic("des: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := encrypted[:des.BlockSize]
|
||||
encrypted = encrypted[des.BlockSize:]
|
||||
ciphertext := encrypted[des.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(encrypted, encrypted)
|
||||
stream.XORKeyStream(ciphertext, ciphertext)
|
||||
|
||||
return encrypted
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
// DesOfbEncrypt encrypt data with key use DES OFB algorithm
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||
func DesOfbEncrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
data = pkcs7Padding(data, des.BlockSize)
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
iv := encrypted[:des.BlockSize]
|
||||
|
||||
iv := make([]byte, des.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
panic("des: failed to generate IV: " + err.Error())
|
||||
}
|
||||
|
||||
encrypted := make([]byte, des.BlockSize+len(data))
|
||||
copy(encrypted[:des.BlockSize], iv)
|
||||
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||
|
||||
@@ -484,25 +648,25 @@ func DesOfbEncrypt(data, key []byte) []byte {
|
||||
// len(key) should be 8.
|
||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||
func DesOfbDecrypt(data, key []byte) []byte {
|
||||
size := len(key)
|
||||
if size != 8 {
|
||||
panic("key length shoud be 8")
|
||||
if len(key) != 8 {
|
||||
panic("des: key length must be 8 bytes")
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("des: failed to create cipher: " + err.Error())
|
||||
}
|
||||
|
||||
if len(data) < des.BlockSize {
|
||||
panic("des: encrypted data too short")
|
||||
}
|
||||
|
||||
iv := data[:des.BlockSize]
|
||||
data = data[des.BlockSize:]
|
||||
if len(data)%des.BlockSize != 0 {
|
||||
return nil
|
||||
}
|
||||
ciphertext := data[des.BlockSize:]
|
||||
|
||||
decrypted := make([]byte, len(data))
|
||||
mode := cipher.NewOFB(block, iv)
|
||||
mode.XORKeyStream(decrypted, data)
|
||||
stream := cipher.NewOFB(block, iv)
|
||||
decrypted := make([]byte, len(ciphertext))
|
||||
stream.XORKeyStream(decrypted, ciphertext)
|
||||
|
||||
decrypted = pkcs7UnPadding(decrypted)
|
||||
|
||||
@@ -693,117 +857,3 @@ func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName stri
|
||||
|
||||
return rsa.VerifyPKCS1v15(publicKey, hash, hashed, signature)
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
|
||||
pubKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pubKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the public key")
|
||||
}
|
||||
|
||||
var pubKey *rsa.PublicKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
if blockType == "RSA PUBLIC KEY" {
|
||||
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
}
|
||||
} else if blockType == "PUBLIC KEY" {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
|
||||
priKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(priKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the private key")
|
||||
}
|
||||
|
||||
var privateKey *rsa.PrivateKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
// PKCS#1 format
|
||||
if blockType == "RSA PRIVATE KEY" {
|
||||
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
|
||||
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ok bool
|
||||
privateKey, ok = priKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// hashData returns the hash value of the data, using the specified hash function
|
||||
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
|
||||
if !hash.Available() {
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
var hashed []byte
|
||||
|
||||
switch hash {
|
||||
case crypto.SHA224:
|
||||
h := sha256.Sum224(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA256:
|
||||
h := sha256.Sum256(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA384:
|
||||
h := sha512.Sum384(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA512:
|
||||
h := sha512.Sum512(data)
|
||||
hashed = h[:]
|
||||
default:
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
return hashed, nil
|
||||
}
|
||||
|
||||
@@ -74,6 +74,32 @@ func ExampleAesCtrCrypt() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
enCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := AesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleAesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
@@ -227,6 +253,19 @@ func ExampleDesCtrCrypt() {
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCtrDecrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
enCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||
deCrypt := DesCtrDecrypt(enCrypt, []byte(key))
|
||||
|
||||
fmt.Println(string(deCrypt))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleDesCfbEncrypt() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
package cryptor
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func generateAesKey(key []byte, size int) []byte {
|
||||
genKey := make([]byte, size)
|
||||
@@ -35,3 +46,139 @@ func pkcs7UnPadding(src []byte) []byte {
|
||||
unPadding := int(src[length-1])
|
||||
return src[:(length - unPadding)]
|
||||
}
|
||||
|
||||
func pkcs5Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs5UnPadding(data []byte) []byte {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
padLen := int(data[length-1])
|
||||
if padLen == 0 || padLen > length {
|
||||
return nil
|
||||
}
|
||||
return data[:length-padLen]
|
||||
}
|
||||
|
||||
func isAesKeyLengthValid(n int) bool {
|
||||
return n == 16 || n == 24 || n == 32
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
|
||||
pubKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pubKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the public key")
|
||||
}
|
||||
|
||||
var pubKey *rsa.PublicKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
if blockType == "RSA PUBLIC KEY" {
|
||||
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
}
|
||||
} else if blockType == "PUBLIC KEY" {
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pubKey, ok = key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
|
||||
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
|
||||
priKeyData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(priKeyData)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing the private key")
|
||||
}
|
||||
|
||||
var privateKey *rsa.PrivateKey
|
||||
blockType := strings.ToUpper(block.Type)
|
||||
|
||||
// PKCS#1 format
|
||||
if blockType == "RSA PRIVATE KEY" {
|
||||
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
|
||||
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ok bool
|
||||
privateKey, ok = priKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse RSA private key")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// hashData returns the hash value of the data, using the specified hash function
|
||||
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
|
||||
if !hash.Available() {
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
var hashed []byte
|
||||
|
||||
switch hash {
|
||||
case crypto.SHA224:
|
||||
h := sha256.Sum224(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA256:
|
||||
h := sha256.Sum256(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA384:
|
||||
h := sha512.Sum384(data)
|
||||
hashed = h[:]
|
||||
case crypto.SHA512:
|
||||
h := sha512.Sum512(data)
|
||||
hashed = h[:]
|
||||
default:
|
||||
return nil, errors.New("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
return hashed, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestAesEcbEncrypt(t *testing.T) {
|
||||
func TestAesEcbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -16,11 +16,11 @@ func TestAesEcbEncrypt(t *testing.T) {
|
||||
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
|
||||
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesEcbEncrypt")
|
||||
assert := internal.NewAssert(t, "TestAesEcbCrypt")
|
||||
assert.Equal(data, string(aesEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesCbcEncrypt(t *testing.T) {
|
||||
func TestAesCbcCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -29,7 +29,7 @@ func TestAesCbcEncrypt(t *testing.T) {
|
||||
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
|
||||
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCbcEncrypt")
|
||||
assert := internal.NewAssert(t, "TestAesCbcCrypt")
|
||||
assert.Equal(data, string(aesCbcDecrypt))
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ func TestAesCtrCrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
aesCtrCrypt := AesCtrCrypt([]byte(data), []byte(key))
|
||||
aesCtrDeCrypt := AesCtrCrypt(aesCtrCrypt, []byte(key))
|
||||
aesCtrCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||
aesCtrDeCrypt := AesCtrDecrypt(aesCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCtrCrypt")
|
||||
assert.Equal(data, string(aesCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestAesCfbEncrypt(t *testing.T) {
|
||||
func TestAesCfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -55,11 +55,11 @@ func TestAesCfbEncrypt(t *testing.T) {
|
||||
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
|
||||
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestAesCfbEncrypt")
|
||||
assert := internal.NewAssert(t, "TestAesCfbCrypt")
|
||||
assert.Equal(data, string(aesCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestAesOfbEncrypt(t *testing.T) {
|
||||
func TestAesOfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -72,7 +72,7 @@ func TestAesOfbEncrypt(t *testing.T) {
|
||||
assert.Equal(data, string(aesOfbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesEcbEncrypt(t *testing.T) {
|
||||
func TestDesEcbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -85,7 +85,7 @@ func TestDesEcbEncrypt(t *testing.T) {
|
||||
assert.Equal(data, string(desEcbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesCbcEncrypt(t *testing.T) {
|
||||
func TestDesCbcCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -104,14 +104,14 @@ func TestDesCtrCrypt(t *testing.T) {
|
||||
data := "hello world"
|
||||
key := "abcdefgh"
|
||||
|
||||
desCtrCrypt := DesCtrCrypt([]byte(data), []byte(key))
|
||||
desCtrDeCrypt := DesCtrCrypt(desCtrCrypt, []byte(key))
|
||||
desCtrCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||
desCtrDeCrypt := DesCtrDecrypt(desCtrCrypt, []byte(key))
|
||||
|
||||
assert := internal.NewAssert(t, "TestDesCtrCrypt")
|
||||
assert.Equal(data, string(desCtrDeCrypt))
|
||||
}
|
||||
|
||||
func TestDesCfbEncrypt(t *testing.T) {
|
||||
func TestDesCfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
@@ -124,7 +124,7 @@ func TestDesCfbEncrypt(t *testing.T) {
|
||||
assert.Equal(data, string(desCfbDecrypt))
|
||||
}
|
||||
|
||||
func TestDesOfbEncrypt(t *testing.T) {
|
||||
func TestDesOfbCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := "hello world"
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,51 +1,51 @@
|
||||
-----BEGIN rsa private key-----
|
||||
MIIJKAIBAAKCAgEA0HaqqGWnNqwRVKoJUUjYiH/dwkSK61La4RGYkvDfcuo9pDgc
|
||||
zHVhFUb/MYWeb3bAlFAGcZLYSBh6WAGxbeMjSkaLroaXnafhKZ2oXXUy8CzyYxZw
|
||||
pLCDgJLG7Pf0jer3STZW93ZT8UJixbKwbqD6b8fkpMANwCYrTlXDWBuZVaEKuQNn
|
||||
C4XufQis4fwRxRgfrZuLMvuQVtnyYmssmnp8JHovYkr87giDjEgvlrC84Lez32Zh
|
||||
EhORRu5NGEDeZ1OewbkXGyQtbcBQYNAxgnOcO44MTTPhEdz4/A0xwzEe+yIOxWIA
|
||||
WuEM2rclXpZBW/MEK41gZ3C04KcX5R0KRdj2PTTH7vJ33yRC4hKtWetwMvsUrewr
|
||||
RKVcUgaeFykDAlgIDoSWvG0nzb70AWSoNcj7krLlwEc2WLGy/kCYWppnfYKafEJX
|
||||
pgblHrDFKlWvte68yrvGdCsWRLQ2uUF0xokQIg4u2bApyWNroxEtjHwUp1VeJuZB
|
||||
Qkfg+vIW+t3moQpMtlhy4aai/oGIxI5TIB82nv8TPVczjHmmE3hwL6nZ5OfbFyWp
|
||||
RFvKLtXcaAKMi2HqOLRkQhqjjJUCO9Wyhu1BkX8RAeKLl3S7gRyhuOB5JNUGEpQ3
|
||||
89MdbUX5S12Qp1xTDS+otbC8tYS1gu0OOOWowxbuF5+Ci2SYgSHv/3zXKoMCAwEA
|
||||
AQKCAgBASCdt0BLVABBEDD7dStpClTNbwWkZEa6e8p8ayJ4OwH6LkiYHQjbSqdbt
|
||||
fWeStYrC8T5bbU22RZ4MX1FIMl9iewh9o9FC806yV4RgdVBk0WdY+MV5c1jJn/kp
|
||||
f3hw/sCMWe5NVrah0bfFgMl5A7jWGUy/JN3Yn6yA9l9LXw9UYVl+Hbd7zSvycGdn
|
||||
NCSCo2K5VRqCeSJUNdbRVH0nkZeQZAESjU8bU5LFAklybUOBBAS6YcaTHWeR+M/b
|
||||
J72tHRL6Z8nhO7Gqg0AF6o0pzd6iWrYeZF4F9R7uEl+C8jW8eQ8W/JZhb75X/1vN
|
||||
pAW5heGGUI0muJF+KOhsZR0S1slvHbCSvwaNBQi8HEbOEPa7wVzsrGLfc+re5ysF
|
||||
X2n5GL4qBwNdMHrADnMWWVa9LEUSr3XmXfpmNDctmdv1ekWpJ44G/qrjKOuimtfk
|
||||
t9haDjhN5lx3V05k18kb53tYAdi3CqrT4syiibtqZLvTRam/yoFJgSOczO5WxNNk
|
||||
xnhnEdr/FQcFQhzuQ6gU196SBi8Bqjz1t0cNozNu3wvp8Fu0G8Rr9gjYnH8m5xXz
|
||||
xb9xevR7X/swfxd+/KUqoenYdU5ZFJpDRCmUMDKWR/3Q7s4SRxMN0+c/uBSaih3J
|
||||
I9I8urHG6mFW2qWQZTvuostmb7vVHaefx0RZOhX0efJVMZPwiQKCAQEA8BUXRw9k
|
||||
Ms3tqmBMVM4sMcPgsffJxjAJUdunUgEpaBAqrDQmZ6uBjAYq7iTzLgJJuLI1o1sv
|
||||
HtEWuGLHRRJpdoEamGweJE/o28OH2MBiiL2ezfRIHsg8RRr6os6KvlKevty6m7d9
|
||||
hAvpS6XvZGK2eCSooL6zZ8XTgnDw+Hw8Q4i+dazjb5dDRRL97eHHUqzcYH5vIsAE
|
||||
EBaNXMKrhf/NyVWCJBVRHg8PZZjGXcrj8ZMx+ejDR0AL5BXUaAX3fYnQyJf6QzrD
|
||||
t5aNDoU6vPO77CLkqBavTePTToiVeTIMZP5yHZFQuPWtP/y67e0csNA6ezwpWEh4
|
||||
4MnpLWzC2YRZ/wKCAQEA3kjpB3977IHFw90Xn8TQu3/SzBMUBqdj5yo6gPBijKNf
|
||||
4mZ1shUp6vZkFOHbAmoafjqCzOO18tW4ci+UWAQgdXQWXwsQDUM+fZsr/5Z9LoIN
|
||||
TYuq98Yh/rjKABxkFdMmkLmykuAblDRwVeMf3e4x6/55hAkwvESBRdfnSjjoNmEM
|
||||
MRVW4v5meDv0W4NS1eyohWPe/IoR09dSsmMhz311b7btBxc97NNqDD4SdxmASnOZ
|
||||
qsE2QxRHRFjpTvytpZH/lTQH6IYdh9REZgX7uvmlmTQwqLy6wX5T/oEzVPRhXgh9
|
||||
rgbPPJuoZwWF+fXipJ8c+laQ/sdzDKCxEEhRZr7HfQKCAQBKflDVtLnjZbA78Fte
|
||||
6QYffubGcdtCyn7pzl0RfdjKOFH1Us0j17x/pR5G/GIUQZN8YpdwE4gAaOJC3it0
|
||||
jCz7Hz4QU2Pa4oyfPAF3yOIKCcQvpX+HRZwl2SQxxiKYwWwOTtD8JiglA8kktt0b
|
||||
6eEyUDWegu2J4oEpdT/f6jSMw+5M9xMu+eFemnD+EdNWHNrYegKj5q6cC1Nbl+++
|
||||
yUuiEA0sgwzDZeriHFBYo+6sc37LS2TkQ8QsxnU8vbU4V73XsAhwOdimq2kjO0Wp
|
||||
gXsq3vzSBw/n/CwBrzGqBFSCNc1UzVUdvuU9+H5L+wmu9z1eJaGyifIv1ZariJbB
|
||||
BWcjAoIBACWRJPEDdqot9IJ5pzh1RuGpZLLgto25VIUI+gI4ni8unVHiBxolwYPY
|
||||
SGnPEfiCfh+/O8Ps6B82R4nkyKlnaSTwjadac0gKiVEpHHKBuH5XtG/anvZpIe2u
|
||||
xVTnd3LI0Me82pVAEuklQ6cAT65uRzmfNGJAO2BWI5LuPkSpAXXPSQQymxCZ9i9z
|
||||
0oR02VcWPBTvIAyGOSUYSv2jC1/J0EMlI0IDh4+y20VeaDiAstHiX6IgLU+A6dp/
|
||||
PE8BHUfSOOO6e2us3ujJ0xV7BWRANOCDlYWu/9EbzI5Cv64n7xy5SqRSukt/8yIW
|
||||
KOJpz/gKfBdC8hZdFvCXZ9Vco4U90PECggEBAIwy5zuWqSaJ8kom7kMZj+JYfhVz
|
||||
rdozFil1CkmWcw4E+rNcq8bKVTa9Er6/3nBTXfKX+4sYzMzbFr6xjFyrPA62wjiT
|
||||
2MRdHu9iB+Rtxn83Ilnh+Aqu/8bPFQUlYFr7nefy2t1aL76diQnsBu4xHOtEMgSv
|
||||
F6d/pDFRC+PZN0B7glrz870jRaw9LNcuIBVoKN07hGBwD76EEZhCrn6eLqMI8srH
|
||||
YESDPUR/wl/PV8ZrA/hNRvjp44PYiLMYRDSQw8kpbLMMc0QgQCvhfMbYVvGteTmG
|
||||
qdCFyy2x70wSh6Rfzb7WEWl15I4yfBTddXkJX82S+MrZu/Xq4FDtng594h0=
|
||||
MIIJKQIBAAKCAgEAuzBz+aC+e7Lvny2zYlcyAfG6AAtPkxZqJ9JkYkM+0CP87pe0
|
||||
xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUoj/TWY/xpoPNXXzz/5dz3u6r/A5Tu
|
||||
mJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgfm4AZOXd/plzBkTrnu6lKG/rH9cnr
|
||||
2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzvjLJWmJ08VrnF9PFtWhL939xSAIic
|
||||
FzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5YlWmuFJ8YKvwR0+4XIRLX9qsMy7q
|
||||
PTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoCLHLqNKf/dD+4vXxgS+f1ObfOcJpD
|
||||
qBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8jIvt2FutT4hsNSd0L6c2mfo5mTno
|
||||
DOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a+kTiW8BedNqcRnVVGT9/OS/ggyKF
|
||||
wxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30znguCdaTTViA9UbrjtcE2bZtnqOMAU2s
|
||||
08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg5hf7ft6FA+bXB9DHwGdv/UyrGr88
|
||||
nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UWDcHAyc+Zv+te0DqPUNcAW5ECAwEA
|
||||
AQKCAgBLJ0Do0Cip8UVTWz3SFb/2F97dda0VGMK2CjpTWTw2xLwf7ric9MesIi3k
|
||||
fBgLhzUduaiGqxD7gSuIcc8/na4TXfFVY1nlTM2fZxY2a2jq59RK09iXXcwanM9y
|
||||
8YPAgpfPI4Jq6Sm5D+aGGKvAlzvZaqy17cxKNqNgc43mQimG4kC15cPTfaIFmkXl
|
||||
doJIbJoWlkzVzNWKuzDp06jBhmeGzXMHAtne1+cqWGPW7hkPb51cqXxBs/gOtkiH
|
||||
QAmliMG9HCvHDnoXbk1K/XolD3aWjFzLVBKrnVxyxQb33gWFDn5kbkmNGshaVDuC
|
||||
EqYsMYJ9U4HLNGTdJXlaY4izGe+UyExET5p2KYKC9S34jMvR5k9Hf4pATSUYRhjL
|
||||
t/EV8EZlWCJGvGRAdtlKNLjRuIAiMTofUZca+sCHDvdcv8+/imOnKCXOETtcOHzw
|
||||
I7MRdIi2JigcBKuKaua9H77cEuvwG9Bb7aLbqQ3XM5JhoBEBe2jHG39GYDAAjEWz
|
||||
XWo2ri8rkU0nhixN26x7DXCfMewxZ/zc4czBTU2giM0Yrh4BMpRpHnw14QXRb58y
|
||||
eTD7GVrC9g1/6HXsAzzBfKyTMZhZhmfjcgSuYMUbzwIvvttJQtw5Ic/LJmR1Eg2F
|
||||
YZ3mUmwJwDEPyVlV2mXNUYYa64v7O3h+NsjXukWXw080fWdsoQKCAQEA8ZcVn863
|
||||
wXQVex7RcXs5frdnKEtHx6V6tXXq4tvK71Jbkny3gOmPqwwEF0fk4m2Fo07CmJkX
|
||||
t5o0tbPxfVbxeWGRGubAstjd5oWgt6nMAgcEkRbAzYM8qLGAGekS4g5+2/SjrQhG
|
||||
oR2phBv+T9w/Oglf6mVzc8YDNP9B0PB0CTICTYhej16Qhc/jpFmXkjXfslGlUp0F
|
||||
WVkNE7BZEk/fNgCbmAV1hCcDt7MwoOYBqGBoWb3tRKNhtBIDfJY1LVPjB6Jo3FWl
|
||||
nolJ1v1In9MhsNudZ6QlYbO8uMadsx3a1Flsu/w69TT+sPjmw+GoSzGuMlH15cFY
|
||||
qZZ6k75WmwyRGwKCAQEAxlq0SEK86+5zAIvVRQI5pQk0HEGy0dtcUgwBhpy13Bga
|
||||
sCezorJwS1tEHXfWYMtwmHytMXbySnFQEx5jJLFaQhPfOHybHV94fqq+qcC+NuEt
|
||||
z2KMoQG+zlupH5LwZv3RzzMSng0AuxNaiPx/tXfXM+5O19wb8VKu7X+hkgOW+psu
|
||||
wGnofT1zYTCWEbRPZENSL6Mi8BShwu3UIMFhKhVZJZH6MOSU/AoULv49ije39Z58
|
||||
B06IERBIGpM6FE6L73BHphbUh9Osr/I9vbi7zCzt/utQ1uzMzzxxJjjadYf1K7xa
|
||||
MYsmtKJ85+dG2/WOw6bRSGk1Dw9KqUBqHQ7bwXq8wwKCAQEAtrCwuotg69qzz8oL
|
||||
SgyL+uYIDTF4U2Iwu/4ypGDfQkD+XHURc1uruAY7JbvJOuzlbQxHHYxPohjrmSg9
|
||||
CrJvooGEcFplCBn1G7ibQ6gUTMgvzOPu4rpGaa7oly9ohye9COojx9qFRpsesHdW
|
||||
xd9gtKuYK7GSL89iZ3ZLuAvNQ5LcqPLhxvsUwQvnMkZJ11gEFF2nbiStgdZUjDoD
|
||||
8VQTEEw/XSNrrYavSgAoWtP0FvbokkyMmyYN4VTp7BHOnrtb6E8Jiuz9dDiPbRNW
|
||||
Ev5e8NXyXwiC+DIqGXSglm2SKJiDIFjp4Lm1i/B82U3QrSQhfY37LEYcnQndIdKC
|
||||
vXcwVwKCAQA0K2UhYFQ6JYQfz6dvOA+bRZlsGSeEJJLajYfVNOBsG/bhAAAyOYZp
|
||||
e36l1YAQA1IA+UHAMc22IKlz7dkbrH3VxU4/mB5gEl0py5TMJwKggoc+9WeRbVkX
|
||||
A2qvAEG0hOuq+H7cDQV1LrjwMKESRIvYf8RC6AR9a0bQ9nGzarhJ/4jDWNeqIQB4
|
||||
voOp8mezMjWqi9jDllmZYF4bo2D/5Y+F3ygTtfstcyUt2vaqpM8AjgeHEHOfMU4V
|
||||
l0V+U85gUoK1v2l0tArGWAs/HBhgsiyCkLe5X5zaoMYNzIRAx1qHf0mloDi058u8
|
||||
Xsr3TVWYRgbjabBn3pi/fU6rh93qvHJrAoIBAQCHNkNclB3UMBoZv2Dnd2ZwUZnP
|
||||
BzN6pq/NjG4GnjSCxPYwYnvB1TakTVLTYOjMS/7TCsFqPFSi93mjDsZ+0ZHBUZOH
|
||||
076AdxzFD+WxxYZ2+vl3iRhgERY3LgqHOBTt7O1/OYcNraJ6Pk2ppU/PqYaVb389
|
||||
2dsgqEEbO0np59I2+BP6giIFr3L2xKk2CRdVLKqCQ8FNx2xpi+1kcJlUW+ZxPiwu
|
||||
JaOA/mNHOz3Kq4DDE+1XjvKq602zm7D1oG67xM/gE5UV/KT7auI9M+zQcTPWZ3T6
|
||||
cl5A3z4tP9KdWJInadUQyOrVIHCbqxIlZCQjYFo2m9HzFr6fWbgtWC+IyGJQ
|
||||
-----END rsa private key-----
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
-----BEGIN rsa public key-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0HaqqGWnNqwRVKoJUUjY
|
||||
iH/dwkSK61La4RGYkvDfcuo9pDgczHVhFUb/MYWeb3bAlFAGcZLYSBh6WAGxbeMj
|
||||
SkaLroaXnafhKZ2oXXUy8CzyYxZwpLCDgJLG7Pf0jer3STZW93ZT8UJixbKwbqD6
|
||||
b8fkpMANwCYrTlXDWBuZVaEKuQNnC4XufQis4fwRxRgfrZuLMvuQVtnyYmssmnp8
|
||||
JHovYkr87giDjEgvlrC84Lez32ZhEhORRu5NGEDeZ1OewbkXGyQtbcBQYNAxgnOc
|
||||
O44MTTPhEdz4/A0xwzEe+yIOxWIAWuEM2rclXpZBW/MEK41gZ3C04KcX5R0KRdj2
|
||||
PTTH7vJ33yRC4hKtWetwMvsUrewrRKVcUgaeFykDAlgIDoSWvG0nzb70AWSoNcj7
|
||||
krLlwEc2WLGy/kCYWppnfYKafEJXpgblHrDFKlWvte68yrvGdCsWRLQ2uUF0xokQ
|
||||
Ig4u2bApyWNroxEtjHwUp1VeJuZBQkfg+vIW+t3moQpMtlhy4aai/oGIxI5TIB82
|
||||
nv8TPVczjHmmE3hwL6nZ5OfbFyWpRFvKLtXcaAKMi2HqOLRkQhqjjJUCO9Wyhu1B
|
||||
kX8RAeKLl3S7gRyhuOB5JNUGEpQ389MdbUX5S12Qp1xTDS+otbC8tYS1gu0OOOWo
|
||||
wxbuF5+Ci2SYgSHv/3zXKoMCAwEAAQ==
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuzBz+aC+e7Lvny2zYlcy
|
||||
AfG6AAtPkxZqJ9JkYkM+0CP87pe0xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUo
|
||||
j/TWY/xpoPNXXzz/5dz3u6r/A5TumJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgf
|
||||
m4AZOXd/plzBkTrnu6lKG/rH9cnr2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzv
|
||||
jLJWmJ08VrnF9PFtWhL939xSAIicFzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5
|
||||
YlWmuFJ8YKvwR0+4XIRLX9qsMy7qPTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoC
|
||||
LHLqNKf/dD+4vXxgS+f1ObfOcJpDqBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8
|
||||
jIvt2FutT4hsNSd0L6c2mfo5mTnoDOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a
|
||||
+kTiW8BedNqcRnVVGT9/OS/ggyKFwxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30zngu
|
||||
CdaTTViA9UbrjtcE2bZtnqOMAU2s08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg
|
||||
5hf7ft6FA+bXB9DHwGdv/UyrGr88nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UW
|
||||
DcHAyc+Zv+te0DqPUNcAW5ECAwEAAQ==
|
||||
-----END rsa public key-----
|
||||
|
||||
@@ -15,7 +15,6 @@ func TestSinglyLink_InsertAtFirst(t *testing.T) {
|
||||
link.InsertAtHead(1)
|
||||
link.InsertAtHead(2)
|
||||
link.InsertAtHead(3)
|
||||
link.Print()
|
||||
|
||||
expected := []int{3, 2, 1}
|
||||
values := link.Values()
|
||||
@@ -32,7 +31,6 @@ func TestSinglyLink_InsertAtTail(t *testing.T) {
|
||||
link.InsertAtTail(1)
|
||||
link.InsertAtTail(2)
|
||||
link.InsertAtTail(3)
|
||||
link.Print()
|
||||
|
||||
expected := []int{1, 2, 3}
|
||||
values := link.Values()
|
||||
@@ -77,7 +75,6 @@ func TestSinglyLink_DeleteAtHead(t *testing.T) {
|
||||
link.InsertAtTail(4)
|
||||
|
||||
link.DeleteAtHead()
|
||||
link.Print()
|
||||
|
||||
expected := []int{2, 3, 4}
|
||||
values := link.Values()
|
||||
|
||||
@@ -55,12 +55,12 @@ func (q *ArrayQueue[T]) IsFull() bool {
|
||||
|
||||
// Front return front value of queue
|
||||
func (q *ArrayQueue[T]) Front() T {
|
||||
return q.data[0]
|
||||
return q.data[q.head]
|
||||
}
|
||||
|
||||
// Back return back value of queue
|
||||
func (q *ArrayQueue[T]) Back() T {
|
||||
return q.data[q.size-1]
|
||||
return q.data[q.tail-1]
|
||||
}
|
||||
|
||||
// EnQueue put element into queue
|
||||
|
||||
@@ -83,13 +83,13 @@ func AddDay(t time.Time, days int64) time.Time {
|
||||
}
|
||||
|
||||
// AddWeek add or sub weeks to the time.
|
||||
// play: todo
|
||||
// play: https://go.dev/play/p/M9TqdMiaA2p
|
||||
func AddWeek(t time.Time, weeks int64) time.Time {
|
||||
return t.Add(7 * 24 * time.Hour * time.Duration(weeks))
|
||||
}
|
||||
|
||||
// AddMonth add or sub months to the time.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/DLoiOnpLvsN
|
||||
func AddMonth(t time.Time, months int64) time.Time {
|
||||
return t.AddDate(0, int(months), 0)
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func AddYear(t time.Time, year int64) time.Time {
|
||||
}
|
||||
|
||||
// AddDaySafe add or sub days to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/JTohZFpoDJ3
|
||||
func AddDaySafe(t time.Time, days int) time.Time {
|
||||
t = t.AddDate(0, 0, days)
|
||||
year, month, day := t.Date()
|
||||
@@ -116,7 +116,7 @@ func AddDaySafe(t time.Time, days int) time.Time {
|
||||
}
|
||||
|
||||
// AddMonthSafe add or sub months to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/KLw0lo6mbVW
|
||||
func AddMonthSafe(t time.Time, months int) time.Time {
|
||||
year := t.Year()
|
||||
month := int(t.Month()) + months
|
||||
@@ -141,7 +141,7 @@ func AddMonthSafe(t time.Time, months int) time.Time {
|
||||
}
|
||||
|
||||
// AddYearSafe add or sub years to the time and ensure that the returned date does not exceed the valid date of the target year and month.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/KVGXWZZ54ZH
|
||||
func AddYearSafe(t time.Time, years int) time.Time {
|
||||
year, month, day := t.Date()
|
||||
year += years
|
||||
@@ -280,13 +280,9 @@ func EndOfDay(t time.Time) time.Time {
|
||||
}
|
||||
|
||||
// BeginOfWeek return beginning week, default week begin from Sunday.
|
||||
// Play: https://go.dev/play/p/ynjoJPz7VNV
|
||||
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
|
||||
var beginFromWeekday = time.Sunday
|
||||
if len(beginFrom) > 0 {
|
||||
beginFromWeekday = beginFrom[0]
|
||||
}
|
||||
y, m, d := t.AddDate(0, 0, int(beginFromWeekday-t.Weekday())).Date()
|
||||
// Play: https://go.dev/play/p/DCHdcL6gnfV
|
||||
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time {
|
||||
y, m, d := t.AddDate(0, 0, int(beginFrom-t.Weekday())).Date()
|
||||
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||
if beginOfWeek.After(t) {
|
||||
return beginOfWeek.AddDate(0, 0, -7)
|
||||
@@ -295,13 +291,9 @@ func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
|
||||
}
|
||||
|
||||
// EndOfWeek return end week time, default week end with Saturday.
|
||||
// Play: https://go.dev/play/p/i08qKXD9flf
|
||||
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time {
|
||||
var endWithWeekday = time.Saturday
|
||||
if len(endWith) > 0 {
|
||||
endWithWeekday = endWith[0]
|
||||
}
|
||||
y, m, d := t.AddDate(0, 0, int(endWithWeekday-t.Weekday())).Date()
|
||||
// Play: https://go.dev/play/p/mGSA162YgX9
|
||||
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time {
|
||||
y, m, d := t.AddDate(0, 0, int(endWith-t.Weekday())).Date()
|
||||
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||
if endWithWeek.Before(t) {
|
||||
endWithWeek = endWithWeek.AddDate(0, 0, 7)
|
||||
|
||||
@@ -299,23 +299,23 @@ func ExampleEndOfDay() {
|
||||
func ExampleBeginOfWeek() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := BeginOfWeek(input)
|
||||
result := BeginOfWeek(input, time.Monday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 00:00:00 +0000 UTC
|
||||
// 2023-01-02 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleEndOfWeek() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
|
||||
result := EndOfWeek(input)
|
||||
result := EndOfWeek(input, time.Sunday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleBeginOfMonth() {
|
||||
|
||||
@@ -611,9 +611,9 @@ func TestBeginOfWeek(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestBeginOfWeek")
|
||||
|
||||
expected := time.Date(2022, 2, 13, 0, 0, 0, 0, time.Local)
|
||||
expected := time.Date(2022, 2, 14, 0, 0, 0, 0, time.Local)
|
||||
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||
actual := BeginOfWeek(td)
|
||||
actual := BeginOfWeek(td, time.Monday)
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
@@ -623,9 +623,9 @@ func TestEndOfWeek(t *testing.T) {
|
||||
|
||||
assert := internal.NewAssert(t, "TestEndOfWeek")
|
||||
|
||||
expected := time.Date(2022, 2, 19, 23, 59, 59, 999999999, time.Local)
|
||||
expected := time.Date(2022, 2, 20, 23, 59, 59, 999999999, time.Local)
|
||||
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||
actual := EndOfWeek(td)
|
||||
actual := EndOfWeek(td, time.Sunday)
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,8 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
],
|
||||
},
|
||||
{ text: 'datetime', link: '/en/api/packages/datetime' },
|
||||
{ text: 'enum', link: '/en/api/packages/enum' },
|
||||
{ text: 'eventbus', link: '/en/api/packages/eventbus' },
|
||||
{ text: 'fileutil', link: '/en/api/packages/fileutil' },
|
||||
{ text: 'formatter', link: '/en/api/packages/formatter' },
|
||||
{ text: 'function', link: '/en/api/packages/function' },
|
||||
@@ -127,9 +129,9 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
{ text: 'stream', link: '/en/api/packages/stream' },
|
||||
{ text: 'struct', link: '/en/api/packages/struct' },
|
||||
{ text: 'strutil', link: '/en/api/packages/strutil' },
|
||||
{ text: 'system', link: '/en/api/packages/system' },
|
||||
{ text: 'tuple', link: '/en/api/packages/tuple' },
|
||||
{ text: 'validator', link: '/en/api/packages/validator' },
|
||||
{ text: 'system', link: '/en/api/packages/system' },
|
||||
{ text: 'xerror', link: '/en/api/packages/xerror' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -127,7 +127,8 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
],
|
||||
},
|
||||
{ text: '日期&时间', link: '/api/packages/datetime' },
|
||||
{ text: '文件', link: '/api/packages/fileutil' },
|
||||
{ text: '事件总线', link: '/api/packages/eventbus' },
|
||||
{ text: '文件处理', link: '/api/packages/fileutil' },
|
||||
{ text: '格式化工具', link: '/api/packages/formatter' },
|
||||
{ text: '函数', link: '/api/packages/function' },
|
||||
{ text: '数学工具', link: '/api/packages/mathutil' },
|
||||
@@ -140,9 +141,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
{ text: '流', link: '/api/packages/stream' },
|
||||
{ text: '结构体', link: '/api/packages/struct' },
|
||||
{ text: '字符串', link: '/api/packages/strutil' },
|
||||
{ text: '系统', link: '/api/packages/system' },
|
||||
{ text: '枚举', link: '/api/packages/enum' },
|
||||
{ text: '元组', link: '/api/packages/tuple' },
|
||||
{ text: '验证器', link: '/api/packages/validator' },
|
||||
{ text: '系统工具函数', link: '/api/packages/system' },
|
||||
{ text: '错误处理', link: '/api/packages/xerror' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# API概述
|
||||
|
||||
<b>lancet(柳叶刀)是一个功能强大、全面、高效、可复用的go语言工具函数库。包含25个包,超过600个工具函数。功能涵盖字符串处理、切片处理、网络、并发、加解密、文件处理、时间/日期、流处理、迭代器等等。</b>
|
||||
# API 概述
|
||||
|
||||
<b>lancet(柳叶刀)是一个功能强大、全面、高效、可复用的 go 语言工具函数库。包含 25 个包,超过 600 个工具函数。功能涵盖字符串处理、切片处理、网络、并发、加解密、文件处理、时间/日期、流处理、迭代器等等。</b>
|
||||
|
||||
<style>
|
||||
.package-title {
|
||||
@@ -27,7 +26,7 @@ outline: deep
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 40px;
|
||||
background: #10b981;
|
||||
background: #6cadf5;
|
||||
border: 1px solid;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -47,6 +46,8 @@ outline: deep
|
||||
<div class="package-cell">cryptor</div>
|
||||
<div class="package-cell">datastructure</div>
|
||||
<div class="package-cell">datetime</div>
|
||||
<div class="package-cell">enum</div>
|
||||
<div class="package-cell">eventbus</div>
|
||||
<div class="package-cell">fileutil</div>
|
||||
<div class="package-cell">formatter</div>
|
||||
<div class="package-cell">function</div>
|
||||
@@ -66,4 +67,4 @@ outline: deep
|
||||
<div class="package-cell">validator</div>
|
||||
<div class="package-cell">xerror</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ algorithm 算法包实现一些基本算法,sort,search,lrucache。
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sort.go](https://github.com/duke-git/lancet/blob/main/algorithm/sort.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
## 源码:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go](https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -35,6 +36,17 @@ import (
|
||||
- [Take](#Take)
|
||||
- [Tee](#Tee)
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
- [NewKeyedLocker](#NewKeyedLocker)
|
||||
- [KeyedLocker_Do](#Do)
|
||||
- [NewRWKeyedLocker](#NewRWKeyedLocker)
|
||||
- [RLock](#RLock)
|
||||
- [Lock](#Lock)
|
||||
- [NewTryKeyedLocker](#NewTryKeyedLocker)
|
||||
- [TryLock](#TryLock)
|
||||
- [Unlock](#Unlock)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
@@ -452,3 +464,389 @@ func main() {
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
### <span id="NewKeyedLocker">NewKeyedLocker</span>
|
||||
|
||||
<p>NewKeyedLocker创建一个新的KeyedLocker,并为锁的过期设置指定的 TTL。KeyedLocker 是一个简单的键值锁实现,允许非阻塞的锁获取。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Do">Do</span>
|
||||
|
||||
<p>为指定的键获取锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewRWKeyedLocker">NewRWKeyedLocker</span>
|
||||
|
||||
<p>NewRWKeyedLocker创建一个新的RWKeyedLocker,并为锁的过期设置指定的 TTL。RWKeyedLocker 是一个简单的键值读写锁实现,允许非阻塞的锁获取。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/CkaJWWwZm9)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RLock">RLock</span>
|
||||
|
||||
<p>RLock为指定的键获取读锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.RLock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Lock">Lock</span>
|
||||
|
||||
<p>Lock为指定的键获取锁并执行提供的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/WgAcXbOPKGk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewTryKeyedLocker">NewTryKeyedLocker</span>
|
||||
|
||||
<p>创建一个TryKeyedLocker实例,TryKeyedLocker是KeyedLocker的非阻塞版本。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TryLock">TryLock</span>
|
||||
|
||||
<p>TryLock尝试获取指定键的锁。如果锁成功获取,则返回true,否则返回false。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Unlock">Unlock</span>
|
||||
|
||||
<p>释放指定键的锁。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
- [AesEcbDecrypt](#AesEcbDecrypt)
|
||||
- [AesCbcEncrypt](#AesCbcEncrypt)
|
||||
- [AesCbcDecrypt](#AesCbcDecrypt)
|
||||
- [AesCtrCrypt](#AesCtrCrypt)
|
||||
- [AesCtrCrypt<sup>deprecated</sup>](#AesCtrCrypt)
|
||||
- [AesCtrEncrypt](#AesCtrEncrypt)
|
||||
- [AesCtrDecrypt](#AesCtrDecrypt)
|
||||
- [AesCfbEncrypt](#AesCfbEncrypt)
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
@@ -40,7 +42,7 @@ import (
|
||||
- [DesEcbDecrypt](#DesEcbDecrypt)
|
||||
- [DesCbcEncrypt](#DesCbcEncrypt)
|
||||
- [DesCbcDecrypt](#DesCbcDecrypt)
|
||||
- [DesCtrCrypt](#DesCtrCrypt)
|
||||
- [DesCtrCrypt<sup>deprecated</sup>](#DesCtrCrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesOfbEncrypt](#DesOfbEncrypt)
|
||||
@@ -72,7 +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>
|
||||
|
||||
@@ -218,6 +227,8 @@ func main() {
|
||||
|
||||
<p>使用AES CTR算法模式加密/解密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`AesCtrEncrypt`和`AesCtrDecrypt`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -248,6 +259,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrEncrypt">AesCtrEncrypt</span>
|
||||
|
||||
<p>使用AES CTR算法模式加密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrDecrypt">AesCtrDecrypt</span>
|
||||
|
||||
<p>使用AES CTR算法模式解密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCfbEncrypt">AesCfbEncrypt</span>
|
||||
|
||||
<p>使用AES CFB算法模式加密数据,参数`key`的长度是16, 24 or 32。</p>
|
||||
@@ -648,10 +727,80 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrEncrypt">DesCtrEncrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密数据,参数`key`的长度是8</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func DesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrDecrypt">DesCtrDecrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密数据,参数`key`的长度是8</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrCrypt">DesCtrCrypt</span>
|
||||
|
||||
<p>使用DES CTR算法模式加密/解密数据,参数`key`的长度是8</p>
|
||||
|
||||
> ⚠️ 本函数已弃用,使用`DesCtrEncrypt`和`DesCtrDecrypt`代替。
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
@@ -1688,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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1092,7 +1092,7 @@ func main() {
|
||||
|
||||
|
||||
### 4. PriorityQueue
|
||||
切片实现的额优先级队列。
|
||||
切片实现的优先级队列。
|
||||
|
||||
### <span id="NewPriorityQueue">NewPriorityQueue</span>
|
||||
<p>返回一个具有特定容量的PriorityQueue指针,参数 `comarator` 用于比较队列中T类型的值。</p>
|
||||
|
||||
@@ -153,7 +153,7 @@ func main() {
|
||||
func AddWeek(t time.Time, weeks int64) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -189,7 +189,7 @@ func main() {
|
||||
func AddMonth(t time.Time, months int64) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -333,7 +333,7 @@ func main() {
|
||||
func AddDaySafe(t time.Time, days int) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -380,7 +380,7 @@ func main() {
|
||||
func AddMonthSafe(t time.Time, months int) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -417,7 +417,7 @@ func main() {
|
||||
func AddYearSafe(t time.Time, years int) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -546,7 +546,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
|
||||
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
|
||||
@@ -562,12 +562,12 @@ import (
|
||||
|
||||
func main() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
result := datetime.BeginOfWeek(input)
|
||||
result := datetime.BeginOfWeek(input, time.Monday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 00:00:00 +0000 UTC
|
||||
// 2023-01-09 00:00:00 +0000 UTC
|
||||
}
|
||||
```
|
||||
|
||||
@@ -727,7 +727,7 @@ func main() {
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
// 2023-01-02 00:00:00 +0000 UTC
|
||||
}
|
||||
```
|
||||
|
||||
@@ -738,10 +738,10 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
|
||||
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/i08qKXD9flf)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/mGSA162YgX9)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -754,12 +754,12 @@ import (
|
||||
|
||||
func main() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
result := datetime.EndOfWeek(input)
|
||||
result := datetime.EndOfWeek(input, time.Sunday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
850
docs/api/packages/enum.md
Normal file
850
docs/api/packages/enum.md
Normal file
@@ -0,0 +1,850 @@
|
||||
# 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)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 用法:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 目录
|
||||
|
||||
- [NewItem](#NewItem)
|
||||
- [NewItemsFromPairs](#NewItemsFromPairs)
|
||||
- [Value](#Value)
|
||||
- [Name](#Name)
|
||||
- [Valid](#Valid)
|
||||
- [MarshalJSON](#MarshalJSON)
|
||||
- [NewRegistry](#NewRegistry)
|
||||
- [Add](#Add)
|
||||
- [Remove](#Remove)
|
||||
- [Update](#Update)
|
||||
- [GetByValue](#GetByValue)
|
||||
- [GetByName](#GetByName)
|
||||
- [Items](#Items)
|
||||
- [Contains](#Contains)
|
||||
- [Size](#Size)
|
||||
- [Range](#Range)
|
||||
- [SortedItems](#SortedItems)
|
||||
- [Filter](#Filter)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## 文档
|
||||
|
||||
### <span id="NewItem">NewItem</span>
|
||||
|
||||
<p>创建枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewItem[T comparable](value T, name string) *Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/8qNsLw01HD5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
fmt.Println(item1.Name(), item1.Value())
|
||||
fmt.Println(item2.Name(), item2.Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
|
||||
|
||||
<p>从Pair结构体的切片创建枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Value">Value</span>
|
||||
|
||||
<p>返回枚举项的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (it *Item[T]) Value() T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Name">Name</span>
|
||||
|
||||
<p>返回枚举项的名称。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (it *Item[T]) Name() string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Valid">Valid</span>
|
||||
|
||||
<p>检查枚举项是否有效。如果提供了自定义检查函数,将使用该函数验证值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (it *Item[T]) Valid(checker ...func(T) bool) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pA3lYY2VSm3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item := enum.NewItem(Active, "Active")
|
||||
fmt.Println(item.Valid())
|
||||
|
||||
invalidItem := enum.NewItem(Unknown, "")
|
||||
fmt.Println(invalidItem.Valid())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="MarshalJSON">MarshalJSON</span>
|
||||
|
||||
<p>枚举项实现json.Marshaler接口。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
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/zIZEdAnneB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item := enum.NewItem(Active, "Active")
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem enum.Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
// Output:
|
||||
// {"name":"Active","value":1}
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewRegistry">NewRegistry</span>
|
||||
|
||||
<p>Registry 定义了一个通用的枚举注册表结构体。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Add">Add</span>
|
||||
|
||||
<p>向枚举注册表添加枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Add(items ...*Item[T])
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Remove">Remove</span>
|
||||
|
||||
<p>在枚举注册表中删除枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Remove(value T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
fmt.Println("Size before removal:", registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
fmt.Println("Removed:", removed)
|
||||
fmt.Println("Size after removal:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Size before removal: 1
|
||||
// Removed: true
|
||||
// Size after removal: 0
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Update">Update</span>
|
||||
|
||||
<p>在枚举注册表中更新枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Update(value T, newName string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ol0moT1J9Xl)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
updated := registry.Update(Active, "Activated")
|
||||
fmt.Println("Updated:", updated)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("New name:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Updated: true
|
||||
// New name: Activated
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetByValue">GetByValue</span>
|
||||
|
||||
<p>在枚举注册表中通过值获取枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/niJ1U2KlE_m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found name by value:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found name by value: Active
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetByName">GetByName</span>
|
||||
|
||||
<p>在枚举注册表中通过名称获取枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/49ie_gpqH0m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByName("Active"); found {
|
||||
fmt.Println("Found value by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found value by name: 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Items">Items</span>
|
||||
|
||||
<p>返回枚举注册表中的枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Items() []*Item[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/lAJFAradbvQ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.Items() {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Contains">Contains</span>
|
||||
|
||||
<p>检查注册表中是否存在具有给定值的枚举项。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Contains(value T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
registry.Add(item)
|
||||
|
||||
fmt.Println(registry.Contains(Active))
|
||||
fmt.Println(registry.Contains(Inactive))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Size">Size</span>
|
||||
|
||||
<p>返回注册表中枚举项的数目。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Size() int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/TeDArWhlQe2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
fmt.Println("Initial size:", registry.Size())
|
||||
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("Size after adding items:", registry.Size())
|
||||
|
||||
registry.Remove(Active)
|
||||
fmt.Println("Size after removing an item:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Initial size: 0
|
||||
// Size after adding items: 2
|
||||
// Size after removing an item: 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Range">Range</span>
|
||||
|
||||
<p>遍历注册表中的所有枚举项,并应用给定的函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GPsZbQbefWN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *enum.Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="SortedItems">SortedItems</span>
|
||||
|
||||
<p>返回按给定比较函数排序的所有枚举项的切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
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/tN9RE_m_WEI)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Inactive, "Inactive")
|
||||
item2 := enum.NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
|
||||
return i1.Value() < i2.Value()
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Filter">Filter</span>
|
||||
|
||||
<p>返回满足给定谓词函数的枚举项切片。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
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/uTUpTdcyoCU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
for _, item := range activeItems {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
@@ -48,7 +48,7 @@ import (
|
||||
func NewEventBus[T any]() *EventBus[T]
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -65,7 +65,7 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -84,7 +84,7 @@ func main() {
|
||||
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/EYGf_8cHei-)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -106,8 +106,8 @@ func main() {
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, filter)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -126,7 +126,7 @@ func main() {
|
||||
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -145,7 +145,7 @@ func main() {
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Unsubscribe("event1", listener)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -161,10 +161,10 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (eb *EventBus[T]) Publish(event Event[T])
|
||||
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gHTtVexFSH9)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -179,7 +179,7 @@ func main() {
|
||||
fmt.Println(eventData)
|
||||
}, false, 0, nil)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
@@ -196,7 +196,7 @@ func main() {
|
||||
func (eb *EventBus[T]) ClearListeners()
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KBfBYlKPgqD)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -213,9 +213,12 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListeners()
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -234,7 +237,7 @@ func main() {
|
||||
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gvMljmJOZmU)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -251,14 +254,17 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListenersByTopic("event1")
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
@@ -272,7 +278,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetListenersCount(topic string) int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -284,14 +290,14 @@ func main() {
|
||||
eb := eventbus.NewEventBus[int]()
|
||||
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||
|
||||
count := eb.GetListenersCount("event1")
|
||||
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
@@ -305,7 +311,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetAllListenersCount() int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -338,7 +344,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetEvents() []string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/etgjjcOtAjX)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -359,8 +365,8 @@ func main() {
|
||||
}
|
||||
|
||||
// Output:
|
||||
// event1
|
||||
// event2
|
||||
// event1
|
||||
}
|
||||
```
|
||||
|
||||
@@ -374,7 +380,7 @@ func main() {
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(err error))
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gmB0gnFe5mc)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -393,7 +399,7 @@ func main() {
|
||||
panic("error")
|
||||
}, false, 0, nil)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// error
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
- [IsDir](#IsDir)
|
||||
- [ListFileNames](#ListFileNames)
|
||||
- [RemoveFile](#RemoveFile)
|
||||
- [RemoveDir](#RemoveDir)
|
||||
- [ReadFileToString](#ReadFileToString)
|
||||
- [ReadFileByLine](#ReadFileByLine)
|
||||
- [Zip](#Zip)
|
||||
@@ -390,12 +391,12 @@ func main() {
|
||||
|
||||
### <span id="RemoveFile">RemoveFile</span>
|
||||
|
||||
<p>删除文件</p>
|
||||
<p>删除文件,支持传入删除前的回调函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RemoveFile(path string) error
|
||||
func RemoveFile(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
|
||||
@@ -416,6 +417,41 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RemoveDir">RemoveDir</span>
|
||||
|
||||
<p>删除目录,支持传入删除前的回调函数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var deletedPaths []string
|
||||
|
||||
err := fileutil.RemoveDir("./tempdir", func(p string) {
|
||||
deletedPaths = append(deletedPaths, p)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println(deletedPaths)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ReadFileToString">ReadFileToString</span>
|
||||
|
||||
<p>读取文件内容并返回字符串</p>
|
||||
@@ -934,7 +970,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
|
||||
@@ -958,9 +994,9 @@ func main() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
fmt.Println(string(dat))
|
||||
|
||||
|
||||
// Output:
|
||||
// User-agent: *
|
||||
// Disallow: /deny
|
||||
@@ -989,7 +1025,7 @@ import (
|
||||
|
||||
func main() {
|
||||
const mb = 1024 * 1024
|
||||
const defaultChunkSizeMB = 100
|
||||
const defaultChunkSizeMB = 100
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
@@ -1053,7 +1089,7 @@ func main() {
|
||||
numParsers := runtime.NumCPU()
|
||||
|
||||
linesCh := make(chan []string, numParsers)
|
||||
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
// Jim,21,male
|
||||
@@ -1078,6 +1114,7 @@ func main() {
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
|
||||
|
||||
<p>返回exe,dll文件版本号(仅Window平台).</p>
|
||||
@@ -1107,4 +1144,4 @@ func main() {
|
||||
// Output:
|
||||
// 3.9.10.19
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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号"
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,7 +78,8 @@ import (
|
||||
- [GetOrSet](#GetOrSet)
|
||||
- [SortByKey](#SortByKey)
|
||||
- [GetOrDefault](#GetOrDefault)
|
||||
|
||||
- [FindValuesBy](#FindValuesBy)
|
||||
- [ToMarkdownTable](#ToMarkdownTable)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1095,7 +1096,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
|
||||
@@ -1259,7 +1260,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
|
||||
|
||||
<p>删除给定键的键值对。</p>
|
||||
@@ -2192,7 +2192,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="GetOrSet">GetOrSet</span>
|
||||
|
||||
<p>返回给定键的值,如果不存在则设置该值。</p>
|
||||
@@ -2276,7 +2275,7 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
|
||||
@@ -2307,4 +2306,108 @@ func main() {
|
||||
// a
|
||||
// default
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### <span id="FindValuesBy">FindValuesBy</span>
|
||||
|
||||
<p>返回一个切片,包含满足给定谓词判断函数的map中的值。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/maputil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := map[int]string{
|
||||
1: "a",
|
||||
2: "b",
|
||||
3: "c",
|
||||
4: "d",
|
||||
}
|
||||
|
||||
result := maputil.FindValuesBy(m, func(k int, v string) bool {
|
||||
return k%2 == 0
|
||||
})
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [b d]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToMarkdownTable">ToMarkdownTable</span>
|
||||
|
||||
<p>将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
编辑
|
||||
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/w_pSLfeyEB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/maputil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 基本用法:自动从数据中提取列名作为表头
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
{"name": "Bob", "age": 30, "salary": 60000},
|
||||
}
|
||||
|
||||
result := maputil.ToMarkdownTable(data, nil, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |name|age|salary|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// 自定义表头显示名称
|
||||
headerMap := map[string]string{
|
||||
"name": "姓名",
|
||||
"age": "年龄",
|
||||
"salary": "薪资",
|
||||
}
|
||||
result = maputil.ToMarkdownTable(data, headerMap, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |姓名|年龄|薪资|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// 自定义列顺序
|
||||
columnOrder := []string{"salary", "name"}
|
||||
result = maputil.ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Println(result)
|
||||
|
||||
// 输出:
|
||||
// |salary|name|
|
||||
// |---|---|
|
||||
// |50000|Alice|
|
||||
// |60000|Bob|
|
||||
}
|
||||
```
|
||||
|
||||
@@ -466,14 +466,14 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="T运行cRound">T运行cRound</span>
|
||||
### <span id="TruncRound">TruncRound</span>
|
||||
|
||||
<p>截短n位小数(不进行四舍五入)</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func T运行cRound[T constraints.Float | constraints.Integer](x T, n int) T
|
||||
func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/aumarSHIGzP)</span></b>
|
||||
@@ -487,9 +487,9 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := mathutil.T运行cRound(0.124, 2)
|
||||
result2 := mathutil.T运行cRound(0.125, 2)
|
||||
result3 := mathutil.T运行cRound(0.125, 3)
|
||||
result1 := mathutil.TruncRound(0.124, 2)
|
||||
result2 := mathutil.TruncRound(0.125, 2)
|
||||
result3 := mathutil.TruncRound(0.125, 3)
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
|
||||
@@ -48,6 +48,8 @@ import (
|
||||
- [UploadFile](#UploadFile)
|
||||
- [IsPingConnected](#IsPingConnected)
|
||||
- [IsTelnetConnected](#IsTelnetConnected)
|
||||
- [BuildUrl](#BuildUrl)
|
||||
- [AddQueryParams](#AddQueryParams)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1031,3 +1033,83 @@ func main() {
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="BuildUrl">BuildUrl</span>
|
||||
|
||||
<p>创建url字符串。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/netutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr, err := netutil.BuildUrl(
|
||||
"https",
|
||||
"example.com",
|
||||
"query",
|
||||
map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AddQueryParams">AddQueryParams</span>
|
||||
|
||||
<p>向url添加查询参数。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/netutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr, err := netutil.BuildUrl(
|
||||
"https",
|
||||
"example.com",
|
||||
"query",
|
||||
map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
- [Contain](#Contain)
|
||||
- [ContainBy](#ContainBy)
|
||||
- [ContainSubSlice](#ContainSubSlice)
|
||||
- [ContainAny](#ContainAny)
|
||||
- [Chunk](#Chunk)
|
||||
- [Compact](#Compact)
|
||||
- [Concat](#Concat)
|
||||
@@ -256,6 +257,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ContainAny">ContainAny</span>
|
||||
|
||||
<p>判断slice是否包含targets切片中的任意一个元素</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Chunk">Chunk</span>
|
||||
|
||||
<p>按照size参数均分slice</p>
|
||||
@@ -887,7 +925,7 @@ func main() {
|
||||
func EqualUnordered[T comparable](slice1, slice2 []T) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1772,7 +1810,7 @@ func main() {
|
||||
func ReverseCopy[T any](slice []T) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2065,7 +2103,7 @@ func main() {
|
||||
func ShuffleCopy[T any](slice []T) []T
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
- [IsStruct](#IsStruct)
|
||||
- [Tag](#Tag)
|
||||
- [Name](#Name)
|
||||
- [TypeName](#TypeName)
|
||||
- [Value](#Value)
|
||||
- [Kind](#Kind)
|
||||
- [IsEmbedded](#IsEmbedded)
|
||||
@@ -53,7 +54,7 @@ import (
|
||||
func New(value any, tagName ...string) *Struct
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/O29l8kk-Z17)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -68,7 +69,11 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
// to do something
|
||||
|
||||
fmt.Println(s.ToMap())
|
||||
|
||||
// Output:
|
||||
// map[name:11] <nil>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -88,7 +93,7 @@ func (s *Struct) ToMap() (map[string]any, error)
|
||||
func ToMap(v any) (map[string]any, error)
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qQbLySBgerZ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -129,7 +134,7 @@ func main() {
|
||||
func (s *Struct) Fields() []*Field
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -161,10 +166,10 @@ func main() {
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (s *Struct) Field(name string) *Field
|
||||
func (s *Struct) Field(name string) (*Field, bool)
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KocZMSYarza)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -180,12 +185,14 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
f := s.Field("Name")
|
||||
f, found := s.Field("Name")
|
||||
|
||||
fmt.Println(f.Value())
|
||||
fmt.Println(found)
|
||||
|
||||
// Output:
|
||||
// 11
|
||||
// true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -199,7 +206,7 @@ func main() {
|
||||
func (s *Struct) IsStruct() bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -233,7 +240,7 @@ func main() {
|
||||
func (f *Field) Tag() *Tag
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -254,7 +261,7 @@ func main() {
|
||||
tag := n.Tag()
|
||||
|
||||
fmt.Println(tag.Name)
|
||||
|
||||
|
||||
// Output:
|
||||
// name
|
||||
}
|
||||
@@ -270,7 +277,7 @@ func main() {
|
||||
func (f *Field) Value() any
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -288,10 +295,10 @@ func main() {
|
||||
|
||||
s := structs.New(p1)
|
||||
n, _ := s.Field("Name")
|
||||
|
||||
|
||||
fmt.Println(n.Value())
|
||||
|
||||
// Output:
|
||||
|
||||
// Output:
|
||||
// 111
|
||||
}
|
||||
```
|
||||
@@ -306,7 +313,7 @@ func main() {
|
||||
func (f *Field) IsEmbedded() bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -331,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
|
||||
}
|
||||
@@ -351,7 +358,7 @@ func main() {
|
||||
func (f *Field) IsExported() bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -370,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
|
||||
}
|
||||
@@ -390,7 +397,7 @@ func main() {
|
||||
func (f *Field) IsZero() bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/RzqpGISf87r)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -409,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
|
||||
}
|
||||
@@ -429,7 +436,7 @@ func main() {
|
||||
func (f *Field) Name() string
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zfIGlqsatee)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -448,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
|
||||
}
|
||||
@@ -468,7 +475,7 @@ func main() {
|
||||
func (f *Field) Kind() reflect.Kind
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -487,16 +494,52 @@ 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
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TypeName">TypeName</span>
|
||||
|
||||
<p>获取结构体类型名。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func (s *Struct) TypeName() string
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/structs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type Parent struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
p := &Parent{Age: 11}
|
||||
s := structs.New(p)
|
||||
|
||||
fmt.Println(s.TypeName())
|
||||
|
||||
// Output:
|
||||
// Parent
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsSlice">IsSlice</span>
|
||||
|
||||
<p>判断属性是否是切片</p>
|
||||
@@ -507,7 +550,7 @@ func main() {
|
||||
func (f *Field) IsSlice() bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -526,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
|
||||
}
|
||||
```
|
||||
@@ -544,7 +587,7 @@ func main() {
|
||||
func (f *Field) IsTargetType(targetType reflect.Kind) bool
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ig75P-agN39)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -565,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
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1772,7 +1772,7 @@ func main() {
|
||||
func FindAllOccurrences(str, substr string) []int
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uvyA6azGLB1)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -65,6 +65,8 @@ import (
|
||||
- [IsAmericanExpress](#IsAmericanExpress)
|
||||
- [IsUnionPay](#IsUnionPay)
|
||||
- [IsChinaUnionPay](#IsChinaUnionPay)
|
||||
- [IsPassport](#IsPassport)
|
||||
- [IsChineseHMPassport](#IsChineseHMPassport)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -547,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 (
|
||||
@@ -557,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
|
||||
}
|
||||
```
|
||||
@@ -824,6 +841,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsAlphaNumeric">IsAlphaNumeric</span>
|
||||
|
||||
<p>验证字符串是字母或数字。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func IsAlphaNumeric(s string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/RHeESLrLg9c)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsAlphaNumeric("ABC")
|
||||
result2 := validator.IsAlphaNumeric("123")
|
||||
result3 := validator.IsAlphaNumeric("abc123")
|
||||
result4 := validator.IsAlphaNumeric("abc123@#$")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsJSON">IsJSON</span>
|
||||
|
||||
<p>验证字符串是否是有效json。</p>
|
||||
@@ -1001,7 +1055,7 @@ func main() {
|
||||
func IsIpPort(str string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xUmls_b9L29)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1513,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"
|
||||
@@ -1530,3 +1586,82 @@ func main() {
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsPassport">IsPassport</span>
|
||||
|
||||
<p>判断护照(正则判断)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func IsPassport(passport, country string) bool
|
||||
```
|
||||
|
||||
<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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsPassport("P123456789", "CN")
|
||||
result2 := validator.IsPassport("123456789", "US")
|
||||
result3 := validator.IsPassport("AB1234567", "RU")
|
||||
result4 := validator.IsPassport("123456789", "CN")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
|
||||
|
||||
<p>判断港澳台通行证(正则判断)。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func IsChineseHMPassport(hmPassport string) bool
|
||||
```
|
||||
|
||||
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xKG6spQTcY0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsChineseHMPassport("C12345678")
|
||||
result2 := validator.IsChineseHMPassport("C00000000")
|
||||
result3 := validator.IsChineseHMPassport("M12345678")
|
||||
result4 := validator.IsChineseHMPassport("c12345678")
|
||||
result5 := validator.IsChineseHMPassport("C1234567")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,7 +6,6 @@ outline: deep
|
||||
|
||||
<b>Lancet (Lancet) is a powerful, comprehensive, efficient and reusable go language tool function library. Contains 25 packages, more than 600 utility functions. Functions cover string processing, slice processing, network, concurrency, encryption and decryption, file processing, time/date, stream processing, iterators, and more.</b>
|
||||
|
||||
|
||||
<style>
|
||||
.package-title {
|
||||
color: black;
|
||||
@@ -27,7 +26,7 @@ outline: deep
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 40px;
|
||||
background: #059669;
|
||||
background: #6cadf5;
|
||||
border: 1px solid;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -47,6 +46,8 @@ outline: deep
|
||||
<div class="package-cell">cryptor</div>
|
||||
<div class="package-cell">datastructure</div>
|
||||
<div class="package-cell">datetime</div>
|
||||
<div class="package-cell">enum</div>
|
||||
<div class="package-cell">eventbus</div>
|
||||
<div class="package-cell">fileutil</div>
|
||||
<div class="package-cell">formatter</div>
|
||||
<div class="package-cell">function</div>
|
||||
@@ -67,4 +68,3 @@ outline: deep
|
||||
<div class="package-cell">xerror</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Package algorithm implements some basic algorithm. eg. sort, search.
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/sort.go](https://github.com/duke-git/lancet/blob/main/algorithm/sort.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/search.go](https://github.com/duke-git/lancet/blob/main/algorithm/search.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lru_cache.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go](https://github.com/duke-git/lancet/blob/main/algorithm/lrucache.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
# Concurrency
|
||||
|
||||
Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel.
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Source:
|
||||
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/channel.go](https://github.com/duke-git/lancet/blob/main/concurrency/channel.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go](https://github.com/duke-git/lancet/blob/main/concurrency/keyed_locker.go)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Usage:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
@@ -19,24 +22,39 @@ import (
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Index
|
||||
|
||||
### Channel
|
||||
- [NewChannel](#NewChannel)
|
||||
- [Bridge](#Bridge)
|
||||
- [FanIn](#FanIn)
|
||||
- [Generate](#Generate)
|
||||
- [Or](#Or)
|
||||
- [OrDone](#OrDone)
|
||||
- [Repeat](#Repeat)
|
||||
- [RepeatFn](#RepeatFn)
|
||||
- [Take](#Take)
|
||||
- [Tee](#Tee)
|
||||
|
||||
- [NewChannel](#NewChannel)
|
||||
- [Bridge](#Bridge)
|
||||
- [FanIn](#FanIn)
|
||||
- [Generate](#Generate)
|
||||
- [Or](#Or)
|
||||
- [OrDone](#OrDone)
|
||||
- [Repeat](#Repeat)
|
||||
- [RepeatFn](#RepeatFn)
|
||||
- [Take](#Take)
|
||||
- [Tee](#Tee)
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
- [NewKeyedLocker](#NewKeyedLocker)
|
||||
- [Do](#Do)
|
||||
- [NewRWKeyedLocker](#NewRWKeyedLocker)
|
||||
- [RLock](#RLock)
|
||||
- [Lock](#Lock)
|
||||
- [NewTryKeyedLocker](#NewTryKeyedLocker)
|
||||
- [TryLock](#TryLock)
|
||||
- [Unlock](#Unlock)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Documentation
|
||||
|
||||
## Channel
|
||||
|
||||
### <span id="NewChannel">NewChannel</span>
|
||||
|
||||
<p>Create a Channel pointer instance.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
@@ -45,6 +63,7 @@ import (
|
||||
type Channel[T any] struct
|
||||
func NewChannel[T any]() *Channel[T]
|
||||
```
|
||||
|
||||
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||
|
||||
```go
|
||||
@@ -69,6 +88,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qmWSy1NVF-Y)</span></b>
|
||||
|
||||
```go
|
||||
@@ -121,6 +141,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/2VYFMexEvTm)</span></b>
|
||||
|
||||
```go
|
||||
@@ -160,6 +181,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -199,6 +221,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Generate(ctx context.Context, values ...T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||
|
||||
```go
|
||||
@@ -237,6 +260,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -279,6 +303,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -322,6 +347,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -360,6 +386,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -406,6 +433,7 @@ func main() {
|
||||
```go
|
||||
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
@@ -430,11 +458,397 @@ func main() {
|
||||
fmt.Println(v)
|
||||
fmt.Println(<-ch2)
|
||||
}
|
||||
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### KeyedLocker
|
||||
|
||||
### <span id="NewKeyedLocker">NewKeyedLocker</span>
|
||||
|
||||
<p>KeyedLocker is a simple implementation of a keyed locker that allows for non-blocking lock acquisition.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewKeyedLocker[K comparable](ttl time.Duration) *KeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Do">Do</span>
|
||||
|
||||
<p>Acquires a lock for the specified key and executes the provided function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GzeyC33T5rw)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewKeyedLocker[string](2 * time.Second)
|
||||
|
||||
task := func() {
|
||||
fmt.Println("Executing task...")
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Println("Task completed.")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := locker.Do(ctx, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := locker.Do(ctx2, "mykey", task); err != nil {
|
||||
log.Fatalf("Error executing task: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Task successfully executed.")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
// Executing task...
|
||||
// Task completed.
|
||||
// Task successfully executed.
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewRWKeyedLocker">NewRWKeyedLocker</span>
|
||||
|
||||
<p>RWKeyedLocker is a read-write version of KeyedLocker.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RLock">RLock</span>
|
||||
|
||||
<p>Acquires a read lock for the specified key and executes the provided function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZrCr8sMo77T)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.RLock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Lock">Lock</span>
|
||||
|
||||
<p>Acquires a write lock for the specified key and executes the provided function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/WgAcXbOPKGk)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := NewRWKeyedLocker[string](2 * time.Second)
|
||||
|
||||
// Simulate a key
|
||||
key := "resource_key"
|
||||
|
||||
fn := func() {
|
||||
fmt.Println("Starting write operation...")
|
||||
// Simulate write operation, assuming it takes 2 seconds
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
fmt.Println("Write operation completed!")
|
||||
}
|
||||
|
||||
// Acquire the write lock and execute the operation
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Execute the lock operation with a 3-second timeout
|
||||
err := locker.Lock(ctx, key, fn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//output:
|
||||
//Starting write operation...
|
||||
//Write operation completed!
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewTryKeyedLocker">NewTryKeyedLocker</span>
|
||||
|
||||
<p>TryKeyedLocker is a non-blocking version of KeyedLocker.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewTryKeyedLocker[K comparable]() *TryKeyedLocker[K]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TryLock">TryLock</span>
|
||||
|
||||
<p>TryLock tries to acquire a lock for the specified key.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) TryLock(key K) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Unlock">Unlock</span>
|
||||
|
||||
<p>Unlock releases the lock for the specified key.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (l *TryKeyedLocker[K]) Unlock(key K)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/VG9qLvyetE2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/concurrency"
|
||||
)
|
||||
|
||||
func main() {
|
||||
locker := concurrency.NewTryKeyedLocker[string]()
|
||||
|
||||
key := "resource_key"
|
||||
|
||||
if locker.TryLock(key) {
|
||||
fmt.Println("Lock acquired")
|
||||
time.Sleep(1 * time.Second)
|
||||
// Unlock after work is done
|
||||
locker.Unlock(key)
|
||||
fmt.Println("Lock released")
|
||||
} else {
|
||||
fmt.Println("Lock failed")
|
||||
}
|
||||
|
||||
//output:
|
||||
//Lock acquired
|
||||
//Lock released
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
- [AesEcbDecrypt](#AesEcbDecrypt)
|
||||
- [AesCbcEncrypt](#AesCbcEncrypt)
|
||||
- [AesCbcDecrypt](#AesCbcDecrypt)
|
||||
- [AesCtrCrypt](#AesCtrCrypt)
|
||||
- [AesCtrCrypt<sup>deprecated</sup>](#AesCtrCrypt)
|
||||
- [AesCtrEncrypt](#AesCtrEncrypt)
|
||||
- [AesCtrDecrypt](#AesCtrDecrypt)
|
||||
- [AesCfbEncrypt](#AesCfbEncrypt)
|
||||
- [AesCfbDecrypt](#AesCfbDecrypt)
|
||||
- [AesOfbEncrypt](#AesOfbEncrypt)
|
||||
@@ -40,7 +42,9 @@ import (
|
||||
- [DesEcbDecrypt](#DesEcbDecrypt)
|
||||
- [DesCbcEncrypt](#DesCbcEncrypt)
|
||||
- [DesCbcDecrypt](#DesCbcDecrypt)
|
||||
- [DesCtrCrypt](#DesCtrCrypt)
|
||||
- [DesCtrCrypt<sup>deprecated</sup>](#DesCtrCrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesCfbEncrypt](#DesCfbEncrypt)
|
||||
- [DesCfbDecrypt](#DesCfbDecrypt)
|
||||
- [DesOfbEncrypt](#DesOfbEncrypt)
|
||||
@@ -72,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>
|
||||
|
||||
@@ -217,6 +229,8 @@ func main() {
|
||||
|
||||
<p>Encrypt or decrypt data with key use AES CTR algorithm. Length of `key` param should be 16, 24 or 32.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `AesCtrEncrypt` and `AesCtrDecrypt` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -247,6 +261,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrEncrypt">AesCtrEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use AES CTR algorithm</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCtrDecrypt">AesCtrDecrypt</span>
|
||||
|
||||
<p>Decrypt data with key use AES CTR algorithm</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/x6pjPAvThRz)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefghijklmnop"
|
||||
|
||||
encrypted := cryptor.AesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.AesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AesCfbEncrypt">AesCfbEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use AES CFB algorithm. Length of `key` param should be 16, 24 or 32.</p>
|
||||
@@ -651,6 +733,8 @@ func main() {
|
||||
|
||||
<p>Encrypt or decrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
> ⚠️ This function is deprecated. use `DesCtrEncrypt` and `DesCtrDecrypt` instead.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
@@ -681,6 +765,74 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrEncrypt">DesCtrCrypt</span>
|
||||
|
||||
<p>Encrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func DesCtrEncrypt(data, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCtrDecrypt">DesCtrDecrypt</span>
|
||||
|
||||
<p>Decrypt data with key use DES CTR algorithm. Length of `key` param should be 8.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func DesCtrDecrypt(encrypted, key []byte) []byte
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/S6p_WHCgH1d)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/cryptor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "hello"
|
||||
key := "abcdefgh"
|
||||
|
||||
encrypted := cryptor.DesCtrEncrypt([]byte(data), []byte(key))
|
||||
decrypted := cryptor.DesCtrDecrypt(encrypted, []byte(key))
|
||||
|
||||
fmt.Println(string(decrypted))
|
||||
|
||||
// Output:
|
||||
// hello
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="DesCfbEncrypt">DesCfbEncrypt</span>
|
||||
|
||||
<p>Encrypt data with key use DES CFB algorithm. Length of `key` param should be 8.</p>
|
||||
@@ -1687,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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -155,7 +155,7 @@ func main() {
|
||||
func AddWeek(t time.Time, weeks int64) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/M9TqdMiaA2p)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -191,7 +191,7 @@ func main() {
|
||||
func AddMonth(t time.Time, months int64) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DLoiOnpLvsN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -335,7 +335,7 @@ func main() {
|
||||
func AddDaySafe(t time.Time, days int) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JTohZFpoDJ3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -382,7 +382,7 @@ func main() {
|
||||
func AddMonthSafe(t time.Time, months int) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KLw0lo6mbVW)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -419,7 +419,7 @@ func main() {
|
||||
func AddYearSafe(t time.Time, years int) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KVGXWZZ54ZH)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -551,7 +551,7 @@ func main() {
|
||||
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DCHdcL6gnfV)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -564,12 +564,13 @@ import (
|
||||
|
||||
func main() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
result := datetime.BeginOfWeek(input)
|
||||
|
||||
result := datetime.BeginOfWeek(input, time.Monday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-08 00:00:00 +0000 UTC
|
||||
// 2023-01-02 00:00:00 +0000 UTC
|
||||
}
|
||||
```
|
||||
|
||||
@@ -743,7 +744,7 @@ func main() {
|
||||
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/i08qKXD9flf)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/mGSA162YgX9)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -756,12 +757,12 @@ import (
|
||||
|
||||
func main() {
|
||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||
result := datetime.EndOfWeek(input)
|
||||
result := datetime.EndOfWeek(input, time.Sunday)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
||||
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
850
docs/en/api/packages/enum.md
Normal file
850
docs/en/api/packages/enum.md
Normal file
@@ -0,0 +1,850 @@
|
||||
# Enum
|
||||
|
||||
Package enum provides a simple enum implementation.
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Source:
|
||||
|
||||
- [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>
|
||||
|
||||
## Usage:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
```
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Index
|
||||
|
||||
- [NewItem](#NewItem)
|
||||
- [NewItemsFromPairs](#NewItemsFromPairs)
|
||||
- [Value](#Value)
|
||||
- [Name](#Name)
|
||||
- [Valid](#Valid)
|
||||
- [MarshalJSON](#MarshalJSON)
|
||||
- [NewRegistry](#NewRegistry)
|
||||
- [Add](#Add)
|
||||
- [Remove](#Remove)
|
||||
- [Update](#Update)
|
||||
- [GetByValue](#GetByValue)
|
||||
- [GetByName](#GetByName)
|
||||
- [Items](#Items)
|
||||
- [Contains](#Contains)
|
||||
- [Size](#Size)
|
||||
- [Range](#Range)
|
||||
- [SortedItems](#SortedItems)
|
||||
- [Filter](#Filter)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Documentation
|
||||
|
||||
### <span id="NewItem">NewItem</span>
|
||||
|
||||
<p>Creates a new enum item.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/8qNsLw01HD5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
fmt.Println(item1.Name(), item1.Value())
|
||||
fmt.Println(item2.Name(), item2.Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
|
||||
|
||||
<p>Creates enum items from a slice of Pair structs.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Value">Value</span>
|
||||
|
||||
<p>Returns the value of the enum item.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (it *Item[T]) Value() T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Name">Name</span>
|
||||
|
||||
<p>Returns the name of the enum item.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (it *Item[T]) Name() string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
items := enum.NewItemsFromPairs(
|
||||
enum.Pair[Status]{Value: Active, Name: "Active"},
|
||||
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Valid">Valid</span>
|
||||
|
||||
<p>Checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/pA3lYY2VSm3)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item := enum.NewItem(Active, "Active")
|
||||
fmt.Println(item.Valid())
|
||||
|
||||
invalidItem := enum.NewItem(Unknown, "")
|
||||
fmt.Println(invalidItem.Valid())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="MarshalJSON">MarshalJSON</span>
|
||||
|
||||
<p>Implementation of json.Marshaler interface.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/zIZEdAnneB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
item := enum.NewItem(Active, "Active")
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem enum.Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
// Output:
|
||||
// {"name":"Active","value":1}
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewRegistry">NewRegistry</span>
|
||||
|
||||
<p>Creates a new enum registry.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Add">Add</span>
|
||||
|
||||
<p>Adds enum items to the registry.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Add(items ...*Item[T])
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Remove">Remove</span>
|
||||
|
||||
<p>Removes an enum item from the registry by its value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Remove(value T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
fmt.Println("Size before removal:", registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
fmt.Println("Removed:", removed)
|
||||
fmt.Println("Size after removal:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Size before removal: 1
|
||||
// Removed: true
|
||||
// Size after removal: 0
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Update">Update</span>
|
||||
|
||||
<p>Updates the name of an enum item in the registry by its value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/Ol0moT1J9Xl)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
updated := registry.Update(Active, "Activated")
|
||||
fmt.Println("Updated:", updated)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("New name:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Updated: true
|
||||
// New name: Activated
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetByValue">GetByValue</span>
|
||||
|
||||
<p>Retrieves an enum item by its value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/niJ1U2KlE_m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found name by value:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found name by value: Active
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="GetByName">GetByName</span>
|
||||
|
||||
<p>Retrieves an enum item by its name.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/49ie_gpqH0m)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item)
|
||||
|
||||
if item, found := registry.GetByName("Active"); found {
|
||||
fmt.Println("Found value by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found value by name: 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Items">Items</span>
|
||||
|
||||
<p>Returns a slice of all enum items in the registry.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Items() []*Item[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/lAJFAradbvQ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.Items() {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Contains">Contains</span>
|
||||
|
||||
<p>Checks if an enum item with the given value exists in the registry.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Contains(value T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item := enum.NewItem(Active, "Active")
|
||||
registry.Add(item)
|
||||
|
||||
fmt.Println(registry.Contains(Active))
|
||||
fmt.Println(registry.Contains(Inactive))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Size">Size</span>
|
||||
|
||||
<p>Returns the number of enum items in the registry.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (r *Registry[T]) Size() int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/TeDArWhlQe2)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
fmt.Println("Initial size:", registry.Size())
|
||||
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("Size after adding items:", registry.Size())
|
||||
|
||||
registry.Remove(Active)
|
||||
fmt.Println("Size after removing an item:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Initial size: 0
|
||||
// Size after adding items: 2
|
||||
// Size after removing an item: 1
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Range">Range</span>
|
||||
|
||||
<p>Iterates over all enum items in the registry and applies the given function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/GPsZbQbefWN)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *enum.Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="SortedItems">SortedItems</span>
|
||||
|
||||
<p>Returns a slice of all enum items sorted by the given less function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/tN9RE_m_WEI)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Inactive, "Inactive")
|
||||
item2 := enum.NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
|
||||
return i1.Value() < i2.Value()
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Filter">Filter</span>
|
||||
|
||||
<p>Returns a slice of enum items that satisfy the given predicate function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
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/uTUpTdcyoCU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/enum"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := enum.NewRegistry[Status]()
|
||||
item1 := enum.NewItem(Active, "Active")
|
||||
item2 := enum.NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
for _, item := range activeItems {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
```
|
||||
@@ -48,7 +48,7 @@ import (
|
||||
func NewEventBus[T any]() *EventBus[T]
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHbOPV_NUOJ)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -65,7 +65,7 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -84,7 +84,7 @@ func main() {
|
||||
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/EYGf_8cHei-)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -106,8 +106,8 @@ func main() {
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, filter)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 2})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -126,7 +126,7 @@ func main() {
|
||||
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T))
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Tmh7Ttfvprf)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -145,7 +145,7 @@ func main() {
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Unsubscribe("event1", listener)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -161,10 +161,10 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (eb *EventBus[T]) Publish(event Event[T])
|
||||
func (eb *EventBus[T]) Publish(event eventbus.Event[T])
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gHTtVexFSH9)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -179,7 +179,7 @@ func main() {
|
||||
fmt.Println(eventData)
|
||||
}, false, 0, nil)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
@@ -196,7 +196,7 @@ func main() {
|
||||
func (eb *EventBus[T]) ClearListeners()
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KBfBYlKPgqD)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -213,9 +213,12 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListeners()
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -234,7 +237,7 @@ func main() {
|
||||
func (eb *EventBus[T]) ClearListenersByTopic(topic string)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gvMljmJOZmU)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -251,14 +254,17 @@ func main() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListenersByTopic("event1")
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 2
|
||||
}
|
||||
```
|
||||
|
||||
@@ -272,7 +278,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetListenersCount(topic string) int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/j6yaJ2xAmFz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -284,14 +290,14 @@ func main() {
|
||||
eb := eventbus.NewEventBus[int]()
|
||||
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||
|
||||
count := eb.GetListenersCount("event1")
|
||||
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// 1
|
||||
}
|
||||
```
|
||||
|
||||
@@ -305,7 +311,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetAllListenersCount() int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/PUlr0xcpEOz)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -338,7 +344,7 @@ func main() {
|
||||
func (eb *EventBus[T]) GetEvents() []string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/etgjjcOtAjX)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -359,8 +365,8 @@ func main() {
|
||||
}
|
||||
|
||||
// Output:
|
||||
// event1
|
||||
// event2
|
||||
// event1
|
||||
}
|
||||
```
|
||||
|
||||
@@ -374,7 +380,7 @@ func main() {
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(err error))
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gmB0gnFe5mc)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -393,7 +399,7 @@ func main() {
|
||||
panic("error")
|
||||
}, false, 0, nil)
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(eventbus.Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// error
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
- [IsDir](#IsDir)
|
||||
- [ListFileNames](#ListFileNames)
|
||||
- [RemoveFile](#RemoveFile)
|
||||
- [RemoveDir](#RemoveDir)
|
||||
- [ReadFileToString](#ReadFileToString)
|
||||
- [ReadFileByLine](#ReadFileByLine)
|
||||
- [Zip](#Zip)
|
||||
@@ -395,7 +396,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RemoveFile(path string) error
|
||||
func RemoveFile(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/P2y0XW8a1SH)</span></b>
|
||||
@@ -416,6 +417,41 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="RemoveDir">RemoveDir</span>
|
||||
|
||||
<p>Delete directory.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Oa6KnPek2uy)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/fileutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var deletedPaths []string
|
||||
|
||||
err := fileutil.RemoveDir("./tempdir", func(p string) {
|
||||
deletedPaths = append(deletedPaths, p)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println(deletedPaths)
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ReadFileToString">ReadFileToString</span>
|
||||
|
||||
<p>Return string of file content.</p>
|
||||
@@ -934,7 +970,7 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uNep3Tr8fqF)</span></b>
|
||||
@@ -987,7 +1023,7 @@ import (
|
||||
|
||||
func main() {
|
||||
const mb = 1024 * 1024
|
||||
const defaultChunkSizeMB = 100
|
||||
const defaultChunkSizeMB = 100
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
@@ -1051,7 +1087,7 @@ func main() {
|
||||
numParsers := runtime.NumCPU()
|
||||
|
||||
linesCh := make(chan []string, numParsers)
|
||||
|
||||
|
||||
// test1.csv file content:
|
||||
// Lili,22,female
|
||||
// Jim,21,male
|
||||
@@ -1104,8 +1140,8 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Println(v)
|
||||
|
||||
|
||||
// Output:
|
||||
// 3.9.10.19
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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号"
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -620,7 +620,7 @@ func main() {
|
||||
|
||||
### <span id="Nand">Nand</span>
|
||||
|
||||
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to true only if all predicates evaluate to false for the given value.</p>
|
||||
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to false only if all predicates evaluate to true for the given value.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
@@ -650,7 +650,7 @@ func main() {
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,7 +10,6 @@ Package maputil includes some functions to manipulate map.
|
||||
- [https://github.com/duke-git/lancet/blob/main/maputil/concurrentmap.go](https://github.com/duke-git/lancet/blob/main/maputil/concurrentmap.go)
|
||||
- [https://github.com/duke-git/lancet/blob/main/maputil/orderedmap.go](https://github.com/duke-git/lancet/blob/main/maputil/orderedmap.go)
|
||||
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
## Example:
|
||||
@@ -79,6 +78,8 @@ import (
|
||||
- [GetOrSet](#GetOrSet)
|
||||
- [SortByKey](#SortByKey)
|
||||
- [GetOrDefault](#GetOrDefault)
|
||||
- [FindValuesBy](#FindValuesBy)
|
||||
- [ToMarkdownTable](#ToMarkdownTable)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1110,7 +1111,7 @@ Translate the key and value of the map into two slices that are sorted according
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
|
||||
@@ -1274,7 +1275,6 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### <span id="OrderedMap_Delete">OrderedMap_Delete</span>
|
||||
|
||||
<p>Deletes the key-value pair for the given key.</p>
|
||||
@@ -1837,7 +1837,7 @@ func main() {
|
||||
fmt.Println(om.Elements())
|
||||
|
||||
// Output:
|
||||
// [{a 1} {b 2} {c 3}]
|
||||
// [{a 1} {b 2} {c 3}]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2276,8 +2276,8 @@ func main() {
|
||||
}
|
||||
|
||||
result := maputil.SortByKey(m, func(a, b int) bool {
|
||||
return a < b
|
||||
})
|
||||
return a < b
|
||||
})
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
@@ -2288,15 +2288,15 @@ func main() {
|
||||
|
||||
### <span id="GetOrDefault">GetOrDefault</span>
|
||||
|
||||
<p>returns the value of the given key or a default value if the key is not present.</p>
|
||||
<p>Returns the value of the given key or a default value if the key is not present.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/99QjSYSBdiM)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/99QjSYSBdiM)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -2324,4 +2324,109 @@ func main() {
|
||||
// a
|
||||
// default
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### <span id="FindValuesBy">FindValuesBy</span>
|
||||
|
||||
<p>Returns a slice of values from the map that satisfy the given predicate function.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bvNwNBZDm6v)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/maputil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := map[int]string{
|
||||
1: "a",
|
||||
2: "b",
|
||||
3: "c",
|
||||
4: "d",
|
||||
}
|
||||
|
||||
result := maputil.FindValuesBy(m, func(k int, v string) bool {
|
||||
return k%2 == 0
|
||||
})
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [b d]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ToMarkdownTable">ToMarkdownTable</span>
|
||||
|
||||
<p>Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
编辑
|
||||
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/w_pSLfeyEB5)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/maputil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// basic usage: automatically extract column names from data as table headers
|
||||
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
{"name": "Bob", "age": 30, "salary": 60000},
|
||||
}
|
||||
|
||||
result := maputil.ToMarkdownTable(data, nil, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// output:
|
||||
// |name|age|salary|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// custom header name
|
||||
headerMap := map[string]string{
|
||||
"name": "n",
|
||||
"age": "a",
|
||||
"salary": "s",
|
||||
}
|
||||
result = maputil.ToMarkdownTable(data, headerMap, nil)
|
||||
fmt.Println(result)
|
||||
|
||||
// ouput:
|
||||
// |m|a|s|
|
||||
// |---|---|---|
|
||||
// |Alice|25|50000|
|
||||
// |Bob|30|60000|
|
||||
|
||||
// custom column display order
|
||||
columnOrder := []string{"salary", "name"}
|
||||
result = maputil.ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Println(result)
|
||||
|
||||
// output:
|
||||
// |salary|name|
|
||||
// |---|---|
|
||||
// |50000|Alice|
|
||||
// |60000|Bob|
|
||||
}
|
||||
```
|
||||
|
||||
@@ -48,6 +48,8 @@ import (
|
||||
- [UploadFile](#UploadFile)
|
||||
- [IsPingConnected](#IsPingConnected)
|
||||
- [IsTelnetConnected](#IsTelnetConnected)
|
||||
- [BuildUrl](#BuildUrl)
|
||||
- [AddQueryParams](#AddQueryParams)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -1031,3 +1033,83 @@ func main() {
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="BuildUrl">BuildUrl</span>
|
||||
|
||||
<p>Builds a URL from the given params.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/netutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr, err := netutil.BuildUrl(
|
||||
"https",
|
||||
"example.com",
|
||||
"query",
|
||||
map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="AddQueryParams">AddQueryParams</span>
|
||||
|
||||
<p>Adds query parameters to the given URL.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error)
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JLXl1hZK7l4)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/netutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr, err := netutil.BuildUrl(
|
||||
"https",
|
||||
"example.com",
|
||||
"query",
|
||||
map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
- [Contain](#Contain)
|
||||
- [ContainBy](#ContainBy)
|
||||
- [ContainSubSlice](#ContainSubSlice)
|
||||
- [ContainAny](#ContainAny)
|
||||
- [Chunk](#Chunk)
|
||||
- [Compact](#Compact)
|
||||
- [Concat](#Concat)
|
||||
@@ -256,6 +257,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="ContainAny">ContainAny</span>
|
||||
|
||||
<p>Check if the slice contains any element from the targets slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Chunk">Chunk</span>
|
||||
|
||||
<p>Creates an slice of elements split into groups the length of `size`.</p>
|
||||
@@ -852,7 +890,7 @@ func main() {
|
||||
func EqualUnordered[T comparable](slice1, slice2 []T) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/n8fSc2w8ZgX)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1768,7 +1806,7 @@ func main() {
|
||||
func ReverseCopy[T any](slice []T) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/c9arEaP7Cg-)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -2062,7 +2100,7 @@ func main() {
|
||||
func ShuffleCopy[T any](slice []T) []T
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/vqDa-Gs1vT0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
- [IsStruct](#IsStruct)
|
||||
- [Tag](#Tag)
|
||||
- [Name](#Name)
|
||||
- [TypeName](#TypeName)
|
||||
- [Value](#Value)
|
||||
- [Kind](#Kind)
|
||||
- [IsEmbedded](#IsEmbedded)
|
||||
@@ -53,12 +54,13 @@ import (
|
||||
func New(value any, tagName ...string) *Struct
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/O29l8kk-Z17)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/structs"
|
||||
)
|
||||
|
||||
@@ -68,7 +70,11 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
// to do something
|
||||
|
||||
fmt.Println(s.ToMap())
|
||||
|
||||
// Output:
|
||||
// map[name:11] <nil>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -88,7 +94,7 @@ func (s *Struct) ToMap() (map[string]any, error)
|
||||
func ToMap(v any) (map[string]any, error)
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qQbLySBgerZ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -130,7 +136,7 @@ func main() {
|
||||
func (s *Struct) Fields() []*Field
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -162,10 +168,10 @@ func main() {
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (s *Struct) Field(name string) *Field
|
||||
func (s *Struct) Field(name string) (*Field, bool)
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KocZMSYarza)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -181,12 +187,14 @@ func main() {
|
||||
}
|
||||
p1 := &People{Name: "11"}
|
||||
s := structs.New(p1)
|
||||
f := s.Field("Name")
|
||||
f, found := s.Field("Name")
|
||||
|
||||
fmt.Println(f.Value())
|
||||
fmt.Println(found)
|
||||
|
||||
// Output:
|
||||
// 11
|
||||
// true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -200,7 +208,7 @@ func main() {
|
||||
func (s *Struct) IsStruct() bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -234,7 +242,7 @@ func main() {
|
||||
func (f *Field) Tag() *Tag
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -271,7 +279,7 @@ func main() {
|
||||
func (f *Field) Value() any
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -307,7 +315,7 @@ func main() {
|
||||
func (f *Field) IsEmbedded() bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -352,7 +360,7 @@ func main() {
|
||||
func (f *Field) IsExported() bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -391,7 +399,7 @@ func main() {
|
||||
func (f *Field) IsZero() bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/RzqpGISf87r)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -430,7 +438,7 @@ func main() {
|
||||
func (f *Field) Name() string
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zfIGlqsatee)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -469,7 +477,7 @@ func main() {
|
||||
func (f *Field) Kind() reflect.Kind
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -498,6 +506,42 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="TypeName">TypeName</span>
|
||||
|
||||
<p>Return struct type name.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func (s *Struct) TypeName() string
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/structs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type Parent struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
p := &Parent{Age: 11}
|
||||
s := structs.New(p)
|
||||
|
||||
fmt.Println(s.TypeName())
|
||||
|
||||
// Output:
|
||||
// Parent
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsSlice">IsSlice</span>
|
||||
|
||||
<p>Check if the field is a slice</p>
|
||||
@@ -508,7 +552,7 @@ func main() {
|
||||
func (f *Field) IsSlice() bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -545,7 +589,7 @@ func main() {
|
||||
func (f *Field) IsTargetType(targetType reflect.Kind) bool
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Ig75P-agN39)</span></b>
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -566,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
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1774,7 +1774,7 @@ func main() {
|
||||
func FindAllOccurrences(str, substr string) []int
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uvyA6azGLB1)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -65,6 +65,8 @@ import (
|
||||
- [IsAmericanExpress](#IsAmericanExpress)
|
||||
- [IsUnionPay](#IsUnionPay)
|
||||
- [IsChinaUnionPay](#IsChinaUnionPay)
|
||||
- [IsPassport](#IsPassport)
|
||||
- [IsChineseHMPassport](#IsChineseHMPassport)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -549,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 (
|
||||
@@ -559,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
|
||||
}
|
||||
```
|
||||
@@ -826,6 +843,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsAlphaNumeric">IsAlphaNumeric</span>
|
||||
|
||||
<p>Check if the string is alphanumeric.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func IsAlphaNumeric(s string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/RHeESLrLg9c)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsAlphaNumeric("ABC")
|
||||
result2 := validator.IsAlphaNumeric("123")
|
||||
result3 := validator.IsAlphaNumeric("abc123")
|
||||
result4 := validator.IsAlphaNumeric("abc123@#$")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsJSON">IsJSON</span>
|
||||
|
||||
<p>Check if the string is valid JSON.</p>
|
||||
@@ -1003,7 +1057,7 @@ func main() {
|
||||
func IsIpPort(str string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](todo)</span></b>
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/xUmls_b9L29)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -1532,3 +1586,80 @@ func main() {
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsPassport">IsPassport</span>
|
||||
|
||||
<p>Passport validation(using regex).</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func IsPassport(passport, country string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/dvOiV2BW7Aw)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsPassport("P123456789", "CN")
|
||||
result2 := validator.IsPassport("123456789", "US")
|
||||
result3 := validator.IsPassport("AB1234567", "RU")
|
||||
result4 := validator.IsPassport("123456789", "CN")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
|
||||
|
||||
<p>Mainland travel permit for Hong Kong, Macao validation (using regex). </p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func IsChineseHMPassport(hmPassport string) bool
|
||||
```
|
||||
|
||||
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/xKG6spQTcY0)</span></b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/validator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
result1 := validator.IsChineseHMPassport("C12345678")
|
||||
result2 := validator.IsChineseHMPassport("C00000000")
|
||||
result3 := validator.IsChineseHMPassport("M12345678")
|
||||
result4 := validator.IsChineseHMPassport("c12345678")
|
||||
result5 := validator.IsChineseHMPassport("C1234567")
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
fmt.Println(result5)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
526
docs/package-lock.json
generated
526
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
317
enum/enum.go
Normal file
317
enum/enum.go
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
// Package enum provides a simple enum implementation.
|
||||
package enum
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Enum defines a common enum interface.
|
||||
type Enum[T comparable] interface {
|
||||
Value() T
|
||||
String() string
|
||||
Name() string
|
||||
Valid(checker ...func(T) bool) bool
|
||||
}
|
||||
|
||||
// Item defines a common enum item struct implement Enum interface.
|
||||
type Item[T comparable] struct {
|
||||
value T
|
||||
name string
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// Pair represents a value-name pair for creating enum items
|
||||
type Pair[T comparable] struct {
|
||||
Value T
|
||||
Name string
|
||||
}
|
||||
|
||||
// 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]{}
|
||||
}
|
||||
|
||||
items := make([]*Item[T], 0, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
items = append(items, &Item[T]{value: pair.Value, name: pair.Name})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// String returns the string representation of the enum item.
|
||||
func (it *Item[T]) String() string {
|
||||
return it.name
|
||||
}
|
||||
|
||||
// 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 != ""
|
||||
}
|
||||
var zero T
|
||||
return it.value != zero && it.name != ""
|
||||
}
|
||||
|
||||
// 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,
|
||||
"name": it.name,
|
||||
})
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var temp alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v T
|
||||
rv := reflect.TypeOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want int family")
|
||||
}
|
||||
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||
it.value = converted.Interface().(T)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want uint family")
|
||||
}
|
||||
converted := reflect.ValueOf(uint64(val)).Convert(rv)
|
||||
it.value = converted.Interface().(T)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, ok := temp.Value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want float family")
|
||||
}
|
||||
converted := reflect.ValueOf(val).Convert(rv)
|
||||
it.value = converted.Interface().(T)
|
||||
case reflect.String:
|
||||
val, ok := temp.Value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want string")
|
||||
}
|
||||
it.value = any(val).(T)
|
||||
case reflect.Bool:
|
||||
val, ok := temp.Value.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type for value, want bool")
|
||||
}
|
||||
it.value = any(val).(T)
|
||||
default:
|
||||
val, ok := temp.Value.(float64)
|
||||
if ok {
|
||||
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||
it.value = converted.Interface().(T)
|
||||
} else {
|
||||
val2, ok2 := temp.Value.(T)
|
||||
if !ok2 {
|
||||
return fmt.Errorf("invalid type for value")
|
||||
}
|
||||
it.value = val2
|
||||
}
|
||||
}
|
||||
it.name = temp.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registry defines a common enum registry struct.
|
||||
type Registry[T comparable] struct {
|
||||
mu sync.RWMutex
|
||||
values map[T]*Item[T]
|
||||
names map[string]*Item[T]
|
||||
items []*Item[T]
|
||||
}
|
||||
|
||||
// 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]),
|
||||
names: make(map[string]*Item[T]),
|
||||
items: make([]*Item[T], 0, len(items)),
|
||||
}
|
||||
|
||||
r.Add(items...)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
for _, item := range items {
|
||||
if _, exists := r.values[item.value]; exists {
|
||||
continue
|
||||
}
|
||||
if _, exists := r.names[item.name]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
r.values[item.value] = item
|
||||
r.names[item.name] = item
|
||||
r.items = append(r.items, item)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
item, ok := r.values[value]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(r.values, value)
|
||||
delete(r.names, item.name)
|
||||
for i, it := range r.items {
|
||||
if it.value == value {
|
||||
r.items = append(r.items[:i], r.items[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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()
|
||||
item, ok := r.values[value]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(r.names, item.name)
|
||||
item.name = newName
|
||||
r.names[newName] = item
|
||||
return true
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
item, ok := r.values[value]
|
||||
return item, ok
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
item, ok := r.names[name]
|
||||
return item, ok
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
result := make([]*Item[T], len(r.items))
|
||||
copy(result, r.items)
|
||||
return result
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
return len(r.items)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
for _, item := range r.items {
|
||||
if !fn(item) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return less(items[i], items[j])
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
var result []*Item[T]
|
||||
for _, item := range r.items {
|
||||
if predicate(item) {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
208
enum/enum_example_test.go
Normal file
208
enum/enum_example_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package enum
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleNewItem() {
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
fmt.Println(item1.Name(), item1.Value())
|
||||
fmt.Println(item2.Name(), item2.Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleNewItemsFromPairs() {
|
||||
items := NewItemsFromPairs(
|
||||
Pair[Status]{Value: Active, Name: "Active"},
|
||||
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
fmt.Println(items[0].Name(), items[0].Value())
|
||||
fmt.Println(items[1].Name(), items[1].Value())
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleItem_Valid() {
|
||||
item := NewItem(Active, "Active")
|
||||
fmt.Println(item.Valid())
|
||||
|
||||
invalidItem := NewItem(Unknown, "")
|
||||
fmt.Println(invalidItem.Valid())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleItem_MarshalJSON() {
|
||||
item := NewItem(Active, "Active")
|
||||
data, _ := item.MarshalJSON()
|
||||
fmt.Println(string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||
|
||||
// Output:
|
||||
// {"name":"Active","value":1}
|
||||
// Active 1
|
||||
}
|
||||
|
||||
func ExampleRegistry_Add() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("Found by value:", item.Name())
|
||||
}
|
||||
|
||||
if item, found := registry.GetByName("Inactive"); found {
|
||||
fmt.Println("Found by name:", item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Found by value: Active
|
||||
// Found by name: 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Remove() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
fmt.Println("Size before removal:", registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
fmt.Println("Removed:", removed)
|
||||
fmt.Println("Size after removal:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Size before removal: 1
|
||||
// Removed: true
|
||||
// Size after removal: 0
|
||||
}
|
||||
|
||||
func ExampleRegistry_Update() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
|
||||
registry.Add(item1)
|
||||
updated := registry.Update(Active, "Activated")
|
||||
fmt.Println("Updated:", updated)
|
||||
|
||||
if item, found := registry.GetByValue(Active); found {
|
||||
fmt.Println("New name:", item.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Updated: true
|
||||
// New name: Activated
|
||||
}
|
||||
|
||||
func ExampleRegistry_Items() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.Items() {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Contains() {
|
||||
registry := NewRegistry[Status]()
|
||||
item := NewItem(Active, "Active")
|
||||
registry.Add(item)
|
||||
|
||||
fmt.Println(registry.Contains(Active))
|
||||
fmt.Println(registry.Contains(Inactive))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleRegistry_Size() {
|
||||
registry := NewRegistry[Status]()
|
||||
fmt.Println("Initial size:", registry.Size())
|
||||
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
fmt.Println("Size after adding items:", registry.Size())
|
||||
|
||||
registry.Remove(Active)
|
||||
fmt.Println("Size after removing an item:", registry.Size())
|
||||
|
||||
// Output:
|
||||
// Initial size: 0
|
||||
// Size after adding items: 2
|
||||
// Size after removing an item: 1
|
||||
}
|
||||
|
||||
func ExampleRegistry_Range() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_SortedItems() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Inactive, "Inactive")
|
||||
item2 := NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
}) {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
// Inactive 2
|
||||
}
|
||||
|
||||
func ExampleRegistry_Filter() {
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
activeItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
for _, item := range activeItems {
|
||||
fmt.Println(item.Name(), item.Value())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Active 1
|
||||
}
|
||||
184
enum/enum_test.go
Normal file
184
enum/enum_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||
// Use of this source code is governed by MIT license
|
||||
|
||||
package enum
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Unknown Status = iota
|
||||
Active
|
||||
Inactive
|
||||
)
|
||||
|
||||
func TestNewItemsFromPairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestNewItemsFromPairs")
|
||||
|
||||
items := NewItemsFromPairs(
|
||||
Pair[Status]{Value: Active, Name: "Active"},
|
||||
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||
)
|
||||
|
||||
assert.Equal(2, len(items))
|
||||
assert.Equal(Active, items[0].Value())
|
||||
assert.Equal("Active", items[0].Name())
|
||||
assert.Equal(Inactive, items[1].Value())
|
||||
assert.Equal("Inactive", items[1].Name())
|
||||
}
|
||||
|
||||
func TestItem_Valid(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestItem_Valid")
|
||||
|
||||
item := NewItem(Active, "Active")
|
||||
assert.Equal(true, item.Valid())
|
||||
|
||||
invalidItem := NewItem(Unknown, "")
|
||||
assert.Equal(false, invalidItem.Valid())
|
||||
}
|
||||
|
||||
func TestItem_MarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestItem_MarshalJSON")
|
||||
|
||||
item := NewItem(Active, "Active")
|
||||
data, err := item.MarshalJSON()
|
||||
assert.IsNil(err)
|
||||
assert.Equal("{\"name\":\"Active\",\"value\":1}", string(data))
|
||||
|
||||
var unmarshaledItem Item[Status]
|
||||
err = unmarshaledItem.UnmarshalJSON(data)
|
||||
assert.IsNil(err)
|
||||
assert.Equal(item.Value(), unmarshaledItem.Value())
|
||||
assert.Equal(item.Name(), unmarshaledItem.Name())
|
||||
}
|
||||
|
||||
func TestRegistry_AddAndGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_AddAndGet")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
|
||||
assert.Equal(2, registry.Size())
|
||||
|
||||
item, ok := registry.GetByValue(Active)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal("Active", item.Name())
|
||||
|
||||
item, ok = registry.GetByName("Inactive")
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal(Inactive, item.Value())
|
||||
}
|
||||
|
||||
func TestRegistry_Remove(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Remove")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
|
||||
registry.Add(item1, item2)
|
||||
assert.Equal(2, registry.Size())
|
||||
|
||||
removed := registry.Remove(Active)
|
||||
assert.Equal(true, removed)
|
||||
assert.Equal(1, registry.Size())
|
||||
|
||||
_, ok := registry.GetByValue(Active)
|
||||
assert.Equal(false, ok)
|
||||
}
|
||||
|
||||
func TestRegistry_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Update")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
registry.Add(item1)
|
||||
|
||||
updated := registry.Update(Active, "Activated")
|
||||
assert.Equal(true, updated)
|
||||
|
||||
item, ok := registry.GetByValue(Active)
|
||||
assert.Equal(true, ok)
|
||||
assert.Equal("Activated", item.Name())
|
||||
}
|
||||
|
||||
func TestRegistry_Contains(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Contains")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
registry.Add(item1)
|
||||
|
||||
assert.Equal(true, registry.Contains(Active))
|
||||
assert.Equal(false, registry.Contains(Inactive))
|
||||
}
|
||||
|
||||
func TestRegistry_Range(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Range")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
var values []Status
|
||||
registry.Range(func(item *Item[Status]) bool {
|
||||
values = append(values, item.Value())
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(2, len(values))
|
||||
assert.Equal(Active, values[0])
|
||||
assert.Equal(Inactive, values[1])
|
||||
}
|
||||
|
||||
func TestRegistry_SortedItems(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_SortedItems")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Inactive, "Inactive")
|
||||
item2 := NewItem(Active, "Active")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
sortedItems := registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||
return i1.value < i2.value
|
||||
})
|
||||
|
||||
assert.Equal(2, len(sortedItems))
|
||||
assert.Equal(Active, sortedItems[0].Value())
|
||||
assert.Equal(Inactive, sortedItems[1].Value())
|
||||
}
|
||||
|
||||
func TestRegistry_Filter(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := internal.NewAssert(t, "TestRegistry_Filter")
|
||||
|
||||
registry := NewRegistry[Status]()
|
||||
item1 := NewItem(Active, "Active")
|
||||
item2 := NewItem(Inactive, "Inactive")
|
||||
registry.Add(item1, item2)
|
||||
|
||||
filteredItems := registry.Filter(func(item *Item[Status]) bool {
|
||||
return item.Value() == Active
|
||||
})
|
||||
|
||||
assert.Equal(1, len(filteredItems))
|
||||
assert.Equal(Active, filteredItems[0].Value())
|
||||
}
|
||||
@@ -21,7 +21,7 @@ type EventBus[T any] struct {
|
||||
// listeners map[string][]*EventListener[T]
|
||||
listeners sync.Map
|
||||
mu sync.RWMutex
|
||||
errorHandler func(err error)
|
||||
errorHandler func(topic string, err error)
|
||||
}
|
||||
|
||||
// EventListener is the struct that holds the listener function and its priority.
|
||||
@@ -33,7 +33,7 @@ type EventListener[T any] struct {
|
||||
}
|
||||
|
||||
// NewEventBus creates a new EventBus.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/gHbOPV_NUOJ
|
||||
func NewEventBus[T any]() *EventBus[T] {
|
||||
return &EventBus[T]{
|
||||
listeners: sync.Map{},
|
||||
@@ -41,7 +41,7 @@ func NewEventBus[T any]() *EventBus[T] {
|
||||
}
|
||||
|
||||
// Subscribe subscribes to an event with a specific event topic and listener function.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/EYGf_8cHei-
|
||||
func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async bool, priority int, filter func(eventData T) bool) {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
@@ -65,7 +65,7 @@ func (eb *EventBus[T]) Subscribe(topic string, listener func(eventData T), async
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes from an event with a specific event topic and listener function.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/Tmh7Ttfvprf
|
||||
func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T)) {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
@@ -89,7 +89,7 @@ func (eb *EventBus[T]) Unsubscribe(topic string, listener func(eventData T)) {
|
||||
}
|
||||
|
||||
// Publish publishes an event with a specific event topic and data payload.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/gHTtVexFSH9
|
||||
func (eb *EventBus[T]) Publish(event Event[T]) {
|
||||
eb.mu.RLock()
|
||||
defer eb.mu.RUnlock()
|
||||
@@ -117,7 +117,7 @@ func (eb *EventBus[T]) Publish(event Event[T]) {
|
||||
func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event[T]) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil && eb.errorHandler != nil {
|
||||
eb.errorHandler(fmt.Errorf("%v", r))
|
||||
eb.errorHandler(event.Topic, fmt.Errorf("%v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -125,12 +125,13 @@ func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event
|
||||
}
|
||||
|
||||
// SetErrorHandler sets the error handler function.
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(err error)) {
|
||||
// Play: https://go.dev/play/p/gmB0gnFe5mc
|
||||
func (eb *EventBus[T]) SetErrorHandler(handler func(topic string, err error)) {
|
||||
eb.errorHandler = handler
|
||||
}
|
||||
|
||||
// ClearListeners clears all the listeners.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/KBfBYlKPgqD
|
||||
func (eb *EventBus[T]) ClearListeners() {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
@@ -139,7 +140,7 @@ func (eb *EventBus[T]) ClearListeners() {
|
||||
}
|
||||
|
||||
// ClearListenersByTopic clears all the listeners by topic.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/gvMljmJOZmU
|
||||
func (eb *EventBus[T]) ClearListenersByTopic(topic string) {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
@@ -148,7 +149,7 @@ func (eb *EventBus[T]) ClearListenersByTopic(topic string) {
|
||||
}
|
||||
|
||||
// GetListenersCount returns the number of listeners for a specific event topic.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/8VPJsMQgStM
|
||||
func (eb *EventBus[T]) GetListenersCount(topic string) int {
|
||||
eb.mu.RLock()
|
||||
defer eb.mu.RUnlock()
|
||||
@@ -163,7 +164,7 @@ func (eb *EventBus[T]) GetListenersCount(topic string) int {
|
||||
}
|
||||
|
||||
// GetAllListenersCount returns the total number of listeners.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/PUlr0xcpEOz
|
||||
func (eb *EventBus[T]) GetAllListenersCount() int {
|
||||
eb.mu.RLock()
|
||||
defer eb.mu.RUnlock()
|
||||
@@ -178,7 +179,7 @@ func (eb *EventBus[T]) GetAllListenersCount() int {
|
||||
}
|
||||
|
||||
// GetEvents returns all the events topics.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/etgjjcOtAjX
|
||||
func (eb *EventBus[T]) GetEvents() []string {
|
||||
eb.mu.RLock()
|
||||
defer eb.mu.RUnlock()
|
||||
|
||||
@@ -2,6 +2,7 @@ package eventbus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -136,9 +137,12 @@ func ExampleEventBus_ClearListeners() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListeners()
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
@@ -155,35 +159,38 @@ func ExampleEventBus_ClearListenersByTopic() {
|
||||
}
|
||||
|
||||
eb.Subscribe("event1", listener, false, 0, nil)
|
||||
eb.Subscribe("event2", listener, false, 0, nil)
|
||||
|
||||
eb.ClearListenersByTopic("event1")
|
||||
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
eb.Publish(Event[int]{Topic: "event2", Payload: 2})
|
||||
|
||||
fmt.Println(receivedData)
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleEventBus_GetListenersCount() {
|
||||
eb := NewEventBus[int]()
|
||||
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event1", func(eventData int) {}, false, 0, nil)
|
||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||
|
||||
count := eb.GetListenersCount("event1")
|
||||
|
||||
fmt.Println(count)
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleEventBus_SetErrorHandler() {
|
||||
eb := NewEventBus[int]()
|
||||
|
||||
eb.SetErrorHandler(func(err error) {
|
||||
fmt.Println(err)
|
||||
eb.SetErrorHandler(func(topic string, err error) {
|
||||
fmt.Println(topic, err)
|
||||
})
|
||||
|
||||
eb.Subscribe("event1", func(eventData int) {
|
||||
@@ -193,7 +200,7 @@ func ExampleEventBus_SetErrorHandler() {
|
||||
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
|
||||
|
||||
// Output:
|
||||
// error
|
||||
// event1 error
|
||||
}
|
||||
|
||||
func ExampleEventBus_GetAllListenersCount() {
|
||||
@@ -218,6 +225,7 @@ func ExampleEventBus_GetEvents() {
|
||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||
|
||||
events := eb.GetEvents()
|
||||
sort.Strings(events)
|
||||
|
||||
for _, event := range events {
|
||||
fmt.Println(event)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eventbus
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -113,7 +114,8 @@ func TestEventBus_ErrorHandler(t *testing.T) {
|
||||
|
||||
eb := NewEventBus[string]()
|
||||
|
||||
eb.SetErrorHandler(func(err error) {
|
||||
eb.SetErrorHandler(func(topic string, err error) {
|
||||
assert.Equal("event1", topic)
|
||||
assert.Equal("error", err.Error())
|
||||
})
|
||||
|
||||
@@ -213,7 +215,8 @@ func TestEventBus_GetEvents(t *testing.T) {
|
||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||
|
||||
events := eb.GetEvents()
|
||||
sort.Strings(events)
|
||||
|
||||
assert.Equal(2, len(events))
|
||||
assert.EqualValues([]string{"event1", "event2"}, events)
|
||||
assert.Equal([]string{"event1", "event2"}, events)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -172,10 +173,54 @@ func IsDir(path string) bool {
|
||||
|
||||
// RemoveFile remove the path file.
|
||||
// Play: https://go.dev/play/p/P2y0XW8a1SH
|
||||
func RemoveFile(path string) error {
|
||||
func RemoveFile(path string, onDelete ...func(path string)) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%s is a directory", path)
|
||||
}
|
||||
|
||||
if len(onDelete) > 0 && onDelete[0] != nil {
|
||||
onDelete[0](path)
|
||||
}
|
||||
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// RemoveDir remove the path directory.
|
||||
// Play: https://go.dev/play/p/Oa6KnPek2uy
|
||||
func RemoveDir(path string, onDelete ...func(path string)) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", path)
|
||||
}
|
||||
|
||||
var callback func(string)
|
||||
if len(onDelete) > 0 {
|
||||
callback = onDelete[0]
|
||||
}
|
||||
|
||||
err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
|
||||
if err == nil && callback != nil {
|
||||
callback(p)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// CopyFile copy src file to dest file.
|
||||
// Play: https://go.dev/play/p/Jg9AMJMLrJi
|
||||
func CopyFile(srcPath string, dstPath string) error {
|
||||
@@ -239,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
|
||||
}
|
||||
|
||||
|
||||
@@ -85,14 +85,37 @@ func TestIsDir(t *testing.T) {
|
||||
|
||||
func TestRemoveFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRemoveFile")
|
||||
|
||||
f := "./text.txt"
|
||||
if !IsExist(f) {
|
||||
CreateFile(f)
|
||||
err := RemoveFile(f)
|
||||
assert.IsNil(err)
|
||||
CreateFile(f)
|
||||
err := RemoveFile(f)
|
||||
assert.IsNil(err)
|
||||
}
|
||||
|
||||
func TestRemoveDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestRemoveDir")
|
||||
|
||||
err := os.MkdirAll("./tempdir/a/b", 0755)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
var deletedPaths []string
|
||||
|
||||
err = RemoveDir("./tempdir", func(p string) {
|
||||
deletedPaths = append(deletedPaths, p)
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
assert.Equal([]string{"./tempdir", "tempdir/a", "tempdir/a/b"}, deletedPaths)
|
||||
assert.Equal(false, IsExist("./tempdir"))
|
||||
}
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -18,7 +18,7 @@ func And[T any](predicates ...func(T) bool) func(T) bool {
|
||||
}
|
||||
|
||||
// Nand returns a composed predicate that represents the logical NAND of a list of predicates.
|
||||
// It evaluates to true only if all predicates evaluate to false for the given value.
|
||||
// It evaluates to false only if all predicates evaluate to true for the given value.
|
||||
// Play: https://go.dev/play/p/Rb-FdNGpgSO
|
||||
func Nand[T any](predicates ...func(T) bool) func(T) bool {
|
||||
if len(predicates) < 2 {
|
||||
@@ -26,11 +26,11 @@ func Nand[T any](predicates ...func(T) bool) func(T) bool {
|
||||
}
|
||||
return func(value T) bool {
|
||||
for _, predicate := range predicates {
|
||||
if predicate(value) {
|
||||
return false // Short-circuit on the first true predicate
|
||||
if !predicate(value) {
|
||||
return true // Short-circuit on the first false predicate
|
||||
}
|
||||
}
|
||||
return true // True if all predicates are false
|
||||
return false // False if all predicates are true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func ExampleNand() {
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestPredicatesNandPure(t *testing.T) {
|
||||
)
|
||||
|
||||
assert.ShouldBeFalse(isNumericAndLength5("12345"))
|
||||
assert.ShouldBeFalse(isNumericAndLength5("1234"))
|
||||
assert.ShouldBeTrue(isNumericAndLength5("1234"))
|
||||
assert.ShouldBeTrue(isNumericAndLength5("abcdef"))
|
||||
}
|
||||
|
||||
|
||||
162
maputil/map.go
162
maputil/map.go
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -666,3 +667,164 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
|
||||
// Play: https://go.dev/play/p/bvNwNBZDm6v
|
||||
func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) bool) []V {
|
||||
result := make([]V, 0)
|
||||
|
||||
for k, v := range m {
|
||||
if predicate(k, v) {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMarkdownTable converts a slice of maps to a Markdown table.
|
||||
// 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"
|
||||
}
|
||||
|
||||
var headers []string
|
||||
|
||||
// 如果提供了columnOrder,则按指定顺序排列
|
||||
if len(columnOrder) > 0 {
|
||||
headers = make([]string, len(columnOrder))
|
||||
copy(headers, columnOrder)
|
||||
} else {
|
||||
// 否则按自然顺序提取headers
|
||||
seen := make(map[string]bool)
|
||||
for _, row := range data {
|
||||
for k := range row {
|
||||
if !seen[k] {
|
||||
seen[k] = true
|
||||
headers = append(headers, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
// Header row - 使用映射的中文标题
|
||||
builder.WriteString("| ")
|
||||
for i, h := range headers {
|
||||
// 如果有映射则使用中文标题,否则使用原字段名
|
||||
displayHeader := h
|
||||
if headerMap != nil && headerMap[h] != "" {
|
||||
displayHeader = headerMap[h]
|
||||
}
|
||||
builder.WriteString(displayHeader)
|
||||
if i < len(headers)-1 {
|
||||
builder.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(" |\n")
|
||||
|
||||
// Separator
|
||||
builder.WriteString("|")
|
||||
for i := range headers {
|
||||
if i > 0 {
|
||||
builder.WriteString("|")
|
||||
}
|
||||
builder.WriteString("---")
|
||||
}
|
||||
builder.WriteString("|\n")
|
||||
|
||||
// Data rows
|
||||
for _, row := range data {
|
||||
builder.WriteString("| ")
|
||||
for i, h := range headers {
|
||||
val, exists := row[h]
|
||||
var cell string
|
||||
if !exists {
|
||||
cell = ""
|
||||
} else {
|
||||
cell = formatValue(val)
|
||||
}
|
||||
builder.WriteString(cell)
|
||||
if i < len(headers)-1 {
|
||||
builder.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(" |\n")
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// formatValue formats any value for display in Markdown table
|
||||
func formatValue(v interface{}) string {
|
||||
switch val := v.(type) {
|
||||
case int:
|
||||
return commaInt64(int64(val))
|
||||
case int8:
|
||||
return commaInt64(int64(val))
|
||||
case int16:
|
||||
return commaInt64(int64(val))
|
||||
case int32:
|
||||
return commaInt64(int64(val))
|
||||
case int64:
|
||||
return commaInt64(val)
|
||||
case uint:
|
||||
return commaUint64(uint64(val))
|
||||
case uint8:
|
||||
return commaUint64(uint64(val))
|
||||
case uint16:
|
||||
return commaUint64(uint64(val))
|
||||
case uint32:
|
||||
return commaUint64(uint64(val))
|
||||
case uint64:
|
||||
return commaUint64(val)
|
||||
case float32:
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
case float64:
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
case string:
|
||||
return val
|
||||
case bool:
|
||||
return fmt.Sprintf("%t", val)
|
||||
case nil:
|
||||
return ""
|
||||
default:
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
}
|
||||
|
||||
// commaInt64 adds comma separators to int64
|
||||
func commaInt64(n int64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := n < 0
|
||||
if neg {
|
||||
n = -n
|
||||
}
|
||||
s := strconv.FormatInt(n, 10)
|
||||
return addCommas(s)
|
||||
}
|
||||
|
||||
// commaUint64 adds comma separators to uint64
|
||||
func commaUint64(n uint64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
s := strconv.FormatUint(n, 10)
|
||||
return addCommas(s)
|
||||
}
|
||||
|
||||
// addCommas inserts commas every 3 digits
|
||||
func addCommas(s string) string {
|
||||
var result strings.Builder
|
||||
for i, c := range s {
|
||||
if i > 0 && (len(s)-i)%3 == 0 {
|
||||
result.WriteRune(',')
|
||||
}
|
||||
result.WriteRune(c)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -829,3 +829,25 @@ func ExampleOrderedMap_UnmarshalJSON() {
|
||||
|
||||
// fmt.Println(om.Elements())
|
||||
}
|
||||
|
||||
func ExampleFindValuesBy() {
|
||||
m := map[int]string{
|
||||
1: "a",
|
||||
2: "b",
|
||||
3: "c",
|
||||
4: "d",
|
||||
}
|
||||
|
||||
result := FindValuesBy(m, func(k int, v string) bool {
|
||||
return k%2 == 0
|
||||
})
|
||||
|
||||
// github action will excute this test currently, so sort the result
|
||||
// to make it deterministic
|
||||
sort.Strings(result)
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
// Output:
|
||||
// [b d]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/cmplx"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -888,3 +889,84 @@ func TestGetOrDefault(t *testing.T) {
|
||||
result2 := GetOrDefault(m1, 5, "123")
|
||||
assert.Equal("123", result2)
|
||||
}
|
||||
|
||||
func TestFindValuesBy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestFindValuesBy")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputMap map[string]string
|
||||
key string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Key exists",
|
||||
inputMap: map[string]string{"a": "1", "b": "2", "c": "3"},
|
||||
key: "b",
|
||||
expected: []string{"2"},
|
||||
},
|
||||
{
|
||||
name: "Key does not exist",
|
||||
inputMap: map[string]string{"a": "1", "b": "2", "c": "3"},
|
||||
key: "d",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "Empty map",
|
||||
inputMap: map[string]string{},
|
||||
key: "a",
|
||||
expected: []string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
result := FindValuesBy(tt.inputMap, func(key string, value string) bool {
|
||||
return key == tt.key
|
||||
})
|
||||
assert.Equal(tt.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToMarkdownTable(t *testing.T) {
|
||||
// 测试空数据
|
||||
emptyResult := ToMarkdownTable([]map[string]interface{}{}, nil, nil)
|
||||
expectedEmpty := "| |\n|---|\n"
|
||||
if emptyResult != expectedEmpty {
|
||||
t.Errorf("Expected empty table, got: %s", emptyResult)
|
||||
}
|
||||
|
||||
// 测试基本数据
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
{"name": "Bob", "age": 30, "salary": 60000},
|
||||
}
|
||||
result := ToMarkdownTable(data, nil, nil)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证结果包含预期的表头和数据行
|
||||
}
|
||||
|
||||
// 测试自定义列顺序
|
||||
func TestToMarkdownTableWithColumnOrder(t *testing.T) {
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25, "salary": 50000},
|
||||
}
|
||||
columnOrder := []string{"salary", "name", "age"}
|
||||
result := ToMarkdownTable(data, nil, columnOrder)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证列顺序是否正确
|
||||
}
|
||||
|
||||
// 测试自定义表头
|
||||
func TestToMarkdownTableWithHeaderMap(t *testing.T) {
|
||||
data := []map[string]interface{}{
|
||||
{"name": "Alice", "age": 25},
|
||||
}
|
||||
headerMap := map[string]string{
|
||||
"name": "姓名",
|
||||
"age": "年龄",
|
||||
}
|
||||
result := ToMarkdownTable(data, headerMap, nil)
|
||||
fmt.Printf("%s", result)
|
||||
// 验证中文表头是否正确显示
|
||||
}
|
||||
|
||||
@@ -238,6 +238,9 @@ func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
|
||||
// Average return average value of numbers.
|
||||
// Play: https://go.dev/play/p/Vv7LBwER-pz
|
||||
func Average[T constraints.Integer | constraints.Float](numbers ...T) float64 {
|
||||
if len(numbers) == 0 {
|
||||
return 0
|
||||
}
|
||||
var sum float64
|
||||
for _, num := range numbers {
|
||||
sum += float64(num)
|
||||
|
||||
@@ -3,7 +3,6 @@ package netutil
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -27,8 +26,8 @@ func TestHttpGet(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpPost(t *testing.T) {
|
||||
@@ -49,8 +48,8 @@ func TestHttpPost(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpPostFormData(t *testing.T) {
|
||||
@@ -69,8 +68,8 @@ func TestHttpPostFormData(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpPut(t *testing.T) {
|
||||
@@ -92,8 +91,8 @@ func TestHttpPut(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpPatch(t *testing.T) {
|
||||
@@ -115,8 +114,8 @@ func TestHttpPatch(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHttpDelete(t *testing.T) {
|
||||
@@ -127,8 +126,8 @@ func TestHttpDelete(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestConvertMapToQueryString(t *testing.T) {
|
||||
@@ -229,8 +228,8 @@ func TestHttpClent_Post(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Log("response: ", resp.StatusCode, string(body))
|
||||
defer resp.Body.Close()
|
||||
t.Log("response status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestStructToUrlValues(t *testing.T) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -305,3 +307,97 @@ func IsTelnetConnected(host string, port string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// BuildUrl builds a URL from the given params.
|
||||
// Play: https://go.dev/play/p/JLXl1hZK7l4
|
||||
func BuildUrl(scheme, host, path string, query map[string][]string) (string, error) {
|
||||
if err := validateScheme(scheme); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
if !hostRegex.MatchString(host) {
|
||||
return "", fmt.Errorf("invalid host: '%s' is not a valid host", host)
|
||||
}
|
||||
}
|
||||
|
||||
parsedUrl := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
parsedUrl.Path = "/"
|
||||
} else if !strings.HasPrefix(path, "/") {
|
||||
parsedUrl.Path = "/" + path
|
||||
} else {
|
||||
parsedUrl.Path = path
|
||||
}
|
||||
|
||||
queryParams := parsedUrl.Query()
|
||||
|
||||
for key, values := range query {
|
||||
for _, value := range values {
|
||||
queryParams.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
parsedUrl.RawQuery = queryParams.Encode()
|
||||
|
||||
return parsedUrl.String(), nil
|
||||
}
|
||||
|
||||
var supportedSchemes = map[string]bool{
|
||||
"http": true,
|
||||
"https": true,
|
||||
"ftp": true,
|
||||
"file": true,
|
||||
"mailto": true,
|
||||
"ws": true, // WebSocket
|
||||
"wss": true, // WebSocket Secure
|
||||
"data": true, // Data URL
|
||||
}
|
||||
|
||||
func validateScheme(scheme string) error {
|
||||
if _, exists := supportedSchemes[scheme]; !exists {
|
||||
return fmt.Errorf("invalid scheme: '%s' is not supported", scheme)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)(\.[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]?)+$`)
|
||||
var pathRegex = regexp.MustCompile(`^\/([a-zA-Z0-9%_-]+(?:\/[a-zA-Z0-9%_-]+)*)$`)
|
||||
|
||||
var alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
|
||||
|
||||
// AddQueryParams adds query parameters to the given URL.
|
||||
// Play: https://go.dev/play/p/JLXl1hZK7l4
|
||||
func AddQueryParams(urlStr string, params map[string][]string) (string, error) {
|
||||
parsedUrl, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
queryParams := parsedUrl.Query()
|
||||
|
||||
for k, values := range params {
|
||||
if k == "" {
|
||||
return "", errors.New("empty key is not allowed")
|
||||
}
|
||||
|
||||
if !alphaNumericRegex.MatchString(k) {
|
||||
return "", fmt.Errorf("query parameter key %s must be alphanumeric", k)
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
if !alphaNumericRegex.MatchString(v) {
|
||||
return "", fmt.Errorf("query parameter value %s must be alphanumeric", v)
|
||||
}
|
||||
queryParams.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
parsedUrl.RawQuery = queryParams.Encode()
|
||||
|
||||
return parsedUrl.String(), nil
|
||||
}
|
||||
|
||||
@@ -201,3 +201,40 @@ func ExampleIsTelnetConnected() {
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleBuildUrl() {
|
||||
urlStr, err := BuildUrl(
|
||||
"https",
|
||||
"example.com",
|
||||
"query",
|
||||
map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com/query?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleAddQueryParams() {
|
||||
urlStr := "https://example.com"
|
||||
|
||||
params := map[string][]string{
|
||||
"a": {"foo", "bar"},
|
||||
"b": {"baz"},
|
||||
}
|
||||
|
||||
urlStr, err := AddQueryParams(urlStr, params)
|
||||
|
||||
fmt.Println(urlStr)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// https://example.com?a=foo&a=bar&b=baz
|
||||
// <nil>
|
||||
}
|
||||
|
||||
@@ -142,3 +142,118 @@ func TestTelnetConnected(t *testing.T) {
|
||||
result2 := IsTelnetConnected("www.baidu.com", "123")
|
||||
assert.Equal(false, result2)
|
||||
}
|
||||
|
||||
func TestBuildUrl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBuildUrl")
|
||||
|
||||
tests := []struct {
|
||||
scheme string
|
||||
host string
|
||||
path string
|
||||
query map[string][]string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
scheme: "http",
|
||||
host: "www.test.com",
|
||||
path: "/path/subpath",
|
||||
query: map[string][]string{"a": {"1"}, "b": {"2"}},
|
||||
want: "http://www.test.com/path/subpath?a=1&b=2",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
scheme: "http",
|
||||
host: "www.test.com",
|
||||
path: "/simple-path",
|
||||
query: map[string][]string{"a": {"1"}, "b": {"2"}},
|
||||
want: "http://www.test.com/simple-path?a=1&b=2",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
scheme: "http",
|
||||
host: "www.test.com",
|
||||
path: "",
|
||||
query: map[string][]string{"a": {"foo", "bar"}, "b": {"baz"}},
|
||||
want: "http://www.test.com/?a=foo&a=bar&b=baz",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
scheme: "https",
|
||||
host: "www.test. com",
|
||||
path: "/path",
|
||||
query: nil,
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
scheme: "https",
|
||||
host: "www.test.com",
|
||||
path: "/path with spaces",
|
||||
query: nil,
|
||||
want: "https://www.test.com/path%20with%20spaces",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
scheme: "https",
|
||||
host: "my.api.edu.cn",
|
||||
path: "/api",
|
||||
query: nil,
|
||||
want: "https://my.api.edu.cn/api",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := BuildUrl(tt.scheme, tt.host, tt.path, tt.query)
|
||||
assert.Equal(tt.want, got)
|
||||
assert.Equal(tt.wantErr, err != nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddQueryParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestAddQueryParams")
|
||||
|
||||
tests := []struct {
|
||||
url string
|
||||
query map[string][]string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
url: "http://www.test.com",
|
||||
query: map[string][]string{"a": {"1"}, "b": {"2"}},
|
||||
want: "http://www.test.com?a=1&b=2",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
url: "http://www.test.com",
|
||||
query: map[string][]string{"a": {"foo", "bar"}, "b": {"baz"}},
|
||||
want: "http://www.test.com?a=foo&a=bar&b=baz",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
url: "http://www.test.com",
|
||||
query: map[string][]string{},
|
||||
want: "http://www.test.com",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
url: "http://www.test.com",
|
||||
query: map[string][]string{"a": {"$%"}},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := AddQueryParams(tt.url, tt.query)
|
||||
assert.Equal(tt.want, got)
|
||||
assert.Equal(tt.wantErr, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -128,16 +128,19 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
|
||||
}
|
||||
|
||||
var i uint
|
||||
var lastErr error
|
||||
for i < config.retryTimes {
|
||||
err := retryFunc()
|
||||
if err != nil {
|
||||
lastErr = retryFunc()
|
||||
if lastErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i < config.retryTimes-1 { // Only wait if it's not the last retry
|
||||
select {
|
||||
case <-time.After(config.backoffStrategy.CalculateInterval()):
|
||||
case <-config.context.Done():
|
||||
return errors.New("retry is cancelled")
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
@@ -146,7 +149,7 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
|
||||
lastSlash := strings.LastIndex(funcPath, "/")
|
||||
funcName := funcPath[lastSlash+1:]
|
||||
|
||||
return fmt.Errorf("function %s run failed after %d times retry", funcName, i)
|
||||
return fmt.Errorf("function %s run failed after %d times retry, last error: %w", funcName, i, lastErr)
|
||||
}
|
||||
|
||||
// BackoffStrategy is an interface that defines a method for calculating backoff intervals.
|
||||
|
||||
@@ -118,7 +118,7 @@ func ExampleRetryTimes() {
|
||||
}
|
||||
|
||||
// Output:
|
||||
// function retry.ExampleRetryTimes.func1 run failed after 2 times retry
|
||||
// function retry.ExampleRetryTimes.func1 run failed after 2 times retry, last error: error occurs
|
||||
}
|
||||
|
||||
func ExampleRetry() {
|
||||
|
||||
@@ -15,14 +15,16 @@ func TestRetryFailed(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestRetryFailed")
|
||||
|
||||
var number int
|
||||
customError := errors.New("error occurs")
|
||||
increaseNumber := func() error {
|
||||
number++
|
||||
return errors.New("error occurs")
|
||||
return customError
|
||||
}
|
||||
|
||||
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
|
||||
|
||||
assert.IsNotNil(err)
|
||||
assert.Equal(errors.Is(err, customError), true)
|
||||
assert.Equal(DefaultRetryTimes, number)
|
||||
}
|
||||
|
||||
|
||||
322
slice/slice.go
322
slice/slice.go
@@ -59,19 +59,42 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
elementMap := make(map[T]struct{}, len(slice))
|
||||
elementCount := make(map[T]int, len(slice))
|
||||
for _, item := range slice {
|
||||
elementMap[item] = struct{}{}
|
||||
elementCount[item]++
|
||||
}
|
||||
|
||||
for _, item := range subSlice {
|
||||
if _, ok := elementMap[item]; !ok {
|
||||
if elementCount[item] == 0 {
|
||||
return false
|
||||
}
|
||||
elementCount[item]--
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainAny check if the slice contains any element from the targets slice.
|
||||
// Play: https://go.dev/play/p/4xoxhc9XSSw
|
||||
func ContainAny[T comparable](slice []T, targets []T) bool {
|
||||
if len(targets) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
sliceMap := make(map[T]struct{}, len(slice))
|
||||
for _, item := range slice {
|
||||
sliceMap[item] = struct{}{}
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if _, exists := sliceMap[target]; exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Chunk creates a slice of elements split into groups the length of size.
|
||||
// Play: https://go.dev/play/p/b4Pou5j2L_C
|
||||
func Chunk[T any](slice []T, size int) [][]T {
|
||||
@@ -81,14 +104,18 @@ func Chunk[T any](slice []T, size int) [][]T {
|
||||
return result
|
||||
}
|
||||
|
||||
for _, item := range slice {
|
||||
l := len(result)
|
||||
if l == 0 || len(result[l-1]) == size {
|
||||
result = append(result, []T{})
|
||||
l++
|
||||
}
|
||||
currentChunk := []T{}
|
||||
|
||||
result[l-1] = append(result[l-1], item)
|
||||
for _, item := range slice {
|
||||
if len(currentChunk) == size {
|
||||
result = append(result, currentChunk)
|
||||
currentChunk = []T{}
|
||||
}
|
||||
currentChunk = append(currentChunk, item)
|
||||
}
|
||||
|
||||
if len(currentChunk) > 0 {
|
||||
result = append(result, currentChunk)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -106,6 +133,7 @@ func Compact[T comparable](slice []T) []T {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result[:len(result):len(result)]
|
||||
}
|
||||
|
||||
@@ -133,8 +161,17 @@ func Concat[T any](slices ...[]T) []T {
|
||||
func Difference[T comparable](slice, comparedSlice []T) []T {
|
||||
result := []T{}
|
||||
|
||||
if len(slice) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
comparedMap := make(map[T]struct{}, len(comparedSlice))
|
||||
for _, v := range comparedSlice {
|
||||
comparedMap[v] = struct{}{}
|
||||
}
|
||||
|
||||
for _, v := range slice {
|
||||
if !Contain(comparedSlice, v) {
|
||||
if _, found := comparedMap[v]; !found {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
@@ -147,13 +184,17 @@ func Difference[T comparable](slice, comparedSlice []T) []T {
|
||||
// like lodash.js differenceBy: https://lodash.com/docs/4.17.15#differenceBy.
|
||||
// Play: https://go.dev/play/p/DiivgwM5OnC
|
||||
func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(index int, item T) T) []T {
|
||||
orginSliceAfterMap := Map(slice, iteratee)
|
||||
comparedSliceAfterMap := Map(comparedSlice, iteratee)
|
||||
|
||||
result := make([]T, 0)
|
||||
for i, v := range orginSliceAfterMap {
|
||||
if !Contain(comparedSliceAfterMap, v) {
|
||||
result = append(result, slice[i])
|
||||
|
||||
comparedMap := make(map[T]struct{}, len(comparedSlice))
|
||||
for _, item := range comparedSlice {
|
||||
comparedMap[iteratee(0, item)] = struct{}{}
|
||||
}
|
||||
|
||||
for i, item := range slice {
|
||||
transformedItem := iteratee(i, item)
|
||||
if _, found := comparedMap[transformedItem]; !found {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,23 +206,32 @@ func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(inde
|
||||
// The comparator is invoked with two arguments: (arrVal, othVal).
|
||||
// Play: https://go.dev/play/p/v2U2deugKuV
|
||||
func DifferenceWith[T any](slice []T, comparedSlice []T, comparator func(item1, item2 T) bool) []T {
|
||||
result := make([]T, 0)
|
||||
|
||||
getIndex := func(arr []T, item T, comparison func(v1, v2 T) bool) int {
|
||||
index := -1
|
||||
for i, v := range arr {
|
||||
if comparison(item, v) {
|
||||
index = i
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(slice))
|
||||
|
||||
comparedMap := make(map[int]T, len(comparedSlice))
|
||||
for _, v := range comparedSlice {
|
||||
comparedMap[getIndex(comparedSlice, v, comparator)] = v
|
||||
}
|
||||
|
||||
for _, v := range slice {
|
||||
found := false
|
||||
for _, existing := range comparedSlice {
|
||||
if comparator(v, existing) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
for i, v := range slice {
|
||||
index := getIndex(comparedSlice, v, comparator)
|
||||
if index == -1 {
|
||||
result = append(result, slice[i])
|
||||
if !found {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +271,7 @@ func EqualWith[T, U any](slice1 []T, slice2 []U, comparator func(T, U) bool) boo
|
||||
}
|
||||
|
||||
// EqualUnordered checks if two slices are equal: the same length and all elements' value are equal (unordered).
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/n8fSc2w8ZgX
|
||||
func EqualUnordered[T comparable](slice1, slice2 []T) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
@@ -423,19 +473,20 @@ func FindLastBy[T any](slice []T, predicate func(index int, item T) bool) (v T,
|
||||
// Flatten flattens slice with one level.
|
||||
// Play: https://go.dev/play/p/hYa3cBEevtm
|
||||
func Flatten(slice any) any {
|
||||
sv := sliceValue(slice)
|
||||
|
||||
var result reflect.Value
|
||||
if sv.Type().Elem().Kind() == reflect.Interface {
|
||||
result = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, sv.Len())
|
||||
} else if sv.Type().Elem().Kind() == reflect.Slice {
|
||||
result = reflect.MakeSlice(sv.Type().Elem(), 0, sv.Len())
|
||||
} else {
|
||||
return result
|
||||
sv := reflect.ValueOf(slice)
|
||||
if sv.Kind() != reflect.Slice {
|
||||
panic("Flatten: input must be a slice")
|
||||
}
|
||||
|
||||
elemType := sv.Type().Elem()
|
||||
if elemType.Kind() == reflect.Slice {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
|
||||
result := reflect.MakeSlice(reflect.SliceOf(elemType), 0, sv.Len())
|
||||
|
||||
for i := 0; i < sv.Len(); i++ {
|
||||
item := reflect.ValueOf(sv.Index(i).Interface())
|
||||
item := sv.Index(i)
|
||||
if item.Kind() == reflect.Slice {
|
||||
for j := 0; j < item.Len(); j++ {
|
||||
result = reflect.Append(result, item.Index(j))
|
||||
@@ -477,19 +528,17 @@ func flattenRecursive(value reflect.Value, result reflect.Value) reflect.Value {
|
||||
}
|
||||
|
||||
// ForEach iterates over elements of slice and invokes function for each element.
|
||||
// Play: https://go.dev/play/p/DrPaa4YsHRF
|
||||
func ForEach[T any](slice []T, iteratee func(index int, item T)) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
iteratee(i, slice[i])
|
||||
for idx, elem := range slice {
|
||||
iteratee(idx, elem)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachWithBreak iterates over elements of slice and invokes function for each element,
|
||||
// when iteratee return false, will break the for each loop.
|
||||
// Play: https://go.dev/play/p/qScs39f3D9W
|
||||
// when function return false, will break the for each loop.
|
||||
func ForEachWithBreak[T any](slice []T, iteratee func(index int, item T) bool) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
if !iteratee(i, slice[i]) {
|
||||
for idx, elem := range slice {
|
||||
if !iteratee(idx, elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -607,7 +656,7 @@ func Repeat[T any](item T, n int) []T {
|
||||
}
|
||||
|
||||
// InterfaceSlice convert param to slice of interface.
|
||||
// This function is deprecated, use generics feature of go1.18+ for replacement.
|
||||
// deprecated: use generics feature of go1.18+ for replacement.
|
||||
// Play: https://go.dev/play/p/FdQXF0Vvqs-
|
||||
func InterfaceSlice(slice any) []any {
|
||||
sv := sliceValue(slice)
|
||||
@@ -624,7 +673,7 @@ func InterfaceSlice(slice any) []any {
|
||||
}
|
||||
|
||||
// StringSlice convert param to slice of string.
|
||||
// This function is deprecated, use generics feature of go1.18+ for replacement.
|
||||
// deprecated: use generics feature of go1.18+ for replacement.
|
||||
// Play: https://go.dev/play/p/W0TZDWCPFcI
|
||||
func StringSlice(slice any) []string {
|
||||
v := sliceValue(slice)
|
||||
@@ -642,7 +691,7 @@ func StringSlice(slice any) []string {
|
||||
}
|
||||
|
||||
// IntSlice convert param to slice of int.
|
||||
// This function is deprecated, use generics feature of go1.18+ for replacement.
|
||||
// deprecated: use generics feature of go1.18+ for replacement.
|
||||
// Play: https://go.dev/play/p/UQDj-on9TGN
|
||||
func IntSlice(slice any) []int {
|
||||
sv := sliceValue(slice)
|
||||
@@ -662,15 +711,23 @@ func IntSlice(slice any) []int {
|
||||
// DeleteAt delete the element of slice at index.
|
||||
// Play: https://go.dev/play/p/800B1dPBYyd
|
||||
func DeleteAt[T any](slice []T, index int) []T {
|
||||
if index >= len(slice) {
|
||||
index = len(slice) - 1
|
||||
result := append([]T(nil), slice...)
|
||||
|
||||
if index < 0 || index >= len(slice) {
|
||||
return result[:len(slice)-1]
|
||||
}
|
||||
|
||||
result := make([]T, len(slice)-1)
|
||||
copy(result, slice[:index])
|
||||
copy(result[index:], slice[index+1:])
|
||||
copy(result[index:], result[index+1:])
|
||||
|
||||
return result
|
||||
// Set the last element to zero value, clean up the memory.
|
||||
result[len(result)-1] = zeroValue[T]()
|
||||
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
|
||||
func zeroValue[T any]() T {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
// DeleteRange delete the element of slice from start index to end index(exclude).
|
||||
@@ -699,7 +756,8 @@ func Drop[T any](slice []T, n int) []T {
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return slice
|
||||
result := make([]T, 0, size)
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, size-n)
|
||||
@@ -717,7 +775,8 @@ func DropRight[T any](slice []T, n int) []T {
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return slice
|
||||
result := make([]T, 0, size)
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, size-n)
|
||||
@@ -763,49 +822,59 @@ func InsertAt[T any](slice []T, index int, value any) []T {
|
||||
size := len(slice)
|
||||
|
||||
if index < 0 || index > size {
|
||||
return slice
|
||||
result := make([]T, size)
|
||||
copy(result, slice)
|
||||
return result
|
||||
}
|
||||
|
||||
if v, ok := value.(T); ok {
|
||||
slice = append(slice[:index], append([]T{v}, slice[index:]...)...)
|
||||
return slice
|
||||
switch v := value.(type) {
|
||||
case T:
|
||||
result := make([]T, size+1)
|
||||
copy(result, slice[:index])
|
||||
result[index] = v
|
||||
copy(result[index+1:], slice[index:])
|
||||
return result
|
||||
case []T:
|
||||
result := make([]T, size+len(v))
|
||||
copy(result, slice[:index])
|
||||
copy(result[index:], v)
|
||||
copy(result[index+len(v):], slice[index:])
|
||||
return result
|
||||
default:
|
||||
result := make([]T, size)
|
||||
copy(result, slice)
|
||||
return result
|
||||
}
|
||||
|
||||
if v, ok := value.([]T); ok {
|
||||
slice = append(slice[:index], append(v, slice[index:]...)...)
|
||||
return slice
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
// UpdateAt update the slice element at index.
|
||||
// Play: https://go.dev/play/p/f3mh2KloWVm
|
||||
func UpdateAt[T any](slice []T, index int, value T) []T {
|
||||
size := len(slice)
|
||||
result := make([]T, len(slice))
|
||||
copy(result, slice)
|
||||
|
||||
if index < 0 || index >= size {
|
||||
return slice
|
||||
if index >= 0 && index < len(slice) {
|
||||
result[index] = value
|
||||
}
|
||||
slice = append(slice[:index], append([]T{value}, slice[index+1:]...)...)
|
||||
|
||||
return slice
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique remove duplicate elements in slice.
|
||||
// Play: https://go.dev/play/p/AXw0R3ZTE6a
|
||||
func Unique[T comparable](slice []T) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
if len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
seen := make(map[T]struct{}, len(slice))
|
||||
result := slice[:0]
|
||||
|
||||
for i := range slice {
|
||||
if _, ok := seen[slice[i]]; ok {
|
||||
continue
|
||||
for _, item := range slice {
|
||||
if _, exists := seen[item]; !exists {
|
||||
seen[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
seen[slice[i]] = struct{}{}
|
||||
|
||||
result = append(result, slice[i])
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -815,18 +884,19 @@ func Unique[T comparable](slice []T) []T {
|
||||
// The function maintains the order of the elements.
|
||||
// Play: https://go.dev/play/p/GY7JE4yikrl
|
||||
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
if len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
seen := make(map[U]struct{}, len(slice))
|
||||
result := slice[:0]
|
||||
|
||||
for i := range slice {
|
||||
key := iteratee(slice[i])
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
for _, item := range slice {
|
||||
key := iteratee(item)
|
||||
if _, exists := seen[key]; !exists {
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
seen[key] = struct{}{}
|
||||
|
||||
result = append(result, slice[i])
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -836,19 +906,20 @@ func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
||||
// The function maintains the order of the elements.
|
||||
// Play: https://go.dev/play/p/rwSacr-ZHsR
|
||||
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
seen := make([]T, 0, len(slice))
|
||||
if len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(slice))
|
||||
for _, item := range slice {
|
||||
duplicate := false
|
||||
for _, seenItem := range seen {
|
||||
if comparator(item, seenItem) {
|
||||
duplicate = true
|
||||
isDuplicate := false
|
||||
for _, existing := range result {
|
||||
if comparator(item, existing) {
|
||||
isDuplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
seen = append(seen, item)
|
||||
if !isDuplicate {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
@@ -974,7 +1045,9 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
|
||||
return []T{}
|
||||
}
|
||||
if len(slices) == 1 {
|
||||
return Unique(slices[0])
|
||||
result := make([]T, len(slices[0]))
|
||||
copy(result, slices[0])
|
||||
return Unique(result)
|
||||
}
|
||||
|
||||
result := make([]T, 0)
|
||||
@@ -995,6 +1068,7 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
|
||||
}
|
||||
|
||||
// Reverse return slice of element order is reversed to the given slice.
|
||||
// Reverse modifies the slice in place.
|
||||
// Play: https://go.dev/play/p/8uI8f1lwNrQ
|
||||
func Reverse[T any](slice []T) {
|
||||
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
|
||||
@@ -1002,8 +1076,9 @@ func Reverse[T any](slice []T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReverseCopy return a new slice of element order is reversed to the given slice.
|
||||
// Play: todo
|
||||
// ReverseCopy return a new slice of element where the order is reversed to the given
|
||||
// slice.
|
||||
// Play: https://go.dev/play/p/c9arEaP7Cg-
|
||||
func ReverseCopy[T any](slice []T) []T {
|
||||
result := make([]T, len(slice))
|
||||
|
||||
@@ -1020,14 +1095,14 @@ func Shuffle[T any](slice []T) []T {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
rand.Shuffle(len(slice), func(i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
swap(slice, i, j)
|
||||
})
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
// ShuffleCopy return a new slice with elements shuffled.
|
||||
// Play: todo
|
||||
// Play: https://go.dev/play/p/vqDa-Gs1vT0
|
||||
func ShuffleCopy[T any](slice []T) []T {
|
||||
result := make([]T, len(slice))
|
||||
copy(result, slice)
|
||||
@@ -1193,11 +1268,12 @@ func SortByField[T any](slice []T, field string, sortType ...string) error {
|
||||
// Without creates a slice excluding all given items.
|
||||
// Play: https://go.dev/play/p/bwhEXEypThg
|
||||
func Without[T comparable](slice []T, items ...T) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
|
||||
if len(items) == 0 || len(slice) == 0 {
|
||||
return slice
|
||||
return append(result, slice...)
|
||||
}
|
||||
|
||||
result := make([]T, 0, len(slice))
|
||||
for _, v := range slice {
|
||||
if !Contain(items, v) {
|
||||
result = append(result, v)
|
||||
@@ -1418,36 +1494,28 @@ func Random[T any](slice []T) (val T, idx int) {
|
||||
return slice[idx], idx
|
||||
}
|
||||
|
||||
// RightPadding adds padding to the right end of a slice.
|
||||
// RightPadding returns a copy of the slice padding the given value to the right end of a slice.
|
||||
// If paddingLength is zero or less, the function returns a copy of the slice.
|
||||
// Play: https://go.dev/play/p/0_2rlLEMBXL
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
suffix := []T{}
|
||||
if paddingLength > 0 {
|
||||
suffix = repeat([]T{paddingValue}, paddingLength)
|
||||
}
|
||||
for i := 0; i < paddingLength; i++ {
|
||||
slice = append(slice, paddingValue)
|
||||
}
|
||||
return slice
|
||||
padded := concat(slice, suffix)
|
||||
return padded
|
||||
}
|
||||
|
||||
// LeftPadding adds padding to the left begin of a slice.
|
||||
// LeftPadding returns a copy of the slice padding the given value to the left begin of a slice.
|
||||
// If paddingLength is zero or less, the function returns a copy of the slice.
|
||||
// Play: https://go.dev/play/p/jlQVoelLl2k
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
prefix := []T{}
|
||||
if paddingLength > 0 {
|
||||
prefix = repeat([]T{paddingValue}, paddingLength)
|
||||
}
|
||||
|
||||
paddedSlice := make([]T, len(slice)+paddingLength)
|
||||
i := 0
|
||||
for ; i < paddingLength; i++ {
|
||||
paddedSlice[i] = paddingValue
|
||||
}
|
||||
for j := 0; j < len(slice); j++ {
|
||||
paddedSlice[i] = slice[j]
|
||||
i++
|
||||
}
|
||||
|
||||
return paddedSlice
|
||||
padded := concat(prefix, slice)
|
||||
return padded
|
||||
}
|
||||
|
||||
// Frequency counts the frequency of each element in the slice.
|
||||
|
||||
@@ -125,6 +125,8 @@ func ReduceConcurrent[T any](slice []T, initial T, reducer func(index int, item
|
||||
func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool, numThreads int) []T {
|
||||
result := make([]T, 0)
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
|
||||
workerChan := make(chan struct{}, numThreads)
|
||||
|
||||
@@ -137,7 +139,9 @@ func FilterConcurrent[T any](slice []T, predicate func(index int, item T) bool,
|
||||
defer wg.Done()
|
||||
|
||||
if predicate(i, v) {
|
||||
mu.Lock()
|
||||
result = append(result, v)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
<-workerChan
|
||||
|
||||
@@ -58,6 +58,24 @@ func ExampleContainSubSlice() {
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleContainAny() {
|
||||
result1 := ContainAny([]string{"a", "b", "c"}, []string{"a"})
|
||||
result2 := ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
|
||||
result3 := ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
|
||||
result4 := ContainAny([]string{"a", "b", "c"}, []string{})
|
||||
|
||||
fmt.Println(result1)
|
||||
fmt.Println(result2)
|
||||
fmt.Println(result3)
|
||||
fmt.Println(result4)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleChunk() {
|
||||
arr := []string{"a", "b", "c", "d", "e"}
|
||||
|
||||
@@ -831,7 +849,7 @@ func ExampleUniqueByComparator() {
|
||||
})
|
||||
|
||||
caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
return strings.EqualFold(item, other)
|
||||
})
|
||||
|
||||
fmt.Println(uniqueNums)
|
||||
|
||||
@@ -2,6 +2,7 @@ package slice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -96,3 +97,71 @@ func partitionAnySlice[T any](slice []T, lowIndex, highIndex int, less func(a, b
|
||||
func swap[T any](slice []T, i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
|
||||
// `repeat` returns a new slice that repeats the provided slice the given number of
|
||||
// times. The result has length and capacity (len(x) * count). The result is never nil.
|
||||
// Repeat panics if count is negative or if the result of (len(x) * count) overflows.
|
||||
//
|
||||
// repeat has been provided in the standard lib within the package `slices` under the
|
||||
// name Repeat since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func repeat[S ~[]E, E any](x S, count int) S {
|
||||
if count < 0 {
|
||||
panic("count cannot be negative")
|
||||
}
|
||||
|
||||
const maxInt = ^uint(0) >> 1
|
||||
hi, lo := bits.Mul(uint(len(x)), uint(count))
|
||||
if hi > 0 || lo > maxInt {
|
||||
panic("the result of (len(x) * count) overflows")
|
||||
}
|
||||
|
||||
newslice := make(S, int(lo)) // lo = len(x) * count
|
||||
n := copy(newslice, x)
|
||||
for n < len(newslice) {
|
||||
n += copy(newslice[n:], newslice[:n])
|
||||
}
|
||||
return newslice
|
||||
}
|
||||
|
||||
// concat returns a new slice concatenating the passed in slices.
|
||||
//
|
||||
// concat has been provided in the standard lib within the package `slices` under the
|
||||
// name Concat since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func concat[S ~[]E, E any](slices ...S) S {
|
||||
size := 0
|
||||
for _, s := range slices {
|
||||
size += len(s)
|
||||
if size < 0 {
|
||||
panic("len out of range")
|
||||
}
|
||||
}
|
||||
// Use Grow, not make, to round up to the size class:
|
||||
// the extra space is otherwise unused and helps
|
||||
// callers that append a few elements to the result.
|
||||
newslice := grow[S](nil, size)
|
||||
for _, s := range slices {
|
||||
newslice = append(newslice, s...)
|
||||
}
|
||||
return newslice
|
||||
}
|
||||
|
||||
// grow increases the slice's capacity, if necessary, to guarantee space for
|
||||
// another n elements. After grow(n), at least n elements can be appended
|
||||
// to the slice without another allocation. If n is negative or too large to
|
||||
// allocate the memory, grow panics.
|
||||
//
|
||||
// grow has been provided in the standard lib within the package `slices` under the
|
||||
// name Grow since GO version 1.21 onwards. As lancet commits to compatibility with GO
|
||||
// 1.18 onwards, we implement the functionality as an internal function.
|
||||
func grow[S ~[]E, E any](s S, n int) S {
|
||||
if n < 0 {
|
||||
panic("cannot be negative")
|
||||
}
|
||||
if n -= cap(s) - len(s); n > 0 {
|
||||
// This expression allocates only once.
|
||||
s = append(s[:cap(s)], make([]E, n)...)[:len(s)]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -89,6 +89,46 @@ func TestContainSubSlice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestContainAny")
|
||||
|
||||
tests := []struct {
|
||||
slice []string
|
||||
targets []string
|
||||
want bool
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, []string{"a"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{"d", "e"}, false},
|
||||
{[]string{"a", "b", "c"}, []string{"d", "a"}, true},
|
||||
{[]string{"a", "b", "c"}, []string{}, false},
|
||||
{[]string{}, []string{"a"}, false},
|
||||
{[]string{}, []string{}, false},
|
||||
{[]string{"a", "b", "c"}, []string{"c", "d", "e"}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
|
||||
}
|
||||
|
||||
intTests := []struct {
|
||||
slice []int
|
||||
targets []int
|
||||
want bool
|
||||
}{
|
||||
{[]int{1, 2, 3, 4, 5}, []int{3}, true},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{6, 7}, false},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{5, 6, 7}, true},
|
||||
{[]int{1, 2, 3, 4, 5}, []int{}, false},
|
||||
}
|
||||
|
||||
for _, tt := range intTests {
|
||||
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunk(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1008,7 +1048,7 @@ func TestUniqueByComparator(t *testing.T) {
|
||||
t.Run("case-insensitive string comparison", func(t *testing.T) {
|
||||
stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"}
|
||||
caseInsensitiveComparator := func(item, other string) bool {
|
||||
return strings.ToLower(item) == strings.ToLower(other)
|
||||
return strings.EqualFold(item, other)
|
||||
}
|
||||
|
||||
result := UniqueByComparator(stringSlice, caseInsensitiveComparator)
|
||||
@@ -1756,6 +1796,20 @@ func TestRightPaddingAndLeftPadding(t *testing.T) {
|
||||
|
||||
padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
|
||||
|
||||
// Test with negative padding length
|
||||
paddedNegative := LeftPadding(RightPadding(nums, 0, -3), 0, -3)
|
||||
assert.Equal([]int{1, 2, 3, 4, 5}, paddedNegative)
|
||||
|
||||
// Test with empty slice
|
||||
empty := []int{}
|
||||
paddedEmpty := LeftPadding(RightPadding(empty, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedEmpty)
|
||||
|
||||
// Test with nil
|
||||
nilSlice := []int(nil)
|
||||
paddedNil := LeftPadding(RightPadding(nilSlice, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 0, 0, 0}, paddedNil)
|
||||
}
|
||||
|
||||
func TestUniqueByConcurrent(t *testing.T) {
|
||||
|
||||
@@ -420,3 +420,12 @@ func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int {
|
||||
func (s Stream[T]) ToSlice() []T {
|
||||
return s.source
|
||||
}
|
||||
|
||||
func ToMap[T any, K comparable, V any](s Stream[T], mapper func(item T) (K, V)) map[K]V {
|
||||
result := map[K]V{}
|
||||
for _, v := range s.source {
|
||||
key, value := mapper(v)
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -412,3 +412,22 @@ func ExampleStream_LastIndexOf() {
|
||||
// -1
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleToMap() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
s := FromSlice([]Person{
|
||||
{Name: "Tom", Age: 10},
|
||||
{Name: "Jim", Age: 20},
|
||||
{Name: "Mike", Age: 30},
|
||||
})
|
||||
m := ToMap(s, func(p Person) (string, Person) {
|
||||
return p.Name, p
|
||||
})
|
||||
fmt.Println(m)
|
||||
|
||||
// Output:
|
||||
// map[Jim:{Jim 20} Mike:{Mike 30} Tom:{Tom 10}]
|
||||
}
|
||||
|
||||
@@ -400,3 +400,26 @@ func TestStream_LastIndexOf(t *testing.T) {
|
||||
assert.Equal(-1, s.LastIndexOf(0, func(a, b int) bool { return a == b }))
|
||||
assert.Equal(4, s.LastIndexOf(2, func(a, b int) bool { return a == b }))
|
||||
}
|
||||
|
||||
func TestStream_ToMap(t *testing.T) {
|
||||
assert := internal.NewAssert(t, "TestStream_ToMap")
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
s := FromSlice([]Person{
|
||||
{Name: "Tom", Age: 10},
|
||||
{Name: "Jim", Age: 20},
|
||||
{Name: "Mike", Age: 30},
|
||||
})
|
||||
m := ToMap(s, func(p Person) (string, Person) {
|
||||
return p.Name, p
|
||||
})
|
||||
expected := map[string]Person{
|
||||
"Tom": {Name: "Tom", Age: 10},
|
||||
"Jim": {Name: "Jim", Age: 20},
|
||||
"Mike": {Name: "Mike", Age: 30},
|
||||
}
|
||||
assert.EqualValues(expected, m)
|
||||
|
||||
}
|
||||
|
||||
@@ -26,26 +26,31 @@ func newField(v reflect.Value, f reflect.StructField, tagName string) *Field {
|
||||
}
|
||||
|
||||
// Tag returns the value that the key in the tag string.
|
||||
// Play: https://go.dev/play/p/DVrx5HvvUJr
|
||||
func (f *Field) Tag() *Tag {
|
||||
return f.tag
|
||||
}
|
||||
|
||||
// Value returns the underlying value of the field.
|
||||
// Play: https://go.dev/play/p/qufYEU2o4Oi
|
||||
func (f *Field) Value() any {
|
||||
return f.rvalue.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an embedded field.
|
||||
// Play: https://go.dev/play/p/wV2PrbYm3Ec
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return len(f.field.Index) > 1
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
// Play: https://go.dev/play/p/csK4AXYaNbJ
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.IsExported()
|
||||
}
|
||||
|
||||
// IsZero returns true if the given field is zero value.
|
||||
// Play: https://go.dev/play/p/RzqpGISf87r
|
||||
func (f *Field) IsZero() bool {
|
||||
z := reflect.Zero(f.rvalue.Type()).Interface()
|
||||
v := f.Value()
|
||||
@@ -63,22 +68,26 @@ func (f *Field) IsNil() bool {
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
// Play: https://go.dev/play/p/zfIGlqsatee
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
// Kind returns the field's kind
|
||||
// Play: https://go.dev/play/p/wg4NlcUNG5o
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.rvalue.Kind()
|
||||
}
|
||||
|
||||
// IsSlice check if a struct field type is slice or not
|
||||
// Play: https://go.dev/play/p/MKz4CgBIUrU
|
||||
func (f *Field) IsSlice() bool {
|
||||
k := f.rvalue.Kind()
|
||||
return k == reflect.Slice
|
||||
}
|
||||
|
||||
// IsTargetType check if a struct field type is target type or not
|
||||
// Play: https://go.dev/play/p/Ig75P-agN39
|
||||
func (f *Field) IsTargetType(targetType reflect.Kind) bool {
|
||||
return f.rvalue.Kind() == targetType
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user