init: 1.0.0

This commit is contained in:
tension
2025-04-25 12:08:29 +09:00
commit c81c9bd724
1031 changed files with 76472 additions and 0 deletions
+42
View File
@@ -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
}
+14
View File
@@ -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)
}
+19
View File
@@ -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]
}
+10
View File
@@ -0,0 +1,10 @@
package tool
import (
"testing"
)
func TestGenerateCipher(t *testing.T) {
pwd := GenerateCipher("serverKey", 128)
t.Logf("pwd: %s", pwd)
}
+32
View File
@@ -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 ""
}
}
+87
View File
@@ -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
}
+51
View File
@@ -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
}
+23
View File
@@ -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
}
+34
View File
@@ -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
}
+7
View File
@@ -0,0 +1,7 @@
package tool
import "testing"
func TestEncodePassWord(t *testing.T) {
t.Logf("EncodePassWord: %v", EncodePassWord(""))
}
+11
View File
@@ -0,0 +1,11 @@
package tool
import (
"crypto/sha256"
"encoding/hex"
)
func GenerateETag(data []byte) string {
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:])
}
+10
View File
@@ -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"))
}
}
+26
View File
@@ -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
}
+45
View File
@@ -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()
}
+24
View File
@@ -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)
}
}
+45
View File
@@ -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
}
+14
View File
@@ -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]
}
+130
View File
@@ -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
}
+46
View File
@@ -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
}
}
}
}
+26
View File
@@ -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
}
+9
View File
@@ -0,0 +1,9 @@
package tool
// Tern 三目运算
func Tern[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}
+146
View File
@@ -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) // 转换为整天数
}
+19
View File
@@ -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)
}
+24
View File
@@ -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
}
+10
View File
@@ -0,0 +1,10 @@
package tool
import (
"fmt"
"time"
)
func MicrosecondsStr(elapsed time.Duration) string {
return fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)
}
+21
View File
@@ -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
}
+12
View File
@@ -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)
}