commit 5f650fe9753eeef458eaef8a1231dadaf5bfdd17 Author: 李寻欢 Date: Fri Aug 26 10:35:14 2022 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1472182 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +vendor +logs +cache +log +config.yaml \ No newline at end of file diff --git a/core.go b/core.go new file mode 100644 index 0000000..ca716b4 --- /dev/null +++ b/core.go @@ -0,0 +1,77 @@ +package sensitive + +import ( + "reflect" + "strings" +) + +// Desensitization +// @description: 脱敏 +// @param r any: 要脱敏的数据(只支持结构体) +// @param skip bool: 是否跳过脱敏 +// @return err error: 错误信息 +func Desensitization(r any, skip bool) (err error) { + // 处理Data的值,如果也是结构体,就处理每个字段的标签,如果包含sen,就把sen改为sen_id + if r == nil || skip { + return + } + // 判断是不是结构体 + isPointer := reflect.TypeOf(r).Kind() == reflect.Ptr + dataType := reflect.TypeOf(r).Kind() + if isPointer { + dataType = reflect.TypeOf(r).Elem().Kind() + } + //log.Printf("数据类型: %v,是否为指针: %v", dataType, isPointer) + + // 处理是数组的情况 + if dataType == reflect.Slice || dataType == reflect.Array { + //log.Println("传入类型为数组") + rs := reflect.ValueOf(r) + if isPointer { + rs = rs.Elem() + } + for i := 0; i < rs.Len(); i++ { + id := rs.Index(i) + err = Desensitization(id.Addr().Interface(), skip) + if err != nil { + return + } + } + } + + // 如果是指针结构体,处理脱敏 + if dataType == reflect.Struct { + val := reflect.ValueOf(r) + if isPointer { + val = val.Elem() + } + + for i := 0; i < val.NumField(); i++ { + f := val.Field(i) + tag := val.Type().Field(i).Tag.Get("sen") + + //log.Printf("类型: %v -> %v 值: %v 脱敏标签是否存在: %v", f.Type(), f.Kind(), f.Interface(), tag != "") + // 如果是结构体,递归调用 + if f.Kind() == reflect.Interface { + //log.Println("开始处理子级") + err = Desensitization(f.Interface(), skip) + } + + if tag != "" { + // 脱敏标签存在,处理一下,取出规则Id和占位符 + ruleId := strings.Split(tag, ",")[0] + placeholder := strings.Split(tag, ",")[1] + //log.Printf("脱敏规则Id: %v, 占位符: %v", ruleId, placeholder) + + // 处理脱敏 + if handle, ok := senRuleMap[ruleId]; ok { + newData := handle(f.Interface().(string), placeholder) + //log.Printf("脱敏后的值: %v", newData) + f.SetString(newData) + } + } + } + } + + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..67bee75 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/lixh00/sensitive + +go 1.18 diff --git a/handle/id_card.go b/handle/id_card.go new file mode 100644 index 0000000..6af8ad2 --- /dev/null +++ b/handle/id_card.go @@ -0,0 +1,17 @@ +package handle + +import ( + "strings" + "unicode/utf8" +) + +// IdCard +// @description: 脱敏规则: 身份证号码 +// @param src string: 待处理字符串 +// @param placeholder string: 占位符 +// @return dst string: 脱敏后的数据 +func IdCard(src, placeholder string) (dst string) { + // 保留前六位后两位 + dst = src[:6] + strings.Repeat(placeholder, utf8.RuneCountInString(src)-7) + src[len(src)-2:] + return +} diff --git a/handle/phone.go b/handle/phone.go new file mode 100644 index 0000000..15fa63c --- /dev/null +++ b/handle/phone.go @@ -0,0 +1,21 @@ +package handle + +import ( + "strings" + "unicode/utf8" +) + +// Phone +// @description: 脱敏规则: 手机号 +// @param src string: 待处理字符串 +// @param placeholder string: 占位符 +// @return dst string: 脱敏后的数据 +func Phone(src, placeholder string) (dst string) { + // 不足7位,直接返回 + if utf8.RuneCountInString(src) <= 7 { + return src + } + // 取前三位和后四位 + dst = src[:3] + strings.Repeat(placeholder, utf8.RuneCountInString(src)-7) + src[len(src)-4:] + return +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..9034960 --- /dev/null +++ b/readme.md @@ -0,0 +1,32 @@ +## 脱敏 + +### 作用 +利用反射处理结构体,处理带`sen`标签的字段(规则: `规则名称,占位符`,如:`phone,*`),将其脱敏。支持自定义处理函数 + +### 定义结构体示例 +```go +package main + +import ( + "github.com/lixh00/sensitive" +) + + +type User struct { + Name string `json:"name"` + Age int `json:"age"` + Phone string `json:"phone" sen:"phone,*"` +} + +data := User{ + Name: "lixh", + Age: 18, + Phone: "13888888888", +} + +if err := sensitive.Desensitize(data); err != nil { + fmt.Println(err) +} +bs, _ := json.Marshal(response) +log.Printf("脱敏后的数据: %v", string(bs)) +``` \ No newline at end of file diff --git a/rule.go b/rule.go new file mode 100644 index 0000000..b02a2c4 --- /dev/null +++ b/rule.go @@ -0,0 +1,23 @@ +package sensitive + +import "sensitive/handle" + +// RuleHandler 脱敏规则处理接口 +type RuleHandler func(src, placeholder string) (dst string) + +var senRuleMap map[string]RuleHandler // 脱敏规则Map + +// 初始化函数,初始化默认的脱敏规则 +func init() { + senRuleMap = make(map[string]RuleHandler) + AddHandler("phone", handle.Phone) + AddHandler("idCard", handle.IdCard) +} + +// AddHandler +// @description: 添加脱敏规则处理函数 +// @param name string: 脱敏规则名称 +// @param handler SensitiveRuleHandler: 脱敏规则处理函数 +func AddHandler(name string, handler RuleHandler) { + senRuleMap[name] = handler +} diff --git a/run_test.go b/run_test.go new file mode 100644 index 0000000..be1884f --- /dev/null +++ b/run_test.go @@ -0,0 +1,78 @@ +package sensitive + +import ( + "encoding/json" + "log" + "net/http" + "testing" +) + +// Response 返回值 +type Response struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +// PageData 分页数据通用结构体 +type PageData struct { + Current int `json:"current"` // 当前页码 + Size int `json:"size"` // 每页数量 + Total int64 `json:"total"` // 总数 + TotalPage int `json:"total_page"` // 总页数 + Records any `json:"records"` // 返回数据 +} + +// Data 模拟返回数据结构体 +type Data struct { + Id string `json:"id"` + Name string `json:"name"` + Phone string `json:"phone" sen:"phone,*"` // 使用脱敏标签,规则为手机号,替换占位字符为* + IdNumber string `json:"idNumber" sen:"idNumber,#"` // 使用脱敏标签,规则为身份证号码,替换占位字符为* +} + +// 模拟测试一下 +func TestDeal(t *testing.T) { + data := []Data{{ + Id: "123", + Name: "张三", + Phone: "13800138000", + IdNumber: "420102199010101010", + }, { + Id: "234", + Name: "李四", + Phone: "13800138001", + IdNumber: "420102199010101011", + }} + + pageData := PageData{ + Current: 1, + Size: 10, + Total: 2, + TotalPage: 1, + Records: &data, + } + + response := Response{ + Code: http.StatusOK, + Msg: "success", + Data: pageData, + } + + bs, _ := json.Marshal(response) + log.Printf("脱敏前的数据: %v", string(bs)) + + //if err := response.Desensitization(true); err != nil { + // log.Println(err) + //} + + //bs, _ = json.Marshal(response) + //log.Printf("假设是管理员,需要跳过处理,脱敏后的数据: %v", string(bs)) + + if err := Desensitization(response, false); err != nil { + log.Println(err) + } + + bs, _ = json.Marshal(response) + log.Printf("脱敏后的数据: %v", string(bs)) +}