diff --git a/net/src/test/go/util/assert/assertion_format.go b/net/src/test/go/util/assert/assertion_format.go new file mode 100644 index 00000000..ab6cc474 --- /dev/null +++ b/net/src/test/go/util/assert/assertion_format.go @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package assert + +import "testing" + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t *testing.T, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + t.Helper() + return Equal(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t *testing.T, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + t.Helper() + return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t *testing.T, object interface{}, msg string, args ...interface{}) bool { + t.Helper() + return Nil(t, object, append([]interface{}{msg}, args...)...) +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t *testing.T, object interface{}, msg string, args ...interface{}) bool { + t.Helper() + return NotNil(t, object, append([]interface{}{msg}, args...)...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t *testing.T, object interface{}, msg string, args ...interface{}) bool { + t.Helper() + return Empty(t, object, append([]interface{}{msg}, args...)...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t *testing.T, object interface{}, msg string, args ...interface{}) bool { + t.Helper() + return NotEmpty(t, object, append([]interface{}{msg}, args...)...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t *testing.T, object interface{}, length int, msg string, args ...interface{}) bool { + t.Helper() + return Len(t, object, length, append([]interface{}{msg}, args...)...) +} diff --git a/net/src/test/go/util/assert/assertions.go b/net/src/test/go/util/assert/assertions.go new file mode 100644 index 00000000..c24d7f78 --- /dev/null +++ b/net/src/test/go/util/assert/assertions.go @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package assert + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "reflect" + "strings" + "testing" + "time" +) + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) bool { + t.Helper() + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) + } + if !ObjectsAreEqual(expected, actual) { + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s", expected, actual), msgAndArgs...) + } + return true +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) bool { + t.Helper() + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", + expected, actual, err), msgAndArgs...) + } + if ObjectsAreEqual(expected, actual) { + return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) + } + return true +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t *testing.T, object interface{}, msgAndArgs ...interface{}) bool { + pass := isEmpty(object) + if !pass { + t.Helper() + Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) + } + return pass +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t *testing.T, object interface{}, msgAndArgs ...interface{}) bool { + pass := !isEmpty(object) + if !pass { + t.Helper() + Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) + } + return pass +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t *testing.T, object interface{}, msgAndArgs ...interface{}) bool { + if isNil(object) { + return true + } + t.Helper() + return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t *testing.T, object interface{}, msgAndArgs ...interface{}) bool { + if !isNil(object) { + return true + } + t.Helper() + return Fail(t, "Expected value not to be nil.", msgAndArgs...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t *testing.T, object interface{}, length int, msgAndArgs ...interface{}) bool { + t.Helper() + ok, l := getLen(object) + if !ok { + return Fail(t, fmt.Sprintf("\"%v\" could not be applied builtin len()", object), msgAndArgs...) + } + if l != length { + return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) + } + return true +} + +// Fail reports a failure through +func Fail(t *testing.T, failureMessage string, msgAndArgs ...interface{}) bool { + t.Helper() + content := []labeledContent{ + {"Error", failureMessage}, + } + message := messageFromMsgAndArgs(msgAndArgs...) + if len(message) > 0 { + content = append(content, labeledContent{"Messages", message}) + } + t.Errorf("\n%s", ""+labeledOutput(content...)) + return false +} + +// ObjectsAreEqual determines if two objects are considered equal.. +func ObjectsAreEqual(expected, actual interface{}) bool { + if expected == nil || actual == nil { + return expected == actual + } + + exp, ok := expected.([]byte) + if !ok { + return reflect.DeepEqual(expected, actual) + } + + act, ok := actual.([]byte) + if !ok { + return false + } + if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) +} + +func validateEqualArgs(expected, actual interface{}) error { + if expected == nil && actual == nil { + return nil + } + + if isFunction(expected) || isFunction(actual) { + return errors.New("cannot take func type as argument") + } + return nil +} + +func formatUnequalValues(expected, actual interface{}) (e string, a string) { + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)), + fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual)) + } + switch expected.(type) { + case time.Duration: + return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual) + } + return truncatingFormat(expected), truncatingFormat(actual) +} + +func truncatingFormat(data interface{}) string { + value := fmt.Sprintf("%#v", data) + max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed. + if len(value) > max { + value = value[0:max] + "<... truncated>" + } + return value +} + +type labeledContent struct { + label string + content string +} + +func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { + if len(msgAndArgs) == 0 || msgAndArgs == nil { + return "" + } + if len(msgAndArgs) == 1 { + msg := msgAndArgs[0] + if msgAsStr, ok := msg.(string); ok { + return msgAsStr + } + return fmt.Sprintf("%+v", msg) + } + if len(msgAndArgs) > 1 { + return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } + return "" +} + +func labeledOutput(content ...labeledContent) string { + longestLabel := 0 + for _, v := range content { + if len(v.label) > longestLabel { + longestLabel = len(v.label) + } + } + var output string + for _, v := range content { + output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" + } + return output +} + +func indentMessageLines(message string, longestLabelLen int) string { + outBuf := new(bytes.Buffer) + for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { + // no need to align first line because it starts at the correct location (after the label) + if i != 0 { + // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab + outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") + } + outBuf.WriteString(scanner.Text()) + } + return outBuf.String() +} + +func getLen(x interface{}) (ok bool, length int) { + v := reflect.ValueOf(x) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + return true, v.Len() +} + +func isFunction(arg interface{}) bool { + if arg == nil { + return false + } + return reflect.TypeOf(arg).Kind() == reflect.Func +} + +func isEmpty(object interface{}) bool { + + if object == nil { + return true + } + objValue := reflect.ValueOf(object) + switch objValue.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + case reflect.Ptr: + if objValue.IsNil() { + return true + } + deref := objValue.Elem().Interface() + return isEmpty(deref) + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) + } +} + +func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool { + for i := 0; i < len(kinds); i++ { + if kind == kinds[i] { + return true + } + } + return false +} + +func isNil(object interface{}) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + kind := value.Kind() + isNilableKind := containsKind( + []reflect.Kind{ + reflect.Chan, reflect.Func, + reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice}, + kind) + if isNilableKind && value.IsNil() { + return true + } + return false +} diff --git a/net/src/test/go/util/byteutil/byte.go b/net/src/test/go/util/byteutil/byte.go new file mode 100644 index 00000000..25db42c7 --- /dev/null +++ b/net/src/test/go/util/byteutil/byte.go @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package byteutil + +import ( + "bytes" + "encoding/binary" + "reflect" + "unsafe" +) + +// StringToBytes 强制转换 []byte(s) +func StringToBytes(s string) []byte { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := reflect.SliceHeader{ + Data: sh.Data, + Len: sh.Len, + Cap: sh.Len, + } + return *(*[]byte)(unsafe.Pointer(&bh)) +} + +// BytesToString 强制转换 string(s) +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// Uint64ToBytes uint64转byte +func Uint64ToBytes(i uint64) []byte { + var buf = make([]byte, 8) + binary.BigEndian.PutUint64(buf, i) + return buf +} + +// BytesToUint64 byte转uint64 +func BytesToUint64(b []byte) uint64 { + return binary.BigEndian.Uint64(b) +} + +// Split 数据分片 +func Split(buf []byte, limit int) [][]byte { + var chunk []byte + chunks := make([][]byte, 0, len(buf)/limit+1) + for len(buf) >= limit { + chunk, buf = buf[:limit], buf[limit:] + chunks = append(chunks, chunk) + } + if len(buf) > 0 { + chunks = append(chunks, buf[:]) + } + return chunks +} + +// Join 数据合并 +func Join(s [][]byte) []byte { + return bytes.Join(s, []byte("")) +} diff --git a/net/src/test/go/util/byteutil/byte_test.go b/net/src/test/go/util/byteutil/byte_test.go new file mode 100644 index 00000000..8315d926 --- /dev/null +++ b/net/src/test/go/util/byteutil/byte_test.go @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package byteutil + +import ( + "bytes" + "testing" +) + +func TestStringToBytes(t *testing.T) { + x := "Hello Gopher!" + y := StringToBytes(x) + z := []byte(x) + + if !bytes.Equal(y, z) { + t.Fail() + } +} + +func TestBytesToString(t *testing.T) { + x := []byte("Hello Gopher!") + y := BytesToString(x) + z := string(x) + + if y != z { + t.Fail() + } +} + +func TestUint64ToBytes(t *testing.T) { + x := uint64(1234567890) + y := Uint64ToBytes(x) + t.Logf("Uint64ToBytes: %b", y) +} + +func TestBytesToUint64(t *testing.T) { + x := uint64(1234567890) + y := Uint64ToBytes(x) + z := BytesToUint64(y) + t.Logf("%d", z) +} + +func TestSplit(t *testing.T) { + slice := Split([]byte("Hello Gopher!"), 1) + t.Logf("%s", slice) +} + +func TestJoin(t *testing.T) { + b := Join(Split([]byte("Hello Gopher!"), 1)) + t.Logf("%s", b) +} diff --git a/net/src/test/go/util/convert/convert.go b/net/src/test/go/util/convert/convert.go new file mode 100644 index 00000000..c02503e2 --- /dev/null +++ b/net/src/test/go/util/convert/convert.go @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package convert + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strconv" + "time" +) + +// AnyToString ... +func AnyToString(i interface{}) string { + var s string + switch v := i.(type) { + case nil: + s = "" + case int: + s = strconv.Itoa(v) + case int8: + s = strconv.Itoa(int(v)) + case int16: + s = strconv.Itoa(int(v)) + case int32: // same as `rune` + s = strconv.Itoa(int(v)) + case int64: + s = strconv.Itoa(int(v)) + case uint: + s = strconv.FormatUint(uint64(v), 10) + case uint8: + s = strconv.FormatUint(uint64(v), 10) + case uint16: + s = strconv.FormatUint(uint64(v), 10) + case uint32: + s = strconv.FormatUint(uint64(v), 10) + case uint64: + s = strconv.FormatUint(v, 10) + case float32: + s = strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + s = strconv.FormatFloat(v, 'f', -1, 64) + case bool: + s = strconv.FormatBool(v) + case string: + s = v + case []byte: + s = string(v) + case time.Duration: + s = v.String() + case json.Number: + s = v.String() + default: + s = fmt.Sprint(v) + } + return s +} + +// IntToString int => string +func IntToString(i int) string { + return strconv.Itoa(i) +} + +// Uint64ToString uint64 => string +func Uint64ToString(i uint64) string { + return strconv.FormatUint(i, 10) +} + +// Float64ToString float64 => string +func Float64ToString(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +// Float32ToString float32 => string +func Float32ToString(f float32) string { + return strconv.FormatFloat(float64(f), 'f', -1, 32) +} + +// StringToFloat64 string => float64 +func StringToFloat64(s string) float64 { + f, _ := strconv.ParseFloat(s, 64) + return f +} + +// StringToFloat32 string => float32 +func StringToFloat32(s string) float32 { + f64, _ := strconv.ParseFloat(s, 32) + return float32(f64) +} + +// StringToInt string => int +func StringToInt(s string) int { + i, _ := strconv.Atoi(s) + return i +} + +// StringToInt32 string => int32 +func StringToInt32(s string) int32 { + return int32(StringToInt64(s)) +} + +// StringToInt64 string => int64 +func StringToInt64(s string) int64 { + i, _ := strconv.ParseInt(s, 10, 64) + return i +} + +// StringToUint64 string => uint64 +func StringToUint64(s string) uint64 { + i, _ := strconv.ParseUint(s, 10, 64) + return i +} + +// IntToUint int => uint +func IntToUint(i int) uint { + return uint(i) +} + +// UintToInt uint => int +func UintToInt(i uint) int { + return int(i) +} + +// JsonNumberToInt json.Number => int +func JsonNumberToInt(n json.Number) int { + i64, _ := n.Int64() + return int(i64) +} + +// MapToJson map => json +func MapToJson(m map[string]string) (string, error) { + b, e := json.Marshal(m) + if e != nil { + return "", e + } + return string(b), nil +} + +// JsonToMap json => map +func JsonToMap(s string) (map[string]string, error) { + m := make(map[string]string) + err := json.Unmarshal([]byte(s), &m) + if err != nil { + return nil, err + } + return m, nil +} + +// Base64Encode base64 编码 +func Base64Encode(src []byte) string { + return base64.StdEncoding.EncodeToString(src) +} + +// Base64Decode base64 解码 +func Base64Decode(src string) ([]byte, error) { + return base64.StdEncoding.DecodeString(src) +} diff --git a/net/src/test/go/util/csvutil/csv.go b/net/src/test/go/util/csvutil/csv.go new file mode 100644 index 00000000..1a54d351 --- /dev/null +++ b/net/src/test/go/util/csvutil/csv.go @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package csvutil + +import ( + "bytes" + "encoding/csv" + "io" + "os" +) + +// WriteFile 追加写文件 +func WriteFile(fileName string, body [][]string, head []string) error { + f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return err + } + defer f.Close() + + w := csv.NewWriter(f) + w.Comma = ',' + w.UseCRLF = true + if len(head) > 0 { + r := csv.NewReader(f) + var row []string + if row, err = r.Read(); err != nil || len(row) == 0 { + f.WriteString("\xEF\xBB\xBF") + if err = w.Write(head); err != nil { + return err + } + } + } + + err = w.WriteAll(body) + if err != nil { + return err + } + w.Flush() + return nil +} + +// WriteBytes 写内存 +func WriteBytes(body [][]string, head []string) ([]byte, error) { + var buf bytes.Buffer + w := csv.NewWriter(&buf) + w.Comma = ',' + w.UseCRLF = true + if len(head) > 0 { + if buf.Len() == 0 { + buf.WriteString("\xEF\xBB\xBF") + if err := w.Write(head); err != nil { + return nil, err + } + } + } + if err := w.WriteAll(body); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// ReadFile 读文件 +func ReadFile(fileName string) ([][]string, []string, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, nil, err + } + defer f.Close() + r := csv.NewReader(f) + content, err := r.ReadAll() + if err != nil { + return nil, nil, err + } + return content[1:], content[0], nil +} + +// ReadFileOffset 按行读取文件 offset >= 2 +func ReadFileOffset(fileName string, offset, limit int) ([][]string, []string, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, nil, err + } + defer f.Close() + r := csv.NewReader(f) + body := make([][]string, 0) + head := make([]string, 0) + counter := 1 + for { + var row []string + if row, err = r.Read(); err != nil && err != io.EOF { + return nil, nil, err + } + if err == io.EOF { + break + } + if counter == 1 { + head = row + } else if counter >= offset && counter < offset+limit { + body = append(body, row) + } else if counter >= offset+limit { + break + } + counter++ + } + return body, head, nil +} diff --git a/net/src/test/go/util/csvutil/csv_test.go b/net/src/test/go/util/csvutil/csv_test.go new file mode 100644 index 00000000..026e36e8 --- /dev/null +++ b/net/src/test/go/util/csvutil/csv_test.go @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package csvutil + +import "testing" + +func TestWriteFile(t *testing.T) { + filePath := "testdata.csv" + head := []string{"序号", "姓名", "电话"} + body := make([][]string, 0) + body = append(body, []string{"1", "Mark", "18812345678"}, []string{"2", "马克", "18612345678"}) + err := WriteFile(filePath, body, head) + t.Logf("WriteFile: %v", err) +} + +func TestWriteBytes(t *testing.T) { + head := []string{"序号", "姓名", "电话"} + body := make([][]string, 0) + body = append(body, []string{"1", "Mark", "18812345678"}, []string{"2", "马克", "18612345678"}) + b, _ := WriteBytes(body, head) + t.Logf("WriteBytes: %s", b) +} + +func TestReadFile(t *testing.T) { + filePath := "testdata.csv" + body, head, _ := ReadFile(filePath) + t.Logf("body: %s", body) + t.Logf("head: %s", head) +} + +func TestReadFileOffset(t *testing.T) { + filePath := "testdata.csv" + body, head, err := ReadFileOffset(filePath, 4, 2) + t.Logf("body: %s", body) + t.Logf("head: %s", head) + t.Logf("err: %v", err) +} diff --git a/net/src/test/go/util/datetime/datetime.go b/net/src/test/go/util/datetime/datetime.go new file mode 100644 index 00000000..fb14f7ff --- /dev/null +++ b/net/src/test/go/util/datetime/datetime.go @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package datetime + +import "time" + +const DefaultLayout string = "2006-01-02 15:04:05" +const DayLayout string = "2006-01-02" + +// Time 获取当前时间戳 +func Time() int64 { + return time.Now().Unix() +} + +// MilliTime 获取当前毫秒时间戳 +func MilliTime() int64 { + return time.Now().UnixNano() / 1e6 +} + +// MicroTime 获取当前微秒时间戳 +func MicroTime() int64 { + return time.Now().UnixNano() / 1e3 +} + +// Date 时间戳格式化 +func Date(timestamp int64, layout string) string { + return time.Unix(timestamp, 0).Format(layout) +} + +// Timestamp 时间转时间戳 +func Timestamp(datetime string, layout string) int64 { + tm2, err := time.ParseInLocation(layout, datetime, time.Local) + if err != nil { + return 0 + } + return tm2.Unix() +} + +// Datetime 获取当前时间 +func Datetime() string { + return Date(Time(), DefaultLayout) +} + +// Today 获取今天日期 +func Today() string { + return Date(Time(), DayLayout) +} + +// TodayStartTime Today => 00:00:00 获取今天起始时间戳 +func TodayStartTime() time.Time { + return DayStartTime(time.Now()) +} + +// TodayEndTime Today => 23:59:59 获取今天结束时间戳 +func TodayEndTime() time.Time { + return DayEndTime(time.Now()) +} + +// DayStartTime Day => 00:00:00 获取当天起始时间戳 +func DayStartTime(t time.Time) time.Time { + tm2 := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local) + return tm2 +} + +// DayEndTime Day => 23:59:59 获取当天结束时间戳 +func DayEndTime(t time.Time) time.Time { + tm2 := time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, time.Local) + return tm2 +} + +// WeekStartTime Monday => 00:00:00 获取周起始时间戳 +func WeekStartTime(t time.Time) time.Time { + offset := int(time.Monday - t.Weekday()) + if offset > 0 { + offset = -6 + } + tm2 := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local). + AddDate(0, 0, offset) + return tm2 +} + +//WeekEndTime Sunday => 23:59:59 获取周结束时间戳 +func WeekEndTime(t time.Time) time.Time { + offset := 0 + if w := t.Weekday(); w != 0 { + offset = int(time.Saturday + 1 - w) + } + tm2 := time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, time.Local). + AddDate(0, 0, offset) + return tm2 +} + +// MonthStartTime 月初 => 00:00:00 获取月起始时间戳 +func MonthStartTime(t time.Time) time.Time { + tm2 := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.Local) + return tm2 +} + +// MonthEndTime 月末 => 23:59:59 获取月结束时间戳 +func MonthEndTime(t time.Time) time.Time { + e := MonthStartTime(t).AddDate(0, 1, -1) + tm2 := time.Date(t.Year(), t.Month(), e.Day(), 23, 59, 59, 0, time.Local) + return tm2 +} + +// YearStartTime 年初 => 00:00:00 获取年起始时间戳 +func YearStartTime(t time.Time) time.Time { + tm2 := time.Date(t.Year(), time.January, 1, 0, 0, 0, 0, time.Local) + return tm2 +} + +// YearEndTime 年末 => 23:59:59 获取年起始时间戳 +func YearEndTime(t time.Time) time.Time { + tm2 := time.Date(t.Year(), time.December, 31, 23, 59, 59, 0, time.Local) + return tm2 +} diff --git a/net/src/test/go/util/datetime/datetime_test.go b/net/src/test/go/util/datetime/datetime_test.go new file mode 100644 index 00000000..dd744cde --- /dev/null +++ b/net/src/test/go/util/datetime/datetime_test.go @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package datetime + +import ( + "testing" + "time" +) + +func TestTime(t *testing.T) { + t.Logf("Time: %d", Time()) +} + +func TestMilliTime(t *testing.T) { + t.Logf("MilliTime: %d", MilliTime()) +} + +func TestMicroTime(t *testing.T) { + t.Logf("MicroTime: %d", MicroTime()) +} + +func TestDateTime(t *testing.T) { + t.Logf("Datetime: %s", Datetime()) +} + +func TestTimestamp(t *testing.T) { + datetime := "2021-06-06 11:11:11" + t.Logf("Date: %d", Timestamp(datetime, DefaultLayout)) +} + +func TestToday(t *testing.T) { + t.Logf("Today: %s", Today()) +} + +func TestTodayStartTime(t *testing.T) { + t.Logf("TodayStartTime: %v", TodayStartTime()) +} + +func TestTodayEndTime(t *testing.T) { + t.Logf("TodayEndTime: %v", TodayEndTime()) +} + +func TestDayStartTime(t *testing.T) { + t.Logf("DayStartTime: %v", DayStartTime(time.Now())) +} + +func TestDayEndTime(t *testing.T) { + t.Logf("DayEndTime: %v", DayEndTime(time.Now())) +} + +func TestWeekStartTime(t *testing.T) { + t.Logf("WeekStartTime: %v", WeekStartTime(time.Now())) +} + +func TestWeekEndTime(t *testing.T) { + t.Logf("WeekEndTime: %v", WeekEndTime(time.Now())) +} + +func TestMonthStartTime(t *testing.T) { + t.Logf("MonthStartTime: %v", MonthStartTime(time.Now())) +} + +func TestMonthEndTime(t *testing.T) { + t.Logf("MonthEndTime: %v", MonthEndTime(time.Now())) +} +func TestYearStartTime(t *testing.T) { + t.Logf("YearStartTime: %v", YearStartTime(time.Now())) +} + +func TestYearEndTime(t *testing.T) { + t.Logf("YearEndTime: %v", YearEndTime(time.Now())) +} + diff --git a/net/src/test/go/util/fileutil/dir.go b/net/src/test/go/util/fileutil/dir.go new file mode 100644 index 00000000..768c1c61 --- /dev/null +++ b/net/src/test/go/util/fileutil/dir.go @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package fileutil + +import ( + "errors" + "io/ioutil" + "os" + "path" + "path/filepath" +) + +// SelfPath gets compiled executable file absolute path. +func SelfPath() string { + selfPath, _ := filepath.Abs(os.Args[0]) + return selfPath +} + +// SelfDir gets compiled executable file directory. +func SelfDir() string { + return filepath.Dir(SelfPath()) +} + +//MkDir 创建目录. +func MkDir(dirPath string) error { + if IsExist(dirPath) { + return nil + } + return os.MkdirAll(dirPath, os.ModePerm) +} + +// IsExist 判断文件或目录是否存在. +func IsExist(filePath string) bool { + _, err := os.Stat(filePath) + return err == nil || os.IsExist(err) +} + +// IsEmpty 判断目录是否为空. +func IsEmpty(dirname string) bool { + dir, _ := ioutil.ReadDir(dirname) + if len(dir) == 0 { + return true + } else { + return false + } +} + +// IsFile 判断文件是否存在. +func IsFile(filePath string) bool { + f, e := os.Stat(filePath) + if e != nil { + return false + } + return !f.IsDir() +} + +// IsDir 判断目录是否存在. +func IsDir(filePath string) bool { + f, e := os.Stat(filePath) + if e != nil { + return false + } + return f.IsDir() +} + +// ListIndex 目录下文件和子目录列表. +func ListIndex(dirPath string) ([]string, []string, error) { + if !IsDir(dirPath) { + return nil, nil, errors.New(dirPath + " not a directory") + } + fs, err := ioutil.ReadDir(dirPath) + var files, dirs []string + for _, fi := range fs { + if !fi.IsDir() { + files = append(files, fi.Name()) + } else { + dirs = append(dirs, fi.Name()) + } + } + return files, dirs, err +} + +// ClearDir 清空目录下所有文件不包括子目录. +func ClearDir(dirPath string) error { + dir, e := ioutil.ReadDir(dirPath) + for _, d := range dir { + if d.IsDir() { + continue + } + _ = os.Remove(path.Join([]string{dirPath, d.Name()}...)) + } + return e +} + +// ClearDirF 清空目录下所有文件和目录. +func ClearDirF(dirPath string) error { + dir, e := ioutil.ReadDir(dirPath) + for _, d := range dir { + _ = os.RemoveAll(path.Join([]string{dirPath, d.Name()}...)) + } + return e +} + +// RemoveDir 删除空目录. +func RemoveDir(dirPath string) error { + if !IsDir(dirPath) { + return errors.New(dirPath + " not a directory") + } + return os.Remove(dirPath) +} diff --git a/net/src/test/go/util/fileutil/dir_test.go b/net/src/test/go/util/fileutil/dir_test.go new file mode 100644 index 00000000..8cd35539 --- /dev/null +++ b/net/src/test/go/util/fileutil/dir_test.go @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package fileutil + +import ( + "testing" +) + +func TestSelfPath(t *testing.T) { + t.Logf("SelfPath: %v", SelfPath()) +} + +func TestSelfDir(t *testing.T) { + t.Logf("SelfDir: %v", SelfDir()) +} + +func TestMkDir(t *testing.T) { + dirPath := "testdata/dir/dir1" + t.Logf("Mkdir: %v", MkDir(dirPath)) +} + +func TestIsEmpty(t *testing.T) { + dirPath := "testdata/dir" + t.Logf("IsEmpty: %v", IsEmpty(dirPath)) +} + +func TestIsDir(t *testing.T) { + dirPath := "testdata/dir" + t.Logf("IsDir: %v", IsDir(dirPath)) +} + +func TestIsFile(t *testing.T) { + dirPath := "testdata/dir" + t.Logf("IsFile: %v", IsFile(dirPath)) +} + +func TestListIndex(t *testing.T) { + dirPath := "testdata" + files, dirs, err := ListIndex(dirPath) + t.Logf("ListFiles: %v, %v, %v", files, dirs, err) +} + +func TestClearDir(t *testing.T) { + dirPath := "testdata/dir/dir1" + t.Logf("ClearDir: %v", ClearDir(dirPath)) +} + +func TestClearDirF(t *testing.T) { + dirPath := "testdata" + t.Logf("ClearDirF: %v", ClearDirF(dirPath)) +} + +func TestRemoveDir(t *testing.T) { + dirPath := "testdata" + t.Logf("RemoveDir: %v", RemoveDir(dirPath)) +} diff --git a/net/src/test/go/util/fileutil/file.go b/net/src/test/go/util/fileutil/file.go new file mode 100644 index 00000000..8ac2558f --- /dev/null +++ b/net/src/test/go/util/fileutil/file.go @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package fileutil + +import ( + "bufio" + "bytes" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" +) + +/** +os.O_WRONLY 只写 +os.O_CREATE 创建文件 +os.O_RDONLY 只读 +os.O_RDWR 读写 +os.O_TRUNC 清空 +os.O_APPEND 追加 +*/ + +// CreateFile 创建或清空文件 +func CreateFile(filePath string) (*os.File, error) { + if err := MkDir(path.Dir(filePath)); err != nil { + return nil, err + } + return os.Create(filePath) +} + +// ReadFileToBytes 读文件 +func ReadFileToBytes(filePath string) ([]byte, error) { + b, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + return b, nil +} + +// WriteBytesToFile 写文件 覆盖 +func WriteBytesToFile(filePath string, b []byte) error { + f, err := CreateFile(filePath) + if err != nil { + return err + } + defer f.Close() + wt := bufio.NewWriter(f) + _, err = io.Copy(wt, bytes.NewReader(b)) + if err != nil { + return err + } + wt.Flush() + return nil +} + +// ReadHttpFileToBytes 读网络文件 +func ReadHttpFileToBytes(fileUrl string) ([]byte, error) { + resp, err := http.Get(fileUrl) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return ioutil.ReadAll(resp.Body) +} + +// Copy 复制文件 +func Copy(sourcePath, targetPath string) error { + b, err := ReadFileToBytes(sourcePath) + if err != nil { + return nil + } + return WriteBytesToFile(targetPath, b) +} + +// Download 下载文件 +func Download(sourceUrl, targetPath string) error { + b, err := ReadHttpFileToBytes(sourceUrl) + if err != nil { + return nil + } + return WriteBytesToFile(targetPath, b) +} + +// Name 获取文件名. +func Name(filePath string) string { + return filepath.Base(filePath) +} diff --git a/net/src/test/go/util/fileutil/file_test.go b/net/src/test/go/util/fileutil/file_test.go new file mode 100644 index 00000000..502e9b08 --- /dev/null +++ b/net/src/test/go/util/fileutil/file_test.go @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package fileutil + +import ( + "testing" +) + +func TestCreateFile(t *testing.T) { + filePath := "testdata/file/create.txt" + fs, err := CreateFile(filePath) + defer fs.Close() + t.Logf("CreateFile: %v", err) +} + +func TestWriteBytesToFile(t *testing.T) { + filePath := "testdata/file/write.txt" + t.Logf("WriteBytesToFile: %v", WriteBytesToFile(filePath, []byte("hello"))) +} + +func TestReadFileToBytes(t *testing.T) { + filePath := "testdata/file/write.txt" + b, err := ReadFileToBytes(filePath) + t.Logf("ReadFileToBytes: %v, %v", string(b), err) +} + +func TestReadHttpFileToBytes(t *testing.T) { + filePath := "https://game.gtimg.cn/images/yxzj/img201606/heroimg/109/109.jpg" + b, err := ReadHttpFileToBytes(filePath) + err = WriteBytesToFile("testdata/file/httpFile.jpeg", b) + t.Logf("ReadHttpFileToBytes: %v", err) +} + +func TestCopy(t *testing.T) { + sourcePath := "testdata/file/write.txt" + targetPath := "testdata/file/target.txt" + t.Logf("Copy: %v", Copy(sourcePath, targetPath)) +} + +func TestDownload(t *testing.T) { + sourceUrl := "https://game.gtimg.cn/images/yxzj/img201606/heroimg/109/109.jpg" + targetPath := "testdata/file/109-download.jpeg" + t.Logf("Copy: %v", Download(sourceUrl, targetPath)) +} + +func TestName(t *testing.T) { + t.Logf("Name: %v", Name("testdata/file/109-download.jpeg")) +} diff --git a/net/src/test/go/util/hashutil/hash.go b/net/src/test/go/util/hashutil/hash.go new file mode 100644 index 00000000..13f9aa6d --- /dev/null +++ b/net/src/test/go/util/hashutil/hash.go @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package hashutil + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "hash" +) + +// Md5 hashes using md5 algorithm +func Md5(b []byte) []byte { + return Hashes(md5.New(), b) +} + +// Md5Hex hashes using md5 algorithm +func Md5Hex(text string) string { + return StringHashes(md5.New(), text) +} + +// Sha1 hashes using sha1 algorithm +func Sha1(b []byte) []byte { + return Hashes(sha1.New(), b) +} + +// Sha1Hex hashes using sha1 algorithm +func Sha1Hex(text string) string { + return StringHashes(sha1.New(), text) +} + +// Sha256 hashes using sha256 algorithm +func Sha256(b []byte) []byte { + return Hashes(sha256.New(), b) +} + +// Sha256Hex hashes using sha256 algorithm +func Sha256Hex(text string) string { + return StringHashes(sha256.New(), text) +} + +// Sha512 hashes using sha512 algorithm +func Sha512(b []byte) []byte { + return Hashes(sha512.New(), b) +} + +// Sha512Hex hashes using sha512 algorithm +func Sha512Hex(text string) string { + return StringHashes(sha512.New(), text) +} + +// HmacMd5 hashes using md5 algorithm with a secret +func HmacMd5(b []byte, secret []byte) []byte { + algorithm := hmac.New(md5.New, secret) + return Hashes(algorithm, b) +} + +// HmacMd5Hex hashes using md5 algorithm with a secret +func HmacMd5Hex(text, secret string) string { + return StringHashes(hmac.New(md5.New, []byte(secret)), text) +} + +// HmacSha256 hashes using sha256 algorithm with a secret +func HmacSha256(b []byte, secret []byte) []byte { + return Hashes(hmac.New(sha256.New, secret), b) +} + +// HmacSha256Hex hashes using sha256 algorithm with a secret +func HmacSha256Hex(text, secret string) string { + return StringHashes(hmac.New(sha256.New, []byte(secret)), text) +} + +// HmacSha512 hashes using sha512 algorithm with a secret +func HmacSha512(b []byte, secret []byte) []byte { + return Hashes(hmac.New(sha512.New, secret), b) +} + +// HmacSha512Hex hashes using sha512 algorithm with a secret +func HmacSha512Hex(text, secret string) string { + return StringHashes(hmac.New(sha512.New, []byte(secret)), text) +} + +// StringHashes hashes string +func StringHashes(algorithm hash.Hash, text string) string { + return hex.EncodeToString(Hashes(algorithm, []byte(text))) +} + +// Hashes hashes +func Hashes(algorithm hash.Hash, b []byte) []byte { + algorithm.Write(b) + return algorithm.Sum(nil) +} diff --git a/net/src/test/go/util/httputil/http.go b/net/src/test/go/util/httputil/http.go new file mode 100644 index 00000000..4acd09d4 --- /dev/null +++ b/net/src/test/go/util/httputil/http.go @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package httputil + +import ( + "io/ioutil" + "net/http" + "strings" +) + +func PostWithHeader(url string, msg []byte, headers map[string]string) (string, error) { + client := &http.Client{} + + req, err := http.NewRequest("POST", url, strings.NewReader(string(msg))) + if err != nil { + return "", err + } + for key, header := range headers { + req.Header.Set(key, header) + } + resp, err := client.Do(req) + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func PostWithAuthorization(url, authorization string, msg []byte) (string, error) { + headers := make(map[string]string) + headers["Authorization"] = authorization + headers["Content-Type"] = "application/json" + return PostWithHeader(url, msg, headers) +} diff --git a/net/src/test/go/util/maputil/map.go b/net/src/test/go/util/maputil/map.go new file mode 100644 index 00000000..cf8a08fe --- /dev/null +++ b/net/src/test/go/util/maputil/map.go @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package maputil + +import "strings" + +// IsEmpty ... +func IsEmpty(mp map[string]string) bool { + return len(mp) == 0 +} + +// HasKey ... +func HasKey(mp map[string]string, key string) bool { + if _, ok := mp[key]; ok { + return true + } + return false +} + +// Value ... +func Value(mp map[string]string, key string) string { + if HasKey(mp, key) { + return mp[key] + } + return "" +} + +// HasValue ... +func HasValue(mp map[string]string, value string) bool { + for _, v := range mp { + if v == value { + return true + } + } + return false +} + +// Keys ... +func Keys(mp map[string]string) []string { + ks := make([]string, 0, len(mp)) + for k, _ := range mp { + ks = append(ks, k) + } + return ks +} + +// Values ... +func Values(mp map[string]string) []string { + vs := make([]string, 0, len(mp)) + for _, v := range mp { + vs = append(vs, v) + } + return vs +} + +// KeyToLower convert keys to lower case. +func KeyToLower(mp map[string]string) map[string]string { + nmp := make(map[string]string, len(mp)) + for k, v := range mp { + k = strings.ToLower(k) + nmp[k] = v + } + return nmp +} + +// Merge src 会覆盖 dst +func Merge(src, dst map[string]string) map[string]string { + for k, v := range src { + dst[k] = v + } + return dst +} diff --git a/net/src/test/go/util/mask/mask.go b/net/src/test/go/util/mask/mask.go new file mode 100644 index 00000000..165ae8a7 --- /dev/null +++ b/net/src/test/go/util/mask/mask.go @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package mask + +import ( + "strings" +) + +// Mask 自定义字符脱敏 保留前f位和最后e位 +func Mask(s string, f, e int) string { + l := len(s) + if f+e >= l || f < 0 || e < 0 { + return "" + } + return s[0:f] + strings.Repeat("*", l-f-e) + s[l-e:] +} + +// Left 保留前f位 +func Left(s string, f int) string { + return Mask(s, f, 0) +} + +// Right 保留后e位 +func Right(s string, e int) string { + return Mask(s, 0, e) +} + +// First 取值前f位 +func First(s string, f int) string { + l := len(s) + if l <= f || f < 0 { + return s + } + return s[0:f] +} + +// Last 取值后e位 +func Last(s string, e int) string { + l := len(s) + if l <= e || e < 0 { + return s + } + return s[l-e:] +} + +// LastFour 后四位 +func LastFour(s string) string { + return Last(s, 4) +} + +// IdCard 身份证号脱敏 +func IdCard(s string) string { + if len(s) != 18 { + return "" + } + return s[0:4] + " **** **** " + s[len(s)-4:] +} + +// IdCardStrict 严格身份证号脱敏 +func IdCardStrict(s string) string { + if len(s) != 18 { + return "" + } + return s[0:1] + "*** **** **** ***" + s[len(s)-1:] +} + +// Mobile 手机号脱敏 +func Mobile(s string) string { + return s[0:3] + "****" + s[len(s)-4:] +} + +// ChineseName 中文姓名脱敏 +func ChineseName(s string) string { + r := []rune(s) + l := len(r) + if l == 2 { + return "*" + string(r[1:]) + } else if l == 3 { + return "*" + string(r[l-2:]) + } else if l == 4 { + return "**" + string(r[l-2:]) + } else if l > 4 { + return string(r[:1]) + strings.Repeat("*", l-3) + string(r[l-2:]) + } + return "**" +} + +// Email 邮箱脱敏 +func Email(s string) string { + ss := strings.Split(s, "@") + l := len(ss[0]) + if l <= 1 { + return "*@" + ss[1] + } + r := []rune(s) + return string(r[0:1]) + strings.Repeat("*", l-1) + "@" + ss[1] +} diff --git a/net/src/test/go/util/mathutil/math.go b/net/src/test/go/util/mathutil/math.go new file mode 100644 index 00000000..06d78bee --- /dev/null +++ b/net/src/test/go/util/mathutil/math.go @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package mathutil + +// MaxInt64 ... +func MaxInt64(is ...int64) int64 { + max := is[0] + for _, v := range is { + if v > max { + max = v + } + } + return max +} + +// MinInt64 ... +func MinInt64(is ...int64) int64 { + min := is[0] + for _, v := range is { + if v < min { + min = v + } + } + return min +} diff --git a/net/src/test/go/util/random/random.go b/net/src/test/go/util/random/random.go new file mode 100644 index 00000000..5224a2e7 --- /dev/null +++ b/net/src/test/go/util/random/random.go @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package random + +import ( + "bytes" + "math/rand" + "sync" + "time" +) + +var ( + randSeek = int64(1) + l sync.Mutex +) + +// GetRandomInt 生成值小于max的随机数 +func GetRandomInt(max int) int { + rand.Seed(getRandSeek()) + return rand.Intn(max) +} + +// GetRandomChars 生成英文字母随机字符串 +func GetRandomChars(num int) string { + ss := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + return GetRandomString(num, ss) +} + +// GetRandomNumbers 生成数字类型随机字符串 +func GetRandomNumbers(num int) string { + return GetRandomString(num) +} + +// GetRandomString 生成随机字符串 +func GetRandomString(num int, str ...string) string { + s := "0123456789" + if len(str) > 0 { + s = str[0] + } + l := len(s) + r := rand.New(rand.NewSource(getRandSeek())) + var buf bytes.Buffer + for i := 0; i < num; i++ { + x := r.Intn(l) + buf.WriteString(s[x : x+1]) + } + return buf.String() +} + +func getRandSeek() int64 { + l.Lock() + if randSeek >= 100000000 { + randSeek = 1 + } + randSeek++ + l.Unlock() + return time.Now().UnixNano() + randSeek +} diff --git a/net/src/test/go/util/random/random_test.go b/net/src/test/go/util/random/random_test.go new file mode 100644 index 00000000..3f138b7a --- /dev/null +++ b/net/src/test/go/util/random/random_test.go @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package random + +import ( + "testing" +) + +func TestGetRandomSring(t *testing.T) { + t.Logf("GetRandomString: %s", GetRandomString(4, "abide123456")) + t.Logf("GetRandomString: %s", GetRandomString(6, "abcdefghijklmnopqrstuvwxyz0123456789")) +} + +func TestGetRandomChars(t *testing.T) { + t.Logf("GetRandomChars: %s", GetRandomChars(4)) + t.Logf("GetRandomChars: %s", GetRandomChars(6)) +} + +func TestGetRandomNumbers(t *testing.T) { + t.Logf("GetRandomNumbers: %s", GetRandomNumbers(4)) + t.Logf("GetRandomNumbers: %s", GetRandomNumbers(6)) +} + +func TestGetRandomInt(t *testing.T) { + t.Logf("GetRandomInt: %d", GetRandomInt(10)) + t.Logf("GetRandomInt: %d", GetRandomInt(10000)) +} diff --git a/net/src/test/go/util/rsautil/crypt.go b/net/src/test/go/util/rsautil/crypt.go new file mode 100644 index 00000000..29f0aeec --- /dev/null +++ b/net/src/test/go/util/rsautil/crypt.go @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package rsautil + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/hex" +) + +// EncryptToBase64 ... +func EncryptToBase64(plainText []byte, base64PubKey string) (string, error) { + publicBytes, err := base64.StdEncoding.DecodeString(base64PubKey) + if err != nil { + return "", err + } + cipherBytes, err := encrypt(plainText, publicBytes) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(cipherBytes), nil +} + +// DecryptByBase64 ... +func DecryptByBase64(cipherText, base64PriKey string) ([]byte, error) { + privateBytes, err := base64.StdEncoding.DecodeString(base64PriKey) + if err != nil { + return nil, err + } + cipherBytes, err := base64.StdEncoding.DecodeString(cipherText) + if err != nil { + return nil, err + } + return decrypt(cipherBytes, privateBytes) +} + +// EncryptToHex ... +func EncryptToHex(plainText []byte, hexPubKey string) (string, error) { + publicBytes, err := hex.DecodeString(hexPubKey) + if err != nil { + return "", err + } + cipherBytes, err := encrypt(plainText, publicBytes) + if err != nil { + return "", err + } + return hex.EncodeToString(cipherBytes), nil +} + +// DecryptByHex ... +func DecryptByHex(cipherText, hexPriKey string) ([]byte, error) { + privateBytes, err := hex.DecodeString(hexPriKey) + if err != nil { + return nil, err + } + cipherTextBytes, err := hex.DecodeString(cipherText) + if err != nil { + return nil, err + } + return decrypt(cipherTextBytes, privateBytes) +} + +// encrypt 加密 +func encrypt(plainText, pubKey []byte) ([]byte, error) { + pub, err := x509.ParsePKIXPublicKey(pubKey) + if err != nil { + return nil, err + } + publicKey := pub.(*rsa.PublicKey) + pubSize, plainTextSize := publicKey.Size(), len(plainText) + // EncryptPKCS1v15 encrypts the given message with RSA and the padding + // scheme from PKCS #1 v1.5. The message must be no longer than the + // length of the public modulus minus 11 bytes. + // + // The rand parameter is used as a source of entropy to ensure that + // encrypting the same message twice doesn't result in the same + // ciphertext. + // + // WARNING: use of this function to encrypt plaintexts other than + // session keys is dangerous. Use RSA OAEP in new protocols. + offSet, once := 0, pubSize-11 + buffer := bytes.Buffer{} + for offSet < plainTextSize { + endIndex := offSet + once + if endIndex > plainTextSize { + endIndex = plainTextSize + } + bytesOnce, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText[offSet:endIndex]) + if err != nil { + return nil, err + } + buffer.Write(bytesOnce) + offSet = endIndex + } + return buffer.Bytes(), nil +} + +// decrypt 解密 +func decrypt(cipherText, priKey []byte) (plainText []byte, err error) { + pri, err := x509.ParsePKCS8PrivateKey(priKey) + if err != nil { + return []byte{}, err + } + privateKey := pri.(*rsa.PrivateKey) + priSize, cipherTextSize := privateKey.Size(), len(cipherText) + var offSet = 0 + var buffer = bytes.Buffer{} + for offSet < cipherTextSize { + endIndex := offSet + priSize + if endIndex > cipherTextSize { + endIndex = cipherTextSize + } + bytesOnce, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText[offSet:endIndex]) + if err != nil { + return nil, err + } + buffer.Write(bytesOnce) + offSet = endIndex + } + return buffer.Bytes(), nil +} diff --git a/net/src/test/go/util/rsautil/rsa.go b/net/src/test/go/util/rsautil/rsa.go new file mode 100644 index 00000000..bee56d61 --- /dev/null +++ b/net/src/test/go/util/rsautil/rsa.go @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package rsautil + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "errors" +) + +var ( + RSABitsErr = errors.New("bits 1024 or 2048") + RSAPrivateKeyPemDecodeErr = errors.New("private key pem.decode error") + RSAPublicKeyPemDecodeErr = errors.New("public key pem.decode error") +) + +type RsaKey struct { + PrivateKey string // PKCS#8 + PublicKey string // PKCS#8 +} + +// GenerateRsaKey 生成公钥私钥 +func GenerateRsaKey(bits int) ([]byte, []byte, error) { + if bits != 1024 && bits != 2048 { + return nil, nil, RSABitsErr + } + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, err + } + priKey, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + pubKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return nil, nil, err + } + return priKey, pubKey, nil +} + +// GenerateRsaKeyBase64 生成公钥私钥 Base64 +func GenerateRsaKeyBase64(bits int) (RsaKey, error) { + priKey, pubKey, err := GenerateRsaKey(bits) + if err != nil { + return RsaKey{}, err + } + return RsaKey{ + PrivateKey: base64.StdEncoding.EncodeToString(priKey), + PublicKey: base64.StdEncoding.EncodeToString(pubKey), + }, nil +} + +// GenerateRsaKeyHex 生成公钥私钥 Hex +func GenerateRsaKeyHex(bits int) (RsaKey, error) { + priKey, pubKey, err := GenerateRsaKey(bits) + if err != nil { + return RsaKey{}, err + } + return RsaKey{ + PrivateKey: hex.EncodeToString(priKey), + PublicKey: hex.EncodeToString(pubKey), + }, nil +} diff --git a/net/src/test/go/util/sliceutil/slice.go b/net/src/test/go/util/sliceutil/slice.go new file mode 100644 index 00000000..5f012091 --- /dev/null +++ b/net/src/test/go/util/sliceutil/slice.go @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package sliceutil + +import ( + "strings" +) + +// InSlice 判断字符串是否存在 +func InSlice(value string, ss []string) bool { + for _, v := range ss { + if v == value { + return true + } + } + return false +} + +// IsEmpty 判断Slice是否为空 +func IsEmpty(ss []string) bool { + if len(ss) == 0 { + return true + } + return false +} + +// Implode 别名 strings.Join +func Implode(sep string, ss ...string) string { + return strings.Join(ss, sep) +} + +// Explode 别名 strings.Split +func Explode(sep string, s string) []string { + return strings.Split(s, sep) +} + +// Unique slice去重 +func Unique(ss []string) []string { + l := len(ss) + // 无法保障顺序 + m := make(map[string]bool) + for i := 0; i < l; i++ { + m[ss[i]] = true + } + + nl := len(m) + n := make([]string, nl) + + i := 0 + for v := range m { + n[i] = v + i++ + } + + return n +} + +// Merge slice合并 - 不去重 +func Merge(slice1, slice2 []string) []string { + n := make([]string, len(slice1)+len(slice2)) + copy(n, slice1) + copy(n[len(slice1):], slice2) + return n +} + +// Intersect slice交集 +func Intersect(slice1, slice2 []string) []string { + m := make(map[string]int) + n := make([]string, 0) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 1 { + n = append(n, v) + } + } + return n +} + +// Union slice并集 +func Union(slice1, slice2 []string) []string { + m := make(map[string]int) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 0 { + slice1 = append(slice1, v) + } + } + return slice1 +} + +// Difference slice差集 - 属于slice1,不属于slice2 +func Difference(slice1, slice2 []string) []string { + m := make(map[string]int) + n := make([]string, 0) + inter := Intersect(slice1, slice2) + for _, v := range inter { + m[v]++ + } + + for _, value := range slice1 { + times, _ := m[value] + if times == 0 { + n = append(n, value) + } + } + return n +} + +// IntersectUint64 slice交集 +func IntersectUint64(slice1, slice2 []uint64) []uint64 { + m := make(map[uint64]int) + n := make([]uint64, 0) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 1 { + n = append(n, v) + } + } + return n +} + +// UnionUint64 slice并集 +func UnionUint64(slice1, slice2 []uint64) []uint64 { + m := make(map[uint64]int) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 0 { + slice1 = append(slice1, v) + } + } + return slice1 +} + +// DifferenceUint64 slice差集 - 属于slice1,不属于slice2 +func DifferenceUint64(slice1, slice2 []uint64) []uint64 { + m := make(map[uint64]int) + n := make([]uint64, 0) + inter := IntersectUint64(slice1, slice2) + for _, v := range inter { + m[v]++ + } + + for _, value := range slice1 { + times, _ := m[value] + if times == 0 { + n = append(n, value) + } + } + return n +} + +// IntersectInterface slice交集 +func IntersectInterface(slice1, slice2 []interface{}) []interface{} { + m := make(map[interface{}]int) + n := make([]interface{}, 0) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 1 { + n = append(n, v) + } + } + return n +} + +// UnionInterface slice并集 +func UnionInterface(slice1, slice2 []interface{}) []interface{} { + m := make(map[interface{}]int) + for _, v := range slice1 { + m[v]++ + } + for _, v := range slice2 { + times, _ := m[v] + if times == 0 { + slice1 = append(slice1, v) + } + } + return slice1 +} + +// DifferenceInterface slice差集 - 属于slice1,不属于slice2 +func DifferenceInterface(slice1, slice2 []interface{}) []interface{} { + m := make(map[interface{}]int) + n := make([]interface{}, 0) + inter := IntersectInterface(slice1, slice2) + for _, v := range inter { + m[v]++ + } + + for _, value := range slice1 { + times, _ := m[value] + if times == 0 { + n = append(n, value) + } + } + return n +} diff --git a/net/src/test/go/util/stringutil/string.go b/net/src/test/go/util/stringutil/string.go new file mode 100644 index 00000000..59562a8b --- /dev/null +++ b/net/src/test/go/util/stringutil/string.go @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package stringutil + +import ( + "bytes" + "net/url" + "regexp" + "strings" + "unicode" +) + +// Reverse 反转字符串 +func Reverse(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} + +// UcFirst 首字母大写 +func UcFirst(s string) string { + for i, v := range s { + return string(unicode.ToUpper(v)) + s[i+1:] + } + return s +} + +// LcFirst 首字母小写 +func LcFirst(s string) string { + for i, v := range s { + return string(unicode.ToLower(v)) + s[i+1:] + } + return s +} + +// CamelToSnake camel => snake 简单实现 +func CamelToSnake(s string) string { + buffer := new(bytes.Buffer) + for i, r := range s { + if unicode.IsUpper(r) { + if i != 0 { + buffer.WriteRune('_') + } + buffer.WriteRune(unicode.ToLower(r)) + } else { + buffer.WriteRune(r) + } + } + return buffer.String() +} + +// SnakeToCamel snake => camel 简单实现 +func SnakeToCamel(s string) string { + s = strings.Replace(s, "_", " ", -1) + s = strings.Title(s) + return strings.Replace(s, " ", "", -1) +} + +func SnakeToSpinal(s string) string { + return strings.Replace(s, "_", "-", -1) +} + +func SpinalToSnake(s string) string { + return strings.Replace(s, "-", "_", -1) +} + +// UrlEncode 空格被编码为+,+被编码为%2B +func UrlEncode(s string) string { + return url.QueryEscape(s) +} + +// UrlDecode URL解码 +func UrlDecode(s string) string { + u, _ := url.QueryUnescape(s) + return u +} + +// Substr 字符串切割 +func Substr(s string, pos, length int) string { + r := []rune(s) + sl := len(r) + if pos >= sl { + return "" + } + idx := pos + length + if length == 0 || idx > sl { + idx = sl + } else if length < 0 { + idx = sl + length + } + + return string(r[pos:idx]) +} + +// InString 判断子字符串是否存在 +func InString(sub, str string) bool { + if str != "" && strings.Contains(str, sub) { + return true + } + return false +} + +// RegexpReplace ... +func RegexpReplace(src, expr, repl string) (string, error) { + reg, err := regexp.Compile(expr) + return reg.ReplaceAllString(src, repl), err +} + +// TrimSpace 去除字符串前后空格、换行等 +func TrimSpace(s string) string { + s = strings.TrimSpace(s) + s = strings.Trim(s, "\r") + s = strings.Trim(s, "\n") + s = strings.Trim(s, "\t") + return s +}