Skip to content

Commit

Permalink
Merge pull request #41 from ccpwcn/dev
Browse files Browse the repository at this point in the history
更强大的字符串掩码功能,可以对任何字符串进行脱敏,有多个选项可用
  • Loading branch information
ccpwcn authored Dec 20, 2023
2 parents 7b3312a + 1f14a03 commit af7d4e5
Show file tree
Hide file tree
Showing 7 changed files with 428 additions and 36 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- MaskChineseIdCard64 中国身份证号脱敏,MaskChineseMobile简化版,保留前6位后4位
- MaskChineseIdCard11 中国身份证号脱敏,MaskChineseMobile简化版,保留前1位后1位
- MaskAnyString 任意字符串脱敏,可指定左侧保留几个字符、右侧保留几个字符
- Masker 更强大的字符串脱敏工具💊,有多个选项可以用于实现您的脱敏需求,位于子包`kg_str`
- [x] 本地缓存相关操作
- Set 设置缓存项,支持仅设置缓存,也支持同时给缓存添加一个过期时间
- Get 获得缓存内容
Expand Down
111 changes: 111 additions & 0 deletions kg_str/str.go
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
}
270 changes: 270 additions & 0 deletions kg_str/str_test.go
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)
}
}
Loading

0 comments on commit af7d4e5

Please sign in to comment.