1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-03-01 00:35:28 +08:00

Compare commits

..

15 Commits

Author SHA1 Message Date
dudaodong
cdefbde9f5 release v2.3.6 2025-05-29 10:07:19 +08:00
dudaodong
1e57a743af Merge branch 'rc' into v2 2025-05-29 10:05:13 +08:00
Yurun
7d4184a365 doc: fix typo (#308) 2025-05-29 10:01:38 +08:00
dudaodong
1b73483945 doc: update doc for v2.3.6 2025-05-28 11:30:12 +08:00
RigelShrimp
622aacaf44 doc: fix typo in chinese translation (#305) 2025-05-14 15:09:10 +08:00
燕归来
e78ac65605 fix: Fixed the issue of missing cap when StringToBytes returns result (#306)
Co-authored-by: 燕归来 <dylan@infinni.io>
2025-05-14 15:08:12 +08:00
dudaodong
03f0d4d905 fix: fix ExampleFindValuesBy 2025-04-29 10:18:51 +08:00
dudaodong
b2ae71c983 update rsa crypto file 2025-04-29 10:04:11 +08:00
dudaodong
093f4a2286 doc: add documention for keyed locker 2025-04-28 19:32:51 +08:00
dudaodong
f7ada6093c feat: add example for keyed_locker 2025-04-27 19:44:15 +08:00
dudaodong
47e82aad39 refactor: refact some sliceutil functions 2025-04-24 10:38:43 +08:00
dudaodong
9e813d236b fix: fix issue #159 2025-04-22 14:46:03 +08:00
dudaodong
72a23d2cb9 fix: fix issue #159 and refact crypto function 2025-04-22 14:22:01 +08:00
dudaodong
27667f8b3a fix: fix bugs in test 2025-04-21 15:08:16 +08:00
dudaodong
0b5dc86d70 refactor: refact some strutil functions 2025-04-21 14:07:52 +08:00
28 changed files with 2064 additions and 687 deletions

View File

@@ -4,7 +4,7 @@
<br/> <br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf) ![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.5-green.svg)](https://github.com/duke-git/lancet/releases) [![Release](https://img.shields.io/badge/release-2.3.6-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2) [![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml) [![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](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...&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a> </h3> <h3 id="condition"> 4. Condition package contains some functions for conditional judgment. eg. And, Or, TernaryOperator...&nbsp; &nbsp; &nbsp; &nbsp;<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)]
@@ -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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">Index</a></h3> <h3 id="eventbus"> 9. EventBus is an event bus used for handling events within an application. &nbsp; &nbsp; &nbsp; &nbsp;<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.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3> <h3 id="maputil"> 12. Maputil package includes some functions to manipulate map.&nbsp; &nbsp; &nbsp; &nbsp;<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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3> <h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. &nbsp; &nbsp; &nbsp; &nbsp;<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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3> <h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. &nbsp; &nbsp; &nbsp; &nbsp;<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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3> <h3 id="slice"> 18. Slice contains some functions to manipulate slice. &nbsp; &nbsp; &nbsp; &nbsp;<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. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3> <h3 id="tuple"> 23. Tuple package implements tuple data type and some operations on it. &nbsp; &nbsp; &nbsp; &nbsp;<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">

View File

@@ -4,7 +4,7 @@
<br/> <br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf) ![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.5-green.svg)](https://github.com/duke-git/lancet/releases) [![Release](https://img.shields.io/badge/release-2.3.6-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2) [![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml) [![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](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 包实现一些基本查找和排序算法。 &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="algorithm"> 1. algorithm 包实现一些基本查找和排序算法。 &nbsp; &nbsp; &nbsp; &nbsp;<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 包含一些用于条件判断的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="condition"> 4. condition 包含一些用于条件判断的函数。&nbsp; &nbsp; &nbsp; &nbsp;<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)]
@@ -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。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="datastructure"> 8. datastructure 包含一些普通的数据结构实现。例如list, linklist, stack, queue, set, tree, graph。&nbsp; &nbsp; &nbsp; &nbsp;<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 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<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 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<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 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<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 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<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 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3> <h3 id="slice"> 19. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <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 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<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 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3> <h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<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)]

View File

