diff --git a/kit/token/excel_test.go b/kit/token/excel_test.go new file mode 100644 index 0000000..e69de29 diff --git a/kit/utils/ddm.go b/kit/utils/ddm.go new file mode 100644 index 0000000..04af9bf --- /dev/null +++ b/kit/utils/ddm.go @@ -0,0 +1,43 @@ +package utils + +/** + * @Description 添加qq群467890197 交流学习 + * @Author 熊猫 + * @Date 2022/1/13 17:16 + **/ + +func DdmKey(data string) string { + if len(data) < 6 { + return data + } + return data[:3] + "****" + data[len(data)-3:] +} + +func IsDdmKey(data string) bool { + if len(data) > 6 && data[3:len(data)-3] == "****" { + return true + } + return false +} + +func DdmMail(data string) string { + return data[:3] + "****" + data[len(data)-8:] +} + +func ISDdmMail(data string) bool { + if len(data) > 11 && data[3:len(data)-8] == "****" { + return true + } + return false +} + +func DdmPassword(data string) string { + return "******" +} + +func IsDdmPassword(data string) bool { + if data == "******" { + return true + } + return false +} diff --git a/kit/utils/ddm_test.go b/kit/utils/ddm_test.go new file mode 100644 index 0000000..3f1cd99 --- /dev/null +++ b/kit/utils/ddm_test.go @@ -0,0 +1,26 @@ +package utils + +import ( + "testing" +) + +/** + * @Description 添加qq群467890197 交流学习 + * @Author 熊猫 + * @Date 2022/1/17 10:01 + **/ + +func TestDdmKey(t *testing.T) { + aa := "fsdfsf535343sdfsdf3" + bb := "23423423@qq.com" + + key := DdmKey(aa) + t.Log(key) + ddmKey := IsDdmKey(key) + t.Log(ddmKey) + + mail := DdmMail(bb) + t.Log(mail) + ismail := ISDdmMail(mail) + t.Log(ismail) +} diff --git a/kit/utils/excel.go b/kit/utils/excel.go new file mode 100644 index 0000000..239d6f8 --- /dev/null +++ b/kit/utils/excel.go @@ -0,0 +1,46 @@ +package utils + +import ( + "fmt" + "github.com/xuri/excelize/v2" + "reflect" +) + +func ExportExcel(head []string, datas [][]any, filePath string) error { + excel := excelize.NewFile() + excel.SetSheetRow("Sheet1", "A1", &head) + for i, data := range datas { + axis := fmt.Sprintf("A%d", i+2) + excel.SetSheetRow("Sheet1", axis, &data) + } + excel.SaveAs(filePath) + return nil +} + +func GetFileName(path, filename string) string { + return path + filename +} + +func InterfaceToExcel(data any, fileName string) { + heads := make([]string, 0) + datas := make([][]any, 0) + v := reflect.ValueOf(data) + max := int64(v.Len()) + for i := 0; i < int(max); i++ { + u := v.Index(i).Interface() + var key = reflect.TypeOf(u) + var val = reflect.ValueOf(u) + num := key.NumField() + if i == 0 { + for i := 0; i < num; i++ { + heads = append(heads, key.Field(i).Name) + } + } + field := make([]any, 0) + for i := 0; i < num; i++ { + field = append(field, val.Field(i).Interface()) + } + datas = append(datas, field) + } + ExportExcel(heads, datas, fileName) +} diff --git a/kit/utils/excel_test.go b/kit/utils/excel_test.go new file mode 100644 index 0000000..dd643f0 --- /dev/null +++ b/kit/utils/excel_test.go @@ -0,0 +1,19 @@ +package utils + +import ( + "testing" +) + +type User struct { + Name string `json:"name"` + Age int64 `json:"age"` +} + +func TestExportExcel(t *testing.T) { + us := make([]User, 0) + us = append(us, User{Name: "张三", Age: 12}) + us = append(us, User{Name: "李四", Age: 23}) + name := GetFileName("./", "字典") + t.Log(name) + InterfaceToExcel(us, name) +} diff --git a/kit/utils/float_to_f.go b/kit/utils/float_to_f.go new file mode 100644 index 0000000..3a8b911 --- /dev/null +++ b/kit/utils/float_to_f.go @@ -0,0 +1,26 @@ +package utils + +import ( + "fmt" + "strconv" +) + +func ParseFloat2F(value float64) float64 { + newValue, err := strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64) + if err != nil { + fmt.Println("保留2位小数, 转换float出错") + return value + } + return newValue + +} + +func ParseStringToInt64(value string) int64 { + + newValue, err := strconv.ParseInt(value, 10, 64) + if err != nil { + fmt.Println("string转换int64出错") + return 0 + } + return newValue +} diff --git a/kit/utils/float_to_f.go.go b/kit/utils/float_to_f.go.go new file mode 100644 index 0000000..e69de29 diff --git a/kit/utils/ip.go b/kit/utils/ip.go new file mode 100644 index 0000000..6216122 --- /dev/null +++ b/kit/utils/ip.go @@ -0,0 +1,53 @@ +package utils + +import ( + "fmt" + "net" + "pandax/kit/httpclient" +) + +const ipurl = "https://ip.cn/api/index" + +const UNKNOWN = "XX XX" + +// GetRealAddressByIP 获取真实地址 +func GetRealAddressByIP(ip string) string { + if ip == "127.0.0.1" || ip == "localhost" { + return "内部IP" + } + url := fmt.Sprintf("%s?ip=%s&type=1", ipurl, ip) + + res := httpclient.NewRequest(url).Get() + if res == nil || res.StatusCode != 200 { + return UNKNOWN + } + toMap, err := res.BodyToMap() + if err != nil { + return UNKNOWN + } + return toMap["address"].(string) +} + +// 获取局域网ip地址 +func GetLocaHonst() string { + netInterfaces, err := net.Interfaces() + if err != nil { + fmt.Println("net.Interfaces failed, err:", err.Error()) + } + + for i := 0; i < len(netInterfaces); i++ { + if (netInterfaces[i].Flags & net.FlagUp) != 0 { + addrs, _ := netInterfaces[i].Addrs() + + for _, address := range addrs { + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + } + + } + return "" +} diff --git a/kit/utils/json_utils.go b/kit/utils/json_utils.go new file mode 100644 index 0000000..10822f5 --- /dev/null +++ b/kit/utils/json_utils.go @@ -0,0 +1,14 @@ +package utils + +import ( + "encoding/json" +) + +func Json2Map(jsonStr string) map[string]any { + var res map[string]any + if jsonStr == "" { + return res + } + _ = json.Unmarshal([]byte(jsonStr), &res) + return res +} diff --git a/kit/utils/regexp.go b/kit/utils/regexp.go new file mode 100644 index 0000000..78b0d7b --- /dev/null +++ b/kit/utils/regexp.go @@ -0,0 +1,42 @@ +package utils + +import ( + "regexp" + "sync" +) + +var ( + regexMu = sync.RWMutex{} + // Cache for regex object. + // Note that: + // 1. It uses sync.RWMutex ensuring the concurrent safety. + // 2. There's no expiring logic for this map. + regexMap = make(map[string]*regexp.Regexp) +) + +// getRegexp returns *regexp.Regexp object with given `pattern`. +// It uses cache to enhance the performance for compiling regular expression pattern, +// which means, it will return the same *regexp.Regexp object with the same regular +// expression pattern. +// +// It is concurrent-safe for multiple goroutines. +func GetRegexp(pattern string) (regex *regexp.Regexp, err error) { + // Retrieve the regular expression object using reading lock. + regexMu.RLock() + regex = regexMap[pattern] + regexMu.RUnlock() + if regex != nil { + return + } + // If it does not exist in the cache, + // it compiles the pattern and creates one. + regex, err = regexp.Compile(pattern) + if err != nil { + return + } + // Cache the result object using writing lock. + regexMu.Lock() + regexMap[pattern] = regex + regexMu.Unlock() + return +} diff --git a/kit/utils/str_utils.go b/kit/utils/str_utils.go new file mode 100644 index 0000000..2f6def5 --- /dev/null +++ b/kit/utils/str_utils.go @@ -0,0 +1,156 @@ +package utils + +import ( + "bytes" + "github.com/kakuilan/kgo" + "strings" + "text/template" +) + +func UnicodeIndex(str, substr string) int { + // 子串在字符串的字节位置 + result := strings.Index(str, substr) + if result >= 0 { + // 获得子串之前的字符串并转换成[]byte + prefix := []byte(str)[0:result] + // 将子串之前的字符串转换成[]rune + rs := []rune(string(prefix)) + // 获得子串之前的字符串的长度,便是子串在字符串的字符位置 + result = len(rs) + } + + return result +} + +func ReplaceString(pattern, replace, src string) (string, error) { + if r, err := GetRegexp(pattern); err == nil { + return r.ReplaceAllString(src, replace), nil + } else { + return "", err + } +} + +func Contains(haystack, needle string, startOffset ...int) int { + length := len(haystack) + offset := 0 + if len(startOffset) > 0 { + offset = startOffset[0] + } + if length == 0 || offset > length || -offset > length { + return -1 + } + + if offset < 0 { + offset += length + } + pos := strings.Index(strings.ToLower(haystack[offset:]), strings.ToLower(needle)) + if pos == -1 { + return -1 + } + return pos + offset +} + +// 字符串模板解析 +func TemplateResolve(temp string, data any) string { + t, _ := template.New("string-temp").Parse(temp) + var tmplBytes bytes.Buffer + + err := t.Execute(&tmplBytes, data) + if err != nil { + panic(err) + } + return tmplBytes.String() +} + +func ReverStrTemplate(temp, str string, res map[string]any) { + index := UnicodeIndex(temp, "{") + ei := UnicodeIndex(temp, "}") + 1 + next := kgo.KStr.Trim(temp[ei:], " ") + nextContain := UnicodeIndex(next, "{") + nextIndexValue := next + if nextContain != -1 { + nextIndexValue = kgo.KStr.Substr(next, 0, nextContain) + } + key := temp[index+1 : ei-1] + // 如果后面没有内容了,则取字符串的长度即可 + var valueLastIndex int + if nextIndexValue == "" { + valueLastIndex = kgo.KStr.MbStrlen(str) + } else { + valueLastIndex = UnicodeIndex(str, nextIndexValue) + } + value := kgo.KStr.Trim(kgo.KStr.Substr(str, index, valueLastIndex), " ") + res[key] = value + // 如果后面的还有需要解析的,则递归调用解析 + if nextContain != -1 { + ReverStrTemplate(next, kgo.KStr.Trim(kgo.KStr.Substr(str, UnicodeIndex(str, value)+kgo.KStr.MbStrlen(value), kgo.KStr.MbStrlen(str))), res) + } +} + +func B2S(bs []uint8) string { + ba := []byte{} + for _, b := range bs { + ba = append(ba, byte(b)) + } + return string(ba) +} + +func IdsStrToIdsIntGroup(keys string) []int64 { + IDS := make([]int64, 0) + ids := strings.Split(keys, ",") + for i := 0; i < len(ids); i++ { + ID := kgo.KConv.Str2Int(ids[i]) + IDS = append(IDS, int64(ID)) + } + return IDS +} + +// 获取部门 +// isP 是父ID 否则子ID +func DeptPCIds(deptIds []string, id int64, isP bool) []int64 { + pRes := make([]int64, 0) + cRes := make([]int64, 0) + is := true + for _, deptId := range deptIds { + did := kgo.KConv.Str2Int64(deptId) + if is { + pRes = append(pRes, did) + } + if kgo.KConv.Str2Int64(deptId) == id { + is = false + } + if !is { + cRes = append(cRes, did) + } + } + if isP { + return pRes + } else { + return cRes + } +} + +// 获取组织 +// isP 是父ID 否则子ID +func OrganizationPCIds(orgIds []string, id int64, isP bool) []int64 { + pRes := make([]int64, 0) + cRes := make([]int64, 0) + is := true + for _, orgId := range orgIds { + did := kgo.KConv.Str2Int64(orgId) + if is { + pRes = append(pRes, did) + } + if kgo.KConv.Str2Int64(orgId) == id { + is = false + } + if !is { + cRes = append(cRes, did) + } + } + if isP { + return pRes + } else { + return cRes + } +} diff --git a/kit/utils/str_utils_test.go b/kit/utils/str_utils_test.go new file mode 100644 index 0000000..9f42174 --- /dev/null +++ b/kit/utils/str_utils_test.go @@ -0,0 +1,24 @@ +package utils + +import ( + "log" + "strings" + "testing" +) + +func TestIdsStrToIdsIntGroup(t *testing.T) { + group := IdsStrToIdsIntGroup("aaa") + t.Log(len(group)) +} + +func TestGetRealAddressByIP(t *testing.T) { + ip := GetRealAddressByIP("10.42.0.1") + t.Log(ip) +} + +func TestDeptPCIds(t *testing.T) { + split := strings.Split(strings.Trim("/0/2", "/"), "/") + log.Println("split", split) + ids := DeptPCIds(split, 2, true) + t.Log(ids) +} diff --git a/kit/utils/struct_utils_test.go b/kit/utils/struct_utils_test.go new file mode 100644 index 0000000..21e84df --- /dev/null +++ b/kit/utils/struct_utils_test.go @@ -0,0 +1,194 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" + "testing" + "time" +) + +type Src struct { + Id *int64 `json:"id"` + Username string `json:"username"` + CreateTime time.Time `json:"time"` + UpdateTime time.Time + Inner *SrcInner +} + +type SrcInner struct { + Name string + Desc string + Id int64 + Dest *Dest +} + +type Dest struct { + Username string + Id int64 + CreateTime time.Time + Inner *DestInner +} + +type DestInner struct { + Desc string +} + +func TestDeepFields(t *testing.T) { + ////src := Src{Username: "test", Id: 1000, CreateTime: time.Now()} + //si := SrcInner{Desc: "desc"} + //src.Inner = &si + ////src.Id = 1222 + //dest := new(Dest) + //err := structutils.Copy(dest, src) + //if err != nil { + // fmt.Println(err.Error()) + //} else { + // fmt.Println(dest) + //} + +} + +func TestGetFieldNames(t *testing.T) { + //names := structutils.GetFieldNames(new(Src)) + //fmt.Println(names) +} + +func TestMaps2Structs(t *testing.T) { + mapInstance := make(map[string]any) + mapInstance["Username"] = "liang637210" + mapInstance["Id"] = 28 + mapInstance["CreateTime"] = time.Now() + mapInstance["Creator"] = "createor" + mapInstance["Inner.Id"] = 10 + mapInstance["Inner.Name"] = "hahah" + mapInstance["Inner.Desc"] = "inner desc" + mapInstance["Inner.Dest.Username"] = "inner dest uername" + mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc" + + mapInstance2 := make(map[string]any) + mapInstance2["Username"] = "liang6372102" + mapInstance2["Id"] = 282 + mapInstance2["CreateTime"] = time.Now() + mapInstance2["Creator"] = "createor2" + mapInstance2["Inner.Id"] = 102 + mapInstance2["Inner.Name"] = "hahah2" + mapInstance2["Inner.Desc"] = "inner desc2" + mapInstance2["Inner.Dest.Username"] = "inner dest uername2" + mapInstance2["Inner.Dest.Inner.Desc"] = "inner dest inner desc2" + + maps := make([]map[string]any, 2) + maps[0] = mapInstance + maps[1] = mapInstance2 + res := new([]Src) + err := Maps2Structs(maps, res) + if err != nil { + fmt.Println(err) + } +} + +func TestMap2Struct(t *testing.T) { + mapInstance := make(map[string]any) + mapInstance["Username"] = "liang637210" + mapInstance["Id"] = 12 + mapInstance["CreateTime"] = time.Now() + mapInstance["Creator"] = "createor" + mapInstance["Inner.Id"] = nil + mapInstance["Inner.Name"] = "hahah" + mapInstance["Inner.Desc"] = "inner desc" + mapInstance["Inner.Dest.Username"] = "inner dest uername" + mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc" + + //innerMap := make(map[string]interface{}) + //innerMap["Name"] = "Innername" + + //a := new(Src) + ////a.Inner = new(SrcInner) + // + //stime := time.Now().UnixNano() + //for i := 0; i < 1000000; i++ { + // err := structutils.Map2Struct(mapInstance, a) + // if err != nil { + // fmt.Println(err) + // } + //} + //etime := time.Now().UnixNano() + //fmt.Println(etime - stime) + //if err != nil { + // fmt.Println(err) + //} else { + // fmt.Println(a) + //} + + s := new(Src) + //name, b := structutils.IndirectType(reflect.TypeOf(s)).FieldByName("Inner") + //if structutils.IndirectType(name.Type).Kind() != reflect.Struct { + // fmt.Println(name.Name + "不是结构体") + //} else { + // //innerType := name.Type + // innerValue := structutils.Indirect(reflect.ValueOf(s)).FieldByName("Inner") + // //if innerValue.IsValid() && innerValue.IsNil() { + // // innerValue.Set(reflect.New(innerValue.Type().Elem())) + // //} + // if !innerValue.IsValid() { + // fmt.Println("is valid") + // } else { + // //innerValue.Set(reflect.New(innerValue.Type())) + // fmt.Println(innerValue.CanSet()) + // fmt.Println(innerValue.CanAddr()) + // //mapstructure.Decode(innerMap, innerValue.Addr().Interface()) + // } + // + //} + // + //fmt.Println(name, b) + //将 map 转换为指定的结构体 + // if err := decode(mapInstance, &s); err != nil { + // fmt.Println(err) + // } + fmt.Printf("map2struct后得到的 struct 内容为:%v", s) +} + +func getPrefixKeyMap(m map[string]any) map[string]map[string]any { + key2map := make(map[string]map[string]any) + for k, v := range m { + if !strings.Contains(k, ".") { + continue + } + lastIndex := strings.LastIndex(k, ".") + prefix := k[0:lastIndex] + m2 := key2map[prefix] + if m2 == nil { + key2map[prefix] = map[string]any{k[lastIndex+1:]: v} + } else { + m2[k[lastIndex+1:]] = v + } + delete(m, k) + } + return key2map +} + +func TestReflect(t *testing.T) { + type dog struct { + LegCount int + } + // 获取dog实例的反射值对象 + valueOfDog := reflect.ValueOf(&dog{}).Elem() + + // 获取legCount字段的值 + vLegCount := valueOfDog.FieldByName("LegCount") + + fmt.Println(vLegCount.CanSet()) + fmt.Println(vLegCount.CanAddr()) + // 尝试设置legCount的值(这里会发生崩溃) + vLegCount.SetInt(4) +} + +func TestTemplateResolve(t *testing.T) { + d := make(map[string]string) + d["Name"] = "黄先生" + d["Age"] = "23jlfdsjf" + resolve := TemplateResolve("{{.Name}} is name, and {{.Age}} is age", d) + fmt.Println(resolve) + +} diff --git a/kit/utils/sturct_utils.go b/kit/utils/sturct_utils.go new file mode 100644 index 0000000..dc51d60 --- /dev/null +++ b/kit/utils/sturct_utils.go @@ -0,0 +1,654 @@ +package utils + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// Copy copy things,引用至copier +func Copy(toValue any, fromValue any) (err error) { + var ( + isSlice bool + amount = 1 + from = Indirect(reflect.ValueOf(fromValue)) + to = Indirect(reflect.ValueOf(toValue)) + ) + + if !to.CanAddr() { + return errors.New("copy to value is unaddressable") + } + + // Return is from value is invalid + if !from.IsValid() { + return + } + + fromType := IndirectType(from.Type()) + toType := IndirectType(to.Type()) + + // Just set it if possible to assign + // And need to do copy anyway if the type is struct + if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) { + to.Set(from) + return + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + return + } + + if to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = Indirect(from.Index(i)) + } else { + source = Indirect(from) + } + // dest + dest = Indirect(reflect.New(toType).Elem()) + } else { + source = Indirect(from) + dest = Indirect(to) + } + + // check source + if source.IsValid() { + fromTypeFields := deepFields(fromType) + //fmt.Printf("%#v", fromTypeFields) + // Copy from field to field or method + for _, field := range fromTypeFields { + name := field.Name + + if fromField := source.FieldByName(name); fromField.IsValid() { + // has field + if toField := dest.FieldByName(name); toField.IsValid() { + if toField.CanSet() { + if !set(toField, fromField) { + if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil { + return err + } + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(name) + } else { + toMethod = dest.MethodByName(name) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from method to field + for _, field := range deepFields(toType) { + name := field.Name + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(name) + } else { + fromMethod = source.MethodByName(name) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 { + if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0]) + } + } + } + } + } + if isSlice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest.Addr())) + } else if dest.Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest)) + } + } + } + return +} + +// 对结构体的每个字段以及字段值执行doWith回调函数, 包括匿名属性的字段 +func DoWithFields(str any, doWith func(fType reflect.StructField, fValue reflect.Value) error) error { + t := IndirectType(reflect.TypeOf(str)) + if t.Kind() != reflect.Struct { + return errors.New("非结构体") + } + + fieldNum := t.NumField() + v := Indirect(reflect.ValueOf(str)) + for i := 0; i < fieldNum; i++ { + ft := t.Field(i) + fv := v.Field(i) + // 如果是匿名属性,则递归调用该方法 + if ft.Anonymous { + DoWithFields(fv.Interface(), doWith) + continue + } + err := doWith(ft, fv) + if err != nil { + return err + } + } + return nil +} + +func deepFields(reflectType reflect.Type) []reflect.StructField { + var fields []reflect.StructField + + if reflectType = IndirectType(reflectType); reflectType.Kind() == reflect.Struct { + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + if v.Anonymous { + fields = append(fields, deepFields(v.Type)...) + } else { + fields = append(fields, v) + } + } + } + + return fields +} + +func Indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func IndirectType(reflectType reflect.Type) reflect.Type { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + } + return reflectType +} + +func set(to, from reflect.Value) bool { + if from.IsValid() { + if to.Kind() == reflect.Ptr { + //set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true + } else if to.IsNil() { + to.Set(reflect.New(to.Type().Elem())) + } + to = to.Elem() + } + + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok { + err := scanner.Scan(from.Interface()) + if err != nil { + return false + } + } else if from.Kind() == reflect.Ptr { + return set(to, from.Elem()) + } else { + return false + } + } + return true +} + +func Map2Struct(m map[string]any, s any) error { + toValue := Indirect(reflect.ValueOf(s)) + if !toValue.CanAddr() { + return errors.New("to value is unaddressable") + } + + innerStructMaps := getInnerStructMaps(m) + if len(innerStructMaps) != 0 { + for k, v := range innerStructMaps { + var fieldV reflect.Value + if strings.Contains(k, ".") { + fieldV = getFiledValueByPath(k, toValue) + } else { + fieldV = toValue.FieldByName(k) + } + + if !fieldV.CanSet() || !fieldV.CanAddr() { + continue + } + fieldT := fieldV.Type().Elem() + if fieldT.Kind() != reflect.Struct { + return errors.New(k + "不是结构体") + } + // 如果值为nil,则默认创建一个并赋值 + if fieldV.IsNil() { + fieldV.Set(reflect.New(fieldT)) + } + err := Map2Struct(v, fieldV.Addr().Interface()) + if err != nil { + return err + } + } + } + var err error + for k, v := range m { + if v == nil { + continue + } + k = strings.Title(k) + // 如果key含有下划线,则将其转为驼峰 + if strings.Contains(k, "_") { + k = Case2Camel(k) + } + + fieldV := toValue.FieldByName(k) + if !fieldV.CanSet() { + continue + } + + err = decode(k, v, fieldV) + if err != nil { + return err + } + } + + return nil +} + +func Maps2Structs(maps []map[string]any, structs any) error { + structsV := reflect.Indirect(reflect.ValueOf(structs)) + valType := structsV.Type() + valElemType := valType.Elem() + sliceType := reflect.SliceOf(valElemType) + + length := len(maps) + + valSlice := structsV + if valSlice.IsNil() { + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, length, length) + } + + for i := 0; i < length; i++ { + err := Map2Struct(maps[i], valSlice.Index(i).Addr().Interface()) + if err != nil { + return err + } + } + structsV.Set(valSlice) + return nil +} + +func getFiledValueByPath(path string, value reflect.Value) reflect.Value { + split := strings.Split(path, ".") + for _, v := range split { + if value.Type().Kind() == reflect.Ptr { + // 如果值为nil,则创建并赋值 + if value.IsNil() { + value.Set(reflect.New(IndirectType(value.Type()))) + } + value = value.Elem() + } + value = value.FieldByName(v) + } + return value +} + +func getInnerStructMaps(m map[string]any) map[string]map[string]any { + key2map := make(map[string]map[string]any) + for k, v := range m { + if !strings.Contains(k, ".") { + continue + } + lastIndex := strings.LastIndex(k, ".") + prefix := k[0:lastIndex] + m2 := key2map[prefix] + if m2 == nil { + key2map[prefix] = map[string]any{k[lastIndex+1:]: v} + } else { + m2[k[lastIndex+1:]] = v + } + delete(m, k) + } + return key2map +} + +// decode等方法摘抄自mapstructure库 + +func decode(name string, input any, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value + // to be the zero value. + outVal.Set(reflect.Zero(outVal.Type())) + return nil + } + + var err error + outputKind := getKind(outVal) + switch outputKind { + case reflect.Int: + err = decodeInt(name, input, outVal) + case reflect.Uint: + err = decodeUint(name, input, outVal) + case reflect.Float32: + err = decodeFloat(name, input, outVal) + case reflect.String: + err = decodeString(name, input, outVal) + case reflect.Ptr: + _, err = decodePtr(name, input, outVal) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + } + return err +} + +func decodeInt(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetInt(dataVal.Int()) + case dataKind == reflect.Uint: + val.SetInt(int64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetInt(int64(dataVal.Float())) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetInt(1) + } else { + val.SetInt(0) + } + case dataKind == reflect.String: + i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeUint(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + i := dataVal.Int() + if i < 0 { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + case dataKind == reflect.Uint: + val.SetUint(dataVal.Uint()) + case dataKind == reflect.Float32: + f := dataVal.Float() + if f < 0 { + return fmt.Errorf("cannot parse '%s', %f overflows uint", + name, f) + } + val.SetUint(uint64(f)) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetUint(1) + } else { + val.SetUint(0) + } + case dataKind == reflect.String: + i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetUint(i) + } else { + return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + if i < 0 { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeFloat(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetFloat(float64(dataVal.Int())) + case dataKind == reflect.Uint: + val.SetFloat(float64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetFloat(dataVal.Float()) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetFloat(1) + } else { + val.SetFloat(0) + } + case dataKind == reflect.String: + f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits()) + if err == nil { + val.SetFloat(f) + } else { + return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeString(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + converted := true + switch { + case dataKind == reflect.String: + val.SetString(dataVal.String()) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetString("1") + } else { + val.SetString("0") + } + case dataKind == reflect.Int: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + case dataKind == reflect.Uint: + val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + case dataKind == reflect.Float32: + val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) + case dataKind == reflect.Slice, + dataKind == reflect.Array: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + switch elemKind { + case reflect.Uint8: + var uints []uint8 + if dataKind == reflect.Array { + uints = make([]uint8, dataVal.Len(), dataVal.Len()) + for i := range uints { + uints[i] = dataVal.Index(i).Interface().(uint8) + } + } else { + uints = dataVal.Interface().([]uint8) + } + val.SetString(string(uints)) + default: + converted = false + } + default: + converted = false + } + + if !converted { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodePtr(name string, data any, val reflect.Value) (bool, error) { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return true, nil + } + + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + valType := val.Type() + valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() { + realVal = reflect.New(valElemType) + } + + if err := decode(name, data, reflect.Indirect(realVal)); err != nil { + return false, err + } + + val.Set(realVal) + } else { + if err := decode(name, data, reflect.Indirect(val)); err != nil { + return false, err + } + } + return false, nil +} + +func getKind(val reflect.Value) reflect.Kind { + kind := val.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} + +// 下划线写法转为驼峰写法 +func Case2Camel(name string) string { + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + return strings.Replace(name, " ", "", -1) +} + +func IsBlank(value reflect.Value) bool { + switch value.Kind() { + case reflect.String: + return value.Len() == 0 + case reflect.Bool: + return !value.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return value.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return value.Uint() == 0 + case reflect.Float32, reflect.Float64: + return value.Float() == 0 + case reflect.Interface, reflect.Ptr: + return value.IsNil() + } + return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface()) +} diff --git a/kit/utils/template.go b/kit/utils/template.go new file mode 100644 index 0000000..285527d --- /dev/null +++ b/kit/utils/template.go @@ -0,0 +1,28 @@ +package utils + +import ( + "bytes" + "text/template" +) + +func parse(t *template.Template, vars any) string { + var tmplBytes bytes.Buffer + + err := t.Execute(&tmplBytes, vars) + if err != nil { + panic(err) + } + return tmplBytes.String() +} + +// 模板字符串解析 +// str 模板字符串 +// vars 参数变量 +func TemplateParse(str string, vars any) string { + tmpl, err := template.New("tmpl").Parse(str) + + if err != nil { + panic(err) + } + return parse(tmpl, vars) +} diff --git a/kit/utils/tree_utils.go b/kit/utils/tree_utils.go new file mode 100644 index 0000000..f14ae36 --- /dev/null +++ b/kit/utils/tree_utils.go @@ -0,0 +1,74 @@ +package utils + +// ConvertToINodeArray 其他的结构体想要生成菜单树,直接实现这个接口 +type INode interface { + // GetId获取id + GetId() int + // GetPid 获取父id + GetPid() int + // IsRoot 判断当前节点是否是顶层根节点 + IsRoot() bool + + SetChildren(childern any) +} + +type INodes []INode + +func (nodes INodes) Len() int { + return len(nodes) +} +func (nodes INodes) Swap(i, j int) { + nodes[i], nodes[j] = nodes[j], nodes[i] +} +func (nodes INodes) Less(i, j int) bool { + return nodes[i].GetId() < nodes[j].GetId() +} + +// GenerateTree 自定义的结构体实现 INode 接口后调用此方法生成树结构 +// nodes 需要生成树的节点 +// selectedNode 生成树后选中的节点 +// menuTrees 生成成功后的树结构对象 +func GenerateTree(nodes []INode) (trees []INode) { + trees = []INode{} + // 定义顶层根和子节点 + var roots, childs []INode + for _, v := range nodes { + if v.IsRoot() { + // 判断顶层根节点 + roots = append(roots, v) + } + childs = append(childs, v) + } + + for _, v := range roots { + // 递归 + setChildren(v, childs) + trees = append(trees, v) + } + return +} + +// recursiveTree 递归生成树结构 +// tree 递归的树对象 +// nodes 递归的节点 +// selectedNodes 选中的节点 +func setChildren(parent INode, nodes []INode) { + children := []INode{} + for _, v := range nodes { + if v.IsRoot() { + // 如果当前节点是顶层根节点就跳过 + continue + } + if parent.GetId() == v.GetPid() { + children = append(children, v) + } + } + if len(children) == 0 { + return + } + + parent.SetChildren(children) + for _, c := range children { + setChildren(c, nodes) + } +} diff --git a/kit/utils/yml.go b/kit/utils/yml.go new file mode 100644 index 0000000..60e75ea --- /dev/null +++ b/kit/utils/yml.go @@ -0,0 +1,27 @@ +package utils + +import ( + "errors" + "io/ioutil" + + "gopkg.in/yaml.v3" +) + +// 从指定路径加载yaml文件 +func LoadYml(path string, out any) error { + yamlFileBytes, readErr := ioutil.ReadFile(path) + if readErr != nil { + return readErr + } + // yaml解析 + err := yaml.Unmarshal(yamlFileBytes, out) + if err != nil { + return errors.New("无法解析 [" + path + "] -- " + err.Error()) + } + return nil +} + +func LoadYmlByString(yamlStr string, out any) error { + // yaml解析 + return yaml.Unmarshal([]byte(yamlStr), out) +} diff --git a/kit/ws/msg.go b/kit/ws/msg.go new file mode 100644 index 0000000..3e3a20a --- /dev/null +++ b/kit/ws/msg.go @@ -0,0 +1,27 @@ +package ws + +const SuccessMsgType = 1 +const ErrorMsgType = 0 +const InfoMsgType = 2 + +// websocket消息 +type Msg struct { + Type int `json:"type"` // 消息类型 + Title string `json:"title"` // 消息标题 + Msg string `json:"msg"` // 消息内容 +} + +// 普通消息 +func NewMsg(title, msg string) *Msg { + return &Msg{Type: InfoMsgType, Title: title, Msg: msg} +} + +// 成功消息 +func SuccessMsg(title, msg string) *Msg { + return &Msg{Type: SuccessMsgType, Title: title, Msg: msg} +} + +// 错误消息 +func ErrMsg(title, msg string) *Msg { + return &Msg{Type: ErrorMsgType, Title: title, Msg: msg} +} diff --git a/kit/ws/ws.go b/kit/ws/ws.go new file mode 100644 index 0000000..25665b9 --- /dev/null +++ b/kit/ws/ws.go @@ -0,0 +1,97 @@ +package ws + +import ( + "encoding/json" + "net/http" + "sync" + "time" + + "pandax/kit/logger" + + "github.com/gorilla/websocket" +) + +var Upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024 * 1024 * 10, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +type Connection struct { + conn *websocket.Conn + lock sync.Mutex +} + +var connections = make(map[uint64]*Connection) +var connLock sync.Mutex + +func init() { + go checkConn() +} + +// 放置ws连接 +func Put(userId uint64, conn *websocket.Conn) { + connLock.Lock() + defer connLock.Unlock() + + Delete(userId) + connections[userId] = &Connection{ + conn: conn, + } +} + +func checkConn() { + heartbeat := time.Duration(20) * time.Second + tick := time.NewTicker(heartbeat) + for range tick.C { + connLock.Lock() + for uid, conn := range connections { + conn.lock.Lock() + err := conn.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(heartbeat/2)) + conn.lock.Unlock() + if err != nil { + logger.Log.Info("删除ping失败的websocket连接:uid = ", uid) + Delete(uid) + } + } + connLock.Unlock() + } +} + +// 删除ws连接 +func Delete(userid uint64) { + connLock.Lock() + defer connLock.Unlock() + + conn := connections[userid] + if conn != nil { + conn.lock.Lock() + conn.conn.Close() + conn.lock.Unlock() + delete(connections, userid) + } +} + +// 对指定用户发送消息 +func SendMsg(userId uint64, msg *Msg) { + connLock.Lock() + defer connLock.Unlock() + + conn := connections[userId] + if conn != nil { + conn.lock.Lock() + defer conn.lock.Unlock() + + bytes, err := json.Marshal(msg) + if err != nil { + logger.Log.Error("发送消息失败:", err) + return + } + err = conn.conn.WriteMessage(websocket.TextMessage, bytes) + if err != nil { + logger.Log.Error("发送消息失败:", err) + } + } +}