1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-04 12:52:28 +08:00

Compare commits

...

40 Commits

Author SHA1 Message Date
dudaodong
c0841d7be5 Merge branch 'v2' of github.com:duke-git/lancet into v2 2026-01-29 10:08:21 +08:00
Javen
b3fd282b50 feat: add address.Smart and Decompose for parse CN address (#346)
* feat: add address.Smart and Decompose for parse CN address

* feat: add Xinjiang directly-administered county-level cities support

- Add '自治区直辖县级市' as a city-level unit (ID: 4043) in A2Data for Xinjiang
- Add 12 directly-administered county-level cities in A3Data (IDs: 4044-4055):
  * 石河子市 (Shihezi, 1976, 8th Division) - ID: 4044
  * 阿拉尔市 (Aral, 2002, 1st Division) - ID: 4045
  * 图木舒克市 (Tumxuk, 2002, 3rd Division) - ID: 4046
  * 五家渠市 (Wujiaqu, 2002, 6th Division) - ID: 4047
  * 北屯市 (Beitun, 2011, 10th Division) - ID: 4048
  * 铁门关市 (Tiemenguan, 2012, 2nd Division) - ID: 4049
  * 双河市 (Shuanghe, 2014, 5th Division) - ID: 4050
  * 可克达拉市 (Kokdala, 2015, 4th Division) - ID: 4051
  * 昆玉市 (Kunyu, 2016, 14th Division) - ID: 4052
  * 胡杨河市 (Huyanghe, 2019, 7th Division) - ID: 4053
  * 新星市 (Xinxing, 2021, 13th Division) - ID: 4054
  * 白杨市 (Baiyang, 2023, 9th Division) - ID: 4055
- All county-level cities are under PID 4043 (自治区直辖县级市)
- Add test case for Xinjiang Shihezi city address parsing
- Now supports parsing addresses like: 新疆石河子市北三路25小区

* docs: formated address data

* fix: parse repeat address error

* feat: update readme file

---------

Co-authored-by: Jiawen <im@linjiawen.com>
2026-01-13 14:00:44 +08:00
efinKiaC
a1cebec9f2 ReadFileByLine bugfix (#350) 2026-01-13 11:24:08 +08:00
Yang Li
88cf1600e8 fix(random): avoid concurrent rand.Seed causing panic (#345) 2025-12-16 13:52:25 +08:00
Javen
0851b68b83 Feat/encryption for sm2 sm3 sm4 (#343)
* feat: add ContainAny

* feat:encryption adds support for SM2, SM3, and SM4 #131

* doc: add docment for SM2, SM3, and SM4 #131

---------

Co-authored-by: Jiawen <im@linjiawen.com>
2025-11-07 19:17:55 +08:00
Javen
5c13fd4f2f Fix/339 (#344)
* feat: add ContainAny

* fix: fix issue #339

---------

Co-authored-by: Jiawen <im@linjiawen.com>
2025-11-07 19:17:09 +08:00
dudaodong
889d0cc3d6 doc: update readme file 2025-11-01 21:45:56 +08:00
dudaodong
aa05027cab doc: update readme file 2025-11-01 21:44:03 +08:00
dudaodong
fabc3483ed doc: update readme file 2025-11-01 21:37:15 +08:00
dudaodong
30363242bb doc: update document for version v2.3.8 2025-11-01 21:25:10 +08:00
dudaodong
5d3964d81a release v2.3.8 2025-11-01 19:39:46 +08:00
dudaodong
6c3dc3e7d6 Merge branch 'rc' of github.com:duke-git/lancet into rc 2025-10-31 14:59:25 +08:00
dudaodong
2a2e1ca551 doc: add enum and update overview index 2025-10-31 14:57:54 +08:00
dudaodong
e1821eed2c doc: add docment for enum package 2025-10-31 14:48:32 +08:00
dudaodong
62f0a96d91 doc: add docment for enum package 2025-10-31 14:48:22 +08:00
dudaodong
cbdc3971dd doc: add docment for new or fixed function 2025-10-31 13:37:41 +08:00
Javen
350450bb67 feat: add ContainAny (#338)
Co-authored-by: Jiawen <im@linjiawen.com>
2025-10-30 19:24:45 +08:00
dudaodong
f407e51b24 fix: fix issue #335 2025-10-30 17:09:48 +08:00
dudaodong
3c6c3a14cf Merge branch 'rc' into v2 2025-10-29 21:10:54 +08:00
dudaodong
41bafdef92 refact: remove unused code 2025-10-29 21:10:30 +08:00
wangxc
fc624195c7 feat:add map to markdown conversion in map.go (#336)
* feat(file): add map to markdown conversion in map.go

* feat(file): add map to markdown conversion in map.go
2025-10-29 21:07:18 +08:00
残念
5b3a59e785 Merge pull request #334 from duke-git/v2
Add enum package
2025-10-25 12:32:12 +08:00
chentong
74abb2d3f1 feat(struct): add struct name function (#328)
* feat(struct): add struct name function

- Add Name() method to Struct type to return the struct name
- Implement unit tests for the new Name() method

* refactor(structs): rename Struct.Name to Struct.TypeName

- Rename method Name to TypeName for clarity and accuracy
- Update corresponding test cases
2025-10-23 11:20:43 +08:00
dudaodong
3f12b34eea fix: fix issue for PR #334, usge Pair struct to instance enum items 2025-10-16 18:43:24 +08:00
dudaodong
cd43004a91 feat: add example for enum package 2025-10-14 16:37:02 +08:00
dudaodong
3ac9461c00 Merge branch 'rc' into v2 2025-09-30 14:57:01 +08:00
dudaodong
309b07ae8a feat: a simple enum implementation 2025-09-30 14:56:25 +08:00
chentong
8fe56b6dc7 fix(eventbus): update error handler to include topic information (#333)
* fix(eventbus): update error handler to include topic information

- Modified error handler signature to accept topic string and error
- Updated panic recovery logic to pass event topic to error handler
- Adjusted SetErrorHandler method parameter signature
- Updated example test to demonstrate new error handler usage
- Enhanced error handling test to verify topic information

* fix(eventbus): unit test
2025-09-28 20:10:12 +08:00
残念
15a0dad0d8 Merge pull request #330 from zoulux/main
fix: return 0 when Average is called with empty slice
2025-09-23 11:22:30 +08:00
jake
93c777a418 Update mathutil.go
fix: return 0 when Average is called with empty slice
2025-09-22 10:04:23 +08:00
jake
5ff1c6578f Merge branch 'duke-git:main' into main 2025-09-22 09:53:09 +08:00
dudaodong
7d4b9510a2 feat: add IsChineseHMPassport 2025-08-21 14:13:43 +08:00
dudaodong
9f0ad2354a feat: update some test fucntions 2025-08-21 10:52:44 +08:00
dudaodong
55b66dee99 Merge branch 'rc' into v2 2025-08-21 10:08:21 +08:00
Idichekop
4c64a16204 fix(package): [slice] functions with inconsistent return behaviour (#326)
* Some functions modified

* Fixes in all the functions of slice.go

* Re-use of swap function internal.
2025-08-19 10:50:25 +08:00
dudaodong
385e64cc52 feat: add IsPassport 2025-08-13 14:01:24 +08:00
dudaodong
be45a259db Merge branch 'v2' into rc 2025-08-13 11:24:27 +08:00
dudaodong
d5b9e67330 fix: fix go lint issue 2025-07-07 11:18:34 +08:00
dudaodong
a81403766f feat: add ToPointers, FromPointer, FromPointers 2025-07-07 11:16:44 +08:00
jake
83c069e234 Update slice_concurrent.go 2025-06-24 19:14:37 +08:00
58 changed files with 11973 additions and 501 deletions

175
README.md
View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.7-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.8-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)
[![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)
@@ -84,6 +84,8 @@ func main() {
- [Cryptor](#user-content-cryptor)
- [Datetime](#user-content-datetime)
- [Datastructure](#user-content-datastructure)
- [EventBus](#user-content-eventbus)
- [Enum](#user-content-enum)
- [Fileutil](#user-content-fileutil)
- [Formatter](#user-content-formatter)
- [Function](#user-content-function)
@@ -313,6 +315,15 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>ToPointer</big>** : return a pointer of passed value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToPointer)]
[[play](https://go.dev/play/p/ASf_etHNlw1)]
- **<big>ToPointers</big>** : convert a slice of values to a slice of pointers.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToPointers)]
[[play](https://go.dev/play/p/ZUoXd2i5ZkV)]
- **<big>FromPointer</big>** : returns the value pointed to by the pointer.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#FromPointer)]
[[play](https://go.dev/play/p/wAp90V7Zu6g)]
- **<big>FromPointers</big>** : convert a slice of pointers to a slice of values.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#FromPointers)]
[[play](https://go.dev/play/p/qIPsyYtNy3Q)]
- **<big>ToString</big>** : convert value to string.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/convertor.md#ToString)]
[[play](https://go.dev/play/p/nF1zOOslpQq)]
@@ -512,6 +523,30 @@ import "github.com/duke-git/lancet/v2/cryptor"
- **<big>RsaVerifySign</big>** : verifies the signature of the data with RSA.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#RsaVerifySign)]
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
- **<big>GenerateSm2Key</big>** : generate SM2 private and public key.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#GenerateSm2Key)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm2Encrypt</big>** : encrypt data with SM2 public key.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm2Encrypt)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm2Decrypt</big>** : decrypt data with SM2 private key.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm2Decrypt)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm3</big>** : return the SM3 hash value (256-bit) of data.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm3)]
[[play](https://go.dev/play/p/zDAQpteAiOc)]
- **<big>Sm4EcbEncrypt</big>** : encrypt data with SM4 ECB mode.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4EcbEncrypt)]
[[play](https://go.dev/play/p/l5IQxYuuaED)]
- **<big>Sm4EcbDecrypt</big>** : decrypt data with SM4 ECB mode.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4EcbDecrypt)]
[[play](https://go.dev/play/p/l5IQxYuuaED)]
- **<big>Sm4CbcEncrypt</big>** : encrypt data with SM4 CBC mode.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4CbcEncrypt)]
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
- **<big>Sm4CbcDecrypt</big>** : decrypt data with SM4 CBC mode.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/cryptor.md#Sm4CbcDecrypt)]
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
<h3 id="datetime"> 7. Datetime package supports date and time format and compare. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -724,7 +759,7 @@ import optional "github.com/duke-git/lancet/v2/datastructure/optional"
import "github.com/duke-git/lancet/v2/eventbus"
```
#### 函数列表:
#### Function list:
- **<big>NewEventBus</big>** : Create an EventBus instance.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#NewEventBus)]
@@ -757,7 +792,70 @@ import "github.com/duke-git/lancet/v2/eventbus"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 9. Fileutil package implements some basic functions for file operations. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="enum"> 10. Package enum provides a simple enum implementation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">Index</a></h3>
```go
import "github.com/duke-git/lancet/v2/enum"
```
#### Function list:
- **<big>NewItem</big>** : Creates a new enum item.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewEventBus)]
[[play](https://go.dev/play/p/8qNsLw01HD5)]
- **<big>NewItemsFromPairs</big>** : Creates enum items from a slice of Pair structs.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewItemsFromPairs)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Value</big>** : Returns the value of the enum item.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Value)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Name</big>** : Returns the name of the enum item.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Name)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Valid</big>** : Checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Valid)]
[[play](https://go.dev/play/p/pA3lYY2VSm3)]
- **<big>MarshalJSON</big>** : Implementation of json.Marshaler interface.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#MarshalJSON)]
[[play](https://go.dev/play/p/zIZEdAnneB5)]
- **<big>NewRegistry</big>** : Creates a new enum registry.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#NewRegistry)]
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
- **<big>Add</big>** : Adds enum items to the registry.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Add)]
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
- **<big>Remove</big>** : Removes an enum item from the registry by its value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Remove)]
[[play](https://go.dev/play/p/dSG84wQ3TuC)]
- **<big>Update</big>** : Updates the name of an enum item in the registry by its value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Update)]
[[play](https://go.dev/play/p/Ol0moT1J9Xl)]
- **<big>GetByValue</big>** : Retrieves an enum item by its value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#GetByValue)]
[[play](https://go.dev/play/p/niJ1U2KlE_m)]
- **<big>GetByName</big>** : Retrieves an enum item by its name.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#GetByName)]
[[play](https://go.dev/play/p/49ie_gpqH0m)]
- **<big>Items</big>** : Returns a slice of all enum items in the registry.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Items)]
[[play](https://go.dev/play/p/lAJFAradbvQ)]
- **<big>Contains</big>** : Checks if an enum item with the given value exists in the registry.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Contains)]
[[play](https://go.dev/play/p/_T-lPYkZn2j)]
- **<big>Size</big>** : Returns the number of enum items in the registry.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Size)]
[[play](https://go.dev/play/p/TeDArWhlQe2)]
- **<big>Range</big>** : Iterates over all enum items in the registry and applies the given function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Range)]
[[play](https://go.dev/play/p/GPsZbQbefWN)]
- **<big>SortedItems</big>** : Returns a slice of all enum items sorted by the given less function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#SortedItems)]
[[play](https://go.dev/play/p/tN9RE_m_WEI)]
- **<big>Filter</big>** : Returns a slice of enum items that satisfy the given predicate function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/enum.md#Filter)]
[[play](https://go.dev/play/p/uTUpTdcyoCU)]
<h3 id="fileutil"> 11. Fileutil package implements some basic functions for file operations. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/fileutil"
@@ -859,7 +957,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#GetExeOrDllVersion)]
[[play](https://go.dev/play/p/iLRrDBhE38E)]
<h3 id="formatter"> 10. Formatter contains some functions for data formatting. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="formatter"> 12. Formatter contains some functions for data formatting. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/formatter"
@@ -888,8 +986,14 @@ import "github.com/duke-git/lancet/v2/formatter"
- **<big>ParseBinaryBytes</big>** : return the human readable bytes size string into the amount it represents(base 1024).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParseBinaryBytes)]
[[play](https://go.dev/play/p/69v1tTT62x8)]
- **<big>ParseCNAddress</big>** : parses a Chinese address string intelligently and extracts structured information (province, city, district, street, name, phone, etc.). Supports various address formats including county-level cities.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParseCNAddress)]
[[play](https://go.dev/play/p/o5l09hQopEV)]
- **<big>ParsePersonInfo</big>** : extracts user information (name, phone, ID card, postal code) from an address string and separates it from the location address.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/formatter.md#ParsePersonInfo)]
[[play](https://go.dev/play/p/JO-uTlJlTy7)]
<h3 id="function"> 11. Function package can control the flow of function execution and support part of functional programming.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="function"> 13. Function package can control the flow of function execution and support part of functional programming.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/function"
@@ -952,7 +1056,7 @@ import "github.com/duke-git/lancet/v2/function"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
<h3 id="maputil"> 12. Maputil package includes some functions to manipulate map.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="maputil"> 14. Maputil package includes some functions to manipulate map.&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/maputil"
@@ -1125,8 +1229,11 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>FindValuesBy</big>** : returns a slice of values from the map that satisfy the given predicate function.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#FindValuesBy)]
[[play](https://go.dev/play/p/bvNwNBZDm6v)]
- **<big>ToMarkdownTable</big>** : Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToMarkdownTable)]
[[play](https://go.dev/play/p/w_pSLfeyEB5)]
<h3 id="mathutil"> 13. Mathutil package implements some functions for math calculation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="mathutil"> 15. Mathutil package implements some functions for math calculation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/mathutil"
@@ -1237,7 +1344,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/mathutil.md#Combination)]
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
<h3 id="netutil"> 14. Netutil package contains functions to get net information and send http request. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="netutil"> 16. Netutil package contains functions to get net information and send http request. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/netutil"
@@ -1316,7 +1423,7 @@ import "github.com/duke-git/lancet/v2/netutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/netutil.md#AddQueryParams)]
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
<h3 id="pointer"> 15. Pointer package contains some util functions to operate go pointer. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="pointer"> 17. Pointer package contains some util functions to operate go pointer. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/pointer"
@@ -1340,7 +1447,7 @@ import "github.com/duke-git/lancet/v2/pointer"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#UnwrapOrDefault)]
[[play](https://go.dev/play/p/ZnGIHf8_o4E)]
<h3 id="random"> 16. Random package implements some basic functions to generate random int and string. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="random"> 18. Random package implements some basic functions to generate random int and string. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/random"
@@ -1406,7 +1513,7 @@ import "github.com/duke-git/lancet/v2/random"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/random.md#RandNumberOfLength)]
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
<h3 id="retry"> 17. Retry package is for executing a function repeatedly until it was successful or canceled by the context. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="retry"> 19. Retry package is for executing a function repeatedly until it was successful or canceled by the context. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/retry"
@@ -1441,7 +1548,7 @@ import "github.com/duke-git/lancet/v2/retry"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
[[play](https://go.dev/play/p/xp1avQmn16X)]
<h3 id="slice"> 18. Slice contains some functions to manipulate slice. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="slice"> 20. Slice contains some functions to manipulate slice. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/slice"
@@ -1461,6 +1568,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ContainSubSlice</big>** : check if the slice contain a given subslice or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainSubSlice)]
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
- **<big>ContainAny</big>** : check if the slice contains any element from the targets slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainAny)]
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
- **<big>Chunk</big>** : creates a slice of elements split into groups the length of size.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Chunk)]
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
@@ -1710,7 +1820,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ConcatBy)]
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
<h3 id="stream"> 19. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="stream"> 21. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/stream"
@@ -1809,7 +1919,7 @@ import "github.com/duke-git/lancet/v2/stream"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/stream.md#LastIndexOf)]
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
<h3 id="structs"> 20. Structs package provides several high level functions to manipulate struct, tag, and field. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="structs"> 22. Structs package provides several high level functions to manipulate struct, tag, and field. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/structs"
@@ -1819,32 +1929,51 @@ import "github.com/duke-git/lancet/v2/structs"
- **<big>New</big>** : creates a `Struct` instance.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#New)]
[[play](https://go.dev/play/p/O29l8kk-Z17)]
- **<big>ToMap</big>** : converts a valid struct to a map.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#ToMap)]
[[play](https://go.dev/play/p/qQbLySBgerZ)]
- **<big>Fields</big>** : get all fields of a given struct, that the fields are abstract struct field.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/w3Kk_CyVY7D)]
- **<big>Field</big>** : get an abstract field of a struct by given field name
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Field)]
[[play](https://go.dev/play/p/KocZMSYarza)]
- **<big>IsStruct</big>** : check if the struct is valid.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsStruct)]
[[play](https://go.dev/play/p/bU2FSdkbK1C)]
- **<big>Tag</big>** : get a `Tag` of the `Field`, `Tag` is a abstract struct field tag.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Tag)]
[[play](https://go.dev/play/p/DVrx5HvvUJr)]
- **<big>Name</big>** : get the field name.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Name)]
[[play](https://go.dev/play/p/zfIGlqsatee)]
- **<big>Value</big>** : get the `Field` underlying value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Value)]
[[play](https://go.dev/play/p/qufYEU2o4Oi)]
- **<big>Kind</big>** : get the field's kind.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#Kind)]
[[play](https://go.dev/play/p/wg4NlcUNG5o)]
- **<big>IsEmbedded</big>** : check if the field is an embedded field.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsEmbedded)]
[[play](https://go.dev/play/p/wV2PrbYm3Ec)]
- **<big>IsExported</big>** : check if the field is exported.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsExported)]
[[play](https://go.dev/play/p/csK4AXYaNbJ)]
- **<big>IsZero</big>** : check if the field is zero value.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsZero)]
[[play](https://go.dev/play/p/RzqpGISf87r)]
- **<big>IsSlice</big>** : check if the field is a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsSlice)]
[[play](https://go.dev/play/p/MKz4CgBIUrU)]
- **<big>IsTargetType</big>** : check if the field is target type.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#IsTargetType)]
[[play](https://go.dev/play/p/Ig75P-agN39)]
- **<big>TypeName</big>** : Return struct type name.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/struct.md#TypeName)]
[[play](https://go.dev/play/p/SWLWd0XBaBb)]
<h3 id="strutil"> 21. Strutil package contains some functions to manipulate string. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="strutil"> 23. Strutil package contains some functions to manipulate string. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/strutil"
@@ -1996,7 +2125,7 @@ import "github.com/duke-git/lancet/v2/strutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/strutil.md#FindAllOccurrences)]
[[play](https://go.dev/play/p/uvyA6azGLB1)]
<h3 id="system"> 22. System package contain some functions about os, runtime, shell command. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="system"> 24. System package contain some functions about os, runtime, shell command. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/system"
@@ -2044,7 +2173,7 @@ import "github.com/duke-git/lancet/v2/system"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/system.md#GetProcessInfo)]
[[play](https://go.dev/play/p/NQDVywEYYx7)]
<h3 id="tuple"> 23. Tuple package implements tuple data type and some operations on it. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="tuple"> 25. Tuple package implements tuple data type and some operations on it. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/tuple"
@@ -2161,7 +2290,7 @@ import "github.com/duke-git/lancet/v2/tuple"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/tuple.md#Unzip10)]
[[play](https://go.dev/play/p/-taQB6Wfre_z)]
<h3 id="validator"> 24. Validator package contains some functions for data validation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="validator"> 26. Validator package contains some functions for data validation. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/validator"
@@ -2210,7 +2339,7 @@ import "github.com/duke-git/lancet/v2/validator"
[[play](https://go.dev/play/p/jlYApVLLGTZ)]
- **<big>IsEmail</big>** : check if the string is a email address.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmail)]
[[play](https://go.dev/play/p/Os9VaFlT33G)]
[[play](https://go.dev/play/p/HVQ5LAe-vFz)]
- **<big>IsEmptyString</big>** : check if the string is empty.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmptyString)]
[[play](https://go.dev/play/p/dpzgUjFnBCX)]
@@ -2301,8 +2430,14 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsChinaUnionPay</big>** : check if a give string is a valid china union pay number or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChinaUnionPay)]
[[play](https://go.dev/play/p/yafpdxLiymu)]
- **<big>IsPassport</big>** : Passport validation(using regex).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsPassport)]
[[play](https://go.dev/play/p/dvOiV2BW7Aw)]
- **<big>IsChineseHMPassport</big>** : Mainland travel permit for Hong Kong, Macao validation (using regex).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsChineseHMPassport)]
[[play](https://go.dev/play/p/xKG6spQTcY0)]
<h3 id="xerror"> 25. Xerror package implements helpers for errors. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
<h3 id="xerror"> 27. Xerror package implements helpers for errors. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
```go
import "github.com/duke-git/lancet/v2/xerror"

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.7-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.8-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)
[![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)
@@ -83,6 +83,8 @@ func main() {
- [Cryptor](#user-content-cryptor)
- [Datetime](#user-content-datetime)
- [Datastructure](#user-content-datastructure)
- [EventBus](#user-content-eventbus)
- [Enum](#user-content-enum)
- [Fileutil](#user-content-fileutil)
- [Formatter](#user-content-formatter)
- [Function](#user-content-function)
@@ -312,6 +314,15 @@ import "github.com/duke-git/lancet/v2/convertor"
- **<big>ToPointer</big>** : 返回传入值的指针。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToPointer)]
[[play](https://go.dev/play/p/ASf_etHNlw1)]
- **<big>ToPointers</big>** : 将值的切片转换为指针的切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToPointers)]
[[play](https://go.dev/play/p/ZUoXd2i5ZkV)]
- **<big>FromPointer</big>** : 返回指针所指向的值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#FromPointer)]
[[play](https://go.dev/play/p/wAp90V7Zu6g)]
- **<big>FromPointers</big>** : 将指针的切片转换为值的切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#FromPointers)]
[[play](https://go.dev/play/p/qIPsyYtNy3Q)]
- **<big>ToString</big>** : 将值转换为字符串,对于数字、字符串、[]byte将转换为字符串。 对于其他类型(切片、映射、数组、结构)将调用 json.Marshal。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/convertor.md#ToString)]
[[play](https://go.dev/play/p/nF1zOOslpQq)]
@@ -524,6 +535,30 @@ import "github.com/duke-git/lancet/v2/cryptor"
- **<big>RsaVerifySign</big>** : 验证数据的签名是否符合 RSA 算法。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#RsaVerifySign)]
[[play](https://go.dev/play/p/qhsbf8BJ6Mf)]
- **<big>GenerateSm2Key</big>** : 生成 SM2 公钥和私钥。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#GenerateSm2Key)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm2Encrypt</big>** : 使用 SM2 公钥加密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm2Encrypt)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm2Decrypt</big>** : 使用 SM2 私钥解密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm2Decrypt)]
[[play](https://go.dev/play/p/bKYMqRLvIx3)]
- **<big>Sm3</big>** : 返回数据的 SM3 哈希值256 位)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm3)]
[[play](https://go.dev/play/p/zDAQpteAiOc)]
- **<big>Sm4EcbEncrypt</big>** : 使用 SM4 ECB 模式加密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4EcbEncrypt)]
[[play](https://go.dev/play/p/l5IQxYuuaED)]
- **<big>Sm4EcbDecrypt</big>** : 使用 SM4 ECB 模式解密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4EcbDecrypt)]
[[play](https://go.dev/play/p/l5IQxYuuaED)]
- **<big>Sm4CbcEncrypt</big>** : 使用 SM4 CBC 模式加密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4CbcEncrypt)]
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
- **<big>Sm4CbcDecrypt</big>** : 使用 SM4 CBC 模式解密数据。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/cryptor.md#Sm4CbcDecrypt)]
[[play](https://go.dev/play/p/65Q6iYhLRTa)]
<h3 id="datetime"> 7. datetime日期时间处理包格式化日期比较日期。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
@@ -767,7 +802,70 @@ import "github.com/duke-git/lancet/v2/eventbus"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/eventbus.md#SetErrorHandler)]
[[play](https://go.dev/play/p/gmB0gnFe5mc)]
<h3 id="fileutil"> 10. fileutil 包含文件基本操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="enum"> 10. Enum实现一个简单枚举工具包。. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">Index</a></h3>
```go
import "github.com/duke-git/lancet/v2/enum"
```
#### Function list:
- **<big>NewItem</big>** : 创建枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewItem)]
[[play](https://go.dev/play/p/8qNsLw01HD5)]
- **<big>NewItemsFromPairs</big>** : 从 Pair 结构体的切片创建枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewItemsFromPairs)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Value</big>** : 返回枚举项的值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Value)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Name</big>** : 返回枚举项的名称。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Name)]
[[play](https://go.dev/play/p/xKnoGa7gnev)]
- **<big>Valid</big>** : 检查枚举项是否有效。如果提供了自定义检查函数,将使用该函数验证值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Valid)]
[[play](https://go.dev/play/p/pA3lYY2VSm3)]
- **<big>MarshalJSON</big>** : 枚举项实现 json.Marshaler 接口。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#MarshalJSON)]
[[play](https://go.dev/play/p/zIZEdAnneB5)]
- **<big>NewRegistry</big>** : Registry 定义了一个通用的枚举注册表结构体。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#NewRegistry)]
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
- **<big>Add</big>** : 向枚举注册表添加枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Add)]
[[play](https://go.dev/play/p/ABEXsYfJKMo)]
- **<big>Remove</big>** : 在枚举注册表中删除枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Remove)]
[[play](https://go.dev/play/p/dSG84wQ3TuC)]
- **<big>Update</big>** : 在枚举注册表中更新枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Update)]
[[play](https://go.dev/play/p/Ol0moT1J9Xl)]
- **<big>GetByValue</big>** : 在枚举注册表中通过值获取枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#GetByValue)]
[[play](https://go.dev/play/p/niJ1U2KlE_m)]
- **<big>GetByName</big>** : 在枚举注册表中通过名称获取枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#GetByName)]
[[play](https://go.dev/play/p/49ie_gpqH0m)]
- **<big>Items</big>** : 返回枚举注册表中的枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Items)]
[[play](https://go.dev/play/p/lAJFAradbvQ)]
- **<big>Contains</big>** : 检查注册表中是否存在具有给定值的枚举项。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Contains)]
[[play](https://go.dev/play/p/_T-lPYkZn2j)]
- **<big>Size</big>** : 返回注册表中枚举项的数目。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Size)]
[[play](https://go.dev/play/p/TeDArWhlQe2)]
- **<big>Range</big>** : 遍历注册表中的所有枚举项,并应用给定的函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Range)]
[[play](https://go.dev/play/p/GPsZbQbefWN)]
- **<big>SortedItems</big>** : 返回按给定比较函数排序的所有枚举项的切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#SortedItems)]
[[play](https://go.dev/play/p/tN9RE_m_WEI)]
- **<big>Filter</big>** : 返回满足给定谓词函数的枚举项切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/enum.md#Filter)]
[[play](https://go.dev/play/p/uTUpTdcyoCU)]
<h3 id="fileutil"> 11. fileutil 包含文件基本操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/fileutil"
@@ -869,7 +967,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#GetExeOrDllVersion)]
[[play](https://go.dev/play/p/iLRrDBhE38E)]
<h3 id="formatter"> 11. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="formatter"> 12. formatter 格式化器包含一些数据格式化处理方法。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/formatter"
@@ -898,8 +996,14 @@ import "github.com/duke-git/lancet/v2/formatter"
- **<big>ParseBinaryBytes</big>** : 将字节单位字符串转换成其所表示的字节数(以 1024 为基数)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParseBinaryBytes)]
[[play](https://go.dev/play/p/69v1tTT62x8)]
- **<big>ParseCNAddress</big>** : 智能解析中国地址字符串并提取结构化信息(省、市、区、街道、姓名、电话等)。支持多种地址格式,包括县级市。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParseCNAddress)]
[[play](https://go.dev/play/p/o5l09hQopEV)]
- **<big>ParsePersonInfo</big>** : 从地址字符串中提取用户信息(姓名、电话、身份证、邮编)并将其与位置地址分离。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/formatter.md#ParsePersonInfo)]
[[play](https://go.dev/play/p/JO-uTlJlTy7)]
<h3 id="function"> 12. function 函数包控制函数执行流程,包含部分函数式编程。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="function"> 13. function 函数包控制函数执行流程,包含部分函数式编程。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/function"
@@ -962,7 +1066,7 @@ import "github.com/duke-git/lancet/v2/function"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/function.md#Watcher)]
[[play](https://go.dev/play/p/l2yrOpCLd1I)]
<h3 id="maputil"> 13. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="maputil"> 14. maputil 包括一些操作 map 的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/maputil"
@@ -1135,8 +1239,11 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<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/bvNwNBZDm6v)]
- **<big>ToMarkdownTable</big>** : 将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToMarkdownTable)]
[[play](https://go.dev/play/p/w_pSLfeyEB5)]
<h3 id="mathutil"> 14. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="mathutil"> 15. mathutil 包实现了一些数学计算的函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/mathutil"
@@ -1247,7 +1354,7 @@ import "github.com/duke-git/lancet/v2/mathutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/mathutil.md#Combination)]
[[play](https://go.dev/play/p/ENFQRDQUFi9)]
<h3 id="netutil"> 15. netutil 网络包支持获取 ip 地址,发送 http 请求。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="netutil"> 16. netutil 网络包支持获取 ip 地址,发送 http 请求。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/netutil"
@@ -1326,7 +1433,7 @@ import "github.com/duke-git/lancet/v2/netutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/netutil.md#AddQueryParams)]
[[play](https://go.dev/play/p/JLXl1hZK7l4)]
<h3 id="pointer"> 16. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="pointer"> 17. pointer 包支持一些指针类型的操作。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/pointer"
@@ -1350,7 +1457,7 @@ import "github.com/duke-git/lancet/v2/pointer"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/pointer.md#UnwrapOrDefault)]
[[play](https://go.dev/play/p/ZnGIHf8_o4E)]
<h3 id="random"> 17. random 随机数生成器包,可以生成随机[]bytes, int, string。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="random"> 18. random 随机数生成器包,可以生成随机[]bytes, int, string。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/random"
@@ -1416,7 +1523,7 @@ import "github.com/duke-git/lancet/v2/random"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/random.md#RandNumberOfLength)]
[[play](https://go.dev/play/p/oyZbuV7bu7b)]
<h3 id="retry"> 18. retry 重试执行函数直到函数运行成功或被 context cancel。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="retry"> 19. retry 重试执行函数直到函数运行成功或被 context cancel。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/retry"
@@ -1448,7 +1555,7 @@ import "github.com/duke-git/lancet/v2/retry"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/retry.md#RetryWithExponentialWithJitterBackoff)]
[[play](https://go.dev/play/p/xp1avQmn16X)]
<h3 id="slice"> 19. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3>
<h3 id="slice"> 20. slice 包含操作切片的方法集合。&nbsp; &nbsp; &nbsp; &nbsp; <a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/slice"
@@ -1468,6 +1575,9 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>ContainSubSlice</big>** : 判断 slice 是否包含 subslice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainSubSlice)]
[[play](https://go.dev/play/p/bcuQ3UT6Sev)]
- **<big>ContainAny</big>** : 判断 slice 是否包含 targets 切片中的任意一个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainAny)]
[[play](https://go.dev/play/p/4xoxhc9XSSw)]
- **<big>Chunk</big>** : 按照 size 参数均分 slice。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Chunk)]
[[play](https://go.dev/play/p/b4Pou5j2L_C)]
@@ -1719,7 +1829,7 @@ import "github.com/duke-git/lancet/v2/slice"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ConcatBy)]
[[play](https://go.dev/play/p/6QcUpcY4UMW)]
<h3 id="stream"> 20. stream 流,该包仅验证简单的 stream 实现,功能有限。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="stream"> 21. stream 流,该包仅验证简单的 stream 实现,功能有限。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/stream"
@@ -1815,7 +1925,7 @@ import "github.com/duke-git/lancet/v2/stream"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#LastIndexOf)]
[[play](https://go.dev/play/p/CjeoNw2eac_G)]
<h3 id="structs"> 21. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="structs"> 22. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/structs"
@@ -1825,34 +1935,51 @@ import "github.com/duke-git/lancet/v2/structs"
- **<big>New</big>** : `Struct`结构体的构造函数。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#New)]
[[play](https://go.dev/play/p/O29l8kk-Z17)]
- **<big>ToMap</big>** : 将一个合法的 struct 对象转换为 map[string]any。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#ToMap)]
[[play](https://go.dev/play/p/qQbLySBgerZ)]
- **<big>Fields</big>** : 获取一个 struct 对象的属性列表。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/w3Kk_CyVY7D)]
- **<big>Field</big>** : 根据属性名获取一个 struct 对象的属性。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Fields)]
[[play](https://go.dev/play/p/KocZMSYarza)]
- **<big>IsStruct</big>** : 判断是否为一个合法的 struct 对象。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsStruct)]
[[play](https://go.dev/play/p/bU2FSdkbK1C)]
- **<big>Tag</big>** : 获取`Field``Tag`,默认的 tag key 是 json。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Tag)]
[[play](https://go.dev/play/p/DVrx5HvvUJr)]
- **<big>Name</big>** : 获取属性名。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Name)]
[[play](https://go.dev/play/p/zfIGlqsatee)]
- **<big>Value</big>** : 获取`Field`属性的值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Value)]
[[play](https://go.dev/play/p/qufYEU2o4Oi)]
- **<big>Kind</big>** : 获取属性 Kind。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#Kind)]
[[play](https://go.dev/play/p/wg4NlcUNG5o)]
- **<big>IsEmbedded</big>** : 判断属性是否为嵌入。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsEmbedded)]
[[play](https://go.dev/play/p/wV2PrbYm3Ec)]
- **<big>IsExported</big>** : 判断属性是否导出。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsExported)]
[[play](https://go.dev/play/p/csK4AXYaNbJ)]
- **<big>IsZero</big>** : 判断属性是否为零值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsZero)]
[[play](https://go.dev/play/p/RzqpGISf87r)]
- **<big>IsSlice</big>** : 判断属性是否是切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsSlice)]
[[play](https://go.dev/play/p/MKz4CgBIUrU)]
- **<big>IsTargetType</big>** : 判断属性是否是目标类型。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#IsTargetType)]
[[play](https://go.dev/play/p/Ig75P-agN39)]
- **<big>TypeName</big>** : 获取结构体类型名。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/struct.md#TypeName)]
[[play](https://go.dev/play/p/SWLWd0XBaBb)]
<h3 id="strutil"> 22. strutil 包含字符串处理的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="strutil"> 23. strutil 包含字符串处理的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/strutil"
@@ -2005,7 +2132,7 @@ import "github.com/duke-git/lancet/v2/strutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/strutil.md#FindAllOccurrences)]
[[play](https://go.dev/play/p/uvyA6azGLB1)]
<h3 id="system"> 23. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="system"> 24. system 包含 os, runtime, shell command 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/system"
@@ -2053,7 +2180,7 @@ import "github.com/duke-git/lancet/v2/system"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/system.md#GetProcessInfo)]
[[play](https://go.dev/play/p/NQDVywEYYx7)]
<h3 id="tuple"> 24. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="tuple"> 25. Tuple 包实现一个元组数据类型。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/tuple"
@@ -2170,7 +2297,7 @@ import "github.com/duke-git/lancet/v2/tuple"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/tuple.md#Unzip10)]
[[play](https://go.dev/play/p/-taQB6Wfre_z)]
<h3 id="validator"> 25. validator 验证器包,包含常用字符串格式验证函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="validator"> 26. validator 验证器包,包含常用字符串格式验证函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/validator"
@@ -2219,7 +2346,7 @@ import "github.com/duke-git/lancet/v2/validator"
[[play](https://go.dev/play/p/jlYApVLLGTZ)]
- **<big>IsEmail</big>** : 验证字符串是否是有效电子邮件地址。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmail)]
[[play](https://go.dev/play/p/Os9VaFlT33G)]
[[play](https://go.dev/play/p/HVQ5LAe-vFz)]
- **<big>IsEmptyString</big>** : 验证字符串是否是空字符串。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmptyString)]
[[play](https://go.dev/play/p/dpzgUjFnBCX)]
@@ -2310,8 +2437,14 @@ import "github.com/duke-git/lancet/v2/validator"
- **<big>IsChinaUnionPay</big>** : 检查字符串是否是有效的中国银联卡号。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChinaUnionPay)]
[[play](https://go.dev/play/p/yafpdxLiymu)]
- **<big>IsPassport</big>** : 判断护照(正则判断)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsPassport)]
[[play](https://go.dev/play/p/dvOiV2BW7Aw)]
- **<big>IsChineseHMPassport</big>** : 判断港澳台通行证(正则判断)。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsChineseHMPassport)]
[[play](https://go.dev/play/p/xKG6spQTcY0)]
<h3 id="xerror"> 26. xerror 包实现一些错误处理函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
<h3 id="xerror"> 27. xerror 包实现一些错误处理函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go
import "github.com/duke-git/lancet/v2/xerror"

View File

@@ -228,6 +228,42 @@ func ToPointer[T any](value T) *T {
return &value
}
// ToPointers convert a slice of values to a slice of pointers.
// Play: https://go.dev/play/p/ZUoXd2i5ZkV
func ToPointers[T any](values []T) []*T {
result := make([]*T, len(values))
for i := range values {
result[i] = &values[i]
}
return result
}
// FromPointer returns the value pointed to by the pointer.
// Play: https://go.dev/play/p/wAp90V7Zu6g
func FromPointer[T any](ptr *T) T {
if ptr == nil {
var zeroValue T
return zeroValue
}
return *ptr
}
// FromPointers convert a slice of pointers to a slice of values.
// Play: https://go.dev/play/p/qIPsyYtNy3Q
func FromPointers[T any](pointers []*T) []T {
result := make([]T, len(pointers))
for i, ptr := range pointers {
if ptr == nil {
var zeroValue T
result[i] = zeroValue
} else {
result[i] = *ptr
}
}
return result
}
// ToMap convert a slice of structs to a map based on iteratee function.
// Play: https://go.dev/play/p/tVFy7E-t24l
func ToMap[T any, K comparable, V any](array []T, iteratee func(T) (K, V)) map[K]V {
@@ -406,15 +442,15 @@ func ToStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.StdEncoding.EncodeToString(value.([]byte))
return base64.StdEncoding.EncodeToString(v)
case string:
return base64.StdEncoding.EncodeToString([]byte(value.(string)))
return base64.StdEncoding.EncodeToString([]byte(v))
case error:
return base64.StdEncoding.EncodeToString([]byte(value.(error).Error()))
return base64.StdEncoding.EncodeToString([]byte(v.Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -428,15 +464,15 @@ func ToUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.URLEncoding.EncodeToString(value.([]byte))
return base64.URLEncoding.EncodeToString(v)
case string:
return base64.URLEncoding.EncodeToString([]byte(value.(string)))
return base64.URLEncoding.EncodeToString([]byte(v))
case error:
return base64.URLEncoding.EncodeToString([]byte(value.(error).Error()))
return base64.URLEncoding.EncodeToString([]byte(v.Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -450,7 +486,7 @@ func ToRawStdBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.RawStdEncoding.EncodeToString(value.([]byte))
case string:
@@ -458,7 +494,7 @@ func ToRawStdBase64(value any) string {
case error:
return base64.RawStdEncoding.EncodeToString([]byte(value.(error).Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}
@@ -472,7 +508,7 @@ func ToRawUrlBase64(value any) string {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return ""
}
switch value.(type) {
switch v := value.(type) {
case []byte:
return base64.RawURLEncoding.EncodeToString(value.([]byte))
case string:
@@ -480,7 +516,7 @@ func ToRawUrlBase64(value any) string {
case error:
return base64.RawURLEncoding.EncodeToString([]byte(value.(error).Error()))
default:
marshal, err := json.Marshal(value)
marshal, err := json.Marshal(v)
if err != nil {
return ""
}

View File

@@ -169,6 +169,45 @@ func ExampleToPointer() {
// 123
}
func ExampleToPointers() {
strs := []string{"a", "b", "c"}
pointerStrs := ToPointers(strs)
fmt.Println(*pointerStrs[0])
fmt.Println(*pointerStrs[1])
fmt.Println(*pointerStrs[2])
// Output:
// a
// b
// c
}
func ExampleFromPointer() {
str := "abc"
strPtr := &str
result := FromPointer(strPtr)
fmt.Println(result)
// Output:
// abc
}
func ExampleFromPointers() {
strs := []string{"a", "b", "c"}
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
result := FromPointers(strPtr)
fmt.Println(result[0])
fmt.Println(result[1])
fmt.Println(result[2])
// Output:
// a
// b
// c
}
func ExampleToMap() {
type Message struct {
name string

View File

@@ -302,6 +302,75 @@ func TestToPointer(t *testing.T) {
assert.Equal(*result, 123)
}
func TestToPointers(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToPointers")
intVals := []int{1, 2, 3}
result := ToPointers(intVals)
assert.Equal(3, len(result))
assert.Equal(1, *result[0])
assert.Equal(2, *result[1])
assert.Equal(3, *result[2])
stringVals := []string{"a", "b", "c"}
resultStr := ToPointers(stringVals)
assert.Equal(3, len(resultStr))
assert.Equal("a", *resultStr[0])
assert.Equal("b", *resultStr[1])
assert.Equal("c", *resultStr[2])
}
func TestFromPointer(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFromPointer")
intVal := 123
pointer := &intVal
result := FromPointer(pointer)
assert.Equal(123, result)
stringVal := "abc"
stringPointer := &stringVal
resultStr := FromPointer(stringPointer)
assert.Equal("abc", resultStr)
}
func TestFromPointers(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestFromPointers")
intPointers := []*int{new(int), new(int), new(int)}
*intPointers[0] = 1
*intPointers[1] = 2
*intPointers[2] = 3
result := FromPointers(intPointers)
assert.Equal(3, len(result))
assert.Equal(1, result[0])
assert.Equal(2, result[1])
assert.Equal(3, result[2])
stringPointers := []*string{new(string), new(string), new(string)}
*stringPointers[0] = "a"
*stringPointers[1] = "b"
*stringPointers[2] = "c"
resultStr := FromPointers(stringPointers)
assert.Equal(3, len(resultStr))
assert.Equal("a", resultStr[0])
assert.Equal("b", resultStr[1])
assert.Equal("c", resultStr[2])
}
func TestEncodeByte(t *testing.T) {
t.Parallel()

View File

@@ -0,0 +1,71 @@
package cryptor_test
import (
"encoding/hex"
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func ExampleSm3() {
data := []byte("hello world")
hash := cryptor.Sm3(data)
fmt.Println(hex.EncodeToString(hash))
// Output:
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
}
func ExampleSm4EcbEncrypt() {
key := []byte("1234567890abcdef") // 16 bytes key
plaintext := []byte("hello world")
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
func ExampleSm4CbcEncrypt() {
key := []byte("1234567890abcdef") // 16 bytes key
plaintext := []byte("hello world")
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
func ExampleGenerateSm2Key() {
// Generate SM2 key pair
privateKey, err := cryptor.GenerateSm2Key()
if err != nil {
return
}
plaintext := []byte("hello world")
// Encrypt with public key
ciphertext, err := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
if err != nil {
return
}
// Decrypt with private key
decrypted, err := cryptor.Sm2Decrypt(privateKey, ciphertext)
if err != nil {
return
}
fmt.Println(string(decrypted))
// Output:
// hello world
}

251
cryptor/gm_sm2.go Normal file
View File

@@ -0,0 +1,251 @@
package cryptor
import (
"crypto/elliptic"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"math/big"
)
// SM2 implements the Chinese SM2 elliptic curve public key algorithm.
// SM2 is based on elliptic curve cryptography and provides encryption, decryption, signing and verification.
//
// Note: This implementation uses crypto/elliptic package methods (GenerateKey, ScalarBaseMult, ScalarMult, IsOnCurve)
// which are marked as deprecated in Go 1.20+. These methods still work correctly and are widely used.
// The //nolint:staticcheck directive suppresses deprecation warnings.
// A future version may replace these with a custom elliptic curve implementation.
var (
sm2P256 *sm2Curve
sm2P256Params = &elliptic.CurveParams{Name: "sm2p256v1"}
)
func init() {
// SM2 curve parameters
sm2P256Params.P, _ = new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
sm2P256Params.N, _ = new(big.Int).SetString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
sm2P256Params.B, _ = new(big.Int).SetString("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
sm2P256Params.Gx, _ = new(big.Int).SetString("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
sm2P256Params.Gy, _ = new(big.Int).SetString("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)
sm2P256Params.BitSize = 256
sm2P256 = &sm2Curve{sm2P256Params}
}
type sm2Curve struct {
*elliptic.CurveParams
}
// Sm2PrivateKey represents an SM2 private key.
type Sm2PrivateKey struct {
D *big.Int
PublicKey Sm2PublicKey
}
// Sm2PublicKey represents an SM2 public key.
type Sm2PublicKey struct {
X, Y *big.Int
}
// GenerateSm2Key generates a new SM2 private/public key pair.
// Play: https://go.dev/play/p/bKYMqRLvIx3
func GenerateSm2Key() (*Sm2PrivateKey, error) {
priv, x, y, err := elliptic.GenerateKey(sm2P256, rand.Reader)
if err != nil {
return nil, err
}
privateKey := &Sm2PrivateKey{
D: new(big.Int).SetBytes(priv),
PublicKey: Sm2PublicKey{
X: x,
Y: y,
},
}
return privateKey, nil
}
// Sm2Encrypt encrypts plaintext using SM2 public key.
// Returns ciphertext in the format: C1 || C3 || C2
// C1 = kG (65 bytes in uncompressed format)
// C3 = Hash(x2 || M || y2) (32 bytes for SM3)
// C2 = M xor t (same length as plaintext)
// Play: https://go.dev/play/p/bKYMqRLvIx3
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error) {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil, errors.New("sm2: invalid public key")
}
for {
// Generate random k
k, err := randFieldElement(sm2P256, rand.Reader)
if err != nil {
return nil, err
}
// C1 = kG
c1x, c1y := sm2P256.ScalarBaseMult(k.Bytes())
// kP = (x2, y2)
x2, y2 := sm2P256.ScalarMult(pub.X, pub.Y, k.Bytes())
// Derive key using KDF
kdfLen := len(plaintext)
t := sm2KDF(append(toBytes(sm2P256, x2), toBytes(sm2P256, y2)...), kdfLen)
// Check if t is all zeros
allZero := true
for _, b := range t {
if b != 0 {
allZero = false
break
}
}
if allZero {
continue
}
// C2 = M xor t
c2 := make([]byte, len(plaintext))
for i := 0; i < len(plaintext); i++ {
c2[i] = plaintext[i] ^ t[i]
}
// C3 = Hash(x2 || M || y2)
c3Input := append(toBytes(sm2P256, x2), plaintext...)
c3Input = append(c3Input, toBytes(sm2P256, y2)...)
c3 := Sm3(c3Input)
// Return C1 || C3 || C2
c1 := sm2MarshalUncompressed(sm2P256, c1x, c1y)
result := append(c1, c3...)
result = append(result, c2...)
return result, nil
}
}
// Sm2Decrypt decrypts ciphertext using SM2 private key.
// Expects ciphertext in the format: C1 || C3 || C2
// Play: https://go.dev/play/p/bKYMqRLvIx3
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error) {
if priv == nil || priv.D == nil {
return nil, errors.New("sm2: invalid private key")
}
// Parse C1 (65 bytes), C3 (32 bytes), C2 (remaining)
if len(ciphertext) < 97 {
return nil, errors.New("sm2: ciphertext too short")
}
c1 := ciphertext[:65]
c3 := ciphertext[65:97]
c2 := ciphertext[97:]
// Parse C1
c1x, c1y := sm2UnmarshalUncompressed(sm2P256, c1)
if c1x == nil {
return nil, errors.New("sm2: invalid C1 point")
}
// Verify C1 is on curve
if !sm2P256.IsOnCurve(c1x, c1y) {
return nil, errors.New("sm2: C1 not on curve")
}
// dC1 = (x2, y2)
x2, y2 := sm2P256.ScalarMult(c1x, c1y, priv.D.Bytes())
// Derive key using KDF
kdfLen := len(c2)
t := sm2KDF(append(toBytes(sm2P256, x2), toBytes(sm2P256, y2)...), kdfLen)
// M = C2 xor t
plaintext := make([]byte, len(c2))
for i := 0; i < len(c2); i++ {
plaintext[i] = c2[i] ^ t[i]
}
// Verify C3 = Hash(x2 || M || y2)
u := append(toBytes(sm2P256, x2), plaintext...)
u = append(u, toBytes(sm2P256, y2)...)
hash := Sm3(u)
for i := 0; i < len(c3); i++ {
if c3[i] != hash[i] {
return nil, errors.New("sm2: hash verification failed")
}
}
return plaintext, nil
}
// SM2 KDF (Key Derivation Function)
func sm2KDF(z []byte, klen int) []byte {
limit := (klen + 31) / 32
result := make([]byte, 0, limit*32)
for i := 1; i <= limit; i++ {
counter := make([]byte, 4)
binary.BigEndian.PutUint32(counter, uint32(i))
hash := Sm3(append(z, counter...))
result = append(result, hash...)
}
return result[:klen]
}
func toBytes(curve elliptic.Curve, value *big.Int) []byte {
byteLen := (curve.Params().BitSize + 7) / 8
buf := make([]byte, byteLen)
b := value.Bytes()
copy(buf[byteLen-len(b):], b)
return buf
}
func sm2MarshalUncompressed(curve *sm2Curve, x, y *big.Int) []byte {
byteLen := (curve.BitSize + 7) / 8
ret := make([]byte, 1+2*byteLen)
ret[0] = 4 // uncompressed point
xBytes := x.Bytes()
copy(ret[1+byteLen-len(xBytes):], xBytes)
yBytes := y.Bytes()
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
return ret
}
func sm2UnmarshalUncompressed(curve *sm2Curve, data []byte) (*big.Int, *big.Int) {
byteLen := (curve.BitSize + 7) / 8
if len(data) != 1+2*byteLen {
return nil, nil
}
if data[0] != 4 {
return nil, nil
}
x := new(big.Int).SetBytes(data[1 : 1+byteLen])
y := new(big.Int).SetBytes(data[1+byteLen:])
return x, y
}
func randFieldElement(c elliptic.Curve, rand io.Reader) (*big.Int, error) {
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err := io.ReadFull(rand, b)
if err != nil {
return nil, err
}
k := new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, big.NewInt(1))
k.Mod(k, n)
k.Add(k, big.NewInt(1))
return k, nil
}

211
cryptor/gm_sm3.go Normal file
View File

@@ -0,0 +1,211 @@
package cryptor
import (
"encoding/binary"
"hash"
)
// SM3 implements the Chinese SM3 cryptographic hash algorithm.
// SM3 produces a 256-bit (32-byte) hash value.
const (
sm3BlockSize = 64
sm3Size = 32
sm3T1 = 0x79cc4519
sm3T2 = 0x7a879d8a
)
var sm3IV = [8]uint32{
0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600,
0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e,
}
type sm3Digest struct {
h [8]uint32
x [sm3BlockSize]byte
nx int
len uint64
}
// Sm3 returns a new hash.Hash computing the SM3 checksum.
// Play: https://go.dev/play/p/zDAQpteAiOc
func Sm3(data []byte) []byte {
h := newSm3()
h.Write(data)
return h.Sum(nil)
}
func newSm3() hash.Hash {
d := new(sm3Digest)
d.Reset()
return d
}
func (d *sm3Digest) Reset() {
d.h = sm3IV
d.nx = 0
d.len = 0
}
func (d *sm3Digest) Size() int {
return sm3Size
}
func (d *sm3Digest) BlockSize() int {
return sm3BlockSize
}
func (d *sm3Digest) Write(p []byte) (nn int, err error) {
nn = len(p)
d.len += uint64(nn)
if d.nx > 0 {
n := copy(d.x[d.nx:], p)
d.nx += n
if d.nx == sm3BlockSize {
sm3Block(d, d.x[:])
d.nx = 0
}
p = p[n:]
}
if len(p) >= sm3BlockSize {
n := len(p) &^ (sm3BlockSize - 1)
sm3Block(d, p[:n])
p = p[n:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d *sm3Digest) Sum(in []byte) []byte {
d0 := *d
hash := d0.checkSum()
return append(in, hash[:]...)
}
func (d *sm3Digest) checkSum() [sm3Size]byte {
len := d.len
var tmp [64]byte
tmp[0] = 0x80
if len%64 < 56 {
d.Write(tmp[0 : 56-len%64])
} else {
d.Write(tmp[0 : 64+56-len%64])
}
len <<= 3
binary.BigEndian.PutUint64(tmp[:], len)
d.Write(tmp[0:8])
if d.nx != 0 {
panic("d.nx != 0")
}
var digest [sm3Size]byte
for i := 0; i < 8; i++ {
binary.BigEndian.PutUint32(digest[i*4:], d.h[i])
}
return digest
}
func sm3Block(dig *sm3Digest, p []byte) {
var w [68]uint32
var w1 [64]uint32
h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7]
for len(p) >= sm3BlockSize {
for i := 0; i < 16; i++ {
j := i * 4
w[i] = binary.BigEndian.Uint32(p[j : j+4])
}
for i := 16; i < 68; i++ {
w[i] = sm3P1(w[i-16]^w[i-9]^sm3RotateLeft(w[i-3], 15)) ^ sm3RotateLeft(w[i-13], 7) ^ w[i-6]
}
for i := 0; i < 64; i++ {
w1[i] = w[i] ^ w[i+4]
}
A, B, C, D, E, F, G, H := h0, h1, h2, h3, h4, h5, h6, h7
for j := 0; j < 64; j++ {
var ss1, ss2, tt1, tt2, t uint32
if j < 16 {
t = sm3T1
} else {
t = sm3T2
}
ss1 = sm3RotateLeft(sm3RotateLeft(A, 12)+E+sm3RotateLeft(t, uint32(j%32)), 7)
ss2 = ss1 ^ sm3RotateLeft(A, 12)
if j < 16 {
tt1 = sm3FF0(A, B, C) + D + ss2 + w1[j]
tt2 = sm3GG0(E, F, G) + H + ss1 + w[j]
} else {
tt1 = sm3FF1(A, B, C) + D + ss2 + w1[j]
tt2 = sm3GG1(E, F, G) + H + ss1 + w[j]
}
D = C
C = sm3RotateLeft(B, 9)
B = A
A = tt1
H = G
G = sm3RotateLeft(F, 19)
F = E
E = sm3P0(tt2)
}
h0 ^= A
h1 ^= B
h2 ^= C
h3 ^= D
h4 ^= E
h5 ^= F
h6 ^= G
h7 ^= H
p = p[sm3BlockSize:]
}
dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7
}
func sm3RotateLeft(x, n uint32) uint32 {
return (x << n) | (x >> (32 - n))
}
func sm3P0(x uint32) uint32 {
return x ^ sm3RotateLeft(x, 9) ^ sm3RotateLeft(x, 17)
}
func sm3P1(x uint32) uint32 {
return x ^ sm3RotateLeft(x, 15) ^ sm3RotateLeft(x, 23)
}
func sm3FF0(x, y, z uint32) uint32 {
return x ^ y ^ z
}
func sm3FF1(x, y, z uint32) uint32 {
return (x & y) | (x & z) | (y & z)
}
func sm3GG0(x, y, z uint32) uint32 {
return x ^ y ^ z
}
func sm3GG1(x, y, z uint32) uint32 {
return (x & y) | (^x & z)
}

270
cryptor/gm_sm4.go Normal file
View File

@@ -0,0 +1,270 @@
package cryptor
import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io"
)
// SM4 implements the Chinese SM4 block cipher.
// SM4 is a 128-bit block cipher with 128-bit keys.
// This implementation uses pre-computed lookup tables for optimal performance.
const sm4BlockSize = 16
// Pre-computed T-transformation lookup tables for performance optimization
var sm4T1Table [256][4]uint32 // S-box + L1 transformation
var sm4T2Table [256][4]uint32 // S-box + L2 transformation
var sm4Sbox = [256]byte{
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
}
var sm4FK = [4]uint32{0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc}
var sm4CK = [32]uint32{
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
}
// 初始化预计算查找表
func init() {
// Pre-compute all possible T1 and T2 transformations
for pos := 0; pos < 4; pos++ {
for i := 0; i < 256; i++ {
// S-box 替换
sboxVal := sm4Sbox[i]
// 根据字节位置计算偏移
shift := uint32((3 - pos) * 8)
b := uint32(sboxVal) << shift
// L1 变换b ^ ROL(b,2) ^ ROL(b,10) ^ ROL(b,18) ^ ROL(b,24)
sm4T1Table[i][pos] = b ^ sm4RotateLeft(b, 2) ^ sm4RotateLeft(b, 10) ^ sm4RotateLeft(b, 18) ^ sm4RotateLeft(b, 24)
// L2 变换b ^ ROL(b,13) ^ ROL(b,23)
sm4T2Table[i][pos] = b ^ sm4RotateLeft(b, 13) ^ sm4RotateLeft(b, 23)
}
}
}
type sm4Cipher struct {
enc [32]uint32
dec [32]uint32
}
// Sm4EcbEncrypt encrypts data using SM4 in ECB mode.
// key must be 16 bytes.
// Play: https://go.dev/play/p/l5IQxYuuaED
func Sm4EcbEncrypt(data, key []byte) []byte {
if len(key) != 16 {
panic("sm4: key length must be 16 bytes")
}
c := newSm4Cipher(key)
padded := pkcs7Padding(data, sm4BlockSize)
encrypted := make([]byte, len(padded))
for i := 0; i < len(padded); i += sm4BlockSize {
c.Encrypt(encrypted[i:i+sm4BlockSize], padded[i:i+sm4BlockSize])
}
return encrypted
}
// Sm4EcbDecrypt decrypts data using SM4 in ECB mode.
// key must be 16 bytes.
// Play: https://go.dev/play/p/l5IQxYuuaED
func Sm4EcbDecrypt(encrypted, key []byte) []byte {
if len(key) != 16 {
panic("sm4: key length must be 16 bytes")
}
if len(encrypted)%sm4BlockSize != 0 {
panic("sm4: encrypted data length must be multiple of block size")
}
c := newSm4Cipher(key)
decrypted := make([]byte, len(encrypted))
for i := 0; i < len(encrypted); i += sm4BlockSize {
c.Decrypt(decrypted[i:i+sm4BlockSize], encrypted[i:i+sm4BlockSize])
}
return pkcs7UnPadding(decrypted)
}
// Sm4CbcEncrypt encrypts data using SM4 in CBC mode.
// key must be 16 bytes.
// Play: https://go.dev/play/p/65Q6iYhLRTa
func Sm4CbcEncrypt(data, key []byte) []byte {
if len(key) != 16 {
panic("sm4: key length must be 16 bytes")
}
c := newSm4Cipher(key)
padded := pkcs7Padding(data, sm4BlockSize)
iv := make([]byte, sm4BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("sm4: failed to generate IV: " + err.Error())
}
encrypted := make([]byte, len(padded))
mode := cipher.NewCBCEncrypter(c, iv)
mode.CryptBlocks(encrypted, padded)
return append(iv, encrypted...)
}
// Sm4CbcDecrypt decrypts data using SM4 in CBC mode.
// key must be 16 bytes.
// Play: https://go.dev/play/p/65Q6iYhLRTa
func Sm4CbcDecrypt(encrypted, key []byte) []byte {
if len(key) != 16 {
panic("sm4: key length must be 16 bytes")
}
if len(encrypted) < sm4BlockSize {
panic("sm4: encrypted data too short")
}
if len(encrypted)%sm4BlockSize != 0 {
panic("sm4: encrypted data length must be multiple of block size")
}
c := newSm4Cipher(key)
iv := encrypted[:sm4BlockSize]
ciphertext := encrypted[sm4BlockSize:]
decrypted := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(c, iv)
mode.CryptBlocks(decrypted, ciphertext)
return pkcs7UnPadding(decrypted)
}
func newSm4Cipher(key []byte) *sm4Cipher {
c := &sm4Cipher{}
var mk [4]uint32
for i := 0; i < 4; i++ {
mk[i] = binary.BigEndian.Uint32(key[i*4 : (i+1)*4])
}
var k [36]uint32
k[0] = mk[0] ^ sm4FK[0]
k[1] = mk[1] ^ sm4FK[1]
k[2] = mk[2] ^ sm4FK[2]
k[3] = mk[3] ^ sm4FK[3]
for i := 0; i < 32; i++ {
k[i+4] = k[i] ^ sm4T2Fast(k[i+1]^k[i+2]^k[i+3]^sm4CK[i])
c.enc[i] = k[i+4]
}
for i := 0; i < 32; i++ {
c.dec[i] = c.enc[31-i]
}
return c
}
func (c *sm4Cipher) BlockSize() int {
return sm4BlockSize
}
func (c *sm4Cipher) Encrypt(dst, src []byte) {
if len(src) < sm4BlockSize {
panic("sm4: input not full block")
}
if len(dst) < sm4BlockSize {
panic("sm4: output not full block")
}
// 使用局部变量避免数组分配,提升性能
x0 := binary.BigEndian.Uint32(src[0:4])
x1 := binary.BigEndian.Uint32(src[4:8])
x2 := binary.BigEndian.Uint32(src[8:12])
x3 := binary.BigEndian.Uint32(src[12:16])
// 32 轮加密
for i := 0; i < 32; i++ {
t := x1 ^ x2 ^ x3 ^ c.enc[i]
x0 ^= sm4T1Fast(t)
x0, x1, x2, x3 = x1, x2, x3, x0
}
binary.BigEndian.PutUint32(dst[0:4], x3)
binary.BigEndian.PutUint32(dst[4:8], x2)
binary.BigEndian.PutUint32(dst[8:12], x1)
binary.BigEndian.PutUint32(dst[12:16], x0)
}
func (c *sm4Cipher) Decrypt(dst, src []byte) {
if len(src) < sm4BlockSize {
panic("sm4: input not full block")
}
if len(dst) < sm4BlockSize {
panic("sm4: output not full block")
}
x0 := binary.BigEndian.Uint32(src[0:4])
x1 := binary.BigEndian.Uint32(src[4:8])
x2 := binary.BigEndian.Uint32(src[8:12])
x3 := binary.BigEndian.Uint32(src[12:16])
// 32 轮解密
for i := 0; i < 32; i++ {
t := x1 ^ x2 ^ x3 ^ c.dec[i]
x0 ^= sm4T1Fast(t)
x0, x1, x2, x3 = x1, x2, x3, x0
}
binary.BigEndian.PutUint32(dst[0:4], x3)
binary.BigEndian.PutUint32(dst[4:8], x2)
binary.BigEndian.PutUint32(dst[8:12], x1)
binary.BigEndian.PutUint32(dst[12:16], x0)
}
// 使用预计算查找表的快速 T1 变换(用于加密轮函数)
func sm4T1Fast(a uint32) uint32 {
return sm4T1Table[byte(a>>24)][0] ^
sm4T1Table[byte(a>>16)][1] ^
sm4T1Table[byte(a>>8)][2] ^
sm4T1Table[byte(a)][3]
}
// 使用预计算查找表的快速 T2 变换(用于密钥扩展)
func sm4T2Fast(a uint32) uint32 {
return sm4T2Table[byte(a>>24)][0] ^
sm4T2Table[byte(a>>16)][1] ^
sm4T2Table[byte(a>>8)][2] ^
sm4T2Table[byte(a)][3]
}
func sm4RotateLeft(x uint32, n uint32) uint32 {
return (x << n) | (x >> (32 - n))
}

163
cryptor/gm_test.go Normal file
View File

@@ -0,0 +1,163 @@
package cryptor
import (
"encoding/hex"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestSm3(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm3")
tests := []struct {
input string
expected string
}{
{
input: "abc",
expected: "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0",
},
{
input: "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
expected: "debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732",
},
{
input: "",
expected: "1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b",
},
}
for _, tt := range tests {
result := Sm3([]byte(tt.input))
resultHex := hex.EncodeToString(result)
assert.Equal(tt.expected, resultHex)
}
}
func TestSm4EcbEncryptDecrypt(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm4EcbEncryptDecrypt")
key := []byte("1234567890abcdef") // 16 bytes
plaintext := []byte("Hello, SM4!")
// Encrypt
encrypted := Sm4EcbEncrypt(plaintext, key)
assert.IsNotNil(encrypted)
// Decrypt
decrypted := Sm4EcbDecrypt(encrypted, key)
assert.Equal(plaintext, decrypted)
}
func TestSm4CbcEncryptDecrypt(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm4CbcEncryptDecrypt")
key := []byte("1234567890abcdef") // 16 bytes
plaintext := []byte("Hello, SM4 CBC mode!")
// Encrypt
encrypted := Sm4CbcEncrypt(plaintext, key)
assert.IsNotNil(encrypted)
// Decrypt
decrypted := Sm4CbcDecrypt(encrypted, key)
assert.Equal(plaintext, decrypted)
}
func TestSm4EcbWithLongData(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm4EcbWithLongData")
key := []byte("1234567890abcdef")
plaintext := []byte("This is a longer message that spans multiple blocks for SM4 encryption testing.")
encrypted := Sm4EcbEncrypt(plaintext, key)
decrypted := Sm4EcbDecrypt(encrypted, key)
assert.Equal(plaintext, decrypted)
}
func TestSm2EncryptDecrypt(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm2EncryptDecrypt")
// Generate key pair
privateKey, err := GenerateSm2Key()
assert.IsNil(err)
assert.IsNotNil(privateKey)
plaintext := []byte("Hello, SM2!")
// Encrypt with public key
ciphertext, err := Sm2Encrypt(&privateKey.PublicKey, plaintext)
assert.IsNil(err)
assert.IsNotNil(ciphertext)
// Decrypt with private key
decrypted, err := Sm2Decrypt(privateKey, ciphertext)
assert.IsNil(err)
assert.Equal(plaintext, decrypted)
}
func TestSm2WithLongData(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm2WithLongData")
privateKey, err := GenerateSm2Key()
assert.IsNil(err)
plaintext := []byte("This is a longer message for SM2 encryption testing. " +
"SM2 is an elliptic curve public key cryptography algorithm.")
ciphertext, err := Sm2Encrypt(&privateKey.PublicKey, plaintext)
assert.IsNil(err)
decrypted, err := Sm2Decrypt(privateKey, ciphertext)
assert.IsNil(err)
assert.Equal(plaintext, decrypted)
}
func TestSm4InvalidKeyLength(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm4InvalidKeyLength")
defer func() {
if r := recover(); r != nil {
assert.IsNotNil(r)
}
}()
key := []byte("short")
plaintext := []byte("test")
Sm4EcbEncrypt(plaintext, key) // Should panic
}
func TestSm2InvalidInput(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSm2InvalidInput")
// Test with nil public key
_, err := Sm2Encrypt(nil, []byte("test"))
assert.IsNotNil(err)
// Test with nil private key
_, err = Sm2Decrypt(nil, []byte("test"))
assert.IsNotNil(err)
// Test with invalid ciphertext
privateKey, _ := GenerateSm2Key()
_, err = Sm2Decrypt(privateKey, []byte("short"))
assert.IsNotNil(err)
}

View File

@@ -1,51 +1,51 @@
-----BEGIN rsa private key-----
MIIJKAIBAAKCAgEAw0anfgtraA2uaZwoLpLBvo1EkfYvDBgeXoMQ4WMKbcw6jU8k
18E+f3WM52I2RssEk6g0X1vIiarHZU5qyxbv2iNoT7EfgizzlXYvx06pM69GNBQr
V46+lNhiOv4eeLZcOHBnxAyizlIKyLuwO+C1cX/6BxuXjX3ogw+6IaBFZN/EOMmT
Wc6sPKrnNCEqgCNiTbPAXb+N8j5Iv8QPs0AwVTB3jC9LP0mRt2GGD0cu+QIZkoGE
hyW9Dd+0tgeIK32uXCF8uDjNpUV1TxCdn5H+lvgddxfiaIJAdY5UaTx48XRIdULh
QrVKXJNyTYWTXbezViYIf1nXOdDI2hsGgKTfNVCocDT1LcT5zebijFHVCWfAAKEP
RLdzPZomkFIa3rQQgChgePzE8Oqsaxuwx8sU09NAo3giSq1OmBBwly7h60Lwtm8+
XuwmWtEgZoSQy2Hm8UMAJ3guotfYuS8mPQBi55JZpdqVN0D5SdwuWXhHpO4xDodN
YAoMMei1NNgrX2zN3FCS8PVi97wD6cQ8xsWM62nByrzIjmPwTfoCb3qP/928FMJM
g7b3bYBrUnOrpVWZfAIDs4H7DoP8VLL4rMgB5PuWNQ13gtX7y5ZOyLhopiKLnar9
XiN8GAMANBdKRI2anGEozrfJoelJ5POXZwSatRjbL+nWO3YnBzEunCw1xmMCAwEA
AQKCAgBAYYABP3SW5sPVD+XzjPERiPPNh7P1MdJ5aI7dMFEU6Bt50VkdRRn83d2p
v6iTaIXGxNMXiWQxdzusO9FbyeEkMz5F3+i6e2WHpmKUPGvunV/w9aFgibBt1HV2
a6fSNpVrCiw758qZaVUi3zZ4V1qa5A2j4EX0IUnSRBIi2ftnCZtg+Zx6JHiGu/Xk
KvcfLgtQAO5wOiJrdnt3tgVTHNuSipsvfbw6TmAbbKzNRrPG5xlVQxxVjmypMVMc
HJmZdSNSPrwm5JtwXNkTSzAclv6v+XeFdztvJ1pnJ5jO5WAegy8MchNgcfLlWLt7
sYlngZQ/1+Q/UHh0GFDQD87yBOmNz8KK5n+5gWB5iumdJ4BHTgUOfXpWilwb0JWG
r7ctqCYrbXXTvsIbRl47zGPzEsbs0mSLAuLzZ3IQ60uaYRt322bqzZQNBwJcUXYM
lRb6nc9BVAzqhvUemOlACbYlqXENmQy/Nz14nsNdy4Qrynsfon92dRZ0m+9rJ9Hj
99K4CNPz0FdayC7jTL76b8QEzoF2MGiKIL5yQYXm9Pt9p0g8g9sER7G7UyrMFqtl
tfylkAWRX5hgDCwQQ/Gqefn7xb/kG/4D1FBE5i2yU4tYw5NCzENo8Y3mUhBqiQql
G33sSv2JK/woxRWSbyGLfu1Iq2L8H/q4CdN55xat2iKbpL8omQKCAQEA6qX7V8A0
uCg0E/Uid6/Xma0dvZ7e5BMKcx0uElSUhUpB6JnEQRlgljRXF/Op8Iae09Gox6bZ
nU5Ow61wtSrnJY+f/2RVs9xYB+SSO0L0yE/XPKsBveH0dH9R4BWmH+KZ3sLaYovs
ZDsApR782Zh+1TthUT2s4vZ0G25f46xsjKpUzQLmgWeC3UEOThtQo/UZzLeprImI
fijMw+5jYUgHSXN80BXO56JzHQJU6SIDmA4BrlD0qPaDyzLVdNhG/nIbYKvf120p
ogWqEYIgVN4KyjLsvVgfxCEF4Ucwov9VCNgsVTlEtYWzAXEXqf2AW1j7Sh4GlVOz
W4UsfiGaSCjM7wKCAQEA1QuDLQ4cf4UEKsdPOnDYrkHwx6JBBHpkyZZcLhlT/S5A
AcvVcEJjeseNfCEexO7SChenlRQEVB8iXO28ZEumWePEz2JK9rq9p7AItk+ba/D9
qzfvZ/XE+1xs5szTfwr12Of8b9dXxhoW8gKcFPnKOHxvua6SyocmRlnZtaJRFZ7x
RxOZhfWoOUnc+ySYKhKyuipKR4KmyDd2d2ovxptlMFnj2RJzfjUIZiQpKTa8kXf7
sYaOgFiNC0AFAs9ZLCEX3NYTKpgVbVKNIaKtNj8GIAG2YPnT/VcbQtj9ULyJcvEw
IdzJXn+Cv6ie1nP05P+eo/gtGmm5okXzMQNv0wcFzQKCAQBmDVBWJtMG8P1NXMTj
1wdm3+LacHkyKpHV5O//qud5XQVzO0UepwHZ8eObGC9l27bCGyJTyt5ESyV4dztY
n9MuA9wrQCEB+6gRrrhmq8U4RXkv+pPkWJxv+lvKoL/CiFQxjP9b8s0Z/otWRTbl
ECzBYnT911wUzelLcOKla30+ZGpDS6qixzkkL0IgeELHPDc/UPWrg5lofSgpYsm4
KpJ4wJCdE48MMRvtlvEE//UeMaFLhgwSXDyPqIkrq1CdI1WC4t2UnPaJb/s6aCTV
pEh/DkzmQKh4LYCYLNUbXv9FvHbzjdezNvXWf7AyD32+vOF1p79nPKL5/96M8OJf
1dbjAoIBABKld02yNnxSwBKebyjGR7C4xMI0SUyDCd868cZ3IQq/yYpetMemh95v
KMr8exzxaiDIATrjDZ3vO6q2hA6jMGQds1QTXkxJ+995YMnUHd5MsWcS9jk7IYp+
hGmO89PiubHKXCXNyzjjf66e29paIoDfI0g1J1PikE8H/i4Pjtk9mBCIfp9i6N5a
wKSah1bnXA0/NlEb9kz/zbaV7KiNYUXiGDcfjkw1iA6oi5G34Lk6ryTSihZhqbaa
W9XrH/rkypnhgrvvo7B10TRocJCW44pZnATQ2OULgq9PHpy6Y61Tvsq38Ef9EQyF
TaGndH+2f8QKLKhrKHwzcx2PF3J44uECggEBAM0UGu/Aj4tIRmrcuPGHypkcxMY6
BS2irwiVD9/8Xnx0/r8RSnBuAXEUY8wTrP0GqGm9PZjFCXKyxk3gi6SkahTu6/SF
WecgomVnONI+ivpHRLmRXTTPEv7iu1F+2jgVQyg0mOR5WLE0r25S6NS6IlnnrTSo
QuIJa1wRIfyXrMpYk77YIOny+mYB4FYr25tChgieQGR4m3dlZICPYqOyFh9GORZ8
k1cVboGtKGYtAemzAh/PyUp716tMz44fnnHPzINUFI3ucybqUwpGiR9s0E3L+GsV
3h7a2v90RdyWcuAPJL0B5FL5NoHhOMYb1rCMu00FyqCKqXCgte2w2psOP60=
MIIJKQIBAAKCAgEAuzBz+aC+e7Lvny2zYlcyAfG6AAtPkxZqJ9JkYkM+0CP87pe0
xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUoj/TWY/xpoPNXXzz/5dz3u6r/A5Tu
mJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgfm4AZOXd/plzBkTrnu6lKG/rH9cnr
2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzvjLJWmJ08VrnF9PFtWhL939xSAIic
FzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5YlWmuFJ8YKvwR0+4XIRLX9qsMy7q
PTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoCLHLqNKf/dD+4vXxgS+f1ObfOcJpD
qBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8jIvt2FutT4hsNSd0L6c2mfo5mTno
DOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a+kTiW8BedNqcRnVVGT9/OS/ggyKF
wxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30znguCdaTTViA9UbrjtcE2bZtnqOMAU2s
08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg5hf7ft6FA+bXB9DHwGdv/UyrGr88
nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UWDcHAyc+Zv+te0DqPUNcAW5ECAwEA
AQKCAgBLJ0Do0Cip8UVTWz3SFb/2F97dda0VGMK2CjpTWTw2xLwf7ric9MesIi3k
fBgLhzUduaiGqxD7gSuIcc8/na4TXfFVY1nlTM2fZxY2a2jq59RK09iXXcwanM9y
8YPAgpfPI4Jq6Sm5D+aGGKvAlzvZaqy17cxKNqNgc43mQimG4kC15cPTfaIFmkXl
doJIbJoWlkzVzNWKuzDp06jBhmeGzXMHAtne1+cqWGPW7hkPb51cqXxBs/gOtkiH
QAmliMG9HCvHDnoXbk1K/XolD3aWjFzLVBKrnVxyxQb33gWFDn5kbkmNGshaVDuC
EqYsMYJ9U4HLNGTdJXlaY4izGe+UyExET5p2KYKC9S34jMvR5k9Hf4pATSUYRhjL
t/EV8EZlWCJGvGRAdtlKNLjRuIAiMTofUZca+sCHDvdcv8+/imOnKCXOETtcOHzw
I7MRdIi2JigcBKuKaua9H77cEuvwG9Bb7aLbqQ3XM5JhoBEBe2jHG39GYDAAjEWz
XWo2ri8rkU0nhixN26x7DXCfMewxZ/zc4czBTU2giM0Yrh4BMpRpHnw14QXRb58y
eTD7GVrC9g1/6HXsAzzBfKyTMZhZhmfjcgSuYMUbzwIvvttJQtw5Ic/LJmR1Eg2F
YZ3mUmwJwDEPyVlV2mXNUYYa64v7O3h+NsjXukWXw080fWdsoQKCAQEA8ZcVn863
wXQVex7RcXs5frdnKEtHx6V6tXXq4tvK71Jbkny3gOmPqwwEF0fk4m2Fo07CmJkX
t5o0tbPxfVbxeWGRGubAstjd5oWgt6nMAgcEkRbAzYM8qLGAGekS4g5+2/SjrQhG
oR2phBv+T9w/Oglf6mVzc8YDNP9B0PB0CTICTYhej16Qhc/jpFmXkjXfslGlUp0F
WVkNE7BZEk/fNgCbmAV1hCcDt7MwoOYBqGBoWb3tRKNhtBIDfJY1LVPjB6Jo3FWl
nolJ1v1In9MhsNudZ6QlYbO8uMadsx3a1Flsu/w69TT+sPjmw+GoSzGuMlH15cFY
qZZ6k75WmwyRGwKCAQEAxlq0SEK86+5zAIvVRQI5pQk0HEGy0dtcUgwBhpy13Bga
sCezorJwS1tEHXfWYMtwmHytMXbySnFQEx5jJLFaQhPfOHybHV94fqq+qcC+NuEt
z2KMoQG+zlupH5LwZv3RzzMSng0AuxNaiPx/tXfXM+5O19wb8VKu7X+hkgOW+psu
wGnofT1zYTCWEbRPZENSL6Mi8BShwu3UIMFhKhVZJZH6MOSU/AoULv49ije39Z58
B06IERBIGpM6FE6L73BHphbUh9Osr/I9vbi7zCzt/utQ1uzMzzxxJjjadYf1K7xa
MYsmtKJ85+dG2/WOw6bRSGk1Dw9KqUBqHQ7bwXq8wwKCAQEAtrCwuotg69qzz8oL
SgyL+uYIDTF4U2Iwu/4ypGDfQkD+XHURc1uruAY7JbvJOuzlbQxHHYxPohjrmSg9
CrJvooGEcFplCBn1G7ibQ6gUTMgvzOPu4rpGaa7oly9ohye9COojx9qFRpsesHdW
xd9gtKuYK7GSL89iZ3ZLuAvNQ5LcqPLhxvsUwQvnMkZJ11gEFF2nbiStgdZUjDoD
8VQTEEw/XSNrrYavSgAoWtP0FvbokkyMmyYN4VTp7BHOnrtb6E8Jiuz9dDiPbRNW
Ev5e8NXyXwiC+DIqGXSglm2SKJiDIFjp4Lm1i/B82U3QrSQhfY37LEYcnQndIdKC
vXcwVwKCAQA0K2UhYFQ6JYQfz6dvOA+bRZlsGSeEJJLajYfVNOBsG/bhAAAyOYZp
e36l1YAQA1IA+UHAMc22IKlz7dkbrH3VxU4/mB5gEl0py5TMJwKggoc+9WeRbVkX
A2qvAEG0hOuq+H7cDQV1LrjwMKESRIvYf8RC6AR9a0bQ9nGzarhJ/4jDWNeqIQB4
voOp8mezMjWqi9jDllmZYF4bo2D/5Y+F3ygTtfstcyUt2vaqpM8AjgeHEHOfMU4V
l0V+U85gUoK1v2l0tArGWAs/HBhgsiyCkLe5X5zaoMYNzIRAx1qHf0mloDi058u8
Xsr3TVWYRgbjabBn3pi/fU6rh93qvHJrAoIBAQCHNkNclB3UMBoZv2Dnd2ZwUZnP
BzN6pq/NjG4GnjSCxPYwYnvB1TakTVLTYOjMS/7TCsFqPFSi93mjDsZ+0ZHBUZOH
076AdxzFD+WxxYZ2+vl3iRhgERY3LgqHOBTt7O1/OYcNraJ6Pk2ppU/PqYaVb389
2dsgqEEbO0np59I2+BP6giIFr3L2xKk2CRdVLKqCQ8FNx2xpi+1kcJlUW+ZxPiwu
JaOA/mNHOz3Kq4DDE+1XjvKq602zm7D1oG67xM/gE5UV/KT7auI9M+zQcTPWZ3T6
cl5A3z4tP9KdWJInadUQyOrVIHCbqxIlZCQjYFo2m9HzFr6fWbgtWC+IyGJQ
-----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw0anfgtraA2uaZwoLpLB
vo1EkfYvDBgeXoMQ4WMKbcw6jU8k18E+f3WM52I2RssEk6g0X1vIiarHZU5qyxbv
2iNoT7EfgizzlXYvx06pM69GNBQrV46+lNhiOv4eeLZcOHBnxAyizlIKyLuwO+C1
cX/6BxuXjX3ogw+6IaBFZN/EOMmTWc6sPKrnNCEqgCNiTbPAXb+N8j5Iv8QPs0Aw
VTB3jC9LP0mRt2GGD0cu+QIZkoGEhyW9Dd+0tgeIK32uXCF8uDjNpUV1TxCdn5H+
lvgddxfiaIJAdY5UaTx48XRIdULhQrVKXJNyTYWTXbezViYIf1nXOdDI2hsGgKTf
NVCocDT1LcT5zebijFHVCWfAAKEPRLdzPZomkFIa3rQQgChgePzE8Oqsaxuwx8sU
09NAo3giSq1OmBBwly7h60Lwtm8+XuwmWtEgZoSQy2Hm8UMAJ3guotfYuS8mPQBi
55JZpdqVN0D5SdwuWXhHpO4xDodNYAoMMei1NNgrX2zN3FCS8PVi97wD6cQ8xsWM
62nByrzIjmPwTfoCb3qP/928FMJMg7b3bYBrUnOrpVWZfAIDs4H7DoP8VLL4rMgB
5PuWNQ13gtX7y5ZOyLhopiKLnar9XiN8GAMANBdKRI2anGEozrfJoelJ5POXZwSa
tRjbL+nWO3YnBzEunCw1xmMCAwEAAQ==
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuzBz+aC+e7Lvny2zYlcy
AfG6AAtPkxZqJ9JkYkM+0CP87pe0xOXQh4dz9iJekOwAq7FKpasUEUzkTm6Z0PUo
j/TWY/xpoPNXXzz/5dz3u6r/A5TumJ6BmX/7K/x8FsokIeP+lWaN1l+7uBKK8rgf
m4AZOXd/plzBkTrnu6lKG/rH9cnr2leKWDqk2jcG6r15r/07MdStpWgt0OBYoHzv
jLJWmJ08VrnF9PFtWhL939xSAIicFzJ0T9fAdmSSYmg22mKgN1zeWtZndJ/Ejv+5
YlWmuFJ8YKvwR0+4XIRLX9qsMy7qPTh7zsZhgtKzyb7qvVqDh2pmwekGJcwxGcoC
LHLqNKf/dD+4vXxgS+f1ObfOcJpDqBajD/U6BXtrZ/p4cvoYnZA383YR/CRG/nJ8
jIvt2FutT4hsNSd0L6c2mfo5mTnoDOEL3mkreZ4Az+GE57jMw1Ia9jwkM1QQoy0a
+kTiW8BedNqcRnVVGT9/OS/ggyKFwxJ/Xh1DfxZXAuyCBRUJUyVl9YCr2y30zngu
CdaTTViA9UbrjtcE2bZtnqOMAU2s08F0IiaGLKKMhrxUoXLgngXSX7gomC4aEfcg
5hf7ft6FA+bXB9DHwGdv/UyrGr88nve5um1OT5kmyOujKpka4QZ5/rU+RznBE0UW
DcHAyc+Zv+te0DqPUNcAW5ECAwEAAQ==
-----END rsa public key-----

View File

@@ -114,6 +114,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
],
},
{ text: 'datetime', link: '/en/api/packages/datetime' },
{ text: 'enum', link: '/en/api/packages/enum' },
{ text: 'eventbus', link: '/en/api/packages/eventbus' },
{ text: 'fileutil', link: '/en/api/packages/fileutil' },
{ text: 'formatter', link: '/en/api/packages/formatter' },
@@ -128,9 +129,9 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: 'stream', link: '/en/api/packages/stream' },
{ text: 'struct', link: '/en/api/packages/struct' },
{ text: 'strutil', link: '/en/api/packages/strutil' },
{ text: 'system', link: '/en/api/packages/system' },
{ text: 'tuple', link: '/en/api/packages/tuple' },
{ text: 'validator', link: '/en/api/packages/validator' },
{ text: 'system', link: '/en/api/packages/system' },
{ text: 'xerror', link: '/en/api/packages/xerror' },
],
},

View File

@@ -128,7 +128,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
{ text: '日期&时间', link: '/api/packages/datetime' },
{ text: '事件总线', link: '/api/packages/eventbus' },
{ text: '文件', link: '/api/packages/fileutil' },
{ text: '文件处理', link: '/api/packages/fileutil' },
{ text: '格式化工具', link: '/api/packages/formatter' },
{ text: '函数', link: '/api/packages/function' },
{ text: '数学工具', link: '/api/packages/mathutil' },
@@ -141,9 +141,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: '流', link: '/api/packages/stream' },
{ text: '结构体', link: '/api/packages/struct' },
{ text: '字符串', link: '/api/packages/strutil' },
{ text: '系统', link: '/api/packages/system' },
{ text: '枚举', link: '/api/packages/enum' },
{ text: '元组', link: '/api/packages/tuple' },
{ text: '验证器', link: '/api/packages/validator' },
{ text: '系统工具函数', link: '/api/packages/system' },
{ text: '错误处理', link: '/api/packages/xerror' },
],
},

View File

@@ -46,6 +46,7 @@ outline: deep
<div class="package-cell">cryptor</div>
<div class="package-cell">datastructure</div>
<div class="package-cell">datetime</div>
<div class="package-cell">enum</div>
<div class="package-cell">eventbus</div>
<div class="package-cell">fileutil</div>
<div class="package-cell">formatter</div>

View File

@@ -33,6 +33,9 @@ import (
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToPointers](#ToPointers)
- [FromPointer](#FromPointer)
- [FromPointers](#FromPointers)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
@@ -456,6 +459,108 @@ func main() {
}
```
### <span id="ToPointers">ToPointers</span>
<p>将值的切片转换为指针的切片。</p>
<b>函数签名:</b>
```go
func ToPointers[T any](values []T) []*T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZUoXd2i5ZkV)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
strs := []string{"a", "b", "c"}
pointerStrs := convertor.ToPointers(strs)
fmt.Println(*pointerStrs[0])
fmt.Println(*pointerStrs[1])
fmt.Println(*pointerStrs[2])
// Output:
// a
// b
// c
}
```
### <span id="FromPointer">FromPointer</span>
<p>返回指针所指向的值。</p>
<b>函数签名:</b>
```go
func FromPointer[T any](ptr *T) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wAp90V7Zu6g)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
str := "abc"
strPtr := &str
result := convertor.FromPointer(strPtr)
fmt.Println(result)
// Output:
// abc
}
```
### <span id="FromPointers">FromPointers</span>
<p>将指针的切片转换为值的切片。</p>
<b>函数签名:</b>
```go
func FromPointers[T any](pointers []*T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qIPsyYtNy3Q)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
strs := []string{"a", "b", "c"}
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
result := convertor.FromPointers(strPtr)
fmt.Println(result[0])
fmt.Println(result[1])
fmt.Println(result[2])
// Output:
// a
// b
// c
}
```
### <span id="ToString">ToString</span>
<p>将值转换为字符串,对于数字、字符串、[]byte将转换为字符串。 对于其他类型(切片、映射、数组、结构体)将调用 json.Marshal</p>
@@ -1179,4 +1284,4 @@ func main() {
// Output:
// 9876543210
}
```
```

View File

@@ -1,6 +1,6 @@
# Cryptor
cryptor 包包含数据加密和解密功能。支持 base64, md5, hmac, hash, aes, des, rsa。
cryptor 包包含数据加密和解密功能。支持 base64, md5, hmac, hash, aes, des, rsa, sm2, sm3, sm4
<div STYLE="page-break-after: always;"></div>
@@ -74,6 +74,14 @@ import (
- [RsaDecryptOAEP](#RsaDecryptOAEP)
- [RsaSign](#RsaSign)
- [RsaVerifySign](#RsaVerifySign)
- [Sm3](#Sm3)
- [Sm4EcbEncrypt](#Sm4EcbEncrypt)
- [Sm4EcbDecrypt](#Sm4EcbDecrypt)
- [Sm4CbcEncrypt](#Sm4CbcEncrypt)
- [Sm4CbcDecrypt](#Sm4CbcDecrypt)
- [GenerateSm2Key](#GenerateSm2Key)
- [Sm2Encrypt](#Sm2Encrypt)
- [Sm2Decrypt](#Sm2Decrypt)
<div STYLE="page-break-after: always;"></div>
@@ -1829,3 +1837,279 @@ func main() {
}
}
```
### <span id="Sm3">Sm3</span>
<p>计算 SM3 哈希值国密SM3密码杂凑算法。SM3 是中国国家密码管理局发布的密码杂凑算法,用于替代 MD5/SHA-1/SHA-2 等国际算法。</p>
<b>函数签名:</b>
```go
func Sm3(data []byte) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zDAQpteAiOc)</span></b>
```go
package main
import (
"encoding/hex"
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("hello world")
hash := cryptor.Sm3(data)
fmt.Println(hex.EncodeToString(hash))
// Output:
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
}
```
### <span id="Sm4EcbEncrypt">Sm4EcbEncrypt</span>
<p>使用 SM4 ECB 模式加密数据国密SM4分组密码算法。密钥长度必须为 16 字节。</p>
<b>函数签名:</b>
```go
func Sm4EcbEncrypt(data, key []byte) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/l5IQxYuuaED)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef") // 16 bytes key
plaintext := []byte("hello world")
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4EcbDecrypt">Sm4EcbDecrypt</span>
<p>使用 SM4 ECB 模式解密数据。密钥长度必须为 16 字节。</p>
<b>函数签名:</b>
```go
func Sm4EcbDecrypt(encrypted, key []byte) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/l5IQxYuuaED)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4CbcEncrypt">Sm4CbcEncrypt</span>
<p>使用 SM4 CBC 模式加密数据。密钥长度必须为 16 字节。返回的密文包含 IV前 16 字节)。</p>
<b>函数签名:</b>
```go
func Sm4CbcEncrypt(data, key []byte) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4CbcDecrypt">Sm4CbcDecrypt</span>
<p>使用 SM4 CBC 模式解密数据。密钥长度必须为 16 字节。密文应包含 IV前 16 字节)。</p>
<b>函数签名:</b>
```go
func Sm4CbcDecrypt(encrypted, key []byte) []byte
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="GenerateSm2Key">GenerateSm2Key</span>
<p>生成 SM2 密钥对国密SM2椭圆曲线公钥密码算法。SM2 是基于椭圆曲线的非对称加密算法。</p>
<b>函数签名:</b>
```go
func GenerateSm2Key() (*Sm2PrivateKey, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, err := cryptor.GenerateSm2Key()
if err != nil {
return
}
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm2Encrypt">Sm2Encrypt</span>
<p>使用 SM2 公钥加密数据。返回的密文格式为C1(65字节) || C3(32字节) || C2(明文长度)。</p>
<b>函数签名:</b>
```go
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, _ := cryptor.GenerateSm2Key()
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm2Decrypt">Sm2Decrypt</span>
<p>使用 SM2 私钥解密数据。密文格式应为C1(65字节) || C3(32字节) || C2(明文长度)。</p>
<b>函数签名:</b>
```go
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, _ := cryptor.GenerateSm2Key()
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```

850
docs/api/packages/enum.md Normal file
View File

@@ -0,0 +1,850 @@
# Enum
Enum 实现一个简单枚举工具包。
<div STYLE="page-break-after: always;"></div>
## 源码:
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
<div STYLE="page-break-after: always;"></div>
## 用法:
```go
import (
"github.com/duke-git/lancet/v2/enum"
)
```
<div STYLE="page-break-after: always;"></div>
## 目录
- [NewItem](#NewItem)
- [NewItemsFromPairs](#NewItemsFromPairs)
- [Value](#Value)
- [Name](#Name)
- [Valid](#Valid)
- [MarshalJSON](#MarshalJSON)
- [NewRegistry](#NewRegistry)
- [Add](#Add)
- [Remove](#Remove)
- [Update](#Update)
- [GetByValue](#GetByValue)
- [GetByName](#GetByName)
- [Items](#Items)
- [Contains](#Contains)
- [Size](#Size)
- [Range](#Range)
- [SortedItems](#SortedItems)
- [Filter](#Filter)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="NewItem">NewItem</span>
<p>创建枚举项。</p>
<b>函数签名:</b>
```go
func NewItem[T comparable](value T, name string) *Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/8qNsLw01HD5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
<p>从Pair结构体的切片创建枚举项。</p>
<b>函数签名:</b>
```go
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Value">Value</span>
<p>返回枚举项的值。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Value() T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Name">Name</span>
<p>返回枚举项的名称。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Name() string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Valid">Valid</span>
<p>检查枚举项是否有效。如果提供了自定义检查函数,将使用该函数验证值。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) Valid(checker ...func(T) bool) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pA3lYY2VSm3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := enum.NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
```
### <span id="MarshalJSON">MarshalJSON</span>
<p>枚举项实现json.Marshaler接口。</p>
<b>函数签名:</b>
```go
func (it *Item[T]) MarshalJSON() ([]byte, error)
func (it *Item[T]) UnmarshalJSON(data []byte) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zIZEdAnneB5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem enum.Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
```
### <span id="NewRegistry">NewRegistry</span>
<p>Registry 定义了一个通用的枚举注册表结构体。</p>
<b>函数签名:</b>
```go
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Add">Add</span>
<p>向枚举注册表添加枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Add(items ...*Item[T])
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Remove">Remove</span>
<p>在枚举注册表中删除枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Remove(value T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
```
### <span id="Update">Update</span>
<p>在枚举注册表中更新枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Update(value T, newName string) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ol0moT1J9Xl)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
```
### <span id="GetByValue">GetByValue</span>
<p>在枚举注册表中通过值获取枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/niJ1U2KlE_m)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found name by value:", item.Name())
}
// Output:
// Found name by value: Active
}
```
### <span id="GetByName">GetByName</span>
<p>在枚举注册表中通过名称获取枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/49ie_gpqH0m)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByName("Active"); found {
fmt.Println("Found value by name:", item.Value())
}
// Output:
// Found value by name: 1
}
```
### <span id="Items">Items</span>
<p>返回枚举注册表中的枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Items() []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/lAJFAradbvQ)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Contains">Contains</span>
<p>检查注册表中是否存在具有给定值的枚举项。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Contains(value T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
```
### <span id="Size">Size</span>
<p>返回注册表中枚举项的数目。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Size() int
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/TeDArWhlQe2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
```
### <span id="Range">Range</span>
<p>遍历注册表中的所有枚举项,并应用给定的函数。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/GPsZbQbefWN)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *enum.Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
```
### <span id="SortedItems">SortedItems</span>
<p>返回按给定比较函数排序的所有枚举项的切片。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/tN9RE_m_WEI)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Inactive, "Inactive")
item2 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
return i1.Value() < i2.Value()
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Filter">Filter</span>
<p>返回满足给定谓词函数的枚举项切片。</p>
<b>函数签名:</b>
```go
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/uTUpTdcyoCU)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}
```

View File

@@ -8,6 +8,7 @@ formatter 格式化器包含一些数据格式化处理方法。
- [https://github.com/duke-git/lancet/blob/main/formatter/formatter.go](https://github.com/duke-git/lancet/blob/main/formatter/formatter.go)
- [https://github.com/duke-git/lancet/blob/main/formatter/byte.go](https://github.com/duke-git/lancet/blob/main/formatter/byte.go)
- [https://github.com/duke-git/lancet/blob/main/formatter/address.go](https://github.com/duke-git/lancet/blob/main/formatter/address.go)
<div STYLE="page-break-after: always;"></div>
@@ -30,6 +31,8 @@ import (
- [BinaryBytes](#BinaryBytes)
- [ParseDecimalBytes](#ParseDecimalBytes)
- [ParseBinaryBytes](#ParseBinaryBytes)
- [ParseCNAddress](#ParseCNAddress)
- [ParsePersonInfo](#ParsePersonInfo)
<div STYLE="page-break-after: always;"></div>
@@ -308,3 +311,134 @@ func main() {
// 12492
}
```
### <span id="ParseCNAddress">ParseCNAddress</span>
<p>智能解析中国地址字符串并提取结构化信息。可以解析带或不带用户信息(姓名、电话、身份证等)的地址。当 withUser 为 true 时,从地址字符串中提取用户信息。当 withUser 为 false 时,仅解析位置信息。支持多种地址格式:标准格式、紧凑格式、带关键词格式、县级市格式等。</p>
<b>函数签名:</b>
```go
func ParseCNAddress(str string, withUser bool) *AddressInfo
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/o5l09hQopEV)</span></b>
```go
package main
import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/formatter"
)
func main() {
// 解析包含用户信息的完整地址
result1 := formatter.ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
jsonData1, _ := json.MarshalIndent(result1, "", " ")
fmt.Println("示例 1 - 带用户信息:")
fmt.Println(string(jsonData1))
// 仅解析地址,不提取用户信息
result2 := formatter.ParseCNAddress("北京市海淀区中关村大街1号", false)
fmt.Printf("\n示例 2 - 仅地址:\n")
fmt.Printf("省: %s, 市: %s, 区: %s, 街道: %s\n",
result2.Province, result2.City, result2.Region, result2.Street)
// 解析县级市地址
result3 := formatter.ParseCNAddress("河北省石家庄市新乐市经济开发区兴工街10号", false)
fmt.Printf("\n示例 3 - 县级市:\n")
fmt.Printf("省: %s, 市: %s, 区/县: %s, 街道: %s\n",
result3.Province, result3.City, result3.Region, result3.Street)
// 紧凑格式
result4 := formatter.ParseCNAddress("马云13593464918陕西省西安市雁塔区丈八沟街道", true)
fmt.Printf("\n示例 4 - 紧凑格式:\n")
fmt.Printf("姓名: %s, 电话: %s, 地址: %s%s%s%s\n",
result4.Name, result4.Mobile, result4.Province, result4.City, result4.Region, result4.Street)
// Output:
// 示例 1 - 带用户信息:
// {
// "name": "张三",
// "mobile": "13800138000",
// "idn": "",
// "postcode": "",
// "province": "北京",
// "city": "北京市",
// "region": "朝阳区",
// "street": "建国路1号",
// "addr": "北京市朝阳区建国路1号"
// }
//
// 示例 2 - 仅地址:
// 省: 北京, 市: 北京市, 区: 海淀区, 街道: 中关村大街1号
//
// 示例 3 - 县级市:
// 省: 河北省, 市: 石家庄市, 区/县: 新乐市, 街道: 经济开发区兴工街10号
//
// 示例 4 - 紧凑格式:
// 姓名: 马云, 电话: 13593464918, 地址: 陕西省西安市雁塔区丈八沟街道
}
```
### <span id="ParsePersonInfo">ParsePersonInfo</span>
<p>从地址字符串中提取用户信息(姓名、电话、身份证、邮编)。将个人信息与地址分离,支持带标签格式、紧凑格式、带分隔符格式。返回包含提取的用户信息和清理后地址字符串的 AddressInfo。</p>
<b>函数签名:</b>
```go
func ParsePersonInfo(str string) *AddressInfo
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/JO-uTlJlTy7)</span></b>
```go
package main
import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/formatter"
)
func main() {
// 提取姓名和手机号
result1 := formatter.ParsePersonInfo("张三 13800138000 北京市朝阳区")
fmt.Println("示例 1 - 姓名和手机号:")
fmt.Printf("姓名: %s, 手机: %s, 地址: %s\n", result1.Name, result1.Mobile, result1.Addr)
// 提取身份证号
result2 := formatter.ParsePersonInfo("李四 110101199001011234 上海市")
fmt.Println("\n示例 2 - 身份证号:")
fmt.Printf("姓名: %s, 身份证: %s, 地址: %s\n", result2.Name, result2.IDN, result2.Addr)
// 带标签格式
result3 := formatter.ParsePersonInfo("收货人:王五 电话13900139000 收货地址天津市河西区友谊路20号")
jsonData3, _ := json.MarshalIndent(result3, "", " ")
fmt.Println("\n示例 3 - 带标签格式:")
fmt.Println(string(jsonData3))
// Output:
// 示例 1 - 姓名和手机号:
// 姓名: 张三, 手机: 13800138000, 地址: 北京市朝阳区
//
// 示例 2 - 身份证号:
// 姓名: 李四, 身份证: 110101199001011234, 地址: 上海市
//
// 示例 3 - 带标签格式:
// {
// "name": "王五",
// "mobile": "13900139000",
// "idn": "",
// "postcode": "",
// "province": "",
// "city": "",
// "region": "",
// "street": "",
// "addr": "天津市河西区友谊路20号"
// }
}
```

View File

@@ -79,6 +79,7 @@ import (
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
- [ToMarkdownTable](#ToMarkdownTable)
<div STYLE="page-break-after: always;"></div>
@@ -2345,3 +2346,68 @@ func main() {
// [b d]
}
```
### <span id="ToMarkdownTable">ToMarkdownTable</span>
<p>将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。</p>
<b>函数签名:</b>
```go
编辑
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/w_pSLfeyEB5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
// 基本用法:自动从数据中提取列名作为表头
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := maputil.ToMarkdownTable(data, nil, nil)
fmt.Println(result)
// 输出:
// |name|age|salary|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// 自定义表头显示名称
headerMap := map[string]string{
"name": "姓名",
"age": "年龄",
"salary": "薪资",
}
result = maputil.ToMarkdownTable(data, headerMap, nil)
fmt.Println(result)
// 输出:
// |姓名|年龄|薪资|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// 自定义列顺序
columnOrder := []string{"salary", "name"}
result = maputil.ToMarkdownTable(data, nil, columnOrder)
fmt.Println(result)
// 输出:
// |salary|name|
// |---|---|
// |50000|Alice|
// |60000|Bob|
}
```

View File

@@ -27,6 +27,7 @@ import (
- [Contain](#Contain)
- [ContainBy](#ContainBy)
- [ContainSubSlice](#ContainSubSlice)
- [ContainAny](#ContainAny)
- [Chunk](#Chunk)
- [Compact](#Compact)
- [Concat](#Concat)
@@ -256,6 +257,43 @@ func main() {
}
```
### <span id="ContainAny">ContainAny</span>
<p>判断slice是否包含targets切片中的任意一个元素</p>
<b>函数签名:</b>
```go
func ContainAny[T comparable](slice []T, targets []T) bool
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
```
### <span id="Chunk">Chunk</span>
<p>按照size参数均分slice</p>

View File

@@ -31,6 +31,7 @@ import (
- [IsStruct](#IsStruct)
- [Tag](#Tag)
- [Name](#Name)
- [TypeName](#TypeName)
- [Value](#Value)
- [Kind](#Kind)
- [IsEmbedded](#IsEmbedded)
@@ -53,7 +54,7 @@ import (
func New(value any, tagName ...string) *Struct
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/O29l8kk-Z17)</span></b>
```go
package main
@@ -68,7 +69,11 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
// to do something
fmt.Println(s.ToMap())
// Output:
// map[name:11] <nil>
}
```
@@ -88,7 +93,7 @@ func (s *Struct) ToMap() (map[string]any, error)
func ToMap(v any) (map[string]any, error)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qQbLySBgerZ)</span></b>
```go
package main
@@ -129,7 +134,7 @@ func main() {
func (s *Struct) Fields() []*Field
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
```go
package main
@@ -161,10 +166,10 @@ func main() {
<b>函数签名:</b>
```go
func (s *Struct) Field(name string) *Field
func (s *Struct) Field(name string) (*Field, bool)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/KocZMSYarza)</span></b>
```go
package main
@@ -180,12 +185,14 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
f := s.Field("Name")
f, found := s.Field("Name")
fmt.Println(f.Value())
fmt.Println(found)
// Output:
// 11
// true
}
```
@@ -199,7 +206,7 @@ func main() {
func (s *Struct) IsStruct() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
```go
package main
@@ -233,7 +240,7 @@ func main() {
func (f *Field) Tag() *Tag
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
```go
package main
@@ -254,7 +261,7 @@ func main() {
tag := n.Tag()
fmt.Println(tag.Name)
// Output:
// name
}
@@ -270,7 +277,7 @@ func main() {
func (f *Field) Value() any
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
```go
package main
@@ -288,10 +295,10 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
fmt.Println(n.Value())
// Output:
// Output:
// 111
}
```
@@ -306,7 +313,7 @@ func main() {
func (f *Field) IsEmbedded() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
```go
package main
@@ -331,11 +338,11 @@ func main() {
s := structs.New(c1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
fmt.Println(n.IsEmbedded())
fmt.Println(a.IsEmbedded())
// Output:
// Output:
// true
// false
}
@@ -351,7 +358,7 @@ func main() {
func (f *Field) IsExported() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
```go
package main
@@ -370,11 +377,11 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("age")
fmt.Println(n.IsExported())
fmt.Println(a.IsExported())
// Output:
// Output:
// true
// false
}
@@ -390,7 +397,7 @@ func main() {
func (f *Field) IsZero() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/RzqpGISf87r)</span></b>
```go
package main
@@ -409,11 +416,11 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
fmt.Println(n.IsZero())
fmt.Println(a.IsZero())
// Output:
// Output:
// true
// false
}
@@ -429,7 +436,7 @@ func main() {
func (f *Field) Name() string
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/zfIGlqsatee)</span></b>
```go
package main
@@ -448,11 +455,11 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
fmt.Println(n.Name())
fmt.Println(a.Name())
// Output:
// Output:
// Name
// Age
}
@@ -468,7 +475,7 @@ func main() {
func (f *Field) Kind() reflect.Kind
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
```go
package main
@@ -487,16 +494,52 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
fmt.Println(n.Kind())
fmt.Println(a.Kind())
// Output:
// Output:
// string
// int
}
```
### <span id="TypeName">TypeName</span>
<p>获取结构体类型名。</p>
<b>函数签名:</b>
```go
func (s *Struct) TypeName() string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
func main() {
type Parent struct {
Name string
Age int
}
p := &Parent{Age: 11}
s := structs.New(p)
fmt.Println(s.TypeName())
// Output:
// Parent
}
```
### <span id="IsSlice">IsSlice</span>
<p>判断属性是否是切片</p>
@@ -507,7 +550,7 @@ func main() {
func (f *Field) IsSlice() bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
```go
package main
@@ -526,10 +569,10 @@ func main() {
p1 := &Parent{arr: []int{1, 2, 3}}
s := structs.New(p1)
a, _ := s.Field("arr")
fmt.Println(a.IsSlice())
// Output:
// Output:
// true
}
```
@@ -544,7 +587,7 @@ func main() {
func (f *Field) IsTargetType(targetType reflect.Kind) bool
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/Ig75P-agN39)</span></b>
```go
package main
@@ -565,12 +608,12 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("arr")
fmt.Println(n.IsTargetType(reflect.String))
fmt.Println(a.IsTargetType(reflect.Slice))
// Output:
// Output:
// true
// true
}
```
```

View File

@@ -65,6 +65,8 @@ import (
- [IsAmericanExpress](#IsAmericanExpress)
- [IsUnionPay](#IsUnionPay)
- [IsChinaUnionPay](#IsChinaUnionPay)
- [IsPassport](#IsPassport)
- [IsChineseHMPassport](#IsChineseHMPassport)
<div STYLE="page-break-after: always;"></div>
@@ -547,7 +549,7 @@ func main() {
func IsEmail(email string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/Os9VaFlT33G)</span></b>
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/HVQ5LAe-vFz)</span></b>
```go
import (
@@ -557,13 +559,28 @@ import (
func main() {
result1 := validator.IsEmail("abc@xyz.com")
result2 := validator.IsEmail("a.b@@com")
result2 := validator.IsEmail("user@domain.co")
result3 := validator.IsEmail("test.user@example.org")
result4 := validator.IsEmail("@abc@xyz.com")
result5 := validator.IsEmail("a.b@@com")
result6 := validator.IsEmail("a.b@com")
result7 := validator.IsEmail("test@example")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
fmt.Println(result6)
fmt.Println(result7)
// Output:
// true
// true
// true
// false
// false
// false
// false
}
```
@@ -844,20 +861,20 @@ import (
func main() {
result1 := validator.IsAlphaNumeric("ABC")
result2 := validator.IsAlphaNumeric("123")
result3 := validator.IsAlphaNumeric("abc123")
result4 := validator.IsAlphaNumeric("abc123@#$")
result2 := validator.IsAlphaNumeric("123")
result3 := validator.IsAlphaNumeric("abc123")
result4 := validator.IsAlphaNumeric("abc123@#$")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
// Output:
// true
// true
// true
// false
}
```
@@ -1550,6 +1567,8 @@ func IsChinaUnionPay(v string) bool
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/yafpdxLiymu)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
@@ -1567,3 +1586,82 @@ func main() {
// false
}
```
### <span id="IsPassport">IsPassport</span>
<p>判断护照(正则判断)。</p>
<b>函数签名:</b>
```go
func IsPassport(passport, country string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/dvOiV2BW7Aw)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsPassport("P123456789", "CN")
result2 := validator.IsPassport("123456789", "US")
result3 := validator.IsPassport("AB1234567", "RU")
result4 := validator.IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
<p>判断港澳台通行证(正则判断)。</p>
<b>函数签名:</b>
```go
func IsChineseHMPassport(hmPassport string) bool
```
<b>示例:<span style="float:right;display:inline-block">[运行](https://go.dev/play/p/xKG6spQTcY0)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsChineseHMPassport("C12345678")
result2 := validator.IsChineseHMPassport("C00000000")
result3 := validator.IsChineseHMPassport("M12345678")
result4 := validator.IsChineseHMPassport("c12345678")
result5 := validator.IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}
```

View File

@@ -46,6 +46,7 @@ outline: deep
<div class="package-cell">cryptor</div>
<div class="package-cell">datastructure</div>
<div class="package-cell">datetime</div>
<div class="package-cell">enum</div>
<div class="package-cell">eventbus</div>
<div class="package-cell">fileutil</div>
<div class="package-cell">formatter</div>

View File

@@ -33,6 +33,9 @@ import (
- [ToJson](#ToJson)
- [ToMap](#ToMap)
- [ToPointer](#ToPointer)
- [ToPointers](#ToPointers)
- [FromPointer](#FromPointer)
- [FromPointers](#FromPointers)
- [ToString](#ToString)
- [StructToMap](#StructToMap)
- [MapToSlice](#MapToSlice)
@@ -456,6 +459,108 @@ func main() {
}
```
### <span id="ToPointers">ToPointers</span>
<p>Convert a slice of values to a slice of pointers.</p>
<b>Signature:</b>
```go
func ToPointers[T any](values []T) []*T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZUoXd2i5ZkV)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
strs := []string{"a", "b", "c"}
pointerStrs := convertor.ToPointers(strs)
fmt.Println(*pointerStrs[0])
fmt.Println(*pointerStrs[1])
fmt.Println(*pointerStrs[2])
// Output:
// a
// b
// c
}
```
### <span id="FromPointer">FromPointer</span>
<p>Returns the value pointed to by the pointer.</p>
<b>Signature:</b>
```go
func FromPointer[T any](ptr *T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wAp90V7Zu6g)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
str := "abc"
strPtr := &str
result := convertor.FromPointer(strPtr)
fmt.Println(result)
// Output:
// abc
}
```
### <span id="FromPointers">FromPointers</span>
<p>Convert a slice of pointers to a slice of values.</p>
<b>Signature:</b>
```go
func FromPointers[T any](pointers []*T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qIPsyYtNy3Q)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/convertor"
)
func main() {
strs := []string{"a", "b", "c"}
strPtr := []*string{&strs[0], &strs[1], &strs[2]}
result := convertor.FromPointers(strPtr)
fmt.Println(result[0])
fmt.Println(result[1])
fmt.Println(result[2])
// Output:
// a
// b
// c
}
```
### <span id="ToString">ToString</span>
<p>ToString convert value to string, for number, string, []byte, will convert to string. For other type (slice, map, array, struct) will call json.Marshal</p>
@@ -572,7 +677,6 @@ func main() {
}
```
### <span id="EncodeByte">EncodeByte</span>
<p>Encode data to byte slice.</p>
@@ -638,7 +742,6 @@ func main() {
}
```
### <span id="CopyProperties">CopyProperties</span>
<p>Copies each field from the source struct into the destination struct. Use json.Marshal/Unmarshal, so json tag should be set for fields of dst and src struct.</p>
@@ -748,7 +851,7 @@ func main() {
// Output:
// true
// true
// true
}
```
@@ -781,7 +884,7 @@ func main() {
// Output:
// true
// hello
// hello
}
```
@@ -857,8 +960,6 @@ func main() {
```
### <span id="ToUrlBase64">ToUrlBase64</span>
<p>Convert a value to a string encoded in url Base64. Error data of type "error" will also be encoded, and complex structures will be converted to a JSON formatted string.</p>
@@ -1147,4 +1248,4 @@ func main() {
// Output:
// 9876543210
}
```
```

View File

@@ -1,6 +1,6 @@
# Cryptor
Package cryptor contains some functions for data encryption and decryption. Support base64, md5, hmac, aes, des, rsa.
Package cryptor contains some functions for data encryption and decryption. Support base64, md5, hmac, aes, des, rsa, sm2, sm3, sm4.
<div STYLE="page-break-after: always;"></div>
@@ -76,6 +76,14 @@ import (
- [RsaDecryptOAEP](#RsaDecryptOAEP)
- [RsaSign](#RsaSign)
- [RsaVerifySign](#RsaVerifySign)
- [Sm3](#Sm3)
- [Sm4EcbEncrypt](#Sm4EcbEncrypt)
- [Sm4EcbDecrypt](#Sm4EcbDecrypt)
- [Sm4CbcEncrypt](#Sm4CbcEncrypt)
- [Sm4CbcDecrypt](#Sm4CbcDecrypt)
- [GenerateSm2Key](#GenerateSm2Key)
- [Sm2Encrypt](#Sm2Encrypt)
- [Sm2Decrypt](#Sm2Decrypt)
<div STYLE="page-break-after: always;"></div>
@@ -1831,3 +1839,278 @@ func main() {
}
}
```
### <span id="Sm3">Sm3</span>
<p>Calculate SM3 hash (Chinese National Cryptography SM3 Hash Algorithm). SM3 is a cryptographic hash algorithm published by the Chinese State Cryptography Administration, designed to replace MD5/SHA-1/SHA-2.</p>
<b>Signature:</b>
```go
func Sm3(data []byte) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zDAQpteAiOc)</span></b>
```go
package main
import (
"encoding/hex"
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("hello world")
hash := cryptor.Sm3(data)
fmt.Println(hex.EncodeToString(hash))
// Output:
// 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
}
```
### <span id="Sm4EcbEncrypt">Sm4EcbEncrypt</span>
<p>Encrypt data using SM4 ECB mode (Chinese National Cryptography SM4 Block Cipher Algorithm). Key length must be 16 bytes.</p>
<b>Signature:</b>
```go
func Sm4EcbEncrypt(data, key []byte) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/l5IQxYuuaED)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef") // 16 bytes key
plaintext := []byte("hello world")
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4EcbDecrypt">Sm4EcbDecrypt</span>
<p>Decrypt data using SM4 ECB mode. Key length must be 16 bytes.</p>
<b>Signature:</b>
```go
func Sm4EcbDecrypt(encrypted, key []byte) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/l5IQxYuuaED)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4EcbEncrypt(plaintext, key)
decrypted := cryptor.Sm4EcbDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4CbcEncrypt">Sm4CbcEncrypt</span>
<p>Encrypt data using SM4 CBC mode. Key length must be 16 bytes. The returned ciphertext contains IV (first 16 bytes).</p>
<b>Signature:</b>
```go
func Sm4CbcEncrypt(data, key []byte) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm4CbcDecrypt">Sm4CbcDecrypt</span>
<p>Decrypt data using SM4 CBC mode. Key length must be 16 bytes. The ciphertext should contain IV (first 16 bytes).</p>
<b>Signature:</b>
```go
func Sm4CbcDecrypt(encrypted, key []byte) []byte
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/65Q6iYhLRTa)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
key := []byte("1234567890abcdef")
plaintext := []byte("hello world")
encrypted := cryptor.Sm4CbcEncrypt(plaintext, key)
decrypted := cryptor.Sm4CbcDecrypt(encrypted, key)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="GenerateSm2Key">GenerateSm2Key</span>
<p>Generate SM2 key pair (Chinese National Cryptography SM2 Elliptic Curve Public Key Algorithm). SM2 is an asymmetric encryption algorithm based on elliptic curve cryptography.</p>
<b>Signature:</b>
```go
func GenerateSm2Key() (*Sm2PrivateKey, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, err := cryptor.GenerateSm2Key()
if err != nil {
return
}
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm2Encrypt">Sm2Encrypt</span>
<p>Encrypt data using SM2 public key. The returned ciphertext format is: C1(65 bytes) || C3(32 bytes) || C2(plaintext length).</p>
<b>Signature:</b>
```go
func Sm2Encrypt(pub *Sm2PublicKey, plaintext []byte) ([]byte, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, _ := cryptor.GenerateSm2Key()
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```
### <span id="Sm2Decrypt">Sm2Decrypt</span>
<p>Decrypt data using SM2 private key. The ciphertext format should be: C1(65 bytes) || C3(32 bytes) || C2(plaintext length).</p>
<b>Signature:</b>
```go
func Sm2Decrypt(priv *Sm2PrivateKey, ciphertext []byte) ([]byte, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bKYMqRLvIx3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
privateKey, _ := cryptor.GenerateSm2Key()
plaintext := []byte("hello world")
ciphertext, _ := cryptor.Sm2Encrypt(&privateKey.PublicKey, plaintext)
decrypted, _ := cryptor.Sm2Decrypt(privateKey, ciphertext)
fmt.Println(string(decrypted))
// Output:
// hello world
}
```

View File

@@ -0,0 +1,850 @@
# Enum
Package enum provides a simple enum implementation.
<div STYLE="page-break-after: always;"></div>
## Source:
- [https://github.com/duke-git/lancet/blob/main/enum/enum.go](https://github.com/duke-git/lancet/blob/main/enum/enum.go)
<div STYLE="page-break-after: always;"></div>
## Usage:
```go
import (
"github.com/duke-git/lancet/v2/enum"
)
```
<div STYLE="page-break-after: always;"></div>
## Index
- [NewItem](#NewItem)
- [NewItemsFromPairs](#NewItemsFromPairs)
- [Value](#Value)
- [Name](#Name)
- [Valid](#Valid)
- [MarshalJSON](#MarshalJSON)
- [NewRegistry](#NewRegistry)
- [Add](#Add)
- [Remove](#Remove)
- [Update](#Update)
- [GetByValue](#GetByValue)
- [GetByName](#GetByName)
- [Items](#Items)
- [Contains](#Contains)
- [Size](#Size)
- [Range](#Range)
- [SortedItems](#SortedItems)
- [Filter](#Filter)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="NewItem">NewItem</span>
<p>Creates a new enum item.</p>
<b>Signature:</b>
```go
func NewItem[T comparable](value T, name string) *Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/8qNsLw01HD5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="NewItemsFromPairs">NewItemsFromPairs</span>
<p>Creates enum items from a slice of Pair structs.</p>
<b>Signature:</b>
```go
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Value">Value</span>
<p>Returns the value of the enum item.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Value() T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Name">Name</span>
<p>Returns the name of the enum item.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Name() string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/xKnoGa7gnev)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
items := enum.NewItemsFromPairs(
enum.Pair[Status]{Value: Active, Name: "Active"},
enum.Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Valid">Valid</span>
<p>Checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.</p>
<b>Signature:</b>
```go
func (it *Item[T]) Valid(checker ...func(T) bool) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/pA3lYY2VSm3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := enum.NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
```
### <span id="MarshalJSON">MarshalJSON</span>
<p>Implementation of json.Marshaler interface.</p>
<b>Signature:</b>
```go
func (it *Item[T]) MarshalJSON() ([]byte, error)
func (it *Item[T]) UnmarshalJSON(data []byte) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zIZEdAnneB5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
item := enum.NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem enum.Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
```
### <span id="NewRegistry">NewRegistry</span>
<p>Creates a new enum registry.</p>
<b>Signature:</b>
```go
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Add">Add</span>
<p>Adds enum items to the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Add(items ...*Item[T])
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ABEXsYfJKMo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
```
### <span id="Remove">Remove</span>
<p>Removes an enum item from the registry by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Remove(value T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/dSG84wQ3TuC)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
```
### <span id="Update">Update</span>
<p>Updates the name of an enum item in the registry by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Update(value T, newName string) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Ol0moT1J9Xl)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
```
### <span id="GetByValue">GetByValue</span>
<p>Retrieves an enum item by its value.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/niJ1U2KlE_m)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found name by value:", item.Name())
}
// Output:
// Found name by value: Active
}
```
### <span id="GetByName">GetByName</span>
<p>Retrieves an enum item by its name.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) GetByName(name string) (*Item[T], bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/49ie_gpqH0m)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
if item, found := registry.GetByName("Active"); found {
fmt.Println("Found value by name:", item.Value())
}
// Output:
// Found value by name: 1
}
```
### <span id="Items">Items</span>
<p>Returns a slice of all enum items in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Items() []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/lAJFAradbvQ)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Contains">Contains</span>
<p>Checks if an enum item with the given value exists in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Contains(value T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/_T-lPYkZn2j)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item := enum.NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
```
### <span id="Size">Size</span>
<p>Returns the number of enum items in the registry.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Size() int
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/TeDArWhlQe2)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
```
### <span id="Range">Range</span>
<p>Iterates over all enum items in the registry and applies the given function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Range(fn func(*Item[T]) bool)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/GPsZbQbefWN)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *enum.Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
```
### <span id="SortedItems">SortedItems</span>
<p>Returns a slice of all enum items sorted by the given less function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/tN9RE_m_WEI)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Inactive, "Inactive")
item2 := enum.NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *enum.Item[Status]) bool {
return i1.Value() < i2.Value()
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
```
### <span id="Filter">Filter</span>
<p>Returns a slice of enum items that satisfy the given predicate function.</p>
<b>Signature:</b>
```go
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T]
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/uTUpTdcyoCU)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/enum"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func main() {
registry := enum.NewRegistry[Status]()
item1 := enum.NewItem(Active, "Active")
item2 := enum.NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *enum.Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}
```

View File

@@ -8,6 +8,7 @@ formatter contains some functions for data formatting.
- [https://github.com/duke-git/lancet/blob/main/formatter/formatter.go](https://github.com/duke-git/lancet/blob/main/formatter/formatter.go)
- [https://github.com/duke-git/lancet/blob/main/formatter/byte.go](https://github.com/duke-git/lancet/blob/main/formatter/byte.go)
- [https://github.com/duke-git/lancet/blob/main/formatter/address.go](https://github.com/duke-git/lancet/blob/main/formatter/address.go)
<div STYLE="page-break-after: always;"></div>
@@ -30,6 +31,8 @@ import (
- [BinaryBytes](#BinaryBytes)
- [ParseDecimalBytes](#ParseDecimalBytes)
- [ParseBinaryBytes](#ParseBinaryBytes)
- [ParseCNAddress](#ParseCNAddress)
- [ParsePersonInfo](#ParsePersonInfo)
<div STYLE="page-break-after: always;"></div>
@@ -308,3 +311,134 @@ func main() {
// 12492
}
```
### <span id="ParseCNAddress">ParseCNAddress</span>
<p>Parses a Chinese address string intelligently and extracts structured information. It can parse addresses with or without user information (name, phone, ID card, etc.). When withUser is true, it extracts user information from the address string. When withUser is false, it only parses the location information. Supports various address formats: standard format, compact format, labeled format, county-level cities format, etc.</p>
<b>Signature:</b>
```go
func ParseCNAddress(str string, withUser bool) *AddressInfo
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/o5l09hQopEV)</span></b>
```go
package main
import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/formatter"
)
func main() {
// Parse complete address with user information
result1 := formatter.ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
jsonData1, _ := json.MarshalIndent(result1, "", " ")
fmt.Println("Example 1 - With user info:")
fmt.Println(string(jsonData1))
// Parse address only, without extracting user information
result2 := formatter.ParseCNAddress("北京市海淀区中关村大街1号", false)
fmt.Printf("\nExample 2 - Address only:\n")
fmt.Printf("Province: %s, City: %s, Region: %s, Street: %s\n",
result2.Province, result2.City, result2.Region, result2.Street)
// Parse county-level city address
result3 := formatter.ParseCNAddress("河北省石家庄市新乐市经济开发区兴工街10号", false)
fmt.Printf("\nExample 3 - County-level city:\n")
fmt.Printf("Province: %s, City: %s, Region: %s, Street: %s\n",
result3.Province, result3.City, result3.Region, result3.Street)
// Compact format
result4 := formatter.ParseCNAddress("马云13593464918陕西省西安市雁塔区丈八沟街道", true)
fmt.Printf("\nExample 4 - Compact format:\n")
fmt.Printf("Name: %s, Phone: %s, Address: %s%s%s%s\n",
result4.Name, result4.Mobile, result4.Province, result4.City, result4.Region, result4.Street)
// Output:
// Example 1 - With user info:
// {
// "name": "张三",
// "mobile": "13800138000",
// "idn": "",
// "postcode": "",
// "province": "北京",
// "city": "北京市",
// "region": "朝阳区",
// "street": "建国路1号",
// "addr": "北京市朝阳区建国路1号"
// }
//
// Example 2 - Address only:
// Province: 北京, City: 北京市, Region: 海淀区, Street: 中关村大街1号
//
// Example 3 - County-level city:
// Province: 河北省, City: 石家庄市, Region: 新乐市, Street: 经济开发区兴工街10号
//
// Example 4 - Compact format:
// Name: 马云, Phone: 13593464918, Address: 陕西省西安市雁塔区丈八沟街道
}
```
### <span id="ParsePersonInfo">ParsePersonInfo</span>
<p>Extracts user information (name, phone, ID card, postal code) from an address string. It separates personal information from the address, supporting labeled format, compact format, and formats with separators. Returns an AddressInfo with extracted user information and cleaned address string.</p>
<b>Signature:</b>
```go
func ParsePersonInfo(str string) *AddressInfo
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/JO-uTlJlTy7)</span></b>
```go
package main
import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/formatter"
)
func main() {
// Extract name and phone
result1 := formatter.ParsePersonInfo("张三 13800138000 北京市朝阳区")
fmt.Println("Example 1 - Name and phone:")
fmt.Printf("Name: %s, Phone: %s, Address: %s\n", result1.Name, result1.Mobile, result1.Addr)
// Extract ID card number
result2 := formatter.ParsePersonInfo("李四 110101199001011234 上海市")
fmt.Println("\nExample 2 - ID card number:")
fmt.Printf("Name: %s, ID Card: %s, Address: %s\n", result2.Name, result2.IDN, result2.Addr)
// Labeled format
result3 := formatter.ParsePersonInfo("收货人:王五 电话13900139000 收货地址天津市河西区友谊路20号")
jsonData3, _ := json.MarshalIndent(result3, "", " ")
fmt.Println("\nExample 3 - Labeled format:")
fmt.Println(string(jsonData3))
// Output:
// Example 1 - Name and phone:
// Name: 张三, Phone: 13800138000, Address: 北京市朝阳区
//
// Example 2 - ID card number:
// Name: 李四, ID Card: 110101199001011234, Address: 上海市
//
// Example 3 - Labeled format:
// {
// "name": "王五",
// "mobile": "13900139000",
// "idn": "",
// "postcode": "",
// "province": "",
// "city": "",
// "region": "",
// "street": "",
// "addr": "天津市河西区友谊路20号"
// }
}
```

View File

@@ -79,6 +79,7 @@ import (
- [SortByKey](#SortByKey)
- [GetOrDefault](#GetOrDefault)
- [FindValuesBy](#FindValuesBy)
- [ToMarkdownTable](#ToMarkdownTable)
<div STYLE="page-break-after: always;"></div>
@@ -2363,3 +2364,69 @@ func main() {
// [b d]
}
```
### <span id="ToMarkdownTable">ToMarkdownTable</span>
<p>Convert a map slice data to a Markdown table string. It supports custom header display names and column display order.</p>
<b>Signature:</b>
```go
编辑
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/w_pSLfeyEB5)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
// basic usage: automatically extract column names from data as table headers
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := maputil.ToMarkdownTable(data, nil, nil)
fmt.Println(result)
// output:
// |name|age|salary|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// custom header name
headerMap := map[string]string{
"name": "n",
"age": "a",
"salary": "s",
}
result = maputil.ToMarkdownTable(data, headerMap, nil)
fmt.Println(result)
// ouput:
// |m|a|s|
// |---|---|---|
// |Alice|25|50000|
// |Bob|30|60000|
// custom column display order
columnOrder := []string{"salary", "name"}
result = maputil.ToMarkdownTable(data, nil, columnOrder)
fmt.Println(result)
// output:
// |salary|name|
// |---|---|
// |50000|Alice|
// |60000|Bob|
}
```

View File

@@ -27,6 +27,7 @@ import (
- [Contain](#Contain)
- [ContainBy](#ContainBy)
- [ContainSubSlice](#ContainSubSlice)
- [ContainAny](#ContainAny)
- [Chunk](#Chunk)
- [Compact](#Compact)
- [Concat](#Concat)
@@ -256,6 +257,43 @@ func main() {
}
```
### <span id="ContainAny">ContainAny</span>
<p>Check if the slice contains any element from the targets slice.</p>
<b>Signature:</b>
```go
func ContainAny[T comparable](slice []T, targets []T) bool
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/4xoxhc9XSSw)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
```
### <span id="Chunk">Chunk</span>
<p>Creates an slice of elements split into groups the length of `size`.</p>

View File

@@ -31,6 +31,7 @@ import (
- [IsStruct](#IsStruct)
- [Tag](#Tag)
- [Name](#Name)
- [TypeName](#TypeName)
- [Value](#Value)
- [Kind](#Kind)
- [IsEmbedded](#IsEmbedded)
@@ -53,12 +54,13 @@ import (
func New(value any, tagName ...string) *Struct
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/O29l8kk-Z17)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
@@ -68,7 +70,11 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
// to do something
fmt.Println(s.ToMap())
// Output:
// map[name:11] <nil>
}
```
@@ -88,7 +94,7 @@ func (s *Struct) ToMap() (map[string]any, error)
func ToMap(v any) (map[string]any, error)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qQbLySBgerZ)</span></b>
```go
package main
@@ -130,7 +136,7 @@ func main() {
func (s *Struct) Fields() []*Field
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/w3Kk_CyVY7D)</span></b>
```go
package main
@@ -162,10 +168,10 @@ func main() {
<b>Signature:</b>
```go
func (s *Struct) Field(name string) *Field
func (s *Struct) Field(name string) (*Field, bool)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/KocZMSYarza)</span></b>
```go
package main
@@ -181,12 +187,14 @@ func main() {
}
p1 := &People{Name: "11"}
s := structs.New(p1)
f := s.Field("Name")
f, found := s.Field("Name")
fmt.Println(f.Value())
fmt.Println(found)
// Output:
// 11
// true
}
```
@@ -200,7 +208,7 @@ func main() {
func (s *Struct) IsStruct() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/bU2FSdkbK1C)</span></b>
```go
package main
@@ -234,7 +242,7 @@ func main() {
func (f *Field) Tag() *Tag
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/DVrx5HvvUJr)</span></b>
```go
package main
@@ -271,7 +279,7 @@ func main() {
func (f *Field) Value() any
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/qufYEU2o4Oi)</span></b>
```go
package main
@@ -307,7 +315,7 @@ func main() {
func (f *Field) IsEmbedded() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wV2PrbYm3Ec)</span></b>
```go
package main
@@ -352,7 +360,7 @@ func main() {
func (f *Field) IsExported() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/csK4AXYaNbJ)</span></b>
```go
package main
@@ -391,7 +399,7 @@ func main() {
func (f *Field) IsZero() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/RzqpGISf87r)</span></b>
```go
package main
@@ -430,7 +438,7 @@ func main() {
func (f *Field) Name() string
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/zfIGlqsatee)</span></b>
```go
package main
@@ -469,7 +477,7 @@ func main() {
func (f *Field) Kind() reflect.Kind
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/wg4NlcUNG5o)</span></b>
```go
package main
@@ -498,6 +506,42 @@ func main() {
}
```
### <span id="TypeName">TypeName</span>
<p>Return struct type name.</p>
<b>Signature:</b>
```go
func (s *Struct) TypeName() string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/SWLWd0XBaBb)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/structs"
)
func main() {
type Parent struct {
Name string
Age int
}
p := &Parent{Age: 11}
s := structs.New(p)
fmt.Println(s.TypeName())
// Output:
// Parent
}
```
### <span id="IsSlice">IsSlice</span>
<p>Check if the field is a slice</p>
@@ -508,7 +552,7 @@ func main() {
func (f *Field) IsSlice() bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/MKz4CgBIUrU)</span></b>
```go
package main
@@ -545,7 +589,7 @@ func main() {
func (f *Field) IsTargetType(targetType reflect.Kind) bool
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/Ig75P-agN39)</span></b>
```go
package main
@@ -566,12 +610,12 @@ func main() {
s := structs.New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("arr")
fmt.Println(n.IsTargetType(reflect.String))
fmt.Println(a.IsTargetType(reflect.Slice))
// Output:
// Output:
// true
// true
}
```
```

View File

@@ -65,6 +65,8 @@ import (
- [IsAmericanExpress](#IsAmericanExpress)
- [IsUnionPay](#IsUnionPay)
- [IsChinaUnionPay](#IsChinaUnionPay)
- [IsPassport](#IsPassport)
- [IsChineseHMPassport](#IsChineseHMPassport)
<div STYLE="page-break-after: always;"></div>
@@ -549,7 +551,7 @@ func main() {
func IsEmail(email string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/Os9VaFlT33G)</span></b>
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/HVQ5LAe-vFz)</span></b>
```go
import (
@@ -559,13 +561,28 @@ import (
func main() {
result1 := validator.IsEmail("abc@xyz.com")
result2 := validator.IsEmail("a.b@@com")
result2 := validator.IsEmail("user@domain.co")
result3 := validator.IsEmail("test.user@example.org")
result4 := validator.IsEmail("@abc@xyz.com")
result5 := validator.IsEmail("a.b@@com")
result6 := validator.IsEmail("a.b@com")
result7 := validator.IsEmail("test@example")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
fmt.Println(result6)
fmt.Println(result7)
// Output:
// true
// true
// true
// false
// false
// false
// false
}
```
@@ -1569,3 +1586,80 @@ func main() {
// false
}
```
### <span id="IsPassport">IsPassport</span>
<p>Passport validation(using regex).</p>
<b>Signature:</b>
```go
func IsPassport(passport, country string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/dvOiV2BW7Aw)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsPassport("P123456789", "CN")
result2 := validator.IsPassport("123456789", "US")
result3 := validator.IsPassport("AB1234567", "RU")
result4 := validator.IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
```
### <span id="IsChineseHMPassport">IsChineseHMPassport</span>
<p>Mainland travel permit for Hong Kong, Macao validation (using regex). </p>
<b>Signature:</b>
```go
func IsChineseHMPassport(hmPassport string) bool
```
<b>Example:<span style="float:right;display:inline-block">[Run](https://go.dev/play/p/xKG6spQTcY0)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/validator"
)
func main() {
result1 := validator.IsChineseHMPassport("C12345678")
result2 := validator.IsChineseHMPassport("C00000000")
result3 := validator.IsChineseHMPassport("M12345678")
result4 := validator.IsChineseHMPassport("c12345678")
result5 := validator.IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}
```

526
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

317
enum/enum.go Normal file
View File

@@ -0,0 +1,317 @@
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
// Use of this source code is governed by MIT license
// Package enum provides a simple enum implementation.
package enum
import (
"encoding/json"
"fmt"
"reflect"
"sort"
"sync"
)
// Enum defines a common enum interface.
type Enum[T comparable] interface {
Value() T
String() string
Name() string
Valid(checker ...func(T) bool) bool
}
// Item defines a common enum item struct implement Enum interface.
type Item[T comparable] struct {
value T
name string
}
// NewItem creates a new enum item.
// Play: https://go.dev/play/p/8qNsLw01HD5
func NewItem[T comparable](value T, name string) *Item[T] {
return &Item[T]{value: value, name: name}
}
// Pair represents a value-name pair for creating enum items
type Pair[T comparable] struct {
Value T
Name string
}
// NewItemsFromPairs creates enum items from a slice of Pair structs.
// Play: https://go.dev/play/p/xKnoGa7gnev
func NewItemsFromPairs[T comparable](pairs ...Pair[T]) []*Item[T] {
if len(pairs) == 0 {
return []*Item[T]{}
}
items := make([]*Item[T], 0, len(pairs))
for _, pair := range pairs {
items = append(items, &Item[T]{value: pair.Value, name: pair.Name})
}
return items
}
// Value returns the value of the enum item.
// Play: https://go.dev/play/p/xKnoGa7gnev
func (it *Item[T]) Value() T {
return it.value
}
// Name returns the name of the enum item.
// Play: https://go.dev/play/p/xKnoGa7gnev
func (it *Item[T]) Name() string {
return it.name
}
// String returns the string representation of the enum item.
func (it *Item[T]) String() string {
return it.name
}
// Valid checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
// Play: https://go.dev/play/p/pA3lYY2VSm3
func (it *Item[T]) Valid(checker ...func(T) bool) bool {
if len(checker) > 0 {
return checker[0](it.value) && it.name != ""
}
var zero T
return it.value != zero && it.name != ""
}
// MarshalJSON implements the json.Marshaler interface.
// Play: https://go.dev/play/p/zIZEdAnneB5
func (it *Item[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"value": it.value,
"name": it.name,
})
}
// UnmarshalJSON implements the json.Unmarshaler interface.
// Play: https://go.dev/play/p/zIZEdAnneB5
func (it *Item[T]) UnmarshalJSON(data []byte) error {
type alias struct {
Value any `json:"value"`
Name string `json:"name"`
}
var temp alias
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
var v T
rv := reflect.TypeOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want int family")
}
converted := reflect.ValueOf(int64(val)).Convert(rv)
it.value = converted.Interface().(T)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want uint family")
}
converted := reflect.ValueOf(uint64(val)).Convert(rv)
it.value = converted.Interface().(T)
case reflect.Float32, reflect.Float64:
val, ok := temp.Value.(float64)
if !ok {
return fmt.Errorf("invalid type for value, want float family")
}
converted := reflect.ValueOf(val).Convert(rv)
it.value = converted.Interface().(T)
case reflect.String:
val, ok := temp.Value.(string)
if !ok {
return fmt.Errorf("invalid type for value, want string")
}
it.value = any(val).(T)
case reflect.Bool:
val, ok := temp.Value.(bool)
if !ok {
return fmt.Errorf("invalid type for value, want bool")
}
it.value = any(val).(T)
default:
val, ok := temp.Value.(float64)
if ok {
converted := reflect.ValueOf(int64(val)).Convert(rv)
it.value = converted.Interface().(T)
} else {
val2, ok2 := temp.Value.(T)
if !ok2 {
return fmt.Errorf("invalid type for value")
}
it.value = val2
}
}
it.name = temp.Name
return nil
}
// Registry defines a common enum registry struct.
type Registry[T comparable] struct {
mu sync.RWMutex
values map[T]*Item[T]
names map[string]*Item[T]
items []*Item[T]
}
// NewRegistry creates a new enum registry.
// Play: https://go.dev/play/p/ABEXsYfJKMo
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
r := &Registry[T]{
values: make(map[T]*Item[T]),
names: make(map[string]*Item[T]),
items: make([]*Item[T], 0, len(items)),
}
r.Add(items...)
return r
}
// Add adds enum items to the registry.
// Play: https://go.dev/play/p/ABEXsYfJKMo
func (r *Registry[T]) Add(items ...*Item[T]) {
r.mu.Lock()
defer r.mu.Unlock()
for _, item := range items {
if _, exists := r.values[item.value]; exists {
continue
}
if _, exists := r.names[item.name]; exists {
continue
}
r.values[item.value] = item
r.names[item.name] = item
r.items = append(r.items, item)
}
}
// Remove removes an enum item from the registry by its value.
// Play: https://go.dev/play/p/dSG84wQ3TuC
func (r *Registry[T]) Remove(value T) bool {
r.mu.Lock()
defer r.mu.Unlock()
item, ok := r.values[value]
if !ok {
return false
}
delete(r.values, value)
delete(r.names, item.name)
for i, it := range r.items {
if it.value == value {
r.items = append(r.items[:i], r.items[i+1:]...)
break
}
}
return true
}
// Update updates the name of an enum item in the registry by its value.
// Play: https://go.dev/play/p/Ol0moT1J9Xl
func (r *Registry[T]) Update(value T, newName string) bool {
r.mu.Lock()
defer r.mu.Unlock()
item, ok := r.values[value]
if !ok {
return false
}
delete(r.names, item.name)
item.name = newName
r.names[newName] = item
return true
}
// GetByValue retrieves an enum item by its value.
// Play: https://go.dev/play/p/niJ1U2KlE_m
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
r.mu.RLock()
defer r.mu.RUnlock()
item, ok := r.values[value]
return item, ok
}
// GetByName retrieves an enum item by its name.
// Play: https://go.dev/play/p/49ie_gpqH0m
func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
r.mu.RLock()
defer r.mu.RUnlock()
item, ok := r.names[name]
return item, ok
}
// Items returns a slice of all enum items in the registry.
// Play: https://go.dev/play/p/lAJFAradbvQ
func (r *Registry[T]) Items() []*Item[T] {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]*Item[T], len(r.items))
copy(result, r.items)
return result
}
// Contains checks if an enum item with the given value exists in the registry.
// Play: https://go.dev/play/p/_T-lPYkZn2j
func (r *Registry[T]) Contains(value T) bool {
_, ok := r.GetByValue(value)
return ok
}
// Size returns the number of enum items in the registry.
// Play: https://go.dev/play/p/TeDArWhlQe2
func (r *Registry[T]) Size() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.items)
}
// Range iterates over all enum items in the registry and applies the given function.
// Play: https://go.dev/play/p/GPsZbQbefWN
func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, item := range r.items {
if !fn(item) {
break
}
}
}
// SortedItems returns a slice of all enum items sorted by the given less function.
// Play: https://go.dev/play/p/tN9RE_m_WEI
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T] {
items := r.Items()
sort.Slice(items, func(i, j int) bool {
return less(items[i], items[j])
})
return items
}
// Filter returns a slice of enum items that satisfy the given predicate function.
// Play: https://go.dev/play/p/uTUpTdcyoCU
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T] {
r.mu.RLock()
defer r.mu.RUnlock()
var result []*Item[T]
for _, item := range r.items {
if predicate(item) {
result = append(result, item)
}
}
return result
}

208
enum/enum_example_test.go Normal file
View File

@@ -0,0 +1,208 @@
package enum
import "fmt"
func ExampleNewItem() {
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
fmt.Println(item1.Name(), item1.Value())
fmt.Println(item2.Name(), item2.Value())
// Output:
// Active 1
// Inactive 2
}
func ExampleNewItemsFromPairs() {
items := NewItemsFromPairs(
Pair[Status]{Value: Active, Name: "Active"},
Pair[Status]{Value: Inactive, Name: "Inactive"},
)
fmt.Println(items[0].Name(), items[0].Value())
fmt.Println(items[1].Name(), items[1].Value())
// Output:
// Active 1
// Inactive 2
}
func ExampleItem_Valid() {
item := NewItem(Active, "Active")
fmt.Println(item.Valid())
invalidItem := NewItem(Unknown, "")
fmt.Println(invalidItem.Valid())
// Output:
// true
// false
}
func ExampleItem_MarshalJSON() {
item := NewItem(Active, "Active")
data, _ := item.MarshalJSON()
fmt.Println(string(data))
var unmarshaledItem Item[Status]
_ = unmarshaledItem.UnmarshalJSON(data)
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
// Output:
// {"name":"Active","value":1}
// Active 1
}
func ExampleRegistry_Add() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
if item, found := registry.GetByValue(Active); found {
fmt.Println("Found by value:", item.Name())
}
if item, found := registry.GetByName("Inactive"); found {
fmt.Println("Found by name:", item.Value())
}
// Output:
// Found by value: Active
// Found by name: 2
}
func ExampleRegistry_Remove() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
fmt.Println("Size before removal:", registry.Size())
removed := registry.Remove(Active)
fmt.Println("Removed:", removed)
fmt.Println("Size after removal:", registry.Size())
// Output:
// Size before removal: 1
// Removed: true
// Size after removal: 0
}
func ExampleRegistry_Update() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
fmt.Println("Updated:", updated)
if item, found := registry.GetByValue(Active); found {
fmt.Println("New name:", item.Name())
}
// Output:
// Updated: true
// New name: Activated
}
func ExampleRegistry_Items() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
for _, item := range registry.Items() {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_Contains() {
registry := NewRegistry[Status]()
item := NewItem(Active, "Active")
registry.Add(item)
fmt.Println(registry.Contains(Active))
fmt.Println(registry.Contains(Inactive))
// Output:
// true
// false
}
func ExampleRegistry_Size() {
registry := NewRegistry[Status]()
fmt.Println("Initial size:", registry.Size())
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
fmt.Println("Size after adding items:", registry.Size())
registry.Remove(Active)
fmt.Println("Size after removing an item:", registry.Size())
// Output:
// Initial size: 0
// Size after adding items: 2
// Size after removing an item: 1
}
func ExampleRegistry_Range() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
registry.Range(func(item *Item[Status]) bool {
fmt.Println(item.Name(), item.Value())
return true // continue iteration
})
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_SortedItems() {
registry := NewRegistry[Status]()
item1 := NewItem(Inactive, "Inactive")
item2 := NewItem(Active, "Active")
registry.Add(item1, item2)
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
}) {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
// Inactive 2
}
func ExampleRegistry_Filter() {
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
activeItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
for _, item := range activeItems {
fmt.Println(item.Name(), item.Value())
}
// Output:
// Active 1
}

184
enum/enum_test.go Normal file
View File

@@ -0,0 +1,184 @@
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
// Use of this source code is governed by MIT license
package enum
import (
"testing"
"github.com/duke-git/lancet/v2/internal"
)
type Status int
const (
Unknown Status = iota
Active
Inactive
)
func TestNewItemsFromPairs(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestNewItemsFromPairs")
items := NewItemsFromPairs(
Pair[Status]{Value: Active, Name: "Active"},
Pair[Status]{Value: Inactive, Name: "Inactive"},
)
assert.Equal(2, len(items))
assert.Equal(Active, items[0].Value())
assert.Equal("Active", items[0].Name())
assert.Equal(Inactive, items[1].Value())
assert.Equal("Inactive", items[1].Name())
}
func TestItem_Valid(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestItem_Valid")
item := NewItem(Active, "Active")
assert.Equal(true, item.Valid())
invalidItem := NewItem(Unknown, "")
assert.Equal(false, invalidItem.Valid())
}
func TestItem_MarshalJSON(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestItem_MarshalJSON")
item := NewItem(Active, "Active")
data, err := item.MarshalJSON()
assert.IsNil(err)
assert.Equal("{\"name\":\"Active\",\"value\":1}", string(data))
var unmarshaledItem Item[Status]
err = unmarshaledItem.UnmarshalJSON(data)
assert.IsNil(err)
assert.Equal(item.Value(), unmarshaledItem.Value())
assert.Equal(item.Name(), unmarshaledItem.Name())
}
func TestRegistry_AddAndGet(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_AddAndGet")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
assert.Equal(2, registry.Size())
item, ok := registry.GetByValue(Active)
assert.Equal(true, ok)
assert.Equal("Active", item.Name())
item, ok = registry.GetByName("Inactive")
assert.Equal(true, ok)
assert.Equal(Inactive, item.Value())
}
func TestRegistry_Remove(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Remove")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
assert.Equal(2, registry.Size())
removed := registry.Remove(Active)
assert.Equal(true, removed)
assert.Equal(1, registry.Size())
_, ok := registry.GetByValue(Active)
assert.Equal(false, ok)
}
func TestRegistry_Update(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Update")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
updated := registry.Update(Active, "Activated")
assert.Equal(true, updated)
item, ok := registry.GetByValue(Active)
assert.Equal(true, ok)
assert.Equal("Activated", item.Name())
}
func TestRegistry_Contains(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Contains")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
registry.Add(item1)
assert.Equal(true, registry.Contains(Active))
assert.Equal(false, registry.Contains(Inactive))
}
func TestRegistry_Range(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Range")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
var values []Status
registry.Range(func(item *Item[Status]) bool {
values = append(values, item.Value())
return true
})
assert.Equal(2, len(values))
assert.Equal(Active, values[0])
assert.Equal(Inactive, values[1])
}
func TestRegistry_SortedItems(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_SortedItems")
registry := NewRegistry[Status]()
item1 := NewItem(Inactive, "Inactive")
item2 := NewItem(Active, "Active")
registry.Add(item1, item2)
sortedItems := registry.SortedItems(func(i1, i2 *Item[Status]) bool {
return i1.value < i2.value
})
assert.Equal(2, len(sortedItems))
assert.Equal(Active, sortedItems[0].Value())
assert.Equal(Inactive, sortedItems[1].Value())
}
func TestRegistry_Filter(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRegistry_Filter")
registry := NewRegistry[Status]()
item1 := NewItem(Active, "Active")
item2 := NewItem(Inactive, "Inactive")
registry.Add(item1, item2)
filteredItems := registry.Filter(func(item *Item[Status]) bool {
return item.Value() == Active
})
assert.Equal(1, len(filteredItems))
assert.Equal(Active, filteredItems[0].Value())
}

View File

@@ -21,7 +21,7 @@ type EventBus[T any] struct {
// listeners map[string][]*EventListener[T]
listeners sync.Map
mu sync.RWMutex
errorHandler func(err error)
errorHandler func(topic string, err error)
}
// EventListener is the struct that holds the listener function and its priority.
@@ -117,7 +117,7 @@ func (eb *EventBus[T]) Publish(event Event[T]) {
func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event[T]) {
defer func() {
if r := recover(); r != nil && eb.errorHandler != nil {
eb.errorHandler(fmt.Errorf("%v", r))
eb.errorHandler(event.Topic, fmt.Errorf("%v", r))
}
}()
@@ -126,7 +126,7 @@ func (eb *EventBus[T]) publishToListener(listener *EventListener[T], event Event
// SetErrorHandler sets the error handler function.
// Play: https://go.dev/play/p/gmB0gnFe5mc
func (eb *EventBus[T]) SetErrorHandler(handler func(err error)) {
func (eb *EventBus[T]) SetErrorHandler(handler func(topic string, err error)) {
eb.errorHandler = handler
}

View File

@@ -189,8 +189,8 @@ func ExampleEventBus_GetListenersCount() {
func ExampleEventBus_SetErrorHandler() {
eb := NewEventBus[int]()
eb.SetErrorHandler(func(err error) {
fmt.Println(err)
eb.SetErrorHandler(func(topic string, err error) {
fmt.Println(topic, err)
})
eb.Subscribe("event1", func(eventData int) {
@@ -200,7 +200,7 @@ func ExampleEventBus_SetErrorHandler() {
eb.Publish(Event[int]{Topic: "event1", Payload: 1})
// Output:
// error
// event1 error
}
func ExampleEventBus_GetAllListenersCount() {

View File

@@ -114,7 +114,8 @@ func TestEventBus_ErrorHandler(t *testing.T) {
eb := NewEventBus[string]()
eb.SetErrorHandler(func(err error) {
eb.SetErrorHandler(func(topic string, err error) {
assert.Equal("event1", topic)
assert.Equal("error", err.Error())
})

View File

@@ -24,9 +24,10 @@ import (
"strings"
"sync"
"github.com/duke-git/lancet/v2/validator"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"github.com/duke-git/lancet/v2/validator"
)
// FileReader is a reader supporting offset seeking and reading one
@@ -283,21 +284,18 @@ func ReadFileByLine(path string) ([]string, error) {
}
defer f.Close()
scanner := bufio.NewScanner(f)
result := make([]string, 0)
buf := bufio.NewReader(f)
for {
line, _, err := buf.ReadLine()
l := string(line)
if err == io.EOF {
break
}
if err != nil {
continue
}
for scanner.Scan() {
l := scanner.Text()
result = append(result, l)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return result, nil
}

494
formatter/address.go Normal file
View File

@@ -0,0 +1,494 @@
// Package formatter implements some functions to format string, struct.
package formatter
import (
"regexp"
"strings"
"unicode/utf8"
)
// AddressInfo represents the parsed address information including user details and location.
// AddressInfo 表示解析后的地址信息,包括用户详细信息和位置信息
type AddressInfo struct {
Name string `json:"name"` // Name of the recipient / 姓名
Mobile string `json:"mobile"` // Mobile phone number or landline / 手机号或座机
IDN string `json:"idn"` // ID card number / 身份证号
Postcode string `json:"postcode"` // Postal code / 邮编
Province string `json:"province"` // Province / 省
City string `json:"city"` // City / 市
Region string `json:"region"` // District or county / 区/县
Street string `json:"street"` // Street address / 街道详细地址
Addr string `json:"addr"` // Original address string / 原始地址字符串
}
// fuzzyResult represents the result of fuzzy address parsing.
// fuzzyResult 表示模糊地址解析的结果
type fuzzyResult struct {
A1 string // Province level / 省级
A2 string // City level / 市级
A3 string // District/County level / 区/县级
Street string // Street address / 街道地址
}
// ParseCNAddress parses a Chinese address string intelligently and extracts structured information.
// It can parse addresses with or without user information (name, phone, ID card, etc.).
// When withUser is true, it extracts user information from the address string.
// When withUser is false, it only parses the location information.
// The function handles various address formats including:
// - Standard format: "Province City District Street"
// - Compact format: "Name Phone Province City District Street"
// - With keywords: "Name: xxx Phone: xxx Address: xxx"
// - County-level cities: "Province City CountyCity District" (e.g., "河北省石家庄市新乐市")
// ParseCNAddress 智能解析中国地址字符串并提取结构化信息。
// 可以解析带或不带用户信息(姓名、电话、身份证等)的地址。
// 当 withUser 为 true 时,从地址字符串中提取用户信息。
// 当 withUser 为 false 时,仅解析位置信息。
// 该函数处理多种地址格式,包括:
// - 标准格式:"省 市 区 街道"
// - 紧凑格式:"姓名 电话 省 市 区 街道"
// - 带关键词:"姓名:xxx 电话:xxx 地址:xxx"
// - 县级市:"省 市 县级市 区"(如"河北省石家庄市新乐市"
func ParseCNAddress(str string, withUser bool) *AddressInfo {
result := &AddressInfo{}
if withUser {
ParsePersonInfo := ParsePersonInfo(str)
result = ParsePersonInfo
} else {
result.Addr = str
}
fuzz := fuzz(result.Addr)
parse := parse(fuzz.A1, fuzz.A2, fuzz.A3)
result.Province = parse.Province
result.City = parse.City
result.Region = parse.Region
// 提取街道地址:从原始地址中找到区/县的位置,提取后面的内容
if result.Region != "" && result.Addr != "" {
// 在原始地址中查找区/县的位置转换为rune数组以正确处理中文
addrRunes := []rune(result.Addr)
regionRunes := []rune(result.Region)
regionPos := mbStrpos(result.Addr, result.Region)
if regionPos != -1 {
// 提取区/县后面的内容作为街道地址
streetStart := regionPos + len(regionRunes)
if streetStart < len(addrRunes) {
result.Street = string(addrRunes[streetStart:])
}
} else if fuzz.Street != "" {
// 如果没找到区/县使用fuzz返回的街道
result.Street = fuzz.Street
}
} else if fuzz.Street != "" {
result.Street = fuzz.Street
}
// 清理街道地址中的重复省市区信息(可能存在部分匹配的残留)
result.Street = strings.ReplaceAll(result.Street, result.Region, "")
result.Street = strings.ReplaceAll(result.Street, result.City, "")
result.Street = strings.ReplaceAll(result.Street, result.Province, "")
// 清理街道地址中的残留片段(如"自治区直辖县级市"被替换后的残留)
result.Street = strings.ReplaceAll(result.Street, "自治区直辖县级市", "")
result.Street = strings.ReplaceAll(result.Street, "直辖县级市", "")
result.Street = strings.TrimSpace(result.Street)
return result
}
// ParsePersonInfo extracts user information (name, phone, ID card, postal code) from an address string.
// It separates personal information from the address, supporting various formats:
// - Labeled format: "Name: xxx Phone: xxx Address: xxx"
// - Compact format: "Name Phone Address" (e.g., "张三13800138000北京市朝阳区")
// - With separators: using colons, commas, newlines as delimiters
// Returns an AddressInfo with extracted user information and cleaned address string in Addr field.
// ParsePersonInfo 从地址字符串中提取用户信息(姓名、电话、身份证、邮编)。
// 将个人信息与地址分离,支持多种格式:
// - 带标签格式:"姓名:xxx 电话:xxx 地址:xxx"
// - 紧凑格式:"姓名 电话 地址"(如"张三13800138000北京市朝阳区"
// - 带分隔符:使用冒号、逗号、换行符作为分隔符
// 返回包含提取的用户信息和清理后地址字符串(在 Addr 字段中)的 AddressInfo。
func ParsePersonInfo(str string) *AddressInfo {
compose := &AddressInfo{}
// 先尝试提取带标签的信息
// 提取姓名 (支持: 姓名:xxx, 收货人:xxx, 收件人:xxx)
nameRe := regexp.MustCompile(`(?:姓名|收货人|收件人)[:]\s*([^\s\d\n]+)`)
if match := nameRe.FindStringSubmatch(str); len(match) > 1 {
compose.Name = strings.TrimSpace(match[1])
str = nameRe.ReplaceAllString(str, " ")
}
// 提取手机号或座机号 (支持: 电话:xxx, 手机:xxx, 联系电话:xxx)
phoneRe := regexp.MustCompile(`(?:电话|手机号码|手机|联系电话)[:]\s*([\d\-]+)`)
if match := phoneRe.FindStringSubmatch(str); len(match) > 1 {
compose.Mobile = strings.TrimSpace(match[1])
str = phoneRe.ReplaceAllString(str, " ")
}
// 提取所在地区 (支持: 所在地区:xxx)
regionRe := regexp.MustCompile(`所在地区[:]\s*([^\n]+)`)
if match := regionRe.FindStringSubmatch(str); len(match) > 1 {
// 将所在地区保留在字符串中,不删除
// str 保持不变,让后续的地址解析处理
}
// 提取详细地址 (支持: 详细地址:xxx, 收货地址:xxx, 地址:xxx)
addrRe := regexp.MustCompile(`(?:详细地址|收货地址|地址)[:]\s*([^\n]+)`)
if match := addrRe.FindStringSubmatch(str); len(match) > 1 {
// 保留详细地址在字符串中
str = addrRe.ReplaceAllString(str, " "+match[1])
}
// 如果还没有提取到姓名和手机号,尝试识别紧凑格式 (如: 马云13593464918陕西省...)
if compose.Name == "" && compose.Mobile == "" {
// 匹配: 2-4个汉字 + 7-12位数字 + 剩余内容
compactRe := regexp.MustCompile(`^([\x{4e00}-\x{9fa5}]{2,4})(\d{7,12})(.*)$`)
if match := compactRe.FindStringSubmatch(str); len(match) > 3 {
compose.Name = match[1]
compose.Mobile = match[2]
str = match[3] // 保留剩余的地址部分
}
}
// 替换常见的地址关键词为空格
replacements := map[string]string{
"收货地址": " ", "详细地址": " ", "地址": " ", "收货人": " ",
"收件人": " ", "收货": " ", "所在地区": " ", "邮编": " ",
"电话": " ", "手机号码": " ", "身份证号码": " ", "身份证号": " ",
"身份证": " ", "姓名": " ", "联系电话": " ", "手机": " ",
"": " ", ":": " ", "": " ", ";": " ",
"": " ", ",": " ", "。": " ", "\n": " ", "\r": " ",
}
for old, new := range replacements {
str = strings.ReplaceAll(str, old, new)
}
// 将多个空格合并为一个
spaceRe := regexp.MustCompile(`\s{1,}`)
str = spaceRe.ReplaceAllString(str, " ")
// 处理座机号格式 (如: 800-8585222)
telRe := regexp.MustCompile(`(\d{3,4})-(\d{6,8})`)
str = telRe.ReplaceAllString(str, "$1$2")
// 提取身份证号 (18位或17位+X)
idnRe := regexp.MustCompile(`\d{18}|\d{17}[Xx]`)
if match := idnRe.FindString(str); match != "" {
compose.IDN = strings.ToUpper(match)
str = strings.ReplaceAll(str, match, "")
}
// 如果之前没有提取到手机号,现在提取
if compose.Mobile == "" {
mobileRe := regexp.MustCompile(`\d{7,12}`)
if match := mobileRe.FindString(str); match != "" {
compose.Mobile = match
str = strings.ReplaceAll(str, match, "")
}
} else {
// 已经提取过手机号,从字符串中删除
str = strings.ReplaceAll(str, compose.Mobile, "")
}
// 提取邮编
postcodeRe := regexp.MustCompile(`\d{6}`)
if match := postcodeRe.FindString(str); match != "" {
compose.Postcode = match
str = strings.ReplaceAll(str, match, "")
}
// 清理多余空格
str = strings.TrimSpace(spaceRe.ReplaceAllString(str, " "))
// 如果之前没有提取到姓名,现在提取
if compose.Name == "" {
// 提取姓名(取最短的词作为姓名,排除空字符串)
splitArr := strings.Split(str, " ")
if len(splitArr) > 0 {
for _, value := range splitArr {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if compose.Name == "" {
compose.Name = value
} else if utf8.RuneCountInString(value) < utf8.RuneCountInString(compose.Name) && utf8.RuneCountInString(value) >= 2 {
compose.Name = value
}
}
if compose.Name != "" {
str = strings.TrimSpace(strings.ReplaceAll(str, compose.Name, ""))
}
}
} else {
// 已经提取过姓名,从字符串中删除
str = strings.TrimSpace(strings.ReplaceAll(str, compose.Name, ""))
}
compose.Addr = str
return compose
}
// fuzz 根据统计规律分析出二三级地址
func fuzz(addr string) *fuzzyResult {
addrOrigin := addr
addr = strings.ReplaceAll(addr, " ", "")
addr = strings.ReplaceAll(addr, ",", "")
// 先替换"自治区直辖县级市"为"市",避免后续"自治区"替换时产生问题
addr = strings.ReplaceAll(addr, "自治区直辖县级市", "市")
addr = strings.ReplaceAll(addr, "自治区", "省")
addr = strings.ReplaceAll(addr, "自治州", "州")
addr = strings.ReplaceAll(addr, "小区", "")
addr = strings.ReplaceAll(addr, "校区", "")
// 过滤"市辖区" - 这是一个行政术语占位符,不是真正的区名
addr = strings.ReplaceAll(addr, "市辖区", "")
a1 := ""
a2 := ""
a3 := ""
street := ""
deep3KeywordPos := -1
// 判断是否包含县/区/旗
countyPos := mbStrpos(addr, "县")
districtPos := mbStrpos(addr, "区")
bannerPos := mbStrpos(addr, "旗")
// 只要存在这些关键词就处理,不再限制位置
hasEarlyCounty := countyPos != -1
hasEarlyDistrict := districtPos != -1
hasEarlyBanner := bannerPos != -1
if hasEarlyCounty || hasEarlyDistrict || hasEarlyBanner {
// 优先检查是否存在县级市(如"新乐市"
// 如果同时存在"XX市"和"XX区"/"XX县",优先处理"市"
hasCountyLevelCity := false
if mbStrstr(addr, "市") {
// 查找所有"市"的位置
cityCount := mbSubstrCount(addr, "市")
if cityCount >= 2 {
// 找到第二个"市"的位置(可能是县级市)
firstCityPos := mbStrpos(addr, "市")
// 从第一个"市"之后继续查找
addrAfterFirstCity := mbSubstr(addr, firstCityPos+1, utf8.RuneCountInString(addr)-firstCityPos-1)
secondCityPos := mbStrpos(addrAfterFirstCity, "市")
if secondCityPos != -1 {
secondCityAbsPos := firstCityPos + 1 + secondCityPos
// 检查第二个"市"后面是否存在"区"或"县"
addrAfterSecondCity := mbSubstr(addr, secondCityAbsPos+1, utf8.RuneCountInString(addr)-secondCityAbsPos-1)
if mbStrstr(addrAfterSecondCity, "区") || mbStrstr(addrAfterSecondCity, "县") {
// 提取两个"市"之间的内容
betweenCities := mbSubstr(addr, firstCityPos+1, secondCityAbsPos-firstCityPos)
// 检查是否是重复的地名(如"北京市北京市"或"杭州市西湖区杭州市"
// 如果两个"市"之间包含"区"或"县",说明不是县级市,而是重复地名
if !mbStrstr(betweenCities, "区") && !mbStrstr(betweenCities, "县") {
// 第一个"市"及之前的内容
firstCityFull := mbSubstr(addr, 0, firstCityPos+1)
if betweenCities != firstCityFull {
// 不是重复地名,这是县级市
a3 = betweenCities
deep3KeywordPos = secondCityAbsPos
hasCountyLevelCity = true
}
}
}
}
}
}
if !hasCountyLevelCity {
// 处理旗
if mbStrstr(addr, "旗") {
deep3KeywordPos = mbStrpos(addr, "旗")
a3 = mbSubstr(addr, deep3KeywordPos-1, 2)
}
// 处理区
if mbStrstr(addr, "区") {
// 使用第一个"区"(避免重复地名干扰,如"西湖区杭州市西湖区"
deep3KeywordPos = mbStrpos(addr, "区")
if mbStrstr(addr, "市") {
// 策略:找到"区"之前的最后一个"市"
// 这样可以避免详细地址中的"市"字干扰(如"农贸市场")
zonePos := deep3KeywordPos
// 从开头到"区"的子串中,查找最后一个"市"
addrBeforeZone := mbSubstr(addr, 0, zonePos)
cityPos := mbStrripos(addrBeforeZone, "市")
if cityPos != -1 {
a3 = mbSubstr(addr, cityPos+1, zonePos-cityPos)
} else {
// 没有找到"市",使用默认逻辑
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
}
} else {
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
}
}
// 处理县
if mbStrstr(addr, "县") {
// 使用第一个"县"(避免重复地名干扰)
deep3KeywordPos = mbStrpos(addr, "县")
if mbStrstr(addr, "市") {
// 从开头到"县"的子串中,查找最后一个"市"
addrBeforeCounty := mbSubstr(addr, 0, deep3KeywordPos)
cityPos := mbStrripos(addrBeforeCounty, "市")
if cityPos != -1 {
a3 = mbSubstr(addr, cityPos+1, deep3KeywordPos-cityPos)
} else {
if mbStrstr(addr, "自治县") {
a3 = mbSubstr(addr, deep3KeywordPos-6, 7)
firstChar := mbSubstr(a3, 0, 1)
if firstChar == "省" || firstChar == "市" || firstChar == "州" {
a3 = mbSubstr(a3, 1, utf8.RuneCountInString(a3)-1)
}
} else {
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
}
}
} else {
if mbStrstr(addr, "自治县") {
a3 = mbSubstr(addr, deep3KeywordPos-6, 7)
firstChar := mbSubstr(a3, 0, 1)
if firstChar == "省" || firstChar == "市" || firstChar == "州" {
a3 = mbSubstr(a3, 1, utf8.RuneCountInString(a3)-1)
}
} else {
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
}
}
}
}
if deep3KeywordPos != -1 {
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
}
} else {
// 处理市
if mbStrripos(addr, "市") != -1 {
cityCount := mbSubstrCount(addr, "市")
if cityCount == 1 {
deep3KeywordPos = mbStrripos(addr, "市")
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
} else if cityCount >= 2 {
deep3KeywordPos = mbStrripos(addr, "市")
a3 = mbSubstr(addr, deep3KeywordPos-2, 3)
street = mbSubstr(addrOrigin, deep3KeywordPos+1, utf8.RuneCountInString(addrOrigin)-deep3KeywordPos-1)
}
} else {
a3 = ""
street = addr
}
}
// 提取市级地址
if mbStrpos(addr, "市") != -1 || mbStrstr(addr, "盟") || mbStrstr(addr, "州") {
tmpPos := -1
if tmpPos = mbStrpos(addr, "市"); tmpPos != -1 {
// 使用第一个"市"(避免重复地名干扰,如"杭州市西湖区杭州市"
// 向前查找省的位置,如果有省就从省后开始,否则从开头开始
addrBeforeCity := mbSubstr(addr, 0, tmpPos)
provincePos := mbStrripos(addrBeforeCity, "省")
startPos := 0
if provincePos != -1 {
startPos = provincePos + 1
}
a2 = mbSubstr(addr, startPos, tmpPos-startPos+1)
} else if tmpPos = mbStrpos(addr, "盟"); tmpPos != -1 {
a2 = mbSubstr(addr, tmpPos-2, 3)
} else if mbStrpos(addr, "州") != -1 {
if tmpPos = mbStrpos(addr, "自治州"); tmpPos != -1 {
a2 = mbSubstr(addr, tmpPos-4, 5)
} else {
tmpPos = mbStrpos(addr, "州")
a2 = mbSubstr(addr, tmpPos-2, 3)
}
}
}
return &fuzzyResult{
A1: a1,
A2: a2,
A3: a3,
Street: street,
}
}
// parse 智能解析出省市区
func parse(a1, a2, a3 string) *AddressInfo {
r := &AddressInfo{}
if a3 == "" {
return r
}
// 在三级地址数据中查找匹配
area3Matches := make(map[int]*Region)
for id, v := range A3Data {
if mbStrpos(v.Name, a3) != -1 {
area3Matches[id] = v
}
}
// 多个匹配项,需要通过二级地址筛选
if len(area3Matches) > 1 {
if a2 != "" {
area2Matches := make(map[int]*Region)
for id, v := range A2Data {
if mbStrpos(v.Name, a2) != -1 {
area2Matches[id] = v
}
}
if len(area2Matches) > 0 {
for _, v := range area3Matches {
if city, ok := area2Matches[v.PID]; ok {
r.City = city.Name
r.Region = v.Name
if province, ok := A1Data[city.PID]; ok {
r.Province = province.Name
}
}
}
}
} else {
r.Province = ""
r.City = ""
r.Region = a3
}
} else if len(area3Matches) == 1 {
// 唯一匹配
for _, v := range area3Matches {
r.Region = v.Name
if city, ok := A2Data[v.PID]; ok {
r.City = city.Name
if province, ok := A1Data[city.PID]; ok {
r.Province = province.Name
}
}
}
} else if len(area3Matches) == 0 && a2 == a3 {
// 没有匹配到三级地址,但二级地址等于三级地址,可能是直辖市
shengID := 0
for _, v := range A2Data {
if mbStrpos(v.Name, a2) != -1 {
r.City = v.Name
shengID = v.PID
break
}
}
if province, ok := A1Data[shengID]; ok {
r.Province = province.Name
}
r.Region = ""
}
return r
}

4470
formatter/address_data.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
package formatter
import (
"strings"
"unicode/utf8"
)
// mbStrpos 返回字符串首次出现的位置UTF-8字符计数
func mbStrpos(haystack, needle string) int {
if needle == "" {
return 0
}
idx := strings.Index(haystack, needle)
if idx == -1 {
return -1
}
return utf8.RuneCountInString(haystack[:idx])
}
// mbStrripos 返回字符串最后出现的位置UTF-8字符计数
func mbStrripos(haystack, needle string) int {
if needle == "" {
return utf8.RuneCountInString(haystack)
}
idx := strings.LastIndex(haystack, needle)
if idx == -1 {
return -1
}
return utf8.RuneCountInString(haystack[:idx])
}
// mbStrstr 检查字符串是否包含子串
func mbStrstr(haystack, needle string) bool {
return strings.Contains(haystack, needle)
}
// mbSubstr 截取字符串UTF-8字符计数
// start: 起始位置从0开始
// length: 截取长度(字符数)
func mbSubstr(str string, start, length int) string {
runes := []rune(str)
strLen := len(runes)
// 处理负数起始位置
if start < 0 {
start = strLen + start
if start < 0 {
start = 0
}
}
// 起始位置超出字符串长度
if start >= strLen {
return ""
}
// 计算结束位置
end := start + length
if end > strLen {
end = strLen
}
if end < start {
return ""
}
return string(runes[start:end])
}
// mbSubstrCount 统计子串出现次数
func mbSubstrCount(haystack, needle string) int {
if needle == "" {
return 0
}
return strings.Count(haystack, needle)
}

360
formatter/address_test.go Normal file
View File

@@ -0,0 +1,360 @@
package formatter
import (
"encoding/json"
"testing"
)
func TestParseCNAddress(t *testing.T) {
tests := []struct {
name string
input string
withUser bool
want *AddressInfo
}{
{
name: "完整地址信息",
input: "张三 13800138000 北京市朝阳区建国路1号",
withUser: true,
want: &AddressInfo{
Name: "张三",
Mobile: "13800138000",
Province: "北京",
City: "北京市",
Region: "朝阳区",
Street: "建国路1号",
},
},
{
name: "带身份证和邮编",
input: "李四 18612345678 110101199001011234 100000 上海市浦东新区世纪大道100号",
withUser: true,
want: &AddressInfo{
Name: "李四",
Mobile: "18612345678",
IDN: "110101199001011234",
Postcode: "100000",
},
},
{
name: "仅地址不含用户信息",
input: "北京市海淀区中关村大街1号",
withUser: false,
want: &AddressInfo{
Province: "北京",
City: "北京市",
Region: "海淀区",
Street: "中关村大街1号",
},
},
{
name: "带收货关键词",
input: "收货人:王五 电话13900139000 收货地址天津市河西区友谊路20号",
withUser: true,
want: &AddressInfo{
Name: "王五",
Mobile: "13900139000",
Province: "天津",
City: "天津市",
Region: "河西区",
},
},
{
name: "紧凑格式地址",
input: "马云13593464918陕西省西安市雁塔区丈八沟街道高新四路南江国际",
withUser: true,
want: &AddressInfo{
Name: "马云",
Mobile: "13593464918",
Province: "陕西省",
City: "西安市",
Region: "雁塔区",
Street: "丈八沟街道高新四路南江国际",
},
},
{
name: "带座机号格式",
input: "姓名:马云\n联系电话800-8585222\n所在地区河北省石家庄市新华区\n详细地址:中华北大街68号鹿城商务中心6号楼1413室",
withUser: true,
want: &AddressInfo{
Name: "马云",
Mobile: "800-8585222",
Province: "河北省",
City: "石家庄市",
Region: "新华区",
Street: "中华北大街68号鹿城商务中心6号楼1413室",
},
},
{
name: "北京市重复格式",
input: "北京市北京市市辖区东城区",
withUser: false,
want: &AddressInfo{
Province: "北京",
City: "北京市",
Region: "东城区",
Street: "",
},
},
{
name: "河北省新乐市地址",
input: "河北省石家庄市新乐市经济开发区兴工街10号来优品仓库",
withUser: false,
want: &AddressInfo{
Province: "河北省",
City: "石家庄市",
Region: "新乐市",
Street: "经济开发区兴工街10号来优品仓库",
},
},
{
name: "江苏仪征市地址",
input: "江苏省扬州市仪征市真州镇解放东路99号",
withUser: false,
want: &AddressInfo{
Province: "江苏省",
City: "扬州市",
Region: "仪征市",
Street: "真州镇解放东路99号",
},
},
{
name: "新疆石河子市地址",
input: "新疆石河子市北三路25小区",
withUser: false,
want: &AddressInfo{
Province: "新疆维吾尔自治区",
City: "自治区直辖县级市",
Region: "石河子市",
},
},
{
name: "新疆石河子市-简化格式省+县级市",
input: "新疆维吾尔自治区石河子市",
withUser: false,
want: &AddressInfo{
Province: "新疆维吾尔自治区",
City: "自治区直辖县级市",
Region: "石河子市",
Street: "",
},
},
{
name: "新疆石河子市-完整行政区划表述",
input: "新疆维吾尔自治区自治区直辖县级市石河子市",
withUser: false,
want: &AddressInfo{
Province: "新疆维吾尔自治区",
City: "自治区直辖县级市",
Region: "石河子市",
Street: "",
},
},
{
name: "浙江杭州西湖区重复地址",
input: "浙江省杭州市西湖区杭州市西湖区人民政府109号",
withUser: false,
want: &AddressInfo{
Province: "浙江省",
City: "杭州市",
Region: "西湖区",
Street: "人民政府109号",
},
},
{
name: "湖南长沙市重复地址",
input: "湖南省长沙市岳麓区银盆岭街道长沙市人民政府长沙市政府大楼",
withUser: false,
want: &AddressInfo{
Province: "湖南省",
City: "长沙市",
Region: "岳麓区",
Street: "银盆岭街道人民政府政府大楼",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ParseCNAddress(tt.input, tt.withUser)
// 打印结果便于调试
jsonData, _ := json.MarshalIndent(got, "", " ")
t.Logf("Result: %s", jsonData)
// 验证主要字段
if tt.want.Name != "" && got.Name != tt.want.Name {
t.Errorf("Name = %v, want %v", got.Name, tt.want.Name)
}
if tt.want.Mobile != "" && got.Mobile != tt.want.Mobile {
t.Errorf("Mobile = %v, want %v", got.Mobile, tt.want.Mobile)
}
if tt.want.Province != "" && got.Province != tt.want.Province {
t.Errorf("Province = %v, want %v", got.Province, tt.want.Province)
}
if tt.want.City != "" && got.City != tt.want.City {
t.Errorf("City = %v, want %v", got.City, tt.want.City)
}
if tt.want.Region != "" && got.Region != tt.want.Region {
t.Errorf("Region = %v, want %v", got.Region, tt.want.Region)
}
})
}
}
func TestParsePersonInfo(t *testing.T) {
tests := []struct {
name string
input string
verify func(*testing.T, *AddressInfo)
}{
{
name: "提取姓名和手机号",
input: "张三 13800138000 北京市朝阳区",
verify: func(t *testing.T, got *AddressInfo) {
if got.Name != "张三" {
t.Errorf("Name = %v, want 张三", got.Name)
}
if got.Mobile != "13800138000" {
t.Errorf("Mobile = %v, want 13800138000", got.Mobile)
}
},
},
{
name: "提取身份证号",
input: "李四 110101199001011234 上海市",
verify: func(t *testing.T, got *AddressInfo) {
if got.Name != "李四" {
t.Errorf("Name = %v, want 李四", got.Name)
}
if got.IDN != "110101199001011234" {
t.Errorf("IDN = %v, want 110101199001011234", got.IDN)
}
},
},
{
name: "提取邮编",
input: "王五 100000 天津市",
verify: func(t *testing.T, got *AddressInfo) {
if got.Name != "王五" {
t.Errorf("Name = %v, want 王五", got.Name)
}
if got.Postcode != "100000" {
t.Errorf("Postcode = %v, want 100000", got.Postcode)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ParsePersonInfo(tt.input)
jsonData, _ := json.MarshalIndent(got, "", " ")
t.Logf("Result: %s", jsonData)
tt.verify(t, got)
})
}
}
func TestFuzz(t *testing.T) {
tests := []struct {
name string
input string
want *fuzzyResult
}{
{
name: "包含区",
input: "北京市朝阳区建国路1号",
want: &fuzzyResult{
A2: "北京市",
A3: "朝阳区",
Street: "建国路1号",
},
},
{
name: "包含县",
input: "河北省石家庄市正定县",
want: &fuzzyResult{
A2: "石家庄市",
A3: "正定县",
},
},
{
name: "复杂街道地址",
input: "浙江省杭州市拱墅区武林街道杭州锦麟宾馆中河片区",
want: &fuzzyResult{
A2: "杭州市",
A3: "拱墅区",
Street: "武林街道杭州锦麟宾馆中河片区",
},
},
{
name: "北京市重复格式",
input: "北京市北京市市辖区东城区",
want: &fuzzyResult{
A2: "北京市",
A3: "东城区",
Street: "",
},
},
{
name: "详细地址包含市字",
input: "北京市朝阳区建外大街1号国贸商城",
want: &fuzzyResult{
A2: "北京市",
A3: "朝阳区",
Street: "建外大街1号国贸商城",
},
},
{
name: "详细地址真的包含市字",
input: "北京市朝阳区农贸市场路1号",
want: &fuzzyResult{
A2: "北京市",
A3: "朝阳区",
Street: "农贸市场路1号",
},
},
{
name: "河北省新乐市地址",
input: "河北省石家庄市新乐市经济开发区兴工街10号来优品仓库",
want: &fuzzyResult{
A2: "石家庄市",
A3: "新乐市",
Street: "经济开发区兴工街10号来优品仓库",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := fuzz(tt.input)
jsonData, _ := json.MarshalIndent(got, "", " ")
t.Logf("Result: %s", jsonData)
if got.A2 != tt.want.A2 {
t.Errorf("A2 = %v, want %v", got.A2, tt.want.A2)
}
if got.A3 != tt.want.A3 {
t.Errorf("A3 = %v, want %v", got.A3, tt.want.A3)
}
if tt.want.Street != "" && got.Street != tt.want.Street {
t.Errorf("Street = %v, want %v", got.Street, tt.want.Street)
}
})
}
}
func ExampleParseCNAddress() {
// 解析包含用户信息的完整地址
result := ParseCNAddress("张三 13800138000 北京市朝阳区建国路1号", true)
jsonData, _ := json.MarshalIndent(result, "", " ")
println(string(jsonData))
}
func ExampleParsePersonInfo() {
// 分离用户信息
result := ParsePersonInfo("收货人:李四 电话18612345678 地址上海市浦东新区世纪大道100号")
jsonData, _ := json.MarshalIndent(result, "", " ")
println(string(jsonData))
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"golang.org/x/exp/constraints"
@@ -680,3 +681,150 @@ func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V)
return result
}
// ToMarkdownTable converts a slice of maps to a Markdown table.
// Play: ttps://go.dev/play/p/w_pSLfeyEB5
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string {
if len(data) == 0 {
return "| |\n|---|\n"
}
var headers []string
// 如果提供了columnOrder则按指定顺序排列
if len(columnOrder) > 0 {
headers = make([]string, len(columnOrder))
copy(headers, columnOrder)
} else {
// 否则按自然顺序提取headers
seen := make(map[string]bool)
for _, row := range data {
for k := range row {
if !seen[k] {
seen[k] = true
headers = append(headers, k)
}
}
}
}
var builder strings.Builder
// Header row - 使用映射的中文标题
builder.WriteString("| ")
for i, h := range headers {
// 如果有映射则使用中文标题,否则使用原字段名
displayHeader := h
if headerMap != nil && headerMap[h] != "" {
displayHeader = headerMap[h]
}
builder.WriteString(displayHeader)
if i < len(headers)-1 {
builder.WriteString(" | ")
}
}
builder.WriteString(" |\n")
// Separator
builder.WriteString("|")
for i := range headers {
if i > 0 {
builder.WriteString("|")
}
builder.WriteString("---")
}
builder.WriteString("|\n")
// Data rows
for _, row := range data {
builder.WriteString("| ")
for i, h := range headers {
val, exists := row[h]
var cell string
if !exists {
cell = ""
} else {
cell = formatValue(val)
}
builder.WriteString(cell)
if i < len(headers)-1 {
builder.WriteString(" | ")
}
}
builder.WriteString(" |\n")
}
return builder.String()
}
// formatValue formats any value for display in Markdown table
func formatValue(v interface{}) string {
switch val := v.(type) {
case int:
return commaInt64(int64(val))
case int8:
return commaInt64(int64(val))
case int16:
return commaInt64(int64(val))
case int32:
return commaInt64(int64(val))
case int64:
return commaInt64(val)
case uint:
return commaUint64(uint64(val))
case uint8:
return commaUint64(uint64(val))
case uint16:
return commaUint64(uint64(val))
case uint32:
return commaUint64(uint64(val))
case uint64:
return commaUint64(val)
case float32:
return fmt.Sprintf("%.2f", val)
case float64:
return fmt.Sprintf("%.2f", val)
case string:
return val
case bool:
return fmt.Sprintf("%t", val)
case nil:
return ""
default:
return fmt.Sprintf("%v", val)
}
}
// commaInt64 adds comma separators to int64
func commaInt64(n int64) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
s := strconv.FormatInt(n, 10)
return addCommas(s)
}
// commaUint64 adds comma separators to uint64
func commaUint64(n uint64) string {
if n == 0 {
return "0"
}
s := strconv.FormatUint(n, 10)
return addCommas(s)
}
// addCommas inserts commas every 3 digits
func addCommas(s string) string {
var result strings.Builder
for i, c := range s {
if i > 0 && (len(s)-i)%3 == 0 {
result.WriteRune(',')
}
result.WriteRune(c)
}
return result.String()
}

View File

@@ -1,6 +1,7 @@
package maputil
import (
"fmt"
"math/cmplx"
"sort"
"strconv"
@@ -926,3 +927,46 @@ func TestFindValuesBy(t *testing.T) {
assert.Equal(tt.expected, result)
}
}
func TestToMarkdownTable(t *testing.T) {
// 测试空数据
emptyResult := ToMarkdownTable([]map[string]interface{}{}, nil, nil)
expectedEmpty := "| |\n|---|\n"
if emptyResult != expectedEmpty {
t.Errorf("Expected empty table, got: %s", emptyResult)
}
// 测试基本数据
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
{"name": "Bob", "age": 30, "salary": 60000},
}
result := ToMarkdownTable(data, nil, nil)
fmt.Printf("%s", result)
// 验证结果包含预期的表头和数据行
}
// 测试自定义列顺序
func TestToMarkdownTableWithColumnOrder(t *testing.T) {
data := []map[string]interface{}{
{"name": "Alice", "age": 25, "salary": 50000},
}
columnOrder := []string{"salary", "name", "age"}
result := ToMarkdownTable(data, nil, columnOrder)
fmt.Printf("%s", result)
// 验证列顺序是否正确
}
// 测试自定义表头
func TestToMarkdownTableWithHeaderMap(t *testing.T) {
data := []map[string]interface{}{
{"name": "Alice", "age": 25},
}
headerMap := map[string]string{
"name": "姓名",
"age": "年龄",
}
result := ToMarkdownTable(data, headerMap, nil)
fmt.Printf("%s", result)
// 验证中文表头是否正确显示
}

View File

@@ -238,6 +238,9 @@ func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
// Average return average value of numbers.
// Play: https://go.dev/play/p/Vv7LBwER-pz
func Average[T constraints.Integer | constraints.Float](numbers ...T) float64 {
if len(numbers) == 0 {
return 0
}
var sum float64
for _, num := range numbers {
sum += float64(num)

View File

@@ -10,7 +10,7 @@ import (
"io"
"math"
"math/rand"
"os"
"sync"
"time"
"unsafe"
@@ -27,7 +27,14 @@ const (
AllChars = Numeral + LowwerLetters + UpperLetters + SymbolChars
)
var rn = rand.NewSource(time.Now().UnixNano())
// var rn = rand.NewSource(time.Now().UnixNano())
// 每个 goroutine 独立的 rand.Rand避免并发问题
var randPool = sync.Pool{
New: func() any {
return rand.New(rand.NewSource(time.Now().UnixNano()))
},
}
func init() {
rand.Seed(time.Now().UnixNano())
@@ -286,10 +293,9 @@ func nearestPowerOfTwo(cap int) int {
// random generate a random string based on given string range.
func random(s string, length int) string {
// 确保随机数生成器的种子是动态的
pid := os.Getpid()
timestamp := time.Now().UnixNano()
rand.Seed(int64(pid) + timestamp)
// 从 pool 中获取 rand.Rand替代全局 rand
rn := randPool.Get().(*rand.Rand)
defer randPool.Put(rn)
// 仿照strings.Builder
// 创建一个长度为 length 的字节切片

View File

@@ -4,6 +4,7 @@ import (
"reflect"
"regexp"
"strconv"
"sync"
"testing"
"github.com/duke-git/lancet/v2/internal"
@@ -376,3 +377,31 @@ func TestRandNumberOfLength(t *testing.T) {
assert := internal.NewAssert(t, "TestRandNumberOfLength")
assert.Equal(6, len(strconv.Itoa(randi)))
}
// TestRandStringConcurrent verifies RandString is safe under high concurrency.
// Before the fix, this test may panic or trigger data races.
// After the fix, it should always pass.
func TestRandStringConcurrent(t *testing.T) {
const (
goroutines = 100
iterations = 1000
length = 32
)
var wg sync.WaitGroup
wg.Add(goroutines)
for g := 0; g < goroutines; g++ {
go func() {
defer wg.Done()
for i := 0; i < iterations; i++ {
s := RandString(length)
if len(s) != length {
t.Fatalf("unexpected string length: got %d, want %d", len(s), length)
}
}
}()
}
wg.Wait()
}

View File

@@ -74,6 +74,27 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool {
return true
}
// ContainAny check if the slice contains any element from the targets slice.
// Play: https://go.dev/play/p/4xoxhc9XSSw
func ContainAny[T comparable](slice []T, targets []T) bool {
if len(targets) == 0 {
return false
}
sliceMap := make(map[T]struct{}, len(slice))
for _, item := range slice {
sliceMap[item] = struct{}{}
}
for _, target := range targets {
if _, exists := sliceMap[target]; exists {
return true
}
}
return false
}
// Chunk creates a slice of elements split into groups the length of size.
// Play: https://go.dev/play/p/b4Pou5j2L_C
func Chunk[T any](slice []T, size int) [][]T {
@@ -690,11 +711,12 @@ func IntSlice(slice any) []int {
// DeleteAt delete the element of slice at index.
// Play: https://go.dev/play/p/800B1dPBYyd
func DeleteAt[T any](slice []T, index int) []T {
result := append([]T(nil), slice...)
if index < 0 || index >= len(slice) {
return slice[:len(slice)-1]
return result[:len(slice)-1]
}
result := append([]T(nil), slice...)
copy(result[index:], result[index+1:])
// Set the last element to zero value, clean up the memory.
@@ -734,7 +756,8 @@ func Drop[T any](slice []T, n int) []T {
}
if n <= 0 {
return slice
result := make([]T, 0, size)
return append(result, slice...)
}
result := make([]T, 0, size-n)
@@ -752,7 +775,8 @@ func DropRight[T any](slice []T, n int) []T {
}
if n <= 0 {
return slice
result := make([]T, 0, size)
return append(result, slice...)
}
result := make([]T, 0, size-n)
@@ -798,7 +822,9 @@ func InsertAt[T any](slice []T, index int, value any) []T {
size := len(slice)
if index < 0 || index > size {
return slice
result := make([]T, size)
copy(result, slice)
return result
}
switch v := value.(type) {
@@ -815,21 +841,21 @@ func InsertAt[T any](slice []T, index int, value any) []T {
copy(result[index+len(v):], slice[index:])
return result
default:
return slice
result := make([]T, size)
copy(result, slice)
return result
}
}
// UpdateAt update the slice element at index.
// Play: https://go.dev/play/p/f3mh2KloWVm
func UpdateAt[T any](slice []T, index int, value T) []T {
if index < 0 || index >= len(slice) {
return slice
}
result := make([]T, len(slice))
copy(result, slice)
result[index] = value
if index >= 0 && index < len(slice) {
result[index] = value
}
return result
}
@@ -1019,7 +1045,9 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
return []T{}
}
if len(slices) == 1 {
return Unique(slices[0])
result := make([]T, len(slices[0]))
copy(result, slices[0])
return Unique(result)
}
result := make([]T, 0)
@@ -1040,6 +1068,7 @@ func SymmetricDifference[T comparable](slices ...[]T) []T {
}
// Reverse return slice of element order is reversed to the given slice.
// Reverse modifies the slice in place.
// Play: https://go.dev/play/p/8uI8f1lwNrQ
func Reverse[T any](slice []T) {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
@@ -1047,7 +1076,8 @@ func Reverse[T any](slice []T) {
}
}
// ReverseCopy return a new slice of element order is reversed to the given slice.
// ReverseCopy return a new slice of element where the order is reversed to the given
// slice.
// Play: https://go.dev/play/p/c9arEaP7Cg-
func ReverseCopy[T any](slice []T) []T {
result := make([]T, len(slice))
@@ -1065,7 +1095,7 @@ func Shuffle[T any](slice []T) []T {
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(slice), func(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
swap(slice, i, j)
})
return slice
@@ -1238,11 +1268,12 @@ func SortByField[T any](slice []T, field string, sortType ...string) error {
// Without creates a slice excluding all given items.
// Play: https://go.dev/play/p/bwhEXEypThg
func Without[T comparable](slice []T, items ...T) []T {
result := make([]T, 0, len(slice))
if len(items) == 0 || len(slice) == 0 {
return slice
return append(result, slice...)
}
result := make([]T, 0, len(slice))
for _, v := range slice {
if !Contain(items, v) {
result = append(result, v)

View File

@@ -58,6 +58,24 @@ func ExampleContainSubSlice() {
// false
}
func ExampleContainAny() {
result1 := ContainAny([]string{"a", "b", "c"}, []string{"a"})
result2 := ContainAny([]string{"a", "b", "c"}, []string{"d", "e"})
result3 := ContainAny([]string{"a", "b", "c"}, []string{"d", "a"})
result4 := ContainAny([]string{"a", "b", "c"}, []string{})
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// false
// true
// false
}
func ExampleChunk() {
arr := []string{"a", "b", "c", "d", "e"}
@@ -831,7 +849,7 @@ func ExampleUniqueByComparator() {
})
caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool {
return strings.ToLower(item) == strings.ToLower(other)
return strings.EqualFold(item, other)
})
fmt.Println(uniqueNums)

View File

@@ -89,6 +89,46 @@ func TestContainSubSlice(t *testing.T) {
}
}
func TestContainAny(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestContainAny")
tests := []struct {
slice []string
targets []string
want bool
}{
{[]string{"a", "b", "c"}, []string{"a"}, true},
{[]string{"a", "b", "c"}, []string{"a", "b"}, true},
{[]string{"a", "b", "c"}, []string{"d", "e"}, false},
{[]string{"a", "b", "c"}, []string{"d", "a"}, true},
{[]string{"a", "b", "c"}, []string{}, false},
{[]string{}, []string{"a"}, false},
{[]string{}, []string{}, false},
{[]string{"a", "b", "c"}, []string{"c", "d", "e"}, true},
}
for _, tt := range tests {
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
}
intTests := []struct {
slice []int
targets []int
want bool
}{
{[]int{1, 2, 3, 4, 5}, []int{3}, true},
{[]int{1, 2, 3, 4, 5}, []int{6, 7}, false},
{[]int{1, 2, 3, 4, 5}, []int{5, 6, 7}, true},
{[]int{1, 2, 3, 4, 5}, []int{}, false},
}
for _, tt := range intTests {
assert.Equal(tt.want, ContainAny(tt.slice, tt.targets))
}
}
func TestChunk(t *testing.T) {
t.Parallel()
@@ -1008,7 +1048,7 @@ func TestUniqueByComparator(t *testing.T) {
t.Run("case-insensitive string comparison", func(t *testing.T) {
stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"}
caseInsensitiveComparator := func(item, other string) bool {
return strings.ToLower(item) == strings.ToLower(other)
return strings.EqualFold(item, other)
}
result := UniqueByComparator(stringSlice, caseInsensitiveComparator)

View File

@@ -26,26 +26,31 @@ func newField(v reflect.Value, f reflect.StructField, tagName string) *Field {
}
// Tag returns the value that the key in the tag string.
// Play: https://go.dev/play/p/DVrx5HvvUJr
func (f *Field) Tag() *Tag {
return f.tag
}
// Value returns the underlying value of the field.
// Play: https://go.dev/play/p/qufYEU2o4Oi
func (f *Field) Value() any {
return f.rvalue.Interface()
}
// IsEmbedded returns true if the given field is an embedded field.
// Play: https://go.dev/play/p/wV2PrbYm3Ec
func (f *Field) IsEmbedded() bool {
return len(f.field.Index) > 1
}
// IsExported returns true if the given field is exported.
// Play: https://go.dev/play/p/csK4AXYaNbJ
func (f *Field) IsExported() bool {
return f.field.IsExported()
}
// IsZero returns true if the given field is zero value.
// Play: https://go.dev/play/p/RzqpGISf87r
func (f *Field) IsZero() bool {
z := reflect.Zero(f.rvalue.Type()).Interface()
v := f.Value()
@@ -63,22 +68,26 @@ func (f *Field) IsNil() bool {
}
// Name returns the name of the given field
// Play: https://go.dev/play/p/zfIGlqsatee
func (f *Field) Name() string {
return f.field.Name
}
// Kind returns the field's kind
// Play: https://go.dev/play/p/wg4NlcUNG5o
func (f *Field) Kind() reflect.Kind {
return f.rvalue.Kind()
}
// IsSlice check if a struct field type is slice or not
// Play: https://go.dev/play/p/MKz4CgBIUrU
func (f *Field) IsSlice() bool {
k := f.rvalue.Kind()
return k == reflect.Slice
}
// IsTargetType check if a struct field type is target type or not
// Play: https://go.dev/play/p/Ig75P-agN39
func (f *Field) IsTargetType(targetType reflect.Kind) bool {
return f.rvalue.Kind() == targetType
}

View File

@@ -20,6 +20,7 @@ type Struct struct {
}
// New returns a new *Struct
// Play: https://go.dev/play/p/O29l8kk-Z17
func New(value any, tagName ...string) *Struct {
value = pointer.ExtractPointer(value)
v := reflect.ValueOf(value)
@@ -60,6 +61,7 @@ func New(value any, tagName ...string) *Struct {
// Name string `json:"myName"`
//
// ToMap convert the exported fields of a struct to map.
// Play: https://go.dev/play/p/qQbLySBgerZ
func (s *Struct) ToMap() (map[string]any, error) {
if !s.IsStruct() {
return nil, fmt.Errorf("invalid struct %v", s)
@@ -87,6 +89,7 @@ func (s *Struct) ToMap() (map[string]any, error) {
}
// Fields returns all the struct fields within a slice
// Play: https://go.dev/play/p/w3Kk_CyVY7D
func (s *Struct) Fields() []*Field {
fieldNum := s.rvalue.NumField()
fields := make([]*Field, 0, fieldNum)
@@ -100,6 +103,7 @@ func (s *Struct) Fields() []*Field {
}
// Field returns a Field if the given field name was found
// Play: https://go.dev/play/p/KocZMSYarza
func (s *Struct) Field(name string) (*Field, bool) {
f, ok := s.rtype.FieldByName(name)
if !ok {
@@ -109,6 +113,7 @@ func (s *Struct) Field(name string) (*Field, bool) {
}
// IsStruct returns true if the given rvalue is a struct
// Play: https://go.dev/play/p/bU2FSdkbK1C
func (s *Struct) IsStruct() bool {
k := s.rvalue.Kind()
if k == reflect.Invalid {
@@ -119,6 +124,13 @@ func (s *Struct) IsStruct() bool {
// ToMap convert struct to map, only convert exported struct field
// map key is specified same as struct field tag `json` value.
// Play: https://go.dev/play/p/qQbLySBgerZ
func ToMap(v any) (map[string]any, error) {
return New(v).ToMap()
}
// TypeName return struct type name
// Play: https://go.dev/play/p/SWLWd0XBaBb
func (s *Struct) TypeName() string {
return s.rtype.Name()
}

View File

@@ -177,3 +177,21 @@ func TestStruct_IsStruct(t *testing.T) {
assert.Equal(true, s1.IsStruct())
assert.Equal(false, s2.IsStruct())
}
func TestStruct_TypeName(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestStruct_TypeName")
type Test1 struct{}
t1 := &Test1{}
s1 := New(t1)
assert.Equal("Test1", s1.TypeName())
type Test2 struct{}
t2 := Test2{}
s2 := New(t2)
assert.Equal("Test2", s2.TypeName())
}

View File

@@ -8,7 +8,6 @@ import (
"encoding/json"
"fmt"
"net"
"net/mail"
"net/url"
"reflect"
"regexp"
@@ -19,30 +18,46 @@ import (
)
var (
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
unionPay *regexp.Regexp = regexp.MustCompile("^62[0-5]\\d{13,16}$")
chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
// dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*(?:xn--[a-zA-Z0-9\-]{1,59}|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)$`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
)
var passportMatcher = map[string]*regexp.Regexp{
"CN": regexp.MustCompile(`^P\d{9}$`),
"US": regexp.MustCompile(`^\d{9}$`),
"GB": regexp.MustCompile(`^[A-Z0-9]{9}$`),
"RU": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
"DE": regexp.MustCompile(`^\d{9}$`),
"FR": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
"JP": regexp.MustCompile(`^\d{8}$`),
"IT": regexp.MustCompile(`^\d{8}$`),
"AU": regexp.MustCompile(`^[A-Z]{1}\d{8}$`),
"BR": regexp.MustCompile(`^\d{9}$`),
"IN": regexp.MustCompile(`^[A-Z]{1,2}\d{7}$`),
"HK": regexp.MustCompile(`^M\d{8}$`),
"MO": regexp.MustCompile(`^[A-Z]\d{8}$`),
}
var (
// Identity card formula
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
@@ -258,21 +273,45 @@ func IsPort(str string) bool {
// IsUrl check if the string is url.
// Play: https://go.dev/play/p/pbJGa7F98Ka
func IsUrl(str string) bool {
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
return false
}
u, err := url.Parse(str)
if err != nil {
return false
}
if strings.HasPrefix(u.Host, ".") {
return false
}
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
if str == "" {
return false
}
return urlMatcher.MatchString(str)
u, err := url.Parse(str)
if err != nil || u.Scheme == "" || u.Host == "" {
return false
}
allowedSchemes := map[string]struct{}{
"http": {},
"https": {},
"ftp": {},
"ws": {},
"wss": {},
"file": {},
"mailto": {},
"data": {},
}
if _, ok := allowedSchemes[u.Scheme]; !ok {
return false
}
if u.Scheme == "file" || u.Scheme == "mailto" || u.Scheme == "data" {
return true
}
host := u.Hostname()
if !strings.Contains(host, ".") || strings.HasSuffix(host, ".") {
return false
}
// domainRegexp := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$`)
if !dnsMatcher.MatchString(host) {
return false
}
return true
}
// IsDns check if the string is dns.
@@ -282,12 +321,9 @@ func IsDns(dns string) bool {
}
// IsEmail check if the string is a email address.
// Play: https://go.dev/play/p/Os9VaFlT33G
// Play: https://go.dev/play/p/HVQ5LAe-vFz
func IsEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
// return emailMatcher.MatchString(email)
return emailMatcher.MatchString(strings.ToLower(email))
}
// IsChineseMobile check if the string is chinese mobile number.
@@ -460,12 +496,13 @@ func IsZeroValue(value any) bool {
func IsGBK(data []byte) bool {
i := 0
for i < len(data) {
if data[i] <= 0xff {
if data[i] < 0x81 {
i++
continue
} else {
if data[i] >= 0x81 &&
data[i] <= 0xfe &&
i+1 < len(data) &&
data[i+1] >= 0x40 &&
data[i+1] <= 0xfe &&
data[i+1] != 0xf7 {
@@ -561,12 +598,53 @@ func IsAmericanExpress(v string) bool {
// IsUnionPay check if a give string is a valid union pay nubmer or not.
// Play: https://go.dev/play/p/CUHPEwEITDf
func IsUnionPay(v string) bool {
return unionPay.MatchString(v)
func IsUnionPay(cardNo string) bool {
if len(cardNo) < 16 || len(cardNo) > 19 {
return false
}
matched, _ := regexp.MatchString(`^\d+$`, cardNo)
if !matched {
return false
}
if len(cardNo) < 3 {
return false
}
prefix := cardNo[:3]
prefixNum, err := strconv.Atoi(prefix)
if err != nil {
return false
}
if prefixNum < 620 || prefixNum > 625 {
return false
}
return true
}
// IsChinaUnionPay check if a give string is a valid china union pay nubmer or not.
// Play: https://go.dev/play/p/yafpdxLiymu
func IsChinaUnionPay(v string) bool {
return chinaUnionPay.MatchString(v)
func IsChinaUnionPay(cardNo string) bool {
return chinaUnionPayMatcher.MatchString(cardNo)
}
// IsPassport checks if the passport number is valid for a given country.
// country is a two-letter country code (ISO 3166-1 alpha-2).
// Play: https://go.dev/play/p/dvOiV2BW7Aw
func IsPassport(passport, country string) bool {
if matcher, ok := passportMatcher[country]; ok {
return matcher.MatchString(passport)
}
return false
}
// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number.
// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678).
// Play: https://go.dev/play/p/xKG6spQTcY0
func IsChineseHMPassport(hmPassport string) bool {
return chineseHMPassportMatcher.MatchString(hmPassport)
}

View File

@@ -214,7 +214,7 @@ func ExampleIsUrl() {
fmt.Println(result3)
// Output:
// true
// false
// true
// false
}
@@ -683,3 +683,42 @@ func ExampleIsAlphaNumeric() {
// true
// false
}
func ExampleIsPassport() {
result1 := IsPassport("P123456789", "CN")
result2 := IsPassport("123456789", "US")
result3 := IsPassport("AB1234567", "RU")
result4 := IsPassport("123456789", "CN")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// true
// true
// true
// false
}
func ExampleIsChineseHMPassport() {
result1 := IsChineseHMPassport("C12345678")
result2 := IsChineseHMPassport("C00000000")
result3 := IsChineseHMPassport("M12345678")
result4 := IsChineseHMPassport("c12345678")
result5 := IsChineseHMPassport("C1234567")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
// Output:
// true
// true
// true
// false
// false
}

View File

@@ -437,10 +437,32 @@ func TestIsUrl(t *testing.T) {
assert := internal.NewAssert(t, "TestIsUrl")
assert.Equal(true, IsUrl("http://abc.com"))
assert.Equal(true, IsUrl("abc.com"))
assert.Equal(true, IsUrl("a.b.com"))
assert.Equal(false, IsUrl("abc"))
tests := []struct {
input string
expected bool
}{
{"http://abc.com", true},
{"https://abc.com", true},
{"ftp://abc.com", true},
{"http://abc.com/path?query=123", true},
{"https://abc.com/path/to/resource", true},
{"ws://abc.com", true},
{"wss://abc.com", true},
{"mailto://abc.com", true},
{"file://path/to/file", true},
{"data://text/plain;base64,SGVsbG8sIFdvcmxkIQ==", true},
{"http://abc.com/path/to/resource?query=123#fragment", true},
{"abc", false},
{"http://", false},
{"http://abc", false},
{"http://abc:8080", false},
{"http://abc:99999999", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsUrl(tt.input))
}
}
func TestIsDns(t *testing.T) {
@@ -455,7 +477,8 @@ func TestIsDns(t *testing.T) {
{"abc.com", true},
{"123.cn", true},
{"a.b.com", true},
{"a.b.c", false},
{"a.b.c", true},
{"www.xn--6qq986b3xl.xn--fiqs8s.com", true},
{"a@b.com", false},
{"http://abc.com", false},
}
@@ -471,18 +494,34 @@ func TestIsEmail(t *testing.T) {
assert := internal.NewAssert(t, "TestIsEmail")
assert.Equal(true, IsEmail("abc@xyz.com"))
assert.Equal(true, IsEmail("user@domain.co"))
assert.Equal(true, IsEmail("test.user@example.org"))
assert.Equal(false, IsEmail("@abc@xyz.com"))
assert.Equal(false, IsEmail("a.b@@com"))
assert.Equal(false, IsEmail("a.b@com"))
assert.Equal(false, IsEmail("test@example"))
}
func TestContainChinese(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestContainChinese")
assert.Equal(true, ContainChinese("你好"))
assert.Equal(true, ContainChinese("你好hello"))
assert.Equal(false, ContainChinese("hello"))
tests := []struct {
input string
expected bool
}{
{"你好", true},
{"hello", false},
{"你好hello", true},
{"hello你好", true},
{"", false},
{"123", false},
{"!@#$%^&*()", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, ContainChinese(tt.input))
}
}
func TestIsChineseMobile(t *testing.T) {
@@ -490,8 +529,20 @@ func TestIsChineseMobile(t *testing.T) {
assert := internal.NewAssert(t, "TestIsChineseMobile")
assert.Equal(true, IsChineseMobile("13263527980"))
assert.Equal(false, IsChineseMobile("434324324"))
tests := []struct {
input string
expected bool
}{
{"13263527980", true},
{"1326352798", false},
{"132635279801", false},
{"1326352798a", false},
{"1326352798@", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChineseMobile(tt.input))
}
}
func TestIsChinesePhone(t *testing.T) {
@@ -887,7 +938,7 @@ func TestIsUnionPay(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsUnionPay")
assert.Equal(true, IsUnionPay("6221263430109903"))
assert.Equal(true, IsUnionPay("6228480402564890"))
assert.Equal(false, IsUnionPay("3782822463100007"))
}
@@ -895,8 +946,25 @@ func TestIsChinaUnionPay(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsChinaUnionPay")
assert.Equal(true, IsChinaUnionPay("6250941006528599"))
assert.Equal(false, IsChinaUnionPay("3782822463100007"))
tests := []struct {
cardNumber string
expected bool
}{
{"6228480420000000000", true},
{"6214830000000000", true},
{"6230580000000000000", true},
{"6259640000000000000", true},
{"6260000000000000000", true},
{"6288888888888888", true},
// 非银联前缀
{"4123456789012345", false},
{"3528000000000000", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChinaUnionPay(tt.cardNumber))
}
}
func TestIsAlphaNumeric(t *testing.T) {
@@ -924,3 +992,72 @@ func TestIsAlphaNumeric(t *testing.T) {
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
}
}
func TestIsPassport(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsPassport")
tests := []struct {
passport string
countryCode string
expected bool
}{
{"P123456789", "CN", true},
{"123456789", "US", true},
{"A12345678", "GB", true},
{"AB1234567", "FR", true},
{"12345678", "JP", true},
{"M12345678", "HK", true},
{"A12345678", "MO", true},
{"A1234567", "IN", true},
{"12345678", "IT", true},
{"A12345678", "AU", true},
{"123456789", "BR", true},
{"AB1234567", "RU", true},
{"123456789", "CN", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsPassport(tt.passport, tt.countryCode))
}
}
func TestIsChineseHMPassport(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIsChineseHMPassport")
tests := []struct {
input string
expected bool
}{
{"C12345678", true},
{"C00000000", true},
{"C99999999", true},
{"M12345678", true}, // M prefix
{"M00000000", true}, // M prefix
{"M99999999", true}, // M prefix
{"c12345678", false}, // lowercase c
{"m12345678", false}, // lowercase m
{"C1234567", false}, // 7 digits
{"M1234567", false}, // 7 digits with M
{"C123456789", false}, // 9 digits
{"M123456789", false}, // 9 digits with M
{"C1234567a", false}, // contains letter
{"M1234567a", false}, // contains letter with M
{"D12345678", false}, // starts with D
{"12345678", false}, // no prefix
{"CC12345678", false}, // double C
{"MM12345678", false}, // double M
{"C 12345678", false}, // contains space
{"M 12345678", false}, // contains space with M
{"C12345-678", false}, // contains dash
{"M12345-678", false}, // contains dash with M
{"", false},
}
for _, tt := range tests {
assert.Equal(tt.expected, IsChineseHMPassport(tt.input))
}
}