|
|
// Package utils 不依赖 joybuywork/internal 里面的东西写到这个文件
|
|
|
package utils
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
"crypto/md5"
|
|
|
"crypto/rand"
|
|
|
"joybuywork/internal/consts"
|
|
|
"encoding/hex"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"math"
|
|
|
"math/big"
|
|
|
"net"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
"github.com/golang-module/carbon/v2"
|
|
|
"github.com/shopspring/decimal"
|
|
|
)
|
|
|
|
|
|
// Ternary 三元运算
|
|
|
func Ternary[T any](cond bool, t, f T) T {
|
|
|
if cond {
|
|
|
return t
|
|
|
}
|
|
|
return f
|
|
|
}
|
|
|
|
|
|
// STernary 如果c==t 就取f 否则 取 c
|
|
|
func STernary[T consts.NormalType](c, t, f T) T {
|
|
|
if c == t {
|
|
|
return f
|
|
|
}
|
|
|
return c
|
|
|
}
|
|
|
|
|
|
// PpDbLo page perPage 获取db limit和offset
|
|
|
func PpDbLo(page, perPage int32) (offset, limit int) {
|
|
|
page = Ternary(page == 0, 1, page)
|
|
|
perPage = Ternary(perPage == 0, 20, perPage)
|
|
|
|
|
|
limit = int(perPage)
|
|
|
offset = int((page - 1) * perPage)
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// DbAllLike 全模糊查询
|
|
|
func DbAllLike(str string) string {
|
|
|
return "%" + str + "%"
|
|
|
}
|
|
|
|
|
|
// BetweenDay 获取区间日期
|
|
|
func BetweenDay(start, end carbon.Carbon, format ...string) (days []string) {
|
|
|
for {
|
|
|
if start.Gte(end) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
if len(format) == 0 {
|
|
|
days = append(days, start.ToShortDateString())
|
|
|
} else if format[0] == time.DateOnly {
|
|
|
days = append(days, start.ToDateString())
|
|
|
}
|
|
|
|
|
|
start = start.AddDay()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// BetweenHour 获取区间小时
|
|
|
func BetweenHour(start, end carbon.Carbon) (hours []int) {
|
|
|
for {
|
|
|
if start.Gte(end) {
|
|
|
return
|
|
|
}
|
|
|
hours = append(hours, start.Hour())
|
|
|
start = start.AddHour()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// PercentNoZero 百分额不要0
|
|
|
func PercentNoZero[T consts.NumberType](num, den T) string {
|
|
|
if den == 0 {
|
|
|
return "0"
|
|
|
}
|
|
|
|
|
|
return strings.TrimSuffix(strings.TrimRight(fmt.Sprintf("%.2f", Percent(num, den)), "0"), ".")
|
|
|
}
|
|
|
|
|
|
// Percent 百分额
|
|
|
func Percent[T consts.NumberType](num, den T) float64 {
|
|
|
if den == 0 {
|
|
|
return 0
|
|
|
}
|
|
|
|
|
|
return (float64(num) / float64(den)) * 100
|
|
|
}
|
|
|
|
|
|
// StrToInt64 字符串转成int64
|
|
|
func StrToInt64(v string) int64 {
|
|
|
id, _ := strconv.ParseInt(v, 10, 64)
|
|
|
return id
|
|
|
}
|
|
|
|
|
|
// StrToInt 字符串转成int
|
|
|
func StrToInt(v string) int {
|
|
|
id, _ := strconv.Atoi(v)
|
|
|
return id
|
|
|
}
|
|
|
|
|
|
// StrToFloat64 字符串转成 float64
|
|
|
func StrToFloat64(v string) decimal.Decimal {
|
|
|
val, _ := decimal.NewFromString(v)
|
|
|
return val
|
|
|
}
|
|
|
|
|
|
// Bs []byte转化成string
|
|
|
func Bs(m []byte) string {
|
|
|
return bytes.NewBuffer(m).String()
|
|
|
}
|
|
|
|
|
|
// Md5String md5字符串
|
|
|
func Md5String(v string) string {
|
|
|
return Md5([]byte(v))
|
|
|
}
|
|
|
|
|
|
// Md5Any 任意md5
|
|
|
func Md5Any(v any) string {
|
|
|
b, _ := json.Marshal(v)
|
|
|
return Md5(b)
|
|
|
}
|
|
|
|
|
|
func Md5(v []byte) string {
|
|
|
hash := md5.New()
|
|
|
hash.Write(v)
|
|
|
b := hash.Sum(nil)
|
|
|
|
|
|
return hex.EncodeToString(b)
|
|
|
}
|
|
|
|
|
|
// StrAdd 字符串相加
|
|
|
func StrAdd(nums ...string) string {
|
|
|
|
|
|
v := decimal.NewFromFloat(0)
|
|
|
for _, num := range nums {
|
|
|
d, _ := decimal.NewFromString(num)
|
|
|
v = v.Add(d)
|
|
|
}
|
|
|
|
|
|
return v.String()
|
|
|
}
|
|
|
|
|
|
// NumberStrCompareGte 第一个数字字符串大于或等于第二个数字字符串
|
|
|
func NumberStrCompareGte(first, second string) bool {
|
|
|
return NumberStrCompare(first, second, "gte")
|
|
|
}
|
|
|
|
|
|
// NumberStrCompare 第一个数字字符串比较第二个数字字符串
|
|
|
func NumberStrCompare(first, second, compare string) bool {
|
|
|
f, s := StrToFloat64(first), StrToFloat64(second)
|
|
|
|
|
|
switch compare {
|
|
|
case ">=", "gte":
|
|
|
return f.GreaterThanOrEqual(s)
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
// UserMark 获取iovd的用户hash标识
|
|
|
func UserMark(ip, os, ver, deviceModel string) string {
|
|
|
return Md5String(fmt.Sprintf("%v_%v_%v_%v", ip, os, ver, deviceModel))
|
|
|
}
|
|
|
|
|
|
// Join 用字符关联数据
|
|
|
func Join[T consts.NumberType](elems []T, sep string) (str string) {
|
|
|
if len(elems) == 0 {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
str = fmt.Sprint(elems[0])
|
|
|
|
|
|
for _, v := range elems[1:] {
|
|
|
str += fmt.Sprintf("%s%v", sep, v)
|
|
|
}
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// RandNumber 随机数字
|
|
|
func RandNumber(max int64) int64 {
|
|
|
n, _ := rand.Int(rand.Reader, big.NewInt(max))
|
|
|
|
|
|
return n.Int64()
|
|
|
}
|
|
|
|
|
|
func GetIp() string {
|
|
|
// 获取所有网络接口的信息
|
|
|
interfaces, _ := net.Interfaces()
|
|
|
|
|
|
// 遍历网络接口,查找非回环和非虚拟接口的IP地址
|
|
|
for _, iface := range interfaces {
|
|
|
if iface.Flags&net.FlagLoopback == 0 && iface.Flags&net.FlagUp != 0 {
|
|
|
addresses, err := iface.Addrs()
|
|
|
if err != nil {
|
|
|
continue
|
|
|
}
|
|
|
for _, addr := range addresses {
|
|
|
ipNet, ok := addr.(*net.IPNet)
|
|
|
if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
|
|
|
return ipNet.IP.String()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return ""
|
|
|
}
|
|
|
|
|
|
// Groups 数据分组
|
|
|
func Groups[T any](ids []T, nums int) (groups [][]T) {
|
|
|
|
|
|
l := len(ids)
|
|
|
if l <= nums {
|
|
|
groups = append(groups, ids)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
var start int
|
|
|
var end = nums
|
|
|
for {
|
|
|
groups = append(groups, ids[start:end])
|
|
|
|
|
|
start += nums
|
|
|
end += nums
|
|
|
if start >= l {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
if end > l {
|
|
|
end = l
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// SplitToInt64 字符串变成int64
|
|
|
func SplitToInt64(str string, sep string) []int64 {
|
|
|
if str == "" {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
arr := strings.Split(str, sep)
|
|
|
|
|
|
var ids = make([]int64, len(arr))
|
|
|
for i := 0; i < len(arr); i++ {
|
|
|
ids[i] = StrToInt64(arr[i])
|
|
|
}
|
|
|
|
|
|
return ids
|
|
|
}
|
|
|
|
|
|
// NotExistGetFirst 选不中就用第一个
|
|
|
func NotExistGetFirst[T consts.NormalType](selected T, options []T) (show T) {
|
|
|
if len(options) == 0 {
|
|
|
return
|
|
|
}
|
|
|
for _, v := range options {
|
|
|
if selected == v {
|
|
|
return v
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return options[0]
|
|
|
}
|
|
|
|
|
|
// SupportPlatform 支持的平台
|
|
|
func SupportPlatform(platform string) string {
|
|
|
if platform == "" {
|
|
|
return ""
|
|
|
}
|
|
|
platform = strings.ToLower(platform)
|
|
|
switch platform {
|
|
|
case consts.PlatformIOS, consts.PlatformWeb, consts.PlatformAndroid, consts.PlatformIPad:
|
|
|
return platform
|
|
|
}
|
|
|
|
|
|
return consts.PlatformOther
|
|
|
}
|
|
|
|
|
|
// PercentCutZero 百分数去掉0
|
|
|
func PercentCutZero(val float64) string {
|
|
|
return strconv.FormatFloat(val, 'f', -1, 64)
|
|
|
}
|
|
|
|
|
|
// Float64PointCutZero 保留位数去0
|
|
|
func Float64PointCutZero(val float64, prec int) string {
|
|
|
fl, _ := strconv.ParseFloat(strconv.FormatFloat(val, 'f', prec, 64), 64)
|
|
|
return strconv.FormatFloat(fl, 'f', -1, 64)
|
|
|
}
|
|
|
|
|
|
// NormalizeDecimalStrings 将数字字符串切片归一化为相同小数位数的整数
|
|
|
// 1. 找出所有字符串中的最大小数位数
|
|
|
// 2. 将所有数字乘以 10^最大小数位数,转换为整数
|
|
|
// 返回: (整数切片, 扩展倍数)
|
|
|
type decimalInfo struct {
|
|
|
integerPart string
|
|
|
decimalPart string
|
|
|
places int
|
|
|
}
|
|
|
|
|
|
func NormalizeDecimalStrings(strs []string) ([]int, int) {
|
|
|
if len(strs) == 0 {
|
|
|
return []int{}, 0
|
|
|
}
|
|
|
|
|
|
// 第一步:找出最大小数位数
|
|
|
maxDecimalPlaces := 0
|
|
|
decimalInfos := make([]decimalInfo, len(strs))
|
|
|
|
|
|
for i, s := range strs {
|
|
|
s = strings.TrimSpace(s)
|
|
|
if s == "" {
|
|
|
s = "0"
|
|
|
}
|
|
|
|
|
|
negative := false
|
|
|
if s[0] == '-' {
|
|
|
negative = true
|
|
|
s = s[1:]
|
|
|
} else if s[0] == '+' {
|
|
|
s = s[1:]
|
|
|
}
|
|
|
|
|
|
// 查找小数点
|
|
|
decimalIndex := strings.Index(s, ".")
|
|
|
|
|
|
var integerPart, decimalPart string
|
|
|
if decimalIndex == -1 {
|
|
|
integerPart = s
|
|
|
decimalPart = ""
|
|
|
decimalInfos[i].places = 0
|
|
|
} else {
|
|
|
integerPart = s[:decimalIndex]
|
|
|
decimalPart = s[decimalIndex+1:]
|
|
|
decimalInfos[i].places = len(decimalPart)
|
|
|
}
|
|
|
|
|
|
decimalInfos[i].integerPart = integerPart
|
|
|
decimalInfos[i].decimalPart = decimalPart
|
|
|
|
|
|
if decimalInfos[i].places > maxDecimalPlaces {
|
|
|
maxDecimalPlaces = decimalInfos[i].places
|
|
|
}
|
|
|
|
|
|
if negative {
|
|
|
decimalInfos[i].integerPart = "-" + decimalInfos[i].integerPart
|
|
|
}
|
|
|
}
|
|
|
|
|
|
multiplier := int(math.Pow10(maxDecimalPlaces))
|
|
|
|
|
|
result := make([]int, len(strs))
|
|
|
for i, info := range decimalInfos {
|
|
|
paddedDecimalPart := info.decimalPart
|
|
|
if len(paddedDecimalPart) < maxDecimalPlaces {
|
|
|
paddedDecimalPart = paddedDecimalPart + strings.Repeat("0", maxDecimalPlaces-len(paddedDecimalPart))
|
|
|
}
|
|
|
|
|
|
num, _ := strconv.Atoi(info.integerPart + paddedDecimalPart)
|
|
|
result[i] = num
|
|
|
}
|
|
|
|
|
|
return result, multiplier
|
|
|
}
|