-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from ccpwcn/dev
更强大的字符串掩码功能,可以对任何字符串进行脱敏,有多个选项可用
- Loading branch information
Showing
7 changed files
with
428 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package kg_str | ||
|
||
import ( | ||
"fmt" | ||
"unicode/utf8" | ||
) | ||
|
||
const DefaultCharCount = 1 | ||
|
||
type MaskOption func(masker *Masker) | ||
|
||
// Masker 字符串脱敏工具 | ||
type Masker struct { | ||
maskCount int | ||
left int | ||
right int | ||
maskChar rune | ||
} | ||
|
||
// WithMaskCount 脱敏字符数量,默认是输入字符串总字符数量-左侧保留字符数-右侧保留字符数,即:默认脱敏之后的字符串字符数量和原字符串相同 | ||
func WithMaskCount(maskCount int) MaskOption { | ||
return func(masker *Masker) { | ||
masker.maskCount = maskCount | ||
} | ||
} | ||
|
||
// WithLeft 脱敏字符左侧保留字符数量,未指定时使用默认值 DefaultCharCount | ||
func WithLeft(left int) MaskOption { | ||
return func(masker *Masker) { | ||
masker.left = left | ||
} | ||
} | ||
|
||
// WithRight 脱敏字符右侧保留字符数量,未指定时使用默认值 DefaultCharCount | ||
func WithRight(right int) MaskOption { | ||
return func(masker *Masker) { | ||
masker.right = right | ||
} | ||
} | ||
|
||
// WithMaskChar 脱敏字符,如果未指定,默认使用星号* | ||
func WithMaskChar(maskChar rune) MaskOption { | ||
return func(masker *Masker) { | ||
masker.maskChar = maskChar | ||
} | ||
} | ||
|
||
// NewMasker 创建脱敏工具实例 | ||
// | ||
// 如果您既没有指定左侧保留字符数量,也没有指定右侧保留字符数量,那么默认左右各保留一个字符 | ||
// 如果您没有指定脱敏字符,那么默认使用星号* | ||
func NewMasker(options ...MaskOption) *Masker { | ||
masker := &Masker{} | ||
for _, option := range options { | ||
option(masker) | ||
} | ||
// 判断并设置默认值 | ||
if masker.maskChar == 0 { | ||
masker.maskChar = '*' | ||
} | ||
if masker.left == 0 && masker.right == 0 { | ||
masker.left = DefaultCharCount | ||
masker.right = DefaultCharCount | ||
} | ||
return masker | ||
} | ||
|
||
// Mask 脱敏任意字符串 | ||
// 入参: s 要脱敏的字符串 | ||
// | ||
// 注意1:如果入参字符串太短,可能默认的左侧保留 DefaultCharCount、右侧保留 DefaultCharCount 无法满足您的需求,此时您在实例化时要指定相应选项 | ||
// 注意2:如果入参字符串为空,也返回空,不会报错 | ||
// 注意3:如果指定了脱敏字符串的数量,那么返回的字符串长度有可能和您提供的入参字符串的长度并不相同 | ||
func (mk *Masker) Mask(s string) string { | ||
var ( | ||
left, middle, right string | ||
) | ||
|
||
size := utf8.RuneCountInString(s) | ||
if size == 0 { | ||
return "" | ||
} | ||
// 如果初始化的时候没有设置starCount,则默认用字符串长度减去左右保留的长度 | ||
maskCount := mk.maskCount | ||
if maskCount == 0 { | ||
maskCount = size - mk.left - mk.right | ||
} | ||
|
||
i := 0 | ||
for _, n := range s { | ||
if i < mk.left { | ||
left += fmt.Sprintf("%c", n) | ||
} else if i >= size-mk.right { | ||
right += fmt.Sprintf("%c", n) | ||
} else if utf8.RuneCountInString(middle) < maskCount { | ||
middle += fmt.Sprintf("%c", mk.maskChar) | ||
} | ||
i++ | ||
} | ||
// 如果字符串太短,以至于无法脱敏,进行扩张 | ||
middleSize := len(middle) | ||
if maskCount > middleSize { | ||
for i := 0; i < maskCount-middleSize; i++ { | ||
middle += fmt.Sprintf("%c", mk.maskChar) | ||
} | ||
} | ||
if middle == "" { | ||
middle = fmt.Sprintf("%c", mk.maskChar) | ||
} | ||
return left + middle + right | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
package kg_str | ||
|
||
import ( | ||
"testing" | ||
"unicode/utf8" | ||
) | ||
|
||
func TestMaskShortStringWithLeft(t *testing.T) { | ||
masker := NewMasker(WithLeft(1)) | ||
name := "张三" | ||
excepted := "张*" | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringWithRight(t *testing.T) { | ||
masker := NewMasker(WithRight(1)) | ||
name := "张三" | ||
excepted := "*三" | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringOnlyLeft(t *testing.T) { | ||
masker := NewMasker(WithLeft(1)) | ||
name := "张三" | ||
excepted := "张*" | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringOnlyRight(t *testing.T) { | ||
name := "张三三" | ||
excepted := "*三三" | ||
masker := NewMasker(WithRight(utf8.RuneCountInString(name) - 1)) | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringOnlyRight1(t *testing.T) { | ||
name := "张三三" | ||
excepted := "**三" | ||
masker := NewMasker(WithRight(1)) | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringWithRightExpended(t *testing.T) { | ||
name := "张三" | ||
excepted := "***三" | ||
masker := NewMasker(WithRight(1), WithMaskCount(3)) | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskShortStringWithLeftExpended(t *testing.T) { | ||
name := "张三" | ||
excepted := "张***" | ||
masker := NewMasker(WithLeft(1), WithMaskCount(3)) | ||
actual := masker.Mask(name) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskDefault(t *testing.T) { | ||
type args struct { | ||
s string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
}{ | ||
{name: "最简人名", args: args{s: "张三"}, want: "张*三"}, | ||
{name: "常规人名", args: args{s: "张一二"}, want: "张*二"}, | ||
{name: "常规字符串", args: args{s: "一二三四五六七八九十"}, want: "一********十"}, | ||
} | ||
|
||
masker := NewMasker() | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
if got := masker.Mask(test.args.s); got != test.want { | ||
t.Errorf("Mask() = %v, want %v", got, test.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMaskDefaultLeftAndRight(t *testing.T) { | ||
masker := NewMasker(WithMaskChar('?'), WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一???十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskDefaultLeftAndRightAndStar(t *testing.T) { | ||
masker := NewMasker(WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一***十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskDefaultLeftAndRightAndMaskChar(t *testing.T) { | ||
masker := NewMasker(WithMaskChar(',')) | ||
s := "一二三四五六七八九十" | ||
excepted := "一,,,,,,,,十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskDefaultMaskCharCount(t *testing.T) { | ||
masker := NewMasker(WithMaskChar('〇')) | ||
s := "一二三四五六七八九十" | ||
excepted := "一〇〇〇〇〇〇〇〇十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeft(t *testing.T) { | ||
masker := NewMasker(WithLeft(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三*******" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三***" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndMaskChar(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithMaskChar('❎')) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三❎❎❎❎❎❎❎" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndMaskCharAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithMaskCount(3), WithMaskChar('❎')) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三❎❎❎" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithRight(t *testing.T) { | ||
masker := NewMasker(WithRight(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "*******八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithRightAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithRight(3), WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "***八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithRightAndMaskChar(t *testing.T) { | ||
masker := NewMasker(WithRight(3), WithMaskChar('?')) | ||
s := "一二三四五六七八九十" | ||
excepted := "???????八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithRightAndMaskCharAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithRight(3), WithMaskChar('?'), WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "???八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndRight(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithRight(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三****八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndRightAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithRight(3), WithMaskCount(2)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三**八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndRightAndMaskChar(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithRight(3), WithMaskChar('.')) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三....八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndRightAndMaskCharAndMaskCount(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithRight(2), WithMaskChar('❓'), WithMaskCount(3)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三❓❓❓九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} | ||
|
||
func TestMaskWithLeftAndRightAndMaskCharAndMaskCount2(t *testing.T) { | ||
masker := NewMasker(WithLeft(3), WithRight(3), WithMaskChar('�'), WithMaskCount(2)) | ||
s := "一二三四五六七八九十" | ||
excepted := "一二三��八九十" | ||
actual := masker.Mask(s) | ||
if actual != excepted { | ||
t.Errorf("预期 %v,实际值:%v", excepted, actual) | ||
} | ||
} |
Oops, something went wrong.