Skip to content

Commit

Permalink
Merge pull request #49 from ccpwcn/dev
Browse files Browse the repository at this point in the history
pref: 中文姓名脱敏功能优化,其他细节优化,新增map功能测试
  • Loading branch information
ccpwcn authored Jul 18, 2024
2 parents 077720b + b4cff17 commit 6c2ec4d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 23 deletions.
31 changes: 30 additions & 1 deletion map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,36 @@ func TestMapKeys(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MapKeys(tt.args.m); !ContainsAll(got, tt.want) {
if got := MapKeys[string, int](tt.args.m); !ContainsAll(got, tt.want) {
t.Errorf("MapKeys() = %v, want %v", got, tt.want)
}
})
}
}

func TestMapKeysEmptyStruct(t *testing.T) {
type args[K comparable, V any] struct {
m map[K]V
}
type testCase[K comparable, V any] struct {
name string
args args[K, V]
want []K
}
tests := []testCase[int64, struct{}]{
{
name: "string keys",
args: args[int64, struct{}]{
map[int64]struct{}{
1: {},
2: {},
3: {},
},
}, want: []int64{1, 2, 3}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MapKeys[int64, struct{}](tt.args.m); !ContainsAll(got, tt.want) {
t.Errorf("MapKeys() = %v, want %v", got, tt.want)
}
})
Expand Down
34 changes: 17 additions & 17 deletions str.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var (
englishWordsPattern = regexp.MustCompile("[a-zA-Z]+")
)

// Clean 清理字符串,去除其实的各种特殊符号,一律替换为下划线
// Clean 清理字符串,去除其实的各种特殊符号,一律替换为下划线,这样变会变成安全的字符串,在绝大多数场景下不会有问题
func Clean(str string) string {
var onlyCharacters string
onlyCharacters = strings.ReplaceAll(str, ".", "__")
Expand Down Expand Up @@ -97,27 +97,27 @@ func MaskChineseName(name string) (masked string) {

// MaskChineseNameEx 中文姓名脱敏,可以指定左右保留字符数量
//
// 示例1:MaskChineseNameEx("张一二", 1, 1) => "张*二" 左边保留1个字符、右边保留1个字符
// 示例1:
//
// MaskChineseNameEx("张一二", 1, 1) // "张*二" 左边保留1个字符、右边保留1个字符
//
// 示例2:
//
// name := "张一二"
// MaskChineseNameEx(name, 0, utf8.RuneCountInString(name)-1) => "*一二" 左边保留0个字符、右边保留所有字符少1个
// MaskChineseNameEx(name, 0, utf8.RuneCountInString(name)-1) // "*一二" 左边保留0个字符、右边保留所有字符少1个,达到对姓氏脱敏的目的
func MaskChineseNameEx(name string, left, right int) (masked string) {
size := utf8.RuneCountInString(name)
if size == 0 {
return ""
}
i := 0
for _, n := range name {
if i < left || i >= size-right {
masked += fmt.Sprintf("%c", n)
} else {
masked += "*"
}
i++
}
return masked
// 将中文字符串转换为rune数组,方便进行字符级别的操作
runes := []rune(name)
size := len(runes)
leftRunes := runes[:left]
rightRunes := runes[size-right:]
middleRunes := make([]rune, size-len(leftRunes)-len(rightRunes))
for i := 0; i < len(middleRunes); i++ {
middleRunes[i] = '*'
}

// 返回脱敏后的名字
return string(leftRunes) + string(middleRunes) + string(rightRunes)
}

// MaskChineseMobile 中国手机号脱敏
Expand Down
9 changes: 9 additions & 0 deletions str_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ func TestMaskChineseNameEx6(t *testing.T) {
}
}

func TestMaskChineseNameExOnlyFirstName(t *testing.T) {
name := "张一二"
excepted := "张**"
actual := MaskChineseNameEx(name, 1, 0)
if actual != excepted {
t.Errorf("预期 %v,实际值:%v", excepted, actual)
}
}

func TestMaskChineseMobile(t *testing.T) {
tel := "13012345678"
excepted := "130****5678"
Expand Down
19 changes: 14 additions & 5 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"strings"
)

// JoinStructsField 将一个结构体切片中的指定字段的值拼接成字符串,请注意:第二个参数指定的字段,要存在且已导出,否则结果可能不是预期的样子
// JoinStructsField 将一个结构体切片中的指定字段的值拼接成字符串,
//
// 注意:第二个参数指定的字段,要存在且已导出,否则结果可能不是预期的样子。
func JoinStructsField[T interface{}](elements []T, fieldName string) (s string) {
// 只处理导出了的字段
if fieldName[0] < 'A' || fieldName[0] > 'Z' {
Expand Down Expand Up @@ -38,7 +40,9 @@ func JoinStructsField[T interface{}](elements []T, fieldName string) (s string)
}
}

// PickStructsField 将指定结构体切片中的指定字段的值提取出来,形成一个数组,请注意:第二个参数指定的字段,要存在且已导出,否则结果可能不是预期的样子
// PickStructsField 将指定结构体切片中的指定字段的值提取出来,形成一个数组,
//
// 请注意:第二个参数指定的字段,要存在且已导出,否则结果可能不是预期的样子。
func PickStructsField[T interface{}, FT comparable](elements []T, fieldName string) (s []FT) {
// 只处理导出了的字段
if fieldName[0] < 'A' || fieldName[0] > 'Z' {
Expand Down Expand Up @@ -93,12 +97,16 @@ func SliceGroupBy[T any, KT comparable](data []T, fieldName string) map[KT][]T {
}
}
}
// Output:
// 分组的结果是一个Map,Key是分组字段的值,Value是分组后的结构体切片
return m
}

// CopyFields 将一个结构体中的所有字段值全部复制到目标结构体中,可以指定忽略某些字段
// 注意:如果你的字段是非指针类型,那么是值拷贝,如果你的字段是指针类型,那么是引用拷贝
// 即:在字段是指针类型的情况下,即使复制值到目标结构体实例中了,修改源结构体实例的字段值,会影响目标结构体实例的值,因为是引用拷贝
//
// 注意:如果你的字段是非引用类型,那么是值拷贝,如果你的字段是指针类型,那么是引用拷贝。总而言之,引用类型是浅拷贝而不是深拷贝。
//
// 即:在字段是引用类型的情况下,即使复制值到目标结构体实例中了,修改源结构体实例的字段值,会影响目标结构体实例的值,因为是引用拷贝
func CopyFields[SRC any, DST any](src SRC, ignore ...string) (dst DST) {
ignoreCount := len(ignore)
srcValue := reflect.ValueOf(src)
Expand Down Expand Up @@ -140,7 +148,8 @@ func CopyFields[SRC any, DST any](src SRC, ignore ...string) (dst DST) {
}

// SetField 给任意结构体的任意字段设置一个新值
// newValue不可以传入反身过的值类型(即:不要传reflect.ValueOf(...)的结果),这会导致panic
//
// 注意: newValue不可以传入反射过的值类型(即:不要传reflect.ValueOf(...)的结果),这会导致panic
func SetField(entity any, field string, newValue any) {
val := reflect.ValueOf(entity)
for val.Type().Kind() == reflect.Pointer {
Expand Down

0 comments on commit 6c2ec4d

Please sign in to comment.