mirror of
https://github.com/duke-git/lancet.git
synced 2026-03-01 00:35:28 +08:00
Compare commits
26 Commits
0ef45b533b
...
v2.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -4,7 +4,7 @@
|
|||||||
<br/>
|
<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://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||||
[](https://goreportcard.com/report/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)
|
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||||
@@ -214,6 +214,30 @@ import "github.com/duke-git/lancet/v2/concurrency"
|
|||||||
- **<big>Tee</big>** : split one chanel into two channels, until cancel the context.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/concurrency.md#Tee)]
|
||||||
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
[[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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
|
||||||
<h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator... <a href="#index">index</a> </h3>
|
<h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator... <a href="#index">index</a> </h3>
|
||||||
|
|
||||||
@@ -331,7 +355,7 @@ import "github.com/duke-git/lancet/v2/convertor"
|
|||||||
- **<big>ToRawUrlBase64</big>** : converts a value to a string encoded in raw url Base64.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToRawUrlBase64)]
|
||||||
[[play](https://go.dev/play/p/HwdDPFcza1O)]
|
[[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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToBigInt)]
|
||||||
[[play](https://go.dev/play/p/X3itkCxwB_x)]
|
[[play](https://go.dev/play/p/X3itkCxwB_x)]
|
||||||
|
|
||||||
@@ -535,7 +559,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
|||||||
[[play](https://go.dev/play/p/94m_UT6cWs9)]
|
[[play](https://go.dev/play/p/94m_UT6cWs9)]
|
||||||
- **<big>BeginOfWeek</big>** : return the date time at the begin of week of specific date.
|
- **<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)]
|
[[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.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#BeginOfMonth)]
|
||||||
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
|
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
|
||||||
@@ -553,7 +577,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
|||||||
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
|
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
|
||||||
- **<big>EndOfWeek</big>** : return the date time at the end of week of specific date.
|
- **<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)]
|
[[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.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datetime.md#EndOfMonth)]
|
||||||
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
|
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
|
||||||
@@ -694,7 +718,6 @@ import optional "github.com/duke-git/lancet/v2/datastructure/optional"
|
|||||||
- **<big>Optional</big>** : Optional container.
|
- **<big>Optional</big>** : Optional container.
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/optional.md)]
|
[[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>
|
<h3 id="eventbus"> 9. EventBus is an event bus used for handling events within an application. <a href="#index">Index</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -778,6 +801,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
|||||||
- **<big>RemoveFile</big>** : remove file, param should be file path.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#RemoveFile)]
|
||||||
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
[[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/todo)]
|
||||||
- **<big>ReadFileToString</big>** : return string of file content.
|
- **<big>ReadFileToString</big>** : return string of file content.
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ReadFileToString)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#ReadFileToString)]
|
||||||
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
||||||
@@ -926,7 +952,6 @@ import "github.com/duke-git/lancet/v2/function"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
|
||||||
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
|
[[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"> 12. Maputil package includes some functions to manipulate map. <a href="#index">index</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -1097,7 +1122,9 @@ 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.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrDefault)]
|
||||||
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
[[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/todo)]
|
||||||
|
|
||||||
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. <a href="#index">index</a></h3>
|
||||||
|
|
||||||
@@ -1282,6 +1309,12 @@ import "github.com/duke-git/lancet/v2/netutil"
|
|||||||
- **<big>IsTelnetConnected</big>** : checks if can if can telnet the specified host or not.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#IsTelnetConnected)]
|
||||||
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
[[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/todo)]
|
||||||
|
- **<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/todo)]
|
||||||
|
|
||||||
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. <a href="#index">index</a></h3>
|
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. <a href="#index">index</a></h3>
|
||||||
|
|
||||||
@@ -1408,7 +1441,6 @@ import "github.com/duke-git/lancet/v2/retry"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
[[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"> 18. Slice contains some functions to manipulate slice. <a href="#index">index</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -2012,7 +2044,6 @@ import "github.com/duke-git/lancet/v2/system"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
|
||||||
[[play](https://go.dev/play/p/NQDVywEYYx7)]
|
[[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"> 23. Tuple package implements tuple data type and some operations on it. <a href="#index">index</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -2195,6 +2226,9 @@ import "github.com/duke-git/lancet/v2/validator"
|
|||||||
- **<big>IsNumberStr</big>** : check if the string can convert to a number.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsNumberStr)]
|
||||||
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
[[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/todo)]
|
||||||
- **<big>IsJSON</big>** : check if the string is valid JSON.
|
- **<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)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsJSON)]
|
||||||
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
||||||
@@ -2324,6 +2358,7 @@ import "github.com/duke-git/lancet/v2/xerror"
|
|||||||
#### [Contribution Guide](./CONTRIBUTION.md)
|
#### [Contribution Guide](./CONTRIBUTION.md)
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Thank you to all the people who contributed to lancet!
|
Thank you to all the people who contributed to lancet!
|
||||||
|
|
||||||
<a href="https://github.com/duke-git/lancet/graphs/contributors">
|
<a href="https://github.com/duke-git/lancet/graphs/contributors">
|
||||||
|
|||||||
+43
-14
@@ -4,7 +4,7 @@
|
|||||||
<br/>
|
<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://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||||
[](https://goreportcard.com/report/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)
|
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||||
@@ -101,7 +101,6 @@ func main() {
|
|||||||
- [Validator](#user-content-validator)
|
- [Validator](#user-content-validator)
|
||||||
- [Xerror](#user-content-xerror)
|
- [Xerror](#user-content-xerror)
|
||||||
|
|
||||||
|
|
||||||
<h3 id="algorithm"> 1. algorithm 包实现一些基本查找和排序算法。 <a href="#index">回到目录</a></h3>
|
<h3 id="algorithm"> 1. algorithm 包实现一些基本查找和排序算法。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -214,6 +213,30 @@ import "github.com/duke-git/lancet/v2/concurrency"
|
|||||||
- **<big>Tee</big>** : 将一个 channel 分成两个 channel,直到取消上下文。
|
- **<big>Tee</big>** : 将一个 channel 分成两个 channel,直到取消上下文。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Tee)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Tee)]
|
||||||
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
[[play](https://go.dev/play/p/3TQPKnCirrP)]
|
||||||
|
- **<big>NewKeyedLocker</big>** : NewKeyedLocker 创建一个新的 KeyedLocker,并为锁的过期设置指定的 TTL。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewKeyedLocker)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>Do</big>** :为指定的键获取锁并执行提供的函数。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Do)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>NewRWKeyedLocker</big>** :RWKeyedLocker 是一个简单的键值读写锁实现,允许非阻塞的锁获取。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewRWKeyedLocker)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>RLock</big>** : 为指定的键获取读锁并执行提供的函数。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#RLock)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>Lock</big>** : 为指定的键获取锁并执行提供的函数。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Lock)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>NewTryKeyedLocker</big>** : 创建一个 TryKeyedLocker 实例,TryKeyedLocker 是 KeyedLocker 的非阻塞版本。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#NewTryKeyedLocker)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>TryLock</big>** : TryLock 尝试获取指定键的锁。如果锁成功获取,则返回 true,否则返回 false。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#TryLock)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>Unlock</big>** : 释放指定键的锁。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/concurrency.md#Unlock)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
|
||||||
<h3 id="condition"> 4. condition 包含一些用于条件判断的函数。 <a href="#index">回到目录</a></h3>
|
<h3 id="condition"> 4. condition 包含一些用于条件判断的函数。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
@@ -331,7 +354,7 @@ import "github.com/duke-git/lancet/v2/convertor"
|
|||||||
- **<big>ToRawUrlBase64</big>** : 将值转换为 RawUrlBase64 编码的字符串。
|
- **<big>ToRawUrlBase64</big>** : 将值转换为 RawUrlBase64 编码的字符串。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToRawUrlBase64)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToRawUrlBase64)]
|
||||||
[[play](https://go.dev/play/p/HwdDPFcza1O)]
|
[[play](https://go.dev/play/p/HwdDPFcza1O)]
|
||||||
- **<big>ToBigInt</big>** : 将整数转为*big.Int。
|
- **<big>ToBigInt</big>** : 将整数转为\*big.Int。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToBigInt)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToBigInt)]
|
||||||
[[play](https://go.dev/play/p/X3itkCxwB_x)]
|
[[play](https://go.dev/play/p/X3itkCxwB_x)]
|
||||||
|
|
||||||
@@ -536,7 +559,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
|||||||
[[play](https://go.dev/play/p/94m_UT6cWs9)]
|
[[play](https://go.dev/play/p/94m_UT6cWs9)]
|
||||||
- **<big>BeginOfWeek</big>** : 返回指定时间的每周开始时间,默认开始时间星期日。
|
- **<big>BeginOfWeek</big>** : 返回指定时间的每周开始时间,默认开始时间星期日。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfWeek)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfWeek)]
|
||||||
[[play](https://go.dev/play/p/ynjoJPz7VNV)]
|
[[play](https://go.dev/play/p/DCHdcL6gnfV)]
|
||||||
- **<big>BeginOfMonth</big>** : 返回指定时间的当月开始时间。
|
- **<big>BeginOfMonth</big>** : 返回指定时间的当月开始时间。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMonth)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#BeginOfMonth)]
|
||||||
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
|
[[play](https://go.dev/play/p/bWXVFsmmzwL)]
|
||||||
@@ -554,7 +577,7 @@ import "github.com/duke-git/lancet/v2/datetime"
|
|||||||
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
|
[[play](https://go.dev/play/p/eMBOvmq5Ih1)]
|
||||||
- **<big>EndOfWeek</big>** : 返回指定时间的星期结束时间,默认结束时间星期六。
|
- **<big>EndOfWeek</big>** : 返回指定时间的星期结束时间,默认结束时间星期六。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfWeek)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfWeek)]
|
||||||
[[play](https://go.dev/play/p/i08qKXD9flf)]
|
[[play](https://go.dev/play/p/mGSA162YgX9)]
|
||||||
- **<big>EndOfMonth</big>** : 返回指定时间的月份结束时间。
|
- **<big>EndOfMonth</big>** : 返回指定时间的月份结束时间。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfMonth)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#EndOfMonth)]
|
||||||
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
|
[[play](https://go.dev/play/p/_GWh10B3Nqi)]
|
||||||
@@ -658,7 +681,6 @@ import "github.com/duke-git/lancet/v2/datetime"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#MaxMin)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/datetime.md#MaxMin)]
|
||||||
[[play](https://go.dev/play/p/rbW51cDtM_2)]
|
[[play](https://go.dev/play/p/rbW51cDtM_2)]
|
||||||
|
|
||||||
|
|
||||||
<h3 id="datastructure"> 8. datastructure 包含一些普通的数据结构实现。例如:list, linklist, stack, queue, set, tree, graph。 <a href="#index">回到目录</a></h3>
|
<h3 id="datastructure"> 8. datastructure 包含一些普通的数据结构实现。例如:list, linklist, stack, queue, set, tree, graph。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -777,6 +799,9 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
|||||||
- **<big>RemoveFile</big>** : 删除文件。
|
- **<big>RemoveFile</big>** : 删除文件。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#RemoveFile)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#RemoveFile)]
|
||||||
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
[[play](https://go.dev/play/p/P2y0XW8a1SH)]
|
||||||
|
- **<big>RemoveDir</big>** : 删除目录,支持传入删除前的回调函数。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#RemoveDir)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
- **<big>ReadFileToString</big>** : 读取文件内容并返回字符串。
|
- **<big>ReadFileToString</big>** : 读取文件内容并返回字符串。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadFileToString)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#ReadFileToString)]
|
||||||
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
[[play](https://go.dev/play/p/cmfwp_5SQTp)]
|
||||||
@@ -832,7 +857,6 @@ import "github.com/duke-git/lancet/v2/fileutil"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#GetExeOrDllVersion)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#GetExeOrDllVersion)]
|
||||||
[[play](https://go.dev/play/p/iLRrDBhE38E)]
|
[[play](https://go.dev/play/p/iLRrDBhE38E)]
|
||||||
|
|
||||||
|
|
||||||
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。 <a href="#index">回到目录</a></h3>
|
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -926,8 +950,6 @@ import "github.com/duke-git/lancet/v2/function"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
|
||||||
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
|
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。 <a href="#index">回到目录</a></h3>
|
<h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -1098,6 +1120,9 @@ import "github.com/duke-git/lancet/v2/maputil"
|
|||||||
- **<big>GetOrDefault</big>** : 返回给定键的值,如果键不存在,则返回默认值。
|
- **<big>GetOrDefault</big>** : 返回给定键的值,如果键不存在,则返回默认值。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrDefault)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrDefault)]
|
||||||
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
[[play](https://go.dev/play/p/99QjSYSBdiM)]
|
||||||
|
- **<big>FindValuesBy</big>** : 返回一个切片,包含满足给定谓词判断函数的 map 中的值。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#FindValuesBy)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
|
||||||
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
@@ -1282,6 +1307,12 @@ import "github.com/duke-git/lancet/v2/netutil"
|
|||||||
- **<big>IsTelnetConnected</big>** : 检查能否 telnet 到主机。
|
- **<big>IsTelnetConnected</big>** : 检查能否 telnet 到主机。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#IsTelnetConnected)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#IsTelnetConnected)]
|
||||||
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
[[play](https://go.dev/play/p/yiLCGtQv_ZG)]
|
||||||
|
- **<big>BuildUrl</big>** : 创建 url 字符串。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#BuildUrl)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
- **<big>AddQueryParams</big>** : 向 url 添加查询参数。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#AddQueryParams)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
|
|
||||||
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。 <a href="#index">回到目录</a></h3>
|
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
@@ -1405,8 +1436,6 @@ import "github.com/duke-git/lancet/v2/retry"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
|
||||||
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
[[play](https://go.dev/play/p/xp1avQmn16X)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="slice"> 19. slice 包含操作切片的方法集合。 <a href="#index">回到目录</a></h3>
|
<h3 id="slice"> 19. slice 包含操作切片的方法集合。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -1964,7 +1993,6 @@ import "github.com/duke-git/lancet/v2/strutil"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
|
||||||
[[play](https://go.dev/play/p/uvyA6azGLB1)]
|
[[play](https://go.dev/play/p/uvyA6azGLB1)]
|
||||||
|
|
||||||
|
|
||||||
<h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。 <a href="#index">回到目录</a></h3>
|
<h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -2013,8 +2041,6 @@ import "github.com/duke-git/lancet/v2/system"
|
|||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
|
||||||
[[play](https://go.dev/play/p/NQDVywEYYx7)]
|
[[play](https://go.dev/play/p/NQDVywEYYx7)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。 <a href="#index">回到目录</a></h3>
|
<h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。 <a href="#index">回到目录</a></h3>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -2197,6 +2223,9 @@ import "github.com/duke-git/lancet/v2/validator"
|
|||||||
- **<big>IsNumberStr</big>** : 验证字符串是否是可以转换为数字。
|
- **<big>IsNumberStr</big>** : 验证字符串是否是可以转换为数字。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsNumberStr)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsNumberStr)]
|
||||||
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
[[play](https://go.dev/play/p/LzaKocSV79u)]
|
||||||
|
- **<big>IsAlphaNumeric</big>** : 验证字符串是字母或数字。
|
||||||
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsAlphaNumeric)]
|
||||||
|
[[play](https://go.dev/play/p/todo)]
|
||||||
- **<big>IsJSON</big>** : 验证字符串是否是有效 json。
|
- **<big>IsJSON</big>** : 验证字符串是否是有效 json。
|
||||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsJSON)]
|
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsJSON)]
|
||||||
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
[[play](https://go.dev/play/p/8Kip1Itjiil)]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package concurrency
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,3 +200,159 @@ func ExampleChannel_Bridge() {
|
|||||||
// true
|
// true
|
||||||
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// 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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
+19
-17
@@ -6,7 +6,6 @@
|
|||||||
package cryptor
|
package cryptor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
@@ -66,34 +65,37 @@ func Md5ByteWithBase64(data []byte) string {
|
|||||||
|
|
||||||
// Md5File return the md5 value of file.
|
// Md5File return the md5 value of file.
|
||||||
func Md5File(filename string) (string, error) {
|
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)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
hash := md5.New()
|
stat, err := file.Stat()
|
||||||
|
|
||||||
chunkSize := 65536
|
|
||||||
for buf, reader := make([]byte, chunkSize), bufio.NewReader(file); ; {
|
|
||||||
n, err := reader.Read(buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
return "", 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 {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hash.Write(buf[:n])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum := fmt.Sprintf("%x", hash.Sum(nil))
|
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||||
return checksum, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HmacMd5 return the hmac hash of string use md5.
|
// HmacMd5 return the hmac hash of string use md5.
|
||||||
|
|||||||
+347
-297
@@ -15,39 +15,40 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||||
func AesEcbEncrypt(data, key []byte) []byte {
|
func AesEcbEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
length := (len(data) + aes.BlockSize) / aes.BlockSize
|
blockSize := aes.BlockSize
|
||||||
plain := make([]byte, length*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 := dataLen; i < paddedLen; i++ {
|
||||||
for i := len(data); i < len(plain); i++ {
|
paddedData[i] = byte(padding)
|
||||||
plain[i] = pad
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, len(plain))
|
cipher, err := aes.NewCipher(generateAesKey(key, len(key)))
|
||||||
cipher, _ := aes.NewCipher(generateAesKey(key, size))
|
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() {
|
encrypted := make([]byte, paddedLen)
|
||||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
for bs := 0; bs < paddedLen; bs += blockSize {
|
||||||
|
cipher.Encrypt(encrypted[bs:], paddedData[bs:])
|
||||||
}
|
}
|
||||||
|
|
||||||
return encrypted
|
return encrypted
|
||||||
@@ -57,77 +58,107 @@ func AesEcbEncrypt(data, key []byte) []byte {
|
|||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/jT5irszHx-j
|
// Play: https://go.dev/play/p/jT5irszHx-j
|
||||||
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
func AesEcbDecrypt(encrypted, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
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))
|
decrypted := make([]byte, len(encrypted))
|
||||||
|
for i := 0; i < len(encrypted); i += blockSize {
|
||||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trim := 0
|
if len(decrypted) == 0 {
|
||||||
if len(decrypted) > 0 {
|
return nil
|
||||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
}
|
||||||
|
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
|
// AesCbcEncrypt encrypt data with key use AES CBC algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||||
func AesCbcEncrypt(data, key []byte) []byte {
|
func AesCbcEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
data = pkcs7Padding(data, block.BlockSize())
|
if err != nil {
|
||||||
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
padding := aes.BlockSize - len(data)%aes.BlockSize
|
||||||
iv := encrypted[: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 {
|
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 := 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
|
// AesCbcDecrypt decrypt data with key use AES CBC algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
// Play: https://go.dev/play/p/IOq_g8_lKZD
|
||||||
func AesCbcDecrypt(encrypted, key []byte) []byte {
|
func AesCbcDecrypt(encrypted, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
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 := cipher.NewCBCDecrypter(block, iv)
|
||||||
mode.CryptBlocks(encrypted, encrypted)
|
mode.CryptBlocks(decrypted, ciphertext)
|
||||||
|
|
||||||
decrypted := pkcs7UnPadding(encrypted)
|
return pkcs7UnPadding(decrypted)
|
||||||
return decrypted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AesCtrCrypt encrypt data with key use AES CTR algorithm
|
// AesCtrCrypt encrypt data with key use AES CTR algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/SpaZO0-5Nsp
|
// Play: https://go.dev/play/p/SpaZO0-5Nsp
|
||||||
|
// deprecated: use AesCtrEncrypt and AesCtrDecrypt instead.
|
||||||
func AesCtrCrypt(data, key []byte) []byte {
|
func AesCtrCrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := aes.NewCipher(key)
|
block, _ := aes.NewCipher(key)
|
||||||
@@ -141,158 +172,214 @@ func AesCtrCrypt(data, key []byte) []byte {
|
|||||||
return dst
|
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.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
// Play: todo
|
||||||
func AesCfbEncrypt(data, key []byte) []byte {
|
func AesCtrEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
iv := make([]byte, aes.BlockSize)
|
||||||
iv := encrypted[:aes.BlockSize]
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
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 := cipher.NewCTR(block, iv)
|
||||||
stream.XORKeyStream(encrypted[aes.BlockSize:], data)
|
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: todo
|
||||||
|
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
|
// AesCfbDecrypt decrypt data with key use AES CFB algorithm
|
||||||
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32.
|
// len(encrypted) should be great than 16, len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/tfkF10B13kH
|
// Play: https://go.dev/play/p/tfkF10B13kH
|
||||||
func AesCfbDecrypt(encrypted, key []byte) []byte {
|
func AesCfbDecrypt(encrypted, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(encrypted) < aes.BlockSize {
|
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]
|
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 := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
stream.XORKeyStream(plaintext, ciphertext)
|
||||||
|
|
||||||
stream.XORKeyStream(encrypted, encrypted)
|
return plaintext
|
||||||
|
|
||||||
return encrypted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
|
// AesOfbEncrypt encrypt data with key use AES OFB algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||||
func AesOfbEncrypt(data, key []byte) []byte {
|
func AesOfbEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
data = pkcs7Padding(data, aes.BlockSize)
|
iv := make([]byte, aes.BlockSize)
|
||||||
encrypted := make([]byte, aes.BlockSize+len(data))
|
|
||||||
iv := encrypted[:aes.BlockSize]
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
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 := 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
|
// AesOfbDecrypt decrypt data with key use AES OFB algorithm
|
||||||
// len(key) should be 16, 24 or 32.
|
// len(key) should be 16, 24 or 32.
|
||||||
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
// Play: https://go.dev/play/p/VtHxtkUj-3F
|
||||||
func AesOfbDecrypt(data, key []byte) []byte {
|
func AesOfbDecrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if !isAesKeyLengthValid(len(key)) {
|
||||||
if size != 16 && size != 24 && size != 32 {
|
panic("aes: invalid key length (must be 16, 24, or 32 bytes)")
|
||||||
panic("key length shoud be 16 or 24 or 32")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := aes.NewCipher(key)
|
if len(data) < aes.BlockSize {
|
||||||
if err != nil {
|
panic("aes: encrypted data too short")
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := data[:aes.BlockSize]
|
iv := data[:aes.BlockSize]
|
||||||
data = data[aes.BlockSize:]
|
ciphertext := data[aes.BlockSize:]
|
||||||
if len(data)%aes.BlockSize != 0 {
|
|
||||||
return nil
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypted := make([]byte, len(data))
|
plaintext := make([]byte, len(ciphertext))
|
||||||
mode := cipher.NewOFB(block, iv)
|
stream := cipher.NewOFB(block, iv)
|
||||||
mode.XORKeyStream(decrypted, data)
|
stream.XORKeyStream(plaintext, ciphertext)
|
||||||
|
|
||||||
decrypted = pkcs7UnPadding(decrypted)
|
return plaintext
|
||||||
|
|
||||||
return decrypted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
|
// AesGcmEncrypt encrypt data with key use AES GCM algorithm
|
||||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||||
func AesGcmEncrypt(data, key []byte) []byte {
|
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)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(block)
|
gcm, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create GCM: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
panic(err)
|
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
|
// AesGcmDecrypt decrypt data with key use AES GCM algorithm
|
||||||
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
// Play: https://go.dev/play/p/rUt0-DmsPCs
|
||||||
func AesGcmDecrypt(data, key []byte) []byte {
|
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)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(block)
|
gcm, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: failed to create GCM: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceSize := gcm.NonceSize()
|
nonceSize := gcm.NonceSize()
|
||||||
if len(data) < nonceSize {
|
if len(data) < nonceSize {
|
||||||
panic("ciphertext too short")
|
panic("aes: ciphertext too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("aes: decryption failed: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return plaintext
|
return plaintext
|
||||||
@@ -302,20 +389,17 @@ func AesGcmDecrypt(data, key []byte) []byte {
|
|||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||||
func DesEcbEncrypt(data, key []byte) []byte {
|
func DesEcbEncrypt(data, key []byte) []byte {
|
||||||
length := (len(data) + des.BlockSize) / des.BlockSize
|
cipher, err := des.NewCipher(generateDesKey(key))
|
||||||
plain := make([]byte, length*des.BlockSize)
|
if err != nil {
|
||||||
copy(plain, data)
|
panic("des: failed to create cipher: " + err.Error())
|
||||||
|
|
||||||
pad := byte(len(plain) - len(data))
|
|
||||||
for i := len(data); i < len(plain); i++ {
|
|
||||||
plain[i] = pad
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, len(plain))
|
blockSize := cipher.BlockSize()
|
||||||
cipher, _ := des.NewCipher(generateDesKey(key))
|
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() {
|
for i := 0; i < len(padded); i += blockSize {
|
||||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
cipher.Encrypt(encrypted[i:], padded[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
return encrypted
|
return encrypted
|
||||||
@@ -325,42 +409,50 @@ func DesEcbEncrypt(data, key []byte) []byte {
|
|||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/8qivmPeZy4P
|
// Play: https://go.dev/play/p/8qivmPeZy4P
|
||||||
func DesEcbDecrypt(encrypted, key []byte) []byte {
|
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))
|
decrypted := make([]byte, len(encrypted))
|
||||||
|
for i := 0; i < len(encrypted); i += blockSize {
|
||||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
cipher.Decrypt(decrypted[i:], encrypted[i:])
|
||||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trim := 0
|
// Remove padding
|
||||||
if len(decrypted) > 0 {
|
return pkcs5UnPadding(decrypted)
|
||||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return decrypted[:trim]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
|
// DesCbcEncrypt encrypt data with key use DES CBC algorithm
|
||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||||
func DesCbcEncrypt(data, key []byte) []byte {
|
func DesCbcEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
data = pkcs7Padding(data, block.BlockSize())
|
if err != nil {
|
||||||
|
panic("des: failed to create cipher: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
encrypted := make([]byte, des.BlockSize+len(data))
|
blockSize := block.BlockSize()
|
||||||
iv := encrypted[:des.BlockSize]
|
data = pkcs7Padding(data, blockSize)
|
||||||
|
|
||||||
|
encrypted := make([]byte, blockSize+len(data))
|
||||||
|
iv := encrypted[:blockSize]
|
||||||
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
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 := cipher.NewCBCEncrypter(block, iv)
|
||||||
mode.CryptBlocks(encrypted[des.BlockSize:], data)
|
mode.CryptBlocks(encrypted[blockSize:], data)
|
||||||
|
|
||||||
return encrypted
|
return encrypted
|
||||||
}
|
}
|
||||||
@@ -369,26 +461,33 @@ func DesCbcEncrypt(data, key []byte) []byte {
|
|||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
// Play: https://go.dev/play/p/4cC4QvWfe3_1
|
||||||
func DesCbcDecrypt(encrypted, key []byte) []byte {
|
func DesCbcDecrypt(encrypted, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
panic("des: failed to create cipher: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
iv := encrypted[:des.BlockSize]
|
blockSize := block.BlockSize()
|
||||||
encrypted = encrypted[des.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 := cipher.NewCBCDecrypter(block, iv)
|
||||||
mode.CryptBlocks(encrypted, encrypted)
|
mode.CryptBlocks(ciphertext, ciphertext)
|
||||||
|
|
||||||
decrypted := pkcs7UnPadding(encrypted)
|
return pkcs7UnPadding(ciphertext)
|
||||||
return decrypted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DesCtrCrypt encrypt data with key use DES CTR algorithm
|
// DesCtrCrypt encrypt data with key use DES CTR algorithm
|
||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/9-T6OjKpcdw
|
// Play: https://go.dev/play/p/9-T6OjKpcdw
|
||||||
|
// deprecated: use DesCtrEncrypt and DesCtrDecrypt instead.
|
||||||
func DesCtrCrypt(data, key []byte) []byte {
|
func DesCtrCrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
size := len(key)
|
||||||
if size != 8 {
|
if size != 8 {
|
||||||
@@ -406,25 +505,83 @@ func DesCtrCrypt(data, key []byte) []byte {
|
|||||||
return dst
|
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.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
// Play: todo
|
||||||
func DesCfbEncrypt(data, key []byte) []byte {
|
func DesCtrEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
if err != nil {
|
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: todo
|
||||||
|
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))
|
encrypted := make([]byte, des.BlockSize+len(data))
|
||||||
iv := encrypted[:des.BlockSize]
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
copy(encrypted[:des.BlockSize], iv)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stream := cipher.NewCFBEncrypter(block, iv)
|
stream := cipher.NewCFBEncrypter(block, iv)
|
||||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
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.
|
// len(encrypted) should be great than 16, len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
// Play: https://go.dev/play/p/y-eNxcFBlxL
|
||||||
func DesCfbDecrypt(encrypted, key []byte) []byte {
|
func DesCfbDecrypt(encrypted, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, _ := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
if len(encrypted) < des.BlockSize {
|
if err != nil {
|
||||||
panic("encrypted data is too short")
|
panic("des: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(encrypted) < des.BlockSize {
|
||||||
|
panic("des: encrypted data too short")
|
||||||
|
}
|
||||||
|
|
||||||
iv := encrypted[:des.BlockSize]
|
iv := encrypted[:des.BlockSize]
|
||||||
encrypted = encrypted[des.BlockSize:]
|
ciphertext := encrypted[des.BlockSize:]
|
||||||
|
|
||||||
stream := cipher.NewCFBDecrypter(block, iv)
|
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
|
// DesOfbEncrypt encrypt data with key use DES OFB algorithm
|
||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||||
func DesOfbEncrypt(data, key []byte) []byte {
|
func DesOfbEncrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("des: failed to create cipher: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
data = pkcs7Padding(data, des.BlockSize)
|
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 {
|
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 := cipher.NewOFB(block, iv)
|
||||||
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
stream.XORKeyStream(encrypted[des.BlockSize:], data)
|
||||||
|
|
||||||
@@ -484,25 +648,25 @@ func DesOfbEncrypt(data, key []byte) []byte {
|
|||||||
// len(key) should be 8.
|
// len(key) should be 8.
|
||||||
// Play: https://go.dev/play/p/74KmNadjN1J
|
// Play: https://go.dev/play/p/74KmNadjN1J
|
||||||
func DesOfbDecrypt(data, key []byte) []byte {
|
func DesOfbDecrypt(data, key []byte) []byte {
|
||||||
size := len(key)
|
if len(key) != 8 {
|
||||||
if size != 8 {
|
panic("des: key length must be 8 bytes")
|
||||||
panic("key length shoud be 8")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := des.NewCipher(key)
|
block, err := des.NewCipher(key)
|
||||||
if err != nil {
|
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]
|
iv := data[:des.BlockSize]
|
||||||
data = data[des.BlockSize:]
|
ciphertext := data[des.BlockSize:]
|
||||||
if len(data)%des.BlockSize != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted := make([]byte, len(data))
|
stream := cipher.NewOFB(block, iv)
|
||||||
mode := cipher.NewOFB(block, iv)
|
decrypted := make([]byte, len(ciphertext))
|
||||||
mode.XORKeyStream(decrypted, data)
|
stream.XORKeyStream(decrypted, ciphertext)
|
||||||
|
|
||||||
decrypted = pkcs7UnPadding(decrypted)
|
decrypted = pkcs7UnPadding(decrypted)
|
||||||
|
|
||||||
@@ -693,117 +857,3 @@ func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName stri
|
|||||||
|
|
||||||
return rsa.VerifyPKCS1v15(publicKey, hash, hashed, signature)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
+148
-1
@@ -1,6 +1,17 @@
|
|||||||
package cryptor
|
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 {
|
func generateAesKey(key []byte, size int) []byte {
|
||||||
genKey := make([]byte, size)
|
genKey := make([]byte, size)
|
||||||
@@ -35,3 +46,139 @@ func pkcs7UnPadding(src []byte) []byte {
|
|||||||
unPadding := int(src[length-1])
|
unPadding := int(src[length-1])
|
||||||
return src[:(length - unPadding)]
|
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
|
||||||
|
}
|
||||||
|
|||||||
+15
-15
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/duke-git/lancet/v2/internal"
|
"github.com/duke-git/lancet/v2/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAesEcbEncrypt(t *testing.T) {
|
func TestAesEcbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -16,11 +16,11 @@ func TestAesEcbEncrypt(t *testing.T) {
|
|||||||
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
|
aesEcbEncrypt := AesEcbEncrypt([]byte(data), []byte(key))
|
||||||
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
|
aesEcbDecrypt := AesEcbDecrypt(aesEcbEncrypt, []byte(key))
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestAesEcbEncrypt")
|
assert := internal.NewAssert(t, "TestAesEcbCrypt")
|
||||||
assert.Equal(data, string(aesEcbDecrypt))
|
assert.Equal(data, string(aesEcbDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAesCbcEncrypt(t *testing.T) {
|
func TestAesCbcCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -29,7 +29,7 @@ func TestAesCbcEncrypt(t *testing.T) {
|
|||||||
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
|
aesCbcEncrypt := AesCbcEncrypt([]byte(data), []byte(key))
|
||||||
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
|
aesCbcDecrypt := AesCbcDecrypt(aesCbcEncrypt, []byte(key))
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestAesCbcEncrypt")
|
assert := internal.NewAssert(t, "TestAesCbcCrypt")
|
||||||
assert.Equal(data, string(aesCbcDecrypt))
|
assert.Equal(data, string(aesCbcDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,14 +39,14 @@ func TestAesCtrCrypt(t *testing.T) {
|
|||||||
data := "hello world"
|
data := "hello world"
|
||||||
key := "abcdefghijklmnop"
|
key := "abcdefghijklmnop"
|
||||||
|
|
||||||
aesCtrCrypt := AesCtrCrypt([]byte(data), []byte(key))
|
aesCtrCrypt := AesCtrEncrypt([]byte(data), []byte(key))
|
||||||
aesCtrDeCrypt := AesCtrCrypt(aesCtrCrypt, []byte(key))
|
aesCtrDeCrypt := AesCtrDecrypt(aesCtrCrypt, []byte(key))
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestAesCtrCrypt")
|
assert := internal.NewAssert(t, "TestAesCtrCrypt")
|
||||||
assert.Equal(data, string(aesCtrDeCrypt))
|
assert.Equal(data, string(aesCtrDeCrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAesCfbEncrypt(t *testing.T) {
|
func TestAesCfbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -55,11 +55,11 @@ func TestAesCfbEncrypt(t *testing.T) {
|
|||||||
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
|
aesCfbEncrypt := AesCfbEncrypt([]byte(data), []byte(key))
|
||||||
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
|
aesCfbDecrypt := AesCfbDecrypt(aesCfbEncrypt, []byte(key))
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestAesCfbEncrypt")
|
assert := internal.NewAssert(t, "TestAesCfbCrypt")
|
||||||
assert.Equal(data, string(aesCfbDecrypt))
|
assert.Equal(data, string(aesCfbDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAesOfbEncrypt(t *testing.T) {
|
func TestAesOfbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -72,7 +72,7 @@ func TestAesOfbEncrypt(t *testing.T) {
|
|||||||
assert.Equal(data, string(aesOfbDecrypt))
|
assert.Equal(data, string(aesOfbDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDesEcbEncrypt(t *testing.T) {
|
func TestDesEcbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -85,7 +85,7 @@ func TestDesEcbEncrypt(t *testing.T) {
|
|||||||
assert.Equal(data, string(desEcbDecrypt))
|
assert.Equal(data, string(desEcbDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDesCbcEncrypt(t *testing.T) {
|
func TestDesCbcCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -104,14 +104,14 @@ func TestDesCtrCrypt(t *testing.T) {
|
|||||||
data := "hello world"
|
data := "hello world"
|
||||||
key := "abcdefgh"
|
key := "abcdefgh"
|
||||||
|
|
||||||
desCtrCrypt := DesCtrCrypt([]byte(data), []byte(key))
|
desCtrCrypt := DesCtrEncrypt([]byte(data), []byte(key))
|
||||||
desCtrDeCrypt := DesCtrCrypt(desCtrCrypt, []byte(key))
|
desCtrDeCrypt := DesCtrDecrypt(desCtrCrypt, []byte(key))
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestDesCtrCrypt")
|
assert := internal.NewAssert(t, "TestDesCtrCrypt")
|
||||||
assert.Equal(data, string(desCtrDeCrypt))
|
assert.Equal(data, string(desCtrDeCrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDesCfbEncrypt(t *testing.T) {
|
func TestDesCfbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
@@ -124,7 +124,7 @@ func TestDesCfbEncrypt(t *testing.T) {
|
|||||||
assert.Equal(data, string(desCfbDecrypt))
|
assert.Equal(data, string(desCfbDecrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDesOfbEncrypt(t *testing.T) {
|
func TestDesOfbCrypt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
data := "hello world"
|
data := "hello world"
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
-----BEGIN rsa private key-----
|
-----BEGIN rsa private key-----
|
||||||
MIIJKQIBAAKCAgEAyA3SmuwPpYGw/JdzJ9Iah+Z09Q27sueuT1DpqFTYKeJmYQxe
|
MIIJKQIBAAKCAgEAw6Q11wDtWHZiyQbv+XzntJbEELkPQ3oVSVCVjTx+ls5I+yR6
|
||||||
I7N8STbmC3dYctsT3Fd+bcfLF2EMscRaae1DAEjli56e41VYvVXPgTSMPWboETDg
|
oq8LxyoUE6SHcBlludkzs02B0rDkkfj6vl0iPCzr3HP1XT1WwFiQJnXKtNNNiLvv
|
||||||
TtPssl0wZHZ23+cKhK9wBgLQ66mzFRM/ro+OLvydO8jZeaRvG5eATrUaB4ywnXp3
|
ewB/aynj/UIBCZZvMTYwK6nc76i4JWcajaiUI4u84Fp7gOn0IgXMhvrwtGHk7HK1
|
||||||
j8r42qTlR05EJWbzwPKkW61vePgOyObRwJrF47WGZG6cPCQMd5zSJPQw5o3xOEuh
|
Zjr0afUJVbodc6pMZfe0QWpnDq7zaeYdkPwz8klWAatLDZTeR5dYli2NiWOBepO1
|
||||||
n0FD/GPtM8f+0dxjXpLkcJwnXyUbuAU2WWweJHaYOA2s2anlG6V8ZTUZvbdsKi24
|
538ZvEK3S/QP2nWJzPJy2PbEHfDaFgr055w/wu0MzAQgkXGV8PubhYboJuQAkuXc
|
||||||
byCzHbKwhI48vKvnPRnJt5wxvg1PsiAYAalWDrIhod+Atdxp4LXzqM5Nyxfp9+BR
|
63IPpmWVL/93WeCNRiL5fvNzUHsbPtT48A9NnnUn4QVuVGeXIxJ1skAFsoh7i+h+
|
||||||
nxJZASrzqd/z3ZpZJ8q9g9A7XPpGgfSB6H9tcjoy5gD+jVJqN26PBqsiW5RyckI3
|
mK/p5rzjPtw56nE20gYXhxf7FzbHYtHn0egoBddUzlOY0yzvCgI5Yy1RdkNe/5vU
|
||||||
TXPAXRMnr2xshZ4pze6mDPyiHHruLUrnvHnVgg62g/MB0p/bipVxd18418yZ2gRl
|
1bpLsdU/WItjR2h6EfqLosQ4iKowPBYQXJOINbpkvIhkhCVbPpMvmhfi/lATiKdo
|
||||||
0PtmqD7EfKwgREcA20zWeQtYkioOUclX30uG3vVSf0j2KyoAqWmES52KqUp9pwNd
|
XgtvuB04wth011c5W/yXA+A3Ob73Sr2+zyxF870tOp3rNTWsYLvCOjf+JdjgM3Az
|
||||||
opWvRtQGAey2b9mpdSgl69rM2RmbzFOIDrfw/1ftPpaUi2CVILviSeYtN2egkeB+
|
NuFxE4NdMLvXDf7CIXSb3X9rj3qM769Lz6XXbRBVbO0HxGyqLTPPmSWV61jQuUTW
|
||||||
ghCPCcIS9VUpnNTYcW8Y+cTnfi9FwSEwn13WpjiYO/K2412uWjNVNvvW3RECAwEA
|
NGnxkovoYLrpgyumIjjIcl0aY1hogmQeA6ZNgJxaUw9r/p8vnThL5UXKlbcCAwEA
|
||||||
AQKCAgA8KMv4gpyB7OpIS1L52pjX3Vm2h5a2Si2E9JQPZtRKZCbdcFau7b6mVbgA
|
AQKCAgA6auEMtyEn5gmvPxwO4iYJvW8EE04kduK+RoqxZpviCeyu2QCpbR1WNhFS
|
||||||
Ireacn8ad8UxRi+wrCKhIqb9y7emUkWVIvIs8vFonnF0LVUu8c8/vC74BYrcaGGx
|
oDE03tCKQvn2EzGtqNZHZBHzH7omP7+ZzyI32iX6Pzej1E5X2jRvnLZwgwCJDjQe
|
||||||
FF7THitvk0JgzF//Plz4LX2tBMaBIr41yER97BDtfzW6dYOKWaiS8s0ZlLQZLBU2
|
nkPTChFfn2+YyhrHkRiTSosaO3koeARI9ZulLbaRJiwhLfML2aNSmxnhUb9DmEBW
|
||||||
yDZ9+OO8VjtOxm0LN61ahO72uJRh0ajEcu7QtxZ4sdQQAM7jxjOP9k54EaxcLcCb
|
UcGTlQlE9UbktZHaR5E1UNoJPdWBBjbhPrWlc4s/00hHZ4i4unaiFFMfscsMdNto
|
||||||
Hb1nZpsLSakgpBTC6+nWANtDMNwvoupLkPE1031uPumVlc5T2uDQHkekQ3UZrS6f
|
mZLWR3yDLu3uLRj4W7+zD0h7lCNR4BiPHrhe1YzRLUAOCe9uFWy6cFqcDy3vIKwA
|
||||||
7hYdtqQnH6YlbPcYa/BXZIdTmJSj9pYKz5Ml1of1KqB6KRxe2vUDtVkTJUwRkZC4
|
bgbEVpG4nJ7lMmp/YJMNGujOPbOOFAhziL4jcbQvcbXZTGr78+BIyploCuitqSP7
|
||||||
G8//taLRuaA1vC5bHaseI0uYlTPxueXSDStj7VcUS27nSRWdGxe24EVrIjsA9CNB
|
lNwM6sL01lBcP3WnpWtGUdggRrIbLlam4KtD/hlXoHWb1YLvnswe9N233FNwyUep
|
||||||
RCVDeU6W4r1eGrvgM8QFNWi1jUMr1rRI7EFHV+24hsfiSWZQwhltzWZ3mn99i9VD
|
5W2Svy+DkVDH7BCl/DD0NoPuDyV6yiM+h8gG79o6UHthDwQZxZ7bi+wwulsNajuB
|
||||||
BmqI/JvX5d8+8/JlPOVG3HuuxNbU2tzHLhgLADfINvi3TpbdgAY8zm5NqumdwXp6
|
uiUBQfUURCshgehUJ6u+a0FxxtVqomXKB8vKbd+St6sxpfPWHiimYF9BvXvPp8/F
|
||||||
JqQ6TXnO/G07fBuZnBP9+Qt5VBQV9rGF5HGpzznbBzSGgBPUNBYqFOyWrdp05J2X
|
KheecohYFmHnB2ecX4Vu/WCUoFVZcuiCFwWabkKL7HlzBUjFuEHdlXSP5+z1CfZM
|
||||||
bpO23G+2RM0ufXbukF3mI45NJUWOeHOGrxWTEI60Uy0Vf/+/MQKCAQEA5YXXfpHo
|
Zov907ESptA4YOp+5uM+9UIijVv482ogmDN+VaGDyAhQc5IjgQKCAQEAzX4SEz59
|
||||||
z72IMQGwEUMw5vkyaoGd2Oi5g+Jjv1q/CKbmCw5sD+mfQTYh41mBpDDACbdSLYDu
|
Ylsz34CiEh3y6KHhUmQVb3pXUevIOKA+hFh3n5pV/4/H8MseVpxqdNLE95hEC8eO
|
||||||
0wh9zw74HFOY9Jm7GO9LoIjZ76xv+RSBXLN8tpH1MiAQgBrepWhhlhmyTtiIDNXG
|
znWutK1GyLJ/KGWryJ7PcFZIfouys1PRRyzSnuIYPwVflDsW7HDua86vtFQiaYM0
|
||||||
crj1Yl76J5hBd4dR+gYlJjB+hWsQvV83RxXIUw6eIM6Ngi7RUNuywI1a9mjSY8H+
|
y9XqjrIVmvMky5NBQIyfnH+UQmxkrCLPbPq0+wr7fy7vh3KxZBZ4Dk2/BkAgbvt3
|
||||||
OET4kqDqJ2YUvE22ftEWPlNMxyfsVaJs8ihLGEhS8Sq3sPfbdGx9qs3nABjvX0Ab
|
qGZIDJfBT1usxgoYe7bchhG8iXt3v1Cn+OoevO7A5LDLTbjMBiYu4TSNa7D/m4pg
|
||||||
MvDdMx7HeLfD2Grn75mClNji0yUdtKNJGvFx33CTdqtOKvELZf9RJmqtBgrfbqQ6
|
i68ROuTbZR+FhVnUrXmYIuodAaEG+H45V+YW+KT9+SA1RPGwEWIszug1CRLkDT7w
|
||||||
qGhoJ23SYD3R6wKCAQEA3yG4vmGB1/bBIV8FwmmH9mKGqd1nWBq4BJAv4dSrkEK1
|
EPWkjo+kD/hc9wKCAQEA87pMGSLSK0tUcPzmLtE6zjj48tNaDXjoBiYksi2oOb1T
|
||||||
xDsuc7ssmivqdBCAnhMx7oBObC2j/uaqeHAR/L2Ik9VdUzVB/50aFuedgN9FEQ/a
|
mB6GEJWaI+UdrdmucsqqS5lcLPizTIVyq3jt0eJV3HV3B1VpYkLKH1HzJNGlqOsm
|
||||||
N3OiQmcYFz5u5On80aSFD4SeuvdDSegFAgSaMYpSKEFziDbESvYmQGe5nWsrxTG7
|
QY8tjJVrCKdyRjsmX6tD3EEt9YH/sgtCN4OHNDMKigbI9HKH1ds5nofsfu+tAmBf
|
||||||
MgLRQym5OJjXX+Am7LAgyOgXD7dOCduWdevfUHHXcMg4KPUju5jCd2EuQYX/CYk2
|
5xBi/ziWZ+wgzOXgkKtUmP09YQ4+mKRYCWtQsWjYn7tLH1IcJ+NLktfOjBa5Z6xP
|
||||||
PkMFime07GL3TQiE2MvhzWsV7qVBqe5T1eBFu2sbpYVkYVmmckrSaKLC8gHDEk1s
|
FIgjCWTpoH0IgVFzzoNtcp4bxZUuI954dhEXRlvNWx3BPV6OEGn3Z8b8crtlq2uB
|
||||||
7WX0K49sxAAJauktikK4viOVvYkmDnaROSQ6TKgR8wKCAQEAtMo2MeZwgaIDMsbn
|
Df9H2EbycZLRouYEjmRIqoLxcaiv4U/JXV/+pqUdQQKCAQEAgivJcW1VaffSLVPD
|
||||||
jtFkJatIgQhT81VtO2sJsuKh8wizzyWvDOGypcUj8FbCfThBK+YQfvM+K1BW+NAL
|
1uwn0tuw60tBFOQP6nIM3GoOEDImXPEcZw6CnnNc3YqqGSTm5t0Kxdd9DCYaLJPT
|
||||||
sxROOO0WqM9kvLDPKbCkCoiSVRi0NYLGppMHLED9+0A7DeZzwxNgJuwEgr0Z3EB/
|
UhFYYZO+NRD84PGXbKo74kNHP+oR5dndeBaETFb+F3sWXS6JuRbO0b3utOzmb8w5
|
||||||
vsx5VNNsSQqqHGi6YBEzgFcNzZpkzreG6sSIRnDmQ1Cqfm1ZpxpuDlHyExSQz76c
|
yXUaqua2IBi0hyN+HHGjeDN46FUMTjbPx+pIi6nI21ksZgsz5da7dPJRA1j/bRK4
|
||||||
cihAUQrNoxrC8Jjgs0bJK7LjKeN/M0NUwc4qHuU9IXmLiwzg1fzOIDL4ualsMyYd
|
vveYw77jcYMNwNP9da3D1mpWbWSJYi9v+65OqwZUH9USP1DWAREXakVHEVUt+fu9
|
||||||
bQMi6sdapio+50vnbvWfbnfnz6c7UQr1vNW2nrB7j/4KR75jR40s7ZPC+IA0+knR
|
SxqjryyPf/CVwhBBnaWOz9zNoHO9u3Zw995CQFFFsk1ZixwndH8F/aoP0v54ZmJx
|
||||||
GinXvQKCAQAfsM5oW7jxT16GutQ6lHp3B+9QWN3AqarRGxlx3Z6wUok2YaFrVn4z
|
kDhJUwKCAQEAiDafHxEx6pZgLAF7V8EBn42BvJxYYN0ot38L1TTlJtfj50lv3cbu
|
||||||
N2GJMn7R9K/2y8pXmvPb1C8KBADKALFRLdCfbMT+5PU7o5G/J/bLQZEgpw+4lqPZ
|
bY84Bhh5gZg4kqW0OUIJWwPd8W1VZI6dM+fGTSf0DMGNGvH+9J3iesRpDgzKBR7g
|
||||||
SUi+wmzh6ZfsaE2TCGNKL+XmL3km6BpbFVCvW/ftHaC5LQIz1XDWBQYtSDEodNiV
|
weZrXErkiGU8ONWIrQQpdkUKjeIJZyf7hOaD2vJDbkbmLe6DQvIKfCCGmKm02jSz
|
||||||
rT+JonBk1W8SmcQMu2AeDk4EWYfSCRksL4OqbYaxsjSMeAT10W4zkIVfum0jBy+v
|
AoOaVUlINzI5xoMJkbuXSlHXDfSXFX+mSacwNeH68GP8saXAvtRYbFOFotDu2+o1
|
||||||
Ey9EhZ04nVxorHjk+BunYu4NRlkhEUlP7O4g1XbKbM4NhnBhptZp1w1Nell/hlNQ
|
E8etQDDYixsyyPMKTGOydMN6CWpF2sGlxH2dqQG2XgALEEnKfwiyqpobd0oryfjk
|
||||||
UyEDSjLpnChY+PzfeocpLrXZg8ez83r5AoIBAQCrufc7Cu36/WoSKoMwd5n9kOQ9
|
uIq4dNUeyMHNRtaFv+Lp4P9pZ7ElrdbrwQKCAQBScEA9ds64/tE6zGM77kGHPOii
|
||||||
W4kzu7ZCv/0PxXj8o3nyNkHD/uZHtGGPowhzt8cZR8JKfmDqrCM28WWyGk79x9bN
|
RaCvaw9FeuC6WyoBPWtIi6clXPrbt/6vVlK2JKtpHfVMOFIQ175s4THQVQczhwgv
|
||||||
s/IVyTc5CHe0/zPJooTgpFA8etMhdc5JIYorSZaAgLiOqzDyGuNi9oyfotbLqiFZ
|
U4rgkoRrdM9fmeaewMUgXJmqWJD3cQSXm5LJg5cQ8OSAm5PV8xdKTr36pAr+9RTS
|
||||||
4uiagMt+hdTP0yEwE1JvzQAwdJvOfMxhiNka6szKb+0QGwBLwCB71OAMXeIfzmBK
|
bYodXZW6BV/sV9HNnWjcx+l3QViJqRCsGFNTHF86e289V4IRIQOPFbhQW+dEsevz
|
||||||
i0PLomu7tlogS5cePt45lp3nAowreSru/DnfZobVlVFLV9rV+xFuLmxC4zB86cto
|
mnitogmSeVUwUJfi0A38Zcmu3GA7NhfGE0XrUs76skYY4Yx+r71rfN99VvnG/OAs
|
||||||
wfZvKgSI/hP6rg0RkcQbBad0ZsVm52NtCWYTB+bISke8wDN552wZVC6lyPat
|
NIpwYPJ/uZB9T1H7Cjlz0BH0P3GNaqbo96uPUhgBGy/PLmHOeDnJ2e1BMAYa
|
||||||
-----END rsa private key-----
|
-----END rsa private key-----
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
-----BEGIN rsa public key-----
|
-----BEGIN rsa public key-----
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyA3SmuwPpYGw/JdzJ9Ia
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw6Q11wDtWHZiyQbv+Xzn
|
||||||
h+Z09Q27sueuT1DpqFTYKeJmYQxeI7N8STbmC3dYctsT3Fd+bcfLF2EMscRaae1D
|
tJbEELkPQ3oVSVCVjTx+ls5I+yR6oq8LxyoUE6SHcBlludkzs02B0rDkkfj6vl0i
|
||||||
AEjli56e41VYvVXPgTSMPWboETDgTtPssl0wZHZ23+cKhK9wBgLQ66mzFRM/ro+O
|
PCzr3HP1XT1WwFiQJnXKtNNNiLvvewB/aynj/UIBCZZvMTYwK6nc76i4JWcajaiU
|
||||||
LvydO8jZeaRvG5eATrUaB4ywnXp3j8r42qTlR05EJWbzwPKkW61vePgOyObRwJrF
|
I4u84Fp7gOn0IgXMhvrwtGHk7HK1Zjr0afUJVbodc6pMZfe0QWpnDq7zaeYdkPwz
|
||||||
47WGZG6cPCQMd5zSJPQw5o3xOEuhn0FD/GPtM8f+0dxjXpLkcJwnXyUbuAU2WWwe
|
8klWAatLDZTeR5dYli2NiWOBepO1538ZvEK3S/QP2nWJzPJy2PbEHfDaFgr055w/
|
||||||
JHaYOA2s2anlG6V8ZTUZvbdsKi24byCzHbKwhI48vKvnPRnJt5wxvg1PsiAYAalW
|
wu0MzAQgkXGV8PubhYboJuQAkuXc63IPpmWVL/93WeCNRiL5fvNzUHsbPtT48A9N
|
||||||
DrIhod+Atdxp4LXzqM5Nyxfp9+BRnxJZASrzqd/z3ZpZJ8q9g9A7XPpGgfSB6H9t
|
nnUn4QVuVGeXIxJ1skAFsoh7i+h+mK/p5rzjPtw56nE20gYXhxf7FzbHYtHn0ego
|
||||||
cjoy5gD+jVJqN26PBqsiW5RyckI3TXPAXRMnr2xshZ4pze6mDPyiHHruLUrnvHnV
|
BddUzlOY0yzvCgI5Yy1RdkNe/5vU1bpLsdU/WItjR2h6EfqLosQ4iKowPBYQXJOI
|
||||||
gg62g/MB0p/bipVxd18418yZ2gRl0PtmqD7EfKwgREcA20zWeQtYkioOUclX30uG
|
NbpkvIhkhCVbPpMvmhfi/lATiKdoXgtvuB04wth011c5W/yXA+A3Ob73Sr2+zyxF
|
||||||
3vVSf0j2KyoAqWmES52KqUp9pwNdopWvRtQGAey2b9mpdSgl69rM2RmbzFOIDrfw
|
870tOp3rNTWsYLvCOjf+JdjgM3AzNuFxE4NdMLvXDf7CIXSb3X9rj3qM769Lz6XX
|
||||||
/1ftPpaUi2CVILviSeYtN2egkeB+ghCPCcIS9VUpnNTYcW8Y+cTnfi9FwSEwn13W
|
bRBVbO0HxGyqLTPPmSWV61jQuUTWNGnxkovoYLrpgyumIjjIcl0aY1hogmQeA6ZN
|
||||||
pjiYO/K2412uWjNVNvvW3RECAwEAAQ==
|
gJxaUw9r/p8vnThL5UXKlbcCAwEAAQ==
|
||||||
-----END rsa public key-----
|
-----END rsa public key-----
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ func TestSinglyLink_InsertAtFirst(t *testing.T) {
|
|||||||
link.InsertAtHead(1)
|
link.InsertAtHead(1)
|
||||||
link.InsertAtHead(2)
|
link.InsertAtHead(2)
|
||||||
link.InsertAtHead(3)
|
link.InsertAtHead(3)
|
||||||
link.Print()
|
|
||||||
|
|
||||||
expected := []int{3, 2, 1}
|
expected := []int{3, 2, 1}
|
||||||
values := link.Values()
|
values := link.Values()
|
||||||
@@ -32,7 +31,6 @@ func TestSinglyLink_InsertAtTail(t *testing.T) {
|
|||||||
link.InsertAtTail(1)
|
link.InsertAtTail(1)
|
||||||
link.InsertAtTail(2)
|
link.InsertAtTail(2)
|
||||||
link.InsertAtTail(3)
|
link.InsertAtTail(3)
|
||||||
link.Print()
|
|
||||||
|
|
||||||
expected := []int{1, 2, 3}
|
expected := []int{1, 2, 3}
|
||||||
values := link.Values()
|
values := link.Values()
|
||||||
@@ -77,7 +75,6 @@ func TestSinglyLink_DeleteAtHead(t *testing.T) {
|
|||||||
link.InsertAtTail(4)
|
link.InsertAtTail(4)
|
||||||
|
|
||||||
link.DeleteAtHead()
|
link.DeleteAtHead()
|
||||||
link.Print()
|
|
||||||
|
|
||||||
expected := []int{2, 3, 4}
|
expected := []int{2, 3, 4}
|
||||||
values := link.Values()
|
values := link.Values()
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (q *ArrayQueue[T]) IsFull() bool {
|
|||||||
|
|
||||||
// Front return front value of queue
|
// Front return front value of queue
|
||||||
func (q *ArrayQueue[T]) Front() T {
|
func (q *ArrayQueue[T]) Front() T {
|
||||||
return q.data[0]
|
return q.data[q.head]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back return back value of queue
|
// Back return back value of queue
|
||||||
|
|||||||
+6
-14
@@ -280,13 +280,9 @@ func EndOfDay(t time.Time) time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BeginOfWeek return beginning week, default week begin from Sunday.
|
// BeginOfWeek return beginning week, default week begin from Sunday.
|
||||||
// Play: https://go.dev/play/p/ynjoJPz7VNV
|
// Play: https://go.dev/play/p/DCHdcL6gnfV
|
||||||
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time {
|
func BeginOfWeek(t time.Time, beginFrom time.Weekday) time.Time {
|
||||||
var beginFromWeekday = time.Sunday
|
y, m, d := t.AddDate(0, 0, int(beginFrom-t.Weekday())).Date()
|
||||||
if len(beginFrom) > 0 {
|
|
||||||
beginFromWeekday = beginFrom[0]
|
|
||||||
}
|
|
||||||
y, m, d := t.AddDate(0, 0, int(beginFromWeekday-t.Weekday())).Date()
|
|
||||||
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
beginOfWeek := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||||
if beginOfWeek.After(t) {
|
if beginOfWeek.After(t) {
|
||||||
return beginOfWeek.AddDate(0, 0, -7)
|
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.
|
// EndOfWeek return end week time, default week end with Saturday.
|
||||||
// Play: https://go.dev/play/p/i08qKXD9flf
|
// Play: https://go.dev/play/p/mGSA162YgX9
|
||||||
func EndOfWeek(t time.Time, endWith ...time.Weekday) time.Time {
|
func EndOfWeek(t time.Time, endWith time.Weekday) time.Time {
|
||||||
var endWithWeekday = time.Saturday
|
y, m, d := t.AddDate(0, 0, int(endWith-t.Weekday())).Date()
|
||||||
if len(endWith) > 0 {
|
|
||||||
endWithWeekday = endWith[0]
|
|
||||||
}
|
|
||||||
y, m, d := t.AddDate(0, 0, int(endWithWeekday-t.Weekday())).Date()
|
|
||||||
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
var endWithWeek = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())
|
||||||
if endWithWeek.Before(t) {
|
if endWithWeek.Before(t) {
|
||||||
endWithWeek = endWithWeek.AddDate(0, 0, 7)
|
endWithWeek = endWithWeek.AddDate(0, 0, 7)
|
||||||
|
|||||||
@@ -299,23 +299,23 @@ func ExampleEndOfDay() {
|
|||||||
func ExampleBeginOfWeek() {
|
func ExampleBeginOfWeek() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||||
|
|
||||||
result := BeginOfWeek(input)
|
result := BeginOfWeek(input, time.Monday)
|
||||||
|
|
||||||
fmt.Println(result)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// 2023-01-08 00:00:00 +0000 UTC
|
// 2023-01-02 00:00:00 +0000 UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleEndOfWeek() {
|
func ExampleEndOfWeek() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
||||||
|
|
||||||
result := EndOfWeek(input)
|
result := EndOfWeek(input, time.Sunday)
|
||||||
|
|
||||||
fmt.Println(result)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleBeginOfMonth() {
|
func ExampleBeginOfMonth() {
|
||||||
|
|||||||
@@ -611,9 +611,9 @@ func TestBeginOfWeek(t *testing.T) {
|
|||||||
|
|
||||||
assert := internal.NewAssert(t, "TestBeginOfWeek")
|
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)
|
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||||
actual := BeginOfWeek(td)
|
actual := BeginOfWeek(td, time.Monday)
|
||||||
|
|
||||||
assert.Equal(expected, actual)
|
assert.Equal(expected, actual)
|
||||||
}
|
}
|
||||||
@@ -623,9 +623,9 @@ func TestEndOfWeek(t *testing.T) {
|
|||||||
|
|
||||||
assert := internal.NewAssert(t, "TestEndOfWeek")
|
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)
|
td := time.Date(2022, 2, 15, 15, 48, 40, 112, time.Local)
|
||||||
actual := EndOfWeek(td)
|
actual := EndOfWeek(td, time.Sunday)
|
||||||
|
|
||||||
assert.Equal(expected, actual)
|
assert.Equal(expected, actual)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/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>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -35,6 +36,17 @@ import (
|
|||||||
- [Take](#Take)
|
- [Take](#Take)
|
||||||
- [Tee](#Tee)
|
- [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>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
@@ -452,3 +464,389 @@ func main() {
|
|||||||
// 1
|
// 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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -1092,7 +1092,7 @@ func main() {
|
|||||||
|
|
||||||
|
|
||||||
### 4. PriorityQueue
|
### 4. PriorityQueue
|
||||||
切片实现的额优先级队列。
|
切片实现的优先级队列。
|
||||||
|
|
||||||
### <span id="NewPriorityQueue">NewPriorityQueue</span>
|
### <span id="NewPriorityQueue">NewPriorityQueue</span>
|
||||||
<p>返回一个具有特定容量的PriorityQueue指针,参数 `comarator` 用于比较队列中T类型的值。</p>
|
<p>返回一个具有特定容量的PriorityQueue指针,参数 `comarator` 用于比较队列中T类型的值。</p>
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ func main() {
|
|||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
```go
|
```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>
|
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ynjoJPz7VNV)</span></b>
|
||||||
@@ -562,12 +562,12 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
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)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// 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)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// 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>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
```go
|
```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
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -754,12 +754,12 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
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)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ import (
|
|||||||
- [GetOrSet](#GetOrSet)
|
- [GetOrSet](#GetOrSet)
|
||||||
- [SortByKey](#SortByKey)
|
- [SortByKey](#SortByKey)
|
||||||
- [GetOrDefault](#GetOrDefault)
|
- [GetOrDefault](#GetOrDefault)
|
||||||
|
- [FindValuesBy](#FindValuesBy)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -2308,3 +2308,42 @@ func main() {
|
|||||||
// default
|
// 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;">[运行](todo)</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]
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -466,14 +466,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### <span id="T运行cRound">T运行cRound</span>
|
### <span id="TruncRound">TruncRound</span>
|
||||||
|
|
||||||
<p>截短n位小数(不进行四舍五入)</p>
|
<p>截短n位小数(不进行四舍五入)</p>
|
||||||
|
|
||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
```go
|
```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>
|
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/aumarSHIGzP)</span></b>
|
||||||
@@ -487,9 +487,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
result1 := mathutil.T运行cRound(0.124, 2)
|
result1 := mathutil.TruncRound(0.124, 2)
|
||||||
result2 := mathutil.T运行cRound(0.125, 2)
|
result2 := mathutil.TruncRound(0.125, 2)
|
||||||
result3 := mathutil.T运行cRound(0.125, 3)
|
result3 := mathutil.TruncRound(0.125, 3)
|
||||||
|
|
||||||
fmt.Println(result1)
|
fmt.Println(result1)
|
||||||
fmt.Println(result2)
|
fmt.Println(result2)
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import (
|
|||||||
- [UploadFile](#UploadFile)
|
- [UploadFile](#UploadFile)
|
||||||
- [IsPingConnected](#IsPingConnected)
|
- [IsPingConnected](#IsPingConnected)
|
||||||
- [IsTelnetConnected](#IsTelnetConnected)
|
- [IsTelnetConnected](#IsTelnetConnected)
|
||||||
- [IsTelnetConnected](#IsTelnetConnected)
|
|
||||||
- [BuildUrl](#BuildUrl)
|
- [BuildUrl](#BuildUrl)
|
||||||
- [AddQueryParams](#AddQueryParams)
|
- [AddQueryParams](#AddQueryParams)
|
||||||
|
|
||||||
|
|||||||
@@ -824,6 +824,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/todo)</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>
|
### <span id="IsJSON">IsJSON</span>
|
||||||
|
|
||||||
<p>验证字符串是否是有效json。</p>
|
<p>验证字符串是否是有效json。</p>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Concurrency
|
# Concurrency
|
||||||
|
|
||||||
Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel.
|
Package concurrency contain some functions to support concurrent programming. eg, goroutine, channel.
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
@@ -6,10 +7,12 @@ Package concurrency contain some functions to support concurrent programming. eg
|
|||||||
## Source:
|
## 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>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
## Usage:
|
## Usage:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"github.com/duke-git/lancet/v2/concurrency"
|
"github.com/duke-git/lancet/v2/concurrency"
|
||||||
@@ -19,7 +22,9 @@ import (
|
|||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
### Channel
|
### Channel
|
||||||
|
|
||||||
- [NewChannel](#NewChannel)
|
- [NewChannel](#NewChannel)
|
||||||
- [Bridge](#Bridge)
|
- [Bridge](#Bridge)
|
||||||
- [FanIn](#FanIn)
|
- [FanIn](#FanIn)
|
||||||
@@ -31,12 +36,25 @@ import (
|
|||||||
- [Take](#Take)
|
- [Take](#Take)
|
||||||
- [Tee](#Tee)
|
- [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>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
## Channel
|
## Channel
|
||||||
|
|
||||||
### <span id="NewChannel">NewChannel</span>
|
### <span id="NewChannel">NewChannel</span>
|
||||||
|
|
||||||
<p>Create a Channel pointer instance.</p>
|
<p>Create a Channel pointer instance.</p>
|
||||||
|
|
||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
@@ -45,6 +63,7 @@ import (
|
|||||||
type Channel[T any] struct
|
type Channel[T any] struct
|
||||||
func NewChannel[T any]() *Channel[T]
|
func NewChannel[T any]() *Channel[T]
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -69,6 +88,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Bridge(ctx context.Context, chanStream <-chan <-chan T) <-chan T
|
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>
|
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qmWSy1NVF-Y)</span></b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -121,6 +141,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) FanIn(ctx context.Context, channels ...<-chan T) <-chan T
|
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>
|
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/2VYFMexEvTm)</span></b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -160,6 +181,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T
|
func (c *Channel[T]) Repeat(ctx context.Context, values ...T) <-chan T
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -199,6 +221,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Generate(ctx context.Context, values ...T) <-chan T
|
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>
|
<b>Example: <span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7aB4KyMMp9A)</span></b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -237,6 +260,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T
|
func (c *Channel[T]) RepeatFn(ctx context.Context, fn func() T) <-chan T
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -279,6 +303,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T
|
func (c *Channel[T]) Or(channels ...<-chan T) <-chan T
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -322,6 +347,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T
|
func (c *Channel[T]) OrDone(ctx context.Context, channel <-chan T) <-chan T
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -360,6 +386,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T
|
func (c *Channel[T]) Take(ctx context.Context, valueStream <-chan T, number int) <-chan T
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -406,6 +433,7 @@ func main() {
|
|||||||
```go
|
```go
|
||||||
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
|
func (c *Channel[T]) Tee(ctx context.Context, in <-chan T) (<-chan T, <-chan T)
|
||||||
```
|
```
|
||||||
|
|
||||||
<b>Example:</b>
|
<b>Example:</b>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -438,3 +466,389 @@ func main() {
|
|||||||
// 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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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/todo)</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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ func main() {
|
|||||||
func BeginOfWeek(t time.Time, beginFrom ...time.Weekday) time.Time
|
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
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -564,12 +564,13 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
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)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// 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
|
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
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -756,12 +757,12 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
input := time.Date(2023, 1, 8, 18, 50, 10, 100, time.UTC)
|
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)
|
fmt.Println(result)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// 2023-01-14 23:59:59.999999999 +0000 UTC
|
// 2023-01-08 23:59:59.999999999 +0000 UTC
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ import (
|
|||||||
- [GetOrSet](#GetOrSet)
|
- [GetOrSet](#GetOrSet)
|
||||||
- [SortByKey](#SortByKey)
|
- [SortByKey](#SortByKey)
|
||||||
- [GetOrDefault](#GetOrDefault)
|
- [GetOrDefault](#GetOrDefault)
|
||||||
|
- [FindValuesBy](#FindValuesBy)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -2288,7 +2289,7 @@ func main() {
|
|||||||
|
|
||||||
### <span id="GetOrDefault">GetOrDefault</span>
|
### <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>
|
<b>Signature:</b>
|
||||||
|
|
||||||
@@ -2296,7 +2297,7 @@ func main() {
|
|||||||
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
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -2325,3 +2326,42 @@ func main() {
|
|||||||
// default
|
// 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](todo)</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]
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -826,6 +826,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/todo)</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>
|
### <span id="IsJSON">IsJSON</span>
|
||||||
|
|
||||||
<p>Check if the string is valid JSON.</p>
|
<p>Check if the string is valid JSON.</p>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eventbus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -224,6 +225,7 @@ func ExampleEventBus_GetEvents() {
|
|||||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||||
|
|
||||||
events := eb.GetEvents()
|
events := eb.GetEvents()
|
||||||
|
sort.Strings(events)
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
fmt.Println(event)
|
fmt.Println(event)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package eventbus
|
package eventbus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -213,7 +214,8 @@ func TestEventBus_GetEvents(t *testing.T) {
|
|||||||
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
eb.Subscribe("event2", func(eventData int) {}, false, 0, nil)
|
||||||
|
|
||||||
events := eb.GetEvents()
|
events := eb.GetEvents()
|
||||||
|
sort.Strings(events)
|
||||||
|
|
||||||
assert.Equal(2, len(events))
|
assert.Equal(2, len(events))
|
||||||
assert.EqualValues([]string{"event1", "event2"}, events)
|
assert.Equal([]string{"event1", "event2"}, events)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -666,3 +666,17 @@ func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultValue V) V {
|
|||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindValuesBy returns a slice of values from the map that satisfy the given predicate function.
|
||||||
|
// Play: todo
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -829,3 +829,25 @@ func ExampleOrderedMap_UnmarshalJSON() {
|
|||||||
|
|
||||||
// fmt.Println(om.Elements())
|
// 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]
|
||||||
|
}
|
||||||
|
|||||||
@@ -888,3 +888,41 @@ func TestGetOrDefault(t *testing.T) {
|
|||||||
result2 := GetOrDefault(m1, 5, "123")
|
result2 := GetOrDefault(m1, 5, "123")
|
||||||
assert.Equal("123", result2)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+14
-15
@@ -3,7 +3,6 @@ package netutil
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -27,8 +26,8 @@ func TestHttpGet(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpPost(t *testing.T) {
|
func TestHttpPost(t *testing.T) {
|
||||||
@@ -49,8 +48,8 @@ func TestHttpPost(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpPostFormData(t *testing.T) {
|
func TestHttpPostFormData(t *testing.T) {
|
||||||
@@ -69,8 +68,8 @@ func TestHttpPostFormData(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpPut(t *testing.T) {
|
func TestHttpPut(t *testing.T) {
|
||||||
@@ -92,8 +91,8 @@ func TestHttpPut(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpPatch(t *testing.T) {
|
func TestHttpPatch(t *testing.T) {
|
||||||
@@ -115,8 +114,8 @@ func TestHttpPatch(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpDelete(t *testing.T) {
|
func TestHttpDelete(t *testing.T) {
|
||||||
@@ -127,8 +126,8 @@ func TestHttpDelete(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertMapToQueryString(t *testing.T) {
|
func TestConvertMapToQueryString(t *testing.T) {
|
||||||
@@ -229,8 +228,8 @@ func TestHttpClent_Post(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
defer resp.Body.Close()
|
||||||
t.Log("response: ", resp.StatusCode, string(body))
|
t.Log("response status:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStructToUrlValues(t *testing.T) {
|
func TestStructToUrlValues(t *testing.T) {
|
||||||
|
|||||||
+131
-84
@@ -59,16 +59,18 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
elementMap := make(map[T]struct{}, len(slice))
|
elementCount := make(map[T]int, len(slice))
|
||||||
for _, item := range slice {
|
for _, item := range slice {
|
||||||
elementMap[item] = struct{}{}
|
elementCount[item]++
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range subSlice {
|
for _, item := range subSlice {
|
||||||
if _, ok := elementMap[item]; !ok {
|
if elementCount[item] == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
elementCount[item]--
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +83,18 @@ func Chunk[T any](slice []T, size int) [][]T {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentChunk := []T{}
|
||||||
|
|
||||||
for _, item := range slice {
|
for _, item := range slice {
|
||||||
l := len(result)
|
if len(currentChunk) == size {
|
||||||
if l == 0 || len(result[l-1]) == size {
|
result = append(result, currentChunk)
|
||||||
result = append(result, []T{})
|
currentChunk = []T{}
|
||||||
l++
|
}
|
||||||
|
currentChunk = append(currentChunk, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
result[l-1] = append(result[l-1], item)
|
if len(currentChunk) > 0 {
|
||||||
|
result = append(result, currentChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -106,6 +112,7 @@ func Compact[T comparable](slice []T) []T {
|
|||||||
result = append(result, v)
|
result = append(result, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[:len(result):len(result)]
|
return result[:len(result):len(result)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +140,17 @@ func Concat[T any](slices ...[]T) []T {
|
|||||||
func Difference[T comparable](slice, comparedSlice []T) []T {
|
func Difference[T comparable](slice, comparedSlice []T) []T {
|
||||||
result := []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 {
|
for _, v := range slice {
|
||||||
if !Contain(comparedSlice, v) {
|
if _, found := comparedMap[v]; !found {
|
||||||
result = append(result, v)
|
result = append(result, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,13 +163,17 @@ func Difference[T comparable](slice, comparedSlice []T) []T {
|
|||||||
// like lodash.js differenceBy: https://lodash.com/docs/4.17.15#differenceBy.
|
// like lodash.js differenceBy: https://lodash.com/docs/4.17.15#differenceBy.
|
||||||
// Play: https://go.dev/play/p/DiivgwM5OnC
|
// Play: https://go.dev/play/p/DiivgwM5OnC
|
||||||
func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(index int, item T) T) []T {
|
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)
|
result := make([]T, 0)
|
||||||
for i, v := range orginSliceAfterMap {
|
|
||||||
if !Contain(comparedSliceAfterMap, v) {
|
comparedMap := make(map[T]struct{}, len(comparedSlice))
|
||||||
result = append(result, slice[i])
|
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 +185,32 @@ func DifferenceBy[T comparable](slice []T, comparedSlice []T, iteratee func(inde
|
|||||||
// The comparator is invoked with two arguments: (arrVal, othVal).
|
// The comparator is invoked with two arguments: (arrVal, othVal).
|
||||||
// Play: https://go.dev/play/p/v2U2deugKuV
|
// Play: https://go.dev/play/p/v2U2deugKuV
|
||||||
func DifferenceWith[T any](slice []T, comparedSlice []T, comparator func(item1, item2 T) bool) []T {
|
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 {
|
getIndex := func(arr []T, item T, comparison func(v1, v2 T) bool) int {
|
||||||
index := -1
|
|
||||||
for i, v := range arr {
|
for i, v := range arr {
|
||||||
if comparison(item, v) {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return index
|
if !found {
|
||||||
}
|
result = append(result, v)
|
||||||
|
|
||||||
for i, v := range slice {
|
|
||||||
index := getIndex(comparedSlice, v, comparator)
|
|
||||||
if index == -1 {
|
|
||||||
result = append(result, slice[i])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,19 +452,20 @@ func FindLastBy[T any](slice []T, predicate func(index int, item T) bool) (v T,
|
|||||||
// Flatten flattens slice with one level.
|
// Flatten flattens slice with one level.
|
||||||
// Play: https://go.dev/play/p/hYa3cBEevtm
|
// Play: https://go.dev/play/p/hYa3cBEevtm
|
||||||
func Flatten(slice any) any {
|
func Flatten(slice any) any {
|
||||||
sv := sliceValue(slice)
|
sv := reflect.ValueOf(slice)
|
||||||
|
if sv.Kind() != reflect.Slice {
|
||||||
var result reflect.Value
|
panic("Flatten: input must be a slice")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++ {
|
for i := 0; i < sv.Len(); i++ {
|
||||||
item := reflect.ValueOf(sv.Index(i).Interface())
|
item := sv.Index(i)
|
||||||
if item.Kind() == reflect.Slice {
|
if item.Kind() == reflect.Slice {
|
||||||
for j := 0; j < item.Len(); j++ {
|
for j := 0; j < item.Len(); j++ {
|
||||||
result = reflect.Append(result, item.Index(j))
|
result = reflect.Append(result, item.Index(j))
|
||||||
@@ -607,7 +637,7 @@ func Repeat[T any](item T, n int) []T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InterfaceSlice convert param to slice of interface.
|
// 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-
|
// Play: https://go.dev/play/p/FdQXF0Vvqs-
|
||||||
func InterfaceSlice(slice any) []any {
|
func InterfaceSlice(slice any) []any {
|
||||||
sv := sliceValue(slice)
|
sv := sliceValue(slice)
|
||||||
@@ -624,7 +654,7 @@ func InterfaceSlice(slice any) []any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StringSlice convert param to slice of string.
|
// 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
|
// Play: https://go.dev/play/p/W0TZDWCPFcI
|
||||||
func StringSlice(slice any) []string {
|
func StringSlice(slice any) []string {
|
||||||
v := sliceValue(slice)
|
v := sliceValue(slice)
|
||||||
@@ -642,7 +672,7 @@ func StringSlice(slice any) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IntSlice convert param to slice of int.
|
// 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
|
// Play: https://go.dev/play/p/UQDj-on9TGN
|
||||||
func IntSlice(slice any) []int {
|
func IntSlice(slice any) []int {
|
||||||
sv := sliceValue(slice)
|
sv := sliceValue(slice)
|
||||||
@@ -662,15 +692,22 @@ func IntSlice(slice any) []int {
|
|||||||
// DeleteAt delete the element of slice at index.
|
// DeleteAt delete the element of slice at index.
|
||||||
// Play: https://go.dev/play/p/800B1dPBYyd
|
// Play: https://go.dev/play/p/800B1dPBYyd
|
||||||
func DeleteAt[T any](slice []T, index int) []T {
|
func DeleteAt[T any](slice []T, index int) []T {
|
||||||
if index >= len(slice) {
|
if index < 0 || index >= len(slice) {
|
||||||
index = len(slice) - 1
|
return slice[:len(slice)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]T, len(slice)-1)
|
result := append([]T(nil), slice...)
|
||||||
copy(result, slice[:index])
|
copy(result[index:], result[index+1:])
|
||||||
copy(result[index:], slice[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).
|
// DeleteRange delete the element of slice from start index to end index(exclude).
|
||||||
@@ -766,46 +803,54 @@ func InsertAt[T any](slice []T, index int, value any) []T {
|
|||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := value.(T); ok {
|
switch v := value.(type) {
|
||||||
slice = append(slice[:index], append([]T{v}, slice[index:]...)...)
|
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:
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// UpdateAt update the slice element at index.
|
||||||
// Play: https://go.dev/play/p/f3mh2KloWVm
|
// Play: https://go.dev/play/p/f3mh2KloWVm
|
||||||
func UpdateAt[T any](slice []T, index int, value T) []T {
|
func UpdateAt[T any](slice []T, index int, value T) []T {
|
||||||
size := len(slice)
|
if index < 0 || index >= len(slice) {
|
||||||
|
|
||||||
if index < 0 || index >= size {
|
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
slice = append(slice[:index], append([]T{value}, slice[index+1:]...)...)
|
|
||||||
|
|
||||||
return slice
|
result := make([]T, len(slice))
|
||||||
|
copy(result, slice)
|
||||||
|
|
||||||
|
result[index] = value
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unique remove duplicate elements in slice.
|
// Unique remove duplicate elements in slice.
|
||||||
// Play: https://go.dev/play/p/AXw0R3ZTE6a
|
// Play: https://go.dev/play/p/AXw0R3ZTE6a
|
||||||
func Unique[T comparable](slice []T) []T {
|
func Unique[T comparable](slice []T) []T {
|
||||||
result := make([]T, 0, len(slice))
|
if len(slice) == 0 {
|
||||||
seen := make(map[T]struct{}, len(slice))
|
return slice
|
||||||
|
|
||||||
for i := range slice {
|
|
||||||
if _, ok := seen[slice[i]]; ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seen[slice[i]] = struct{}{}
|
seen := make(map[T]struct{}, len(slice))
|
||||||
|
result := slice[:0]
|
||||||
|
|
||||||
result = append(result, slice[i])
|
for _, item := range slice {
|
||||||
|
if _, exists := seen[item]; !exists {
|
||||||
|
seen[item] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -815,18 +860,19 @@ func Unique[T comparable](slice []T) []T {
|
|||||||
// The function maintains the order of the elements.
|
// The function maintains the order of the elements.
|
||||||
// Play: https://go.dev/play/p/GY7JE4yikrl
|
// Play: https://go.dev/play/p/GY7JE4yikrl
|
||||||
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
||||||
result := make([]T, 0, len(slice))
|
if len(slice) == 0 {
|
||||||
seen := make(map[U]struct{}, len(slice))
|
return slice
|
||||||
|
|
||||||
for i := range slice {
|
|
||||||
key := iteratee(slice[i])
|
|
||||||
if _, ok := seen[key]; ok {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seen[key] = struct{}{}
|
seen := make(map[U]struct{}, len(slice))
|
||||||
|
result := slice[:0]
|
||||||
|
|
||||||
result = append(result, slice[i])
|
for _, item := range slice {
|
||||||
|
key := iteratee(item)
|
||||||
|
if _, exists := seen[key]; !exists {
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -836,19 +882,20 @@ func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T {
|
|||||||
// The function maintains the order of the elements.
|
// The function maintains the order of the elements.
|
||||||
// Play: https://go.dev/play/p/rwSacr-ZHsR
|
// Play: https://go.dev/play/p/rwSacr-ZHsR
|
||||||
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T {
|
func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T {
|
||||||
result := make([]T, 0, len(slice))
|
if len(slice) == 0 {
|
||||||
seen := make([]T, 0, len(slice))
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]T, 0, len(slice))
|
||||||
for _, item := range slice {
|
for _, item := range slice {
|
||||||
duplicate := false
|
isDuplicate := false
|
||||||
for _, seenItem := range seen {
|
for _, existing := range result {
|
||||||
if comparator(item, seenItem) {
|
if comparator(item, existing) {
|
||||||
duplicate = true
|
isDuplicate = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !duplicate {
|
if !isDuplicate {
|
||||||
seen = append(seen, item)
|
|
||||||
result = append(result, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,3 +420,12 @@ func (s Stream[T]) LastIndexOf(target T, equal func(a, b T) bool) int {
|
|||||||
func (s Stream[T]) ToSlice() []T {
|
func (s Stream[T]) ToSlice() []T {
|
||||||
return s.source
|
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
|
// -1
|
||||||
// 3
|
// 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(-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 }))
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
+46
-40
@@ -37,16 +37,17 @@ func CamelCase(s string) string {
|
|||||||
// Capitalize converts the first character of a string to upper case and the remaining to lower case.
|
// Capitalize converts the first character of a string to upper case and the remaining to lower case.
|
||||||
// Play: https://go.dev/play/p/2OAjgbmAqHZ
|
// Play: https://go.dev/play/p/2OAjgbmAqHZ
|
||||||
func Capitalize(s string) string {
|
func Capitalize(s string) string {
|
||||||
result := make([]rune, len(s))
|
if s == "" {
|
||||||
for i, v := range s {
|
return s
|
||||||
if i == 0 {
|
|
||||||
result[i] = unicode.ToUpper(v)
|
|
||||||
} else {
|
|
||||||
result[i] = unicode.ToLower(v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(result)
|
runes := []rune(s)
|
||||||
|
runes[0] = unicode.ToUpper(runes[0])
|
||||||
|
for i := 1; i < len(runes); i++ {
|
||||||
|
runes[i] = unicode.ToLower(runes[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpperFirst converts the first character of string to upper case.
|
// UpperFirst converts the first character of string to upper case.
|
||||||
@@ -127,51 +128,59 @@ func UpperSnakeCase(s string) string {
|
|||||||
// Before returns the substring of the source string up to the first occurrence of the specified string.
|
// Before returns the substring of the source string up to the first occurrence of the specified string.
|
||||||
// Play: https://go.dev/play/p/JAWTZDS4F5w
|
// Play: https://go.dev/play/p/JAWTZDS4F5w
|
||||||
func Before(s, char string) string {
|
func Before(s, char string) string {
|
||||||
i := strings.Index(s, char)
|
if char == "" {
|
||||||
|
|
||||||
if s == "" || char == "" || i == -1 {
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
return s[0:i]
|
if i := strings.Index(s, char); i >= 0 {
|
||||||
|
return s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeLast returns the substring of the source string up to the last occurrence of the specified string.
|
// BeforeLast returns the substring of the source string up to the last occurrence of the specified string.
|
||||||
// Play: https://go.dev/play/p/pJfXXAoG_Te
|
// Play: https://go.dev/play/p/pJfXXAoG_Te
|
||||||
func BeforeLast(s, char string) string {
|
func BeforeLast(s, char string) string {
|
||||||
i := strings.LastIndex(s, char)
|
if char == "" {
|
||||||
|
|
||||||
if s == "" || char == "" || i == -1 {
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
return s[0:i]
|
if i := strings.LastIndex(s, char); i >= 0 {
|
||||||
|
return s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// After returns the substring after the first occurrence of a specified string in the source string.
|
// After returns the substring after the first occurrence of a specified string in the source string.
|
||||||
// Play: https://go.dev/play/p/RbCOQqCDA7m
|
// Play: https://go.dev/play/p/RbCOQqCDA7m
|
||||||
func After(s, char string) string {
|
func After(s, char string) string {
|
||||||
i := strings.Index(s, char)
|
if char == "" {
|
||||||
|
|
||||||
if s == "" || char == "" || i == -1 {
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i := strings.Index(s, char); i >= 0 {
|
||||||
return s[i+len(char):]
|
return s[i+len(char):]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// AfterLast returns the substring after the last occurrence of a specified string in the source string.
|
// AfterLast returns the substring after the last occurrence of a specified string in the source string.
|
||||||
// Play: https://go.dev/play/p/1TegARrb8Yn
|
// Play: https://go.dev/play/p/1TegARrb8Yn
|
||||||
func AfterLast(s, char string) string {
|
func AfterLast(s, char string) string {
|
||||||
i := strings.LastIndex(s, char)
|
if char == "" {
|
||||||
|
|
||||||
if s == "" || char == "" || i == -1 {
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i := strings.LastIndex(s, char); i >= 0 {
|
||||||
return s[i+len(char):]
|
return s[i+len(char):]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// IsString check if the value data type is string or not.
|
// IsString check if the value data type is string or not.
|
||||||
// Play: https://go.dev/play/p/IOgq7oF9ERm
|
// Play: https://go.dev/play/p/IOgq7oF9ERm
|
||||||
func IsString(v any) bool {
|
func IsString(v any) bool {
|
||||||
@@ -213,22 +222,17 @@ func Wrap(str string, wrapWith string) string {
|
|||||||
// Unwrap a given string from anther string. will change source string.
|
// Unwrap a given string from anther string. will change source string.
|
||||||
// Play: https://go.dev/play/p/Ec2q4BzCpG-
|
// Play: https://go.dev/play/p/Ec2q4BzCpG-
|
||||||
func Unwrap(str string, wrapToken string) string {
|
func Unwrap(str string, wrapToken string) string {
|
||||||
if str == "" || wrapToken == "" {
|
if wrapToken == "" || !strings.HasPrefix(str, wrapToken) || !strings.HasSuffix(str, wrapToken) {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
firstIndex := strings.Index(str, wrapToken)
|
if len(str) < 2*len(wrapToken) {
|
||||||
lastIndex := strings.LastIndex(str, wrapToken)
|
|
||||||
|
|
||||||
if firstIndex == 0 && lastIndex > 0 && lastIndex <= len(str)-1 {
|
|
||||||
if len(wrapToken) <= lastIndex {
|
|
||||||
str = str[len(wrapToken):lastIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return str[len(wrapToken) : len(str)-len(wrapToken)]
|
||||||
|
}
|
||||||
|
|
||||||
// SplitEx split a given string which can control the result slice contains empty string or not.
|
// SplitEx split a given string which can control the result slice contains empty string or not.
|
||||||
// Play: https://go.dev/play/p/Us-ySSbWh-3
|
// Play: https://go.dev/play/p/Us-ySSbWh-3
|
||||||
func SplitEx(s, sep string, removeEmptyString bool) []string {
|
func SplitEx(s, sep string, removeEmptyString bool) []string {
|
||||||
@@ -286,22 +290,21 @@ func Substring(s string, offset int, length uint) string {
|
|||||||
size := len(rs)
|
size := len(rs)
|
||||||
|
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
offset = size + offset
|
offset += size
|
||||||
|
}
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
offset = 0
|
offset = 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if offset > size {
|
if offset > size {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if length > uint(size)-uint(offset) {
|
end := offset + int(length)
|
||||||
length = uint(size - offset)
|
if end > size {
|
||||||
|
end = size
|
||||||
}
|
}
|
||||||
|
|
||||||
str := string(rs[offset : offset+int(length)])
|
return strings.ReplaceAll(string(rs[offset:end]), "\x00", "")
|
||||||
|
|
||||||
return strings.Replace(str, "\x00", "", -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitWords splits a string into words, word only contains alphabetic characters.
|
// SplitWords splits a string into words, word only contains alphabetic characters.
|
||||||
@@ -393,7 +396,10 @@ func RemoveNonPrintable(str string) string {
|
|||||||
// StringToBytes converts a string to byte slice without a memory allocation.
|
// StringToBytes converts a string to byte slice without a memory allocation.
|
||||||
// Play: https://go.dev/play/p/7OyFBrf9AxA
|
// Play: https://go.dev/play/p/7OyFBrf9AxA
|
||||||
func StringToBytes(str string) (b []byte) {
|
func StringToBytes(str string) (b []byte) {
|
||||||
return *(*[]byte)(unsafe.Pointer(&str))
|
return *(*[]byte)(unsafe.Pointer(&struct {
|
||||||
|
string
|
||||||
|
Cap int
|
||||||
|
}{str, len(str)}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BytesToString converts a byte slice to string without a memory allocation.
|
// BytesToString converts a byte slice to string without a memory allocation.
|
||||||
|
|||||||
+15
-18
@@ -1,6 +1,7 @@
|
|||||||
package strutil
|
package strutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,31 +112,27 @@ func padAtPosition(str string, length int, padStr string, position int) string {
|
|||||||
padStr = " "
|
padStr = " "
|
||||||
}
|
}
|
||||||
|
|
||||||
length = length - len(str)
|
totalPad := length - len(str)
|
||||||
startPadLen := 0
|
startPad := 0
|
||||||
|
|
||||||
if position == 0 {
|
if position == 0 {
|
||||||
startPadLen = length / 2
|
startPad = totalPad / 2
|
||||||
} else if position == 1 {
|
} else if position == 1 {
|
||||||
startPadLen = length
|
startPad = totalPad
|
||||||
|
} else if position == 2 {
|
||||||
|
startPad = 0
|
||||||
}
|
}
|
||||||
endPadLen := length - startPadLen
|
endPad := totalPad - startPad
|
||||||
|
|
||||||
charLen := len(padStr)
|
repeatPad := func(n int) string {
|
||||||
leftPad := ""
|
repeated := strings.Repeat(padStr, (n+len(padStr)-1)/len(padStr))
|
||||||
cur := 0
|
return repeated[:n]
|
||||||
for cur < startPadLen {
|
|
||||||
leftPad += string(padStr[cur%charLen])
|
|
||||||
cur++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur = 0
|
left := repeatPad(startPad)
|
||||||
rightPad := ""
|
right := repeatPad(endPad)
|
||||||
for cur < endPadLen {
|
|
||||||
rightPad += string(padStr[cur%charLen])
|
|
||||||
cur++
|
|
||||||
}
|
|
||||||
|
|
||||||
return leftPad + str + rightPad
|
return left + str + right
|
||||||
}
|
}
|
||||||
|
|
||||||
// isLetter checks r is a letter but not CJK character.
|
// isLetter checks r is a letter but not CJK character.
|
||||||
|
|||||||
@@ -518,7 +518,8 @@ func TestStringToBytes(t *testing.T) {
|
|||||||
assert := internal.NewAssert(t, "TestStringToBytes")
|
assert := internal.NewAssert(t, "TestStringToBytes")
|
||||||
|
|
||||||
bytes := StringToBytes("abc")
|
bytes := StringToBytes("abc")
|
||||||
assert.Equal(bytes, []byte{'a', 'b', 'c'})
|
assert.Equal([]byte{'a', 'b', 'c'}, bytes)
|
||||||
|
assert.Equal(3, cap(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBytesToString(t *testing.T) {
|
func TestBytesToString(t *testing.T) {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func IsJSON(str string) bool {
|
|||||||
return json.Unmarshal([]byte(str), &js) == nil
|
return json.Unmarshal([]byte(str), &js) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAlphaNumericStr check if the string is alphanumeric.
|
// IsAlphaNumeric check if the string is alphanumeric.
|
||||||
// Play: todo
|
// Play: todo
|
||||||
func IsAlphaNumeric(s string) bool {
|
func IsAlphaNumeric(s string) bool {
|
||||||
return alphaNumericMatcher.MatchString(s)
|
return alphaNumericMatcher.MatchString(s)
|
||||||
|
|||||||
@@ -665,3 +665,21 @@ func ExampleIsChinaUnionPay() {
|
|||||||
// true
|
// true
|
||||||
// false
|
// false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleIsAlphaNumeric() {
|
||||||
|
result1 := IsAlphaNumeric("ABC")
|
||||||
|
result2 := IsAlphaNumeric("123")
|
||||||
|
result3 := IsAlphaNumeric("abc123")
|
||||||
|
result4 := IsAlphaNumeric("abc123@#$")
|
||||||
|
|
||||||
|
fmt.Println(result1)
|
||||||
|
fmt.Println(result2)
|
||||||
|
fmt.Println(result3)
|
||||||
|
fmt.Println(result4)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user