init: 1.0.0
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsValidImageSize 检查base64图片是否有效且未超出大小限制
|
||||
// base64Str: base64编码的图片字符串
|
||||
// maxSizeKB: 最大允许大小(KB),int64类型
|
||||
// 返回: bool - true表示图片有效且未超限,false表示无效或超限
|
||||
func IsValidImageSize(base64Str string, maxSizeKB int64) bool {
|
||||
// 输入验证
|
||||
if base64Str == "" || maxSizeKB < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 提取纯base64数据
|
||||
data := base64Str
|
||||
if idx := strings.Index(base64Str, ","); idx != -1 {
|
||||
data = base64Str[idx+1:]
|
||||
}
|
||||
|
||||
// 快速估算大小(避免完整解码)
|
||||
// base64编码后每3字节原始数据变成4字节,解码后大小约为输入长度的3/4
|
||||
approxSizeBytes := int64(len(data)) * 3 / 4
|
||||
approxSizeKB := approxSizeBytes / 1024
|
||||
|
||||
// 如果估算大小已超限,无需解码
|
||||
if approxSizeKB > maxSizeKB {
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证base64有效性
|
||||
decoded, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 精确检查大小
|
||||
return int64(len(decoded))/1024 <= maxSizeKB
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidImageSize(t *testing.T) {
|
||||
testBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
maxSize := int64(10)
|
||||
result := IsValidImageSize(testBase64, maxSize)
|
||||
assert.Equal(t, result, true)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// GenerateCipher 根据公钥生成固定长度密文
|
||||
func GenerateCipher(serverKey string, length int) string {
|
||||
h := hmac.New(sha256.New, []byte(serverKey))
|
||||
hash := h.Sum(nil)
|
||||
hashStr := hex.EncodeToString(hash)
|
||||
// Prevent overflow
|
||||
if length > len(hashStr) {
|
||||
length = len(hashStr)
|
||||
}
|
||||
return hashStr[:length]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateCipher(t *testing.T) {
|
||||
pwd := GenerateCipher("serverKey", 128)
|
||||
t.Logf("pwd: %s", pwd)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ConvertValueToString converts the value to string
|
||||
func ConvertValueToString(value reflect.Value) string {
|
||||
switch value.Kind() {
|
||||
case reflect.String:
|
||||
return value.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(value.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return strconv.FormatUint(value.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return strconv.FormatFloat(value.Float(), 'f', -1, 64)
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(value.Bool())
|
||||
case reflect.Ptr:
|
||||
switch value.Type().Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
return fmt.Sprintf("%v", value.Elem().Bool())
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
)
|
||||
|
||||
func DeepCopy[T, K interface{}](destStruct T, srcStruct K) T {
|
||||
var dst = destStruct
|
||||
var src = srcStruct
|
||||
_ = copier.CopyWithOption(dst, src, copier.Option{
|
||||
DeepCopy: true,
|
||||
IgnoreEmpty: true,
|
||||
Converters: []copier.TypeConverter{
|
||||
{
|
||||
SrcType: time.Time{},
|
||||
DstType: constant.Int64,
|
||||
Fn: func(src interface{}) (interface{}, error) {
|
||||
s, ok := src.(time.Time)
|
||||
if !ok {
|
||||
return nil, errors.New("src type not matching")
|
||||
}
|
||||
return s.UnixMilli(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return dst
|
||||
}
|
||||
func ShallowCopy[T, K interface{}](destStruct T, srcStruct K) T {
|
||||
var dst = destStruct
|
||||
var src = srcStruct
|
||||
_ = copier.CopyWithOption(dst, src, copier.Option{
|
||||
IgnoreEmpty: true,
|
||||
Converters: []copier.TypeConverter{
|
||||
{
|
||||
SrcType: time.Time{},
|
||||
DstType: constant.Int64,
|
||||
Fn: func(src interface{}) (interface{}, error) {
|
||||
s, ok := src.(time.Time)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("src type not matching")
|
||||
}
|
||||
return s.UnixMilli(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
func Int64ToStringSlice(intSlice []int64) []string {
|
||||
strSlice := make([]string, len(intSlice))
|
||||
for i, n := range intSlice {
|
||||
strSlice[i] = strconv.FormatInt(n, 10)
|
||||
}
|
||||
return strSlice
|
||||
}
|
||||
|
||||
func CloneMapToStruct(input any, output interface{}) error {
|
||||
// 确保 output 是一个指针,并且指向一个结构体
|
||||
val := reflect.ValueOf(output)
|
||||
if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("output must be a pointer to a struct")
|
||||
}
|
||||
|
||||
// 使用 JSON 编解码将 map 转换为结构体
|
||||
data, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal input: %w", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, output)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data to struct: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
func Curve25519Genkey(StdEncoding bool, inputBase64 string) (public, private string, err error) {
|
||||
var privateKey, publicKey []byte
|
||||
var encoding *base64.Encoding
|
||||
if StdEncoding {
|
||||
encoding = base64.StdEncoding
|
||||
} else {
|
||||
encoding = base64.RawURLEncoding
|
||||
}
|
||||
|
||||
if len(inputBase64) > 0 {
|
||||
privateKey, err = encoding.DecodeString(inputBase64)
|
||||
if err != nil {
|
||||
goto out
|
||||
}
|
||||
if len(privateKey) != curve25519.ScalarSize {
|
||||
err = errors.New("Invalid length of private key.")
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
privateKey = make([]byte, curve25519.ScalarSize)
|
||||
if _, err = rand.Read(privateKey); err != nil {
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
privateKey[0] &= 248
|
||||
privateKey[31] &= 127 | 64
|
||||
|
||||
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
|
||||
goto out
|
||||
}
|
||||
public = encoding.EncodeToString(publicKey)
|
||||
private = encoding.EncodeToString(privateKey)
|
||||
out:
|
||||
return public, private, err
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MaskEmail(email string) string {
|
||||
atIndex := strings.Index(email, "@")
|
||||
if atIndex == -1 || atIndex == 0 || atIndex == len(email)-1 {
|
||||
return email
|
||||
}
|
||||
localPart := email[:atIndex]
|
||||
domainPart := email[atIndex+1:]
|
||||
|
||||
// 本地部分需要至少保留首字符和末字符
|
||||
if len(localPart) < 2 {
|
||||
return email
|
||||
}
|
||||
// 替换本地部分中间字符为星号
|
||||
maskedLocal := string(localPart[0]) + strings.Repeat("*", len(localPart)-2) + string(localPart[len(localPart)-1])
|
||||
// 返回处理后的邮箱地址
|
||||
return maskedLocal + "@" + domainPart
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anaskhan96/go-password-encoder"
|
||||
)
|
||||
|
||||
var options = &password.Options{SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New}
|
||||
|
||||
func EncodePassWord(str string) string {
|
||||
salt, encodedPwd := password.Encode(str, options)
|
||||
newPassword := fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)
|
||||
return newPassword
|
||||
}
|
||||
|
||||
func VerifyPassWord(passwd, EncodePasswd string) bool {
|
||||
info := strings.Split(EncodePasswd, "$")
|
||||
return password.Verify(passwd, info[2], info[3], options)
|
||||
}
|
||||
|
||||
func Md5Encode(str string, isUpper bool) string {
|
||||
sum := md5.Sum([]byte(str))
|
||||
res := hex.EncodeToString(sum[:])
|
||||
//转大写,strings.ToUpper(res)
|
||||
if isUpper {
|
||||
res = strings.ToUpper(res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package tool
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncodePassWord(t *testing.T) {
|
||||
t.Logf("EncodePassWord: %v", EncodePassWord(""))
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func GenerateETag(data []byte) string {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package tool
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatStringToFloat(t *testing.T) {
|
||||
var value = 1.23
|
||||
if FormatStringToFloat("1.23") != value {
|
||||
t.Errorf("Expected %f, but got %f", value, FormatStringToFloat("1.23"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func FormatFloat(num float64, decimal int) string {
|
||||
// 默认乘1
|
||||
d := float64(1)
|
||||
if decimal > 0 {
|
||||
// 10的N次方
|
||||
d = math.Pow10(decimal)
|
||||
}
|
||||
// math.trunc作用就是返回浮点数的整数部分
|
||||
// 再除回去,小数点后无效的0也就不存在了
|
||||
return strconv.FormatFloat(math.Trunc(num*d)/d, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func FormatStringToFloat(str string) float64 {
|
||||
value, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ParseRedisURI(uri string) (addr, password string, database int, err error) {
|
||||
parsedURI, err := url.Parse(uri)
|
||||
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
host := parsedURI.Hostname()
|
||||
port := parsedURI.Port()
|
||||
if port == "" {
|
||||
port = "6379"
|
||||
}
|
||||
addr = fmt.Sprintf("%s:%s", host, port)
|
||||
|
||||
// password
|
||||
if parsedURI.User != nil {
|
||||
password, _ = parsedURI.User.Password()
|
||||
}
|
||||
if len(parsedURI.Path) > 1 { // Path: "/0"
|
||||
var dbIndex int
|
||||
_, err = fmt.Sscanf(parsedURI.Path, "/%d", &dbIndex)
|
||||
if err == nil {
|
||||
database = dbIndex
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RedisPing(addr, password string, database int) error {
|
||||
rds := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: password,
|
||||
DB: database,
|
||||
})
|
||||
return rds.Ping(context.Background()).Err()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tool
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseRedisURI(t *testing.T) {
|
||||
uri := "redis://localhost:6379"
|
||||
addr, password, database, err := ParseRedisURI(uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(addr, password, database)
|
||||
}
|
||||
|
||||
func TestRedisPing(t *testing.T) {
|
||||
uri := "redis://localhost:6379"
|
||||
addr, password, database, err := ParseRedisURI(uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = RedisPing(addr, password, database)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package tool
|
||||
|
||||
func SliceIntersectInt64(slice1, slice2 []int64) []int64 {
|
||||
m := make(map[int64]int)
|
||||
nn := make([]int64, 0)
|
||||
for _, v := range slice1 {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, v := range slice2 {
|
||||
times := m[v]
|
||||
if times == 1 {
|
||||
nn = append(nn, v)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// SliceDifferenceInt64 returns the difference of two slices
|
||||
func SliceDifferenceInt64(slice1, slice2 []int64) []int64 {
|
||||
m := make(map[int64]int)
|
||||
nn := make([]int64, 0)
|
||||
inter := SliceIntersectInt64(slice1, slice2)
|
||||
for _, v := range inter {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, value := range slice1 {
|
||||
times := m[value]
|
||||
if times == 0 {
|
||||
nn = append(nn, value)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// SliceIsExistInt64 checks if a value exists in a slice
|
||||
func SliceIsExistInt64(slice []int64, value int64) bool {
|
||||
for _, item := range slice {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func GenerateShortID(privateKey string) string {
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(privateKey))
|
||||
hashValue := hash.Sum(nil)
|
||||
hashString := fmt.Sprintf("%x", hashValue)
|
||||
return hashString[:8]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
)
|
||||
|
||||
func Int64SliceToStringSlice(slice []int64) []string {
|
||||
stringSlice := make([]string, len(slice))
|
||||
for i, num := range slice {
|
||||
stringSlice[i] = strconv.Itoa(int(num))
|
||||
}
|
||||
return stringSlice
|
||||
}
|
||||
func StringSliceToInt64Slice(slice []string) []int64 {
|
||||
int64Slice := make([]int64, len(slice))
|
||||
for i, str := range slice {
|
||||
num, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
fmt.Println("Error converting string to int:", err)
|
||||
continue
|
||||
}
|
||||
int64Slice[i] = int64(num)
|
||||
}
|
||||
return int64Slice
|
||||
}
|
||||
|
||||
func StringToInt64Slice(s string) []int64 {
|
||||
|
||||
stringSlice := strings.Split(s, ",")
|
||||
var intSlice []int64
|
||||
if len(s) == 0 {
|
||||
return intSlice
|
||||
}
|
||||
for _, str := range stringSlice {
|
||||
num, err := strconv.ParseInt(strings.TrimSpace(str), 10, 64)
|
||||
if err != nil {
|
||||
logger.Error("[Tools] StringToInt64Slice",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("str", str),
|
||||
)
|
||||
continue
|
||||
}
|
||||
intSlice = append(intSlice, num)
|
||||
}
|
||||
return intSlice
|
||||
}
|
||||
|
||||
func Int64SliceToString(intSlice []int64) string {
|
||||
var strSlice []string
|
||||
for _, num := range intSlice {
|
||||
strSlice = append(strSlice, strconv.FormatInt(num, 10))
|
||||
}
|
||||
return strings.Join(strSlice, ",")
|
||||
}
|
||||
|
||||
// string slice to string
|
||||
func StringSliceToString(stringSlice []string) string {
|
||||
return strings.Join(stringSlice, ",")
|
||||
}
|
||||
|
||||
// StringMergeAndRemoveDuplicates Tool function to convert multiple comma separated strings into [] strings and deduplicate them
|
||||
func StringMergeAndRemoveDuplicates(strs ...string) []string {
|
||||
if len(strs) == 1 && strs[0] == "" {
|
||||
return []string{}
|
||||
}
|
||||
merged := make([]string, 0)
|
||||
for _, str := range strs {
|
||||
merged = append(merged, strings.Split(str, ",")...)
|
||||
}
|
||||
uniqueMap := make(map[string]bool)
|
||||
var uniqueList []string
|
||||
|
||||
for _, item := range merged {
|
||||
if !uniqueMap[item] {
|
||||
uniqueMap[item] = true
|
||||
uniqueList = append(uniqueList, item)
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueList
|
||||
}
|
||||
|
||||
func StringSliceContains(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RemoveElementBySlice[T comparable](slice []T, element T) []T {
|
||||
result := make([]T, 0, len(slice))
|
||||
for _, s := range slice {
|
||||
if s != element {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func RemoveDuplicateElements[T comparable](input ...T) []T {
|
||||
uniqueMap := make(map[T]struct{})
|
||||
var result []T
|
||||
|
||||
for _, item := range input {
|
||||
// 仅在 T 是 string 类型时跳过空字符串
|
||||
if v, ok := any(item).(string); ok && v == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := uniqueMap[item]; !exists {
|
||||
uniqueMap[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Contains[T comparable](slice []T, target T) bool {
|
||||
for _, v := range slice {
|
||||
if v == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
)
|
||||
|
||||
func SystemConfigSliceReflectToStruct(slice []*system.System, structType any) {
|
||||
v := reflect.ValueOf(structType).Elem()
|
||||
|
||||
for _, config := range slice {
|
||||
field := v.FieldByName(config.Key)
|
||||
if field.Kind() == reflect.Ptr && field.Type().Elem().Kind() == reflect.Bool {
|
||||
if config.Value == "" {
|
||||
field.Set(reflect.Zero(field.Type()))
|
||||
} else {
|
||||
boolValue, _ := strconv.ParseBool(config.Value)
|
||||
field.Set(reflect.ValueOf(&boolValue))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if field.IsValid() && field.CanSet() {
|
||||
switch config.Type {
|
||||
case "string":
|
||||
field.SetString(config.Value)
|
||||
case "bool":
|
||||
boolValue, _ := strconv.ParseBool(config.Value)
|
||||
field.SetBool(boolValue)
|
||||
case "int":
|
||||
intValue, _ := strconv.Atoi(config.Value)
|
||||
field.SetInt(int64(intValue))
|
||||
case "int64":
|
||||
intValue, _ := strconv.ParseInt(config.Value, 10, 64)
|
||||
field.SetInt(intValue)
|
||||
case "interface":
|
||||
_ = json.Unmarshal([]byte(config.Value), field.Addr().Interface())
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func RenderTemplateToString(tmpl string, data interface{}) (string, error) {
|
||||
// 解析模板
|
||||
t, err := template.New("template").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建缓冲区存储结果
|
||||
var buf bytes.Buffer
|
||||
|
||||
// 执行模板
|
||||
err = t.Execute(&buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 返回结果字符串
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package tool
|
||||
|
||||
// Tern 三目运算
|
||||
func Tern[T any](cond bool, a, b T) T {
|
||||
if cond {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
)
|
||||
|
||||
func AddTime(unit string, quantity int64, baseTime ...time.Time) time.Time {
|
||||
basic := time.Now()
|
||||
if len(baseTime) != 0 {
|
||||
basic = baseTime[0]
|
||||
}
|
||||
switch unit {
|
||||
case "Year":
|
||||
return basic.AddDate(int(quantity), 0, 0)
|
||||
case "Month":
|
||||
return basic.AddDate(0, int(quantity), 0)
|
||||
case "Day":
|
||||
return basic.AddDate(0, 0, int(quantity))
|
||||
case "Hour":
|
||||
return basic.Add(time.Hour * time.Duration(quantity))
|
||||
case "Minute":
|
||||
return basic.Add(time.Minute * time.Duration(quantity))
|
||||
case "NoLimit":
|
||||
return time.UnixMilli(0)
|
||||
default:
|
||||
logger.Error("[Tool] Unknown time unit", logger.Field("unit", unit))
|
||||
return basic
|
||||
}
|
||||
}
|
||||
|
||||
func MonthDiff(startTime, endTime time.Time) int {
|
||||
startYear, startMonth, startDay := startTime.Year(), int(startTime.Month()), startTime.Day()
|
||||
endYear, endMonth, endDay := endTime.Year(), int(endTime.Month()), endTime.Day()
|
||||
|
||||
// 计算初步月份差
|
||||
monthDiff := (endYear-startYear)*12 + (endMonth - startMonth)
|
||||
|
||||
// 检查是否要扣除不足一个完整月的部分
|
||||
if endDay <= startDay {
|
||||
monthDiff-- // 如果结束时间的日期小于开始时间的日期,则不计为完整自然月
|
||||
}
|
||||
|
||||
return monthDiff
|
||||
}
|
||||
func DaysToMonthDay(t time.Time, targetDay int) int64 {
|
||||
currentDay := t.Day()
|
||||
year, month := t.Year(), t.Month()
|
||||
|
||||
var targetDate time.Time
|
||||
|
||||
// 如果当前号数大于目标号数,计算本月目标号数
|
||||
if currentDay > targetDay {
|
||||
targetDate = getValidDate(year, month, targetDay, t.Location())
|
||||
} else { // 如果当前号数小于等于目标号数,计算上个月目标号数
|
||||
if month == time.January {
|
||||
year--
|
||||
month = time.December
|
||||
} else {
|
||||
month--
|
||||
}
|
||||
targetDate = getValidDate(year, month, targetDay, t.Location())
|
||||
}
|
||||
|
||||
// 计算时间差
|
||||
duration := t.Sub(targetDate)
|
||||
return int64(duration.Hours() / 24) // 转换为整天数
|
||||
}
|
||||
|
||||
func DaysToNextMonth(t time.Time) int64 {
|
||||
// 获取下个月的1号
|
||||
year, month := t.Year(), t.Month()
|
||||
if month == 12 {
|
||||
year++
|
||||
month = 1
|
||||
} else {
|
||||
month++
|
||||
}
|
||||
nextMonthFirstDay := time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
|
||||
|
||||
// 计算时间差
|
||||
duration := nextMonthFirstDay.Sub(t)
|
||||
return int64(duration.Hours() / 24) // 转换为整天数
|
||||
}
|
||||
|
||||
func getValidDate(year int, month time.Month, day int, loc *time.Location) time.Time {
|
||||
// 构造当月的 1 号
|
||||
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
||||
|
||||
// 获取当月的天数
|
||||
lastDayOfMonth := firstOfMonth.AddDate(0, 1, -1).Day()
|
||||
|
||||
// 如果目标号数超过当月的天数,则使用最后一天
|
||||
if day > lastDayOfMonth {
|
||||
day = lastDayOfMonth
|
||||
}
|
||||
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, loc)
|
||||
}
|
||||
|
||||
// GetLastDayOfMonth 获取指定时间所在月份的最后一天
|
||||
func GetLastDayOfMonth(t time.Time) int64 {
|
||||
// 获取当前月份的第一天
|
||||
firstDayOfMonth := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
|
||||
// 获取下个月的第一天,然后减去一天
|
||||
lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, 0).Add(-time.Hour * 24)
|
||||
return int64(lastDayOfMonth.Day())
|
||||
}
|
||||
|
||||
func GetYearDays(t time.Time, month int, day int) int64 {
|
||||
startTime := time.Date(t.Year(), time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
endTime := time.Date(t.Year(), time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
if endTime.After(t) {
|
||||
startTime = time.Date(t.Year()-1, time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
} else {
|
||||
endTime = time.Date(t.Year()+1, time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
return int64(endTime.Sub(startTime).Hours() / 24)
|
||||
}
|
||||
|
||||
func DaysToYearDay(t time.Time, month int, day int) int64 {
|
||||
targetTime := time.Date(t.Year(), time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
if targetTime.Before(t) {
|
||||
targetTime = time.Date(t.Year()+1, time.Month(month), day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
return int64(t.Sub(targetTime).Hours() / 24)
|
||||
}
|
||||
|
||||
func YearDiff(startTime, endTime time.Time) int {
|
||||
// 计算基础年份差
|
||||
yearDiff := endTime.Year() - startTime.Year()
|
||||
|
||||
// 检查结束时间是否在开始时间之前(同一年的同一天之前)
|
||||
if endTime.Month() < startTime.Month() || (endTime.Month() == startTime.Month() && endTime.Day() < startTime.Day()) {
|
||||
yearDiff-- // 不足一年则减去一年
|
||||
}
|
||||
return yearDiff
|
||||
}
|
||||
|
||||
func DayDiff(startTime, endTime time.Time) int64 {
|
||||
// 计算时间差
|
||||
duration := endTime.Sub(startTime)
|
||||
return int64(duration.Hours() / 24) // 转换为整天数
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAddTime(t *testing.T) {
|
||||
basic := time.Now()
|
||||
expAt := AddTime("Month", 1, basic)
|
||||
|
||||
t.Logf("AddTime() success, expected year %d, got year %d, full: %v", basic.Year()+1, expAt.Year(), expAt.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
func TestGetYearDays(t *testing.T) {
|
||||
days := GetYearDays(time.Now(), 2, 1)
|
||||
t.Logf("GetYearDays() success, expected 365, got %d", days)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateTradeNo() string {
|
||||
now := time.Now()
|
||||
formattedTime := now.Format("20060102150405") + strconv.Itoa(now.Nanosecond())
|
||||
numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
r := len(numeric)
|
||||
source := rand.NewSource(time.Now().UnixNano())
|
||||
random := rand.New(source)
|
||||
var code strings.Builder
|
||||
for i := 0; i < 4; i++ {
|
||||
_, _ = fmt.Fprintf(&code, "%d", numeric[random.Intn(r)])
|
||||
}
|
||||
formattedTime += code.String()
|
||||
return formattedTime
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MicrosecondsStr(elapsed time.Duration) string {
|
||||
return fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ExtractVersionNumber(versionStr string) int {
|
||||
// 正则表达式匹配括号中的数字 eg. 1.0.0(10000)
|
||||
re := regexp.MustCompile(`\((\d+)\)`)
|
||||
matches := re.FindStringSubmatch(versionStr)
|
||||
|
||||
if len(matches) > 1 {
|
||||
// 转换成数字
|
||||
num, err := strconv.Atoi(matches[1])
|
||||
if err == nil {
|
||||
return num
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
)
|
||||
|
||||
func TestExtractVersionNumber(t *testing.T) {
|
||||
versionNumber := ExtractVersionNumber(constant.Version)
|
||||
t.Log(versionNumber)
|
||||
}
|
||||
Reference in New Issue
Block a user