@@ -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
}

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by MIT license // 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 contain some functions to support concurrent programming. eg, goroutine, channel, locker.
package concurrency package concurrency
import ( import (

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -1,51 +1,51 @@
-----BEGIN rsa private key----- -----BEGIN rsa private key-----
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
-----END rsa private key----- -----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key----- -----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtKEukXkdl8ggX5I5Us0M MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw6Q11wDtWHZiyQbv+Xzn
BcsrFzbTgwOn8u72IhJzruvsHcjlGGF5L1eXh3C0zbcoWq+Bxf8NnXvSXASZlZHT tJbEELkPQ3oVSVCVjTx+ls5I+yR6oq8LxyoUE6SHcBlludkzs02B0rDkkfj6vl0i
iSVLFerxFw2LwTNcyfOz/Y/wIbtrYu4ZdttLKdJS607UvnZ57O7SQni5rxYAP5j+ PCzr3HP1XT1WwFiQJnXKtNNNiLvvewB/aynj/UIBCZZvMTYwK6nc76i4JWcajaiU
veKHEJgAO7Sn0lEEWwkeudsfVrQmQx6JHYtCtx78p2GHXPj9wXWHxWHKMkczKtmB I4u84Fp7gOn0IgXMhvrwtGHk7HK1Zjr0afUJVbodc6pMZfe0QWpnDq7zaeYdkPwz
FDLlYocPhQfWzx87sYKjGmTMGzVddsrllAqVozlxXTQndOdozx8BWHSBiYWguOvW 8klWAatLDZTeR5dYli2NiWOBepO1538ZvEK3S/QP2nWJzPJy2PbEHfDaFgr055w/
iYjvryrIf0B7wn4wt3o2Yre3w4BkwrTUE73JWG2q1NLuPoT27m7WcdVFGGcKAygA wu0MzAQgkXGV8PubhYboJuQAkuXc63IPpmWVL/93WeCNRiL5fvNzUHsbPtT48A9N
dMMSwS530v8tnAw876/w1SfUmtkIcqknysMjsvxHx1fbEfkl3b0yBnzcUrT1qxzm nnUn4QVuVGeXIxJ1skAFsoh7i+h+mK/p5rzjPtw56nE20gYXhxf7FzbHYtHn0ego
NEqJSQXBtFYFyGgyeL8J1ul/T5hPSNz0we5TO/xRkX5Y1HNJ+a2r8EnE1PU8zgQG BddUzlOY0yzvCgI5Yy1RdkNe/5vU1bpLsdU/WItjR2h6EfqLosQ4iKowPBYQXJOI
BxYSIub4ajSUccyn5yHQ/k2FlsolZiu/UzfnzMgdg55svgf5x1ntKYBoVHuYe84A NbpkvIhkhCVbPpMvmhfi/lATiKdoXgtvuB04wth011c5W/yXA+A3Ob73Sr2+zyxF
b3WRAeVlUi0OphvFlBzfIQvrG2sRbiPZRZZ0E8kBH94zPmrFmyX+7TwlSqVHtNFc 870tOp3rNTWsYLvCOjf+JdjgM3AzNuFxE4NdMLvXDf7CIXSb3X9rj3qM769Lz6XX
+Ynz+hrHcd4z6N7fIb+D1gSYuukkcRqr4ANO0ugZk0YYKaWDf86H7OZOXna8ObIA bRBVbO0HxGyqLTPPmSWV61jQuUTWNGnxkovoYLrpgyumIjjIcl0aY1hogmQeA6ZN
i+3gx+Wcp1lm7Ffk9sn+dGkCAwEAAQ== gJxaUw9r/p8vnThL5UXKlbcCAwEAAQ==
-----END rsa public key----- -----END rsa public key-----

View File

@@ -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()

View File

@@ -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
}
```

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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>

View File

@@ -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
}
```

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -842,6 +842,10 @@ func ExampleFindValuesBy() {
return k%2 == 0 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) fmt.Println(result)
// Output: // Output:

View File

@@ -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) {

View File

@@ -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 indexexclude). // DeleteRange delete the element of slice from start index to end indexexclude).
@@ -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)
} }
} }

View File

@@ -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.

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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
}