first commit

This commit is contained in:
Yangtao
2025-11-18 17:48:20 +08:00
commit 6e56cab848
196 changed files with 65809 additions and 0 deletions

332
pkg/tools/strings.go Normal file
View File

@ -0,0 +1,332 @@
package tools
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"math/rand"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/google/uuid"
"github.com/shopspring/decimal"
)
const (
defaultSaltLen = 4
)
func Uuid() string {
u7, _ := uuid.NewV7()
return strings.ReplaceAll(u7.String(), "-", "")
}
func SafePStr(p *string) string {
if p == nil {
return ""
}
return *p
}
func SafeI32(p *int32) int32 {
if p == nil {
return 0
}
return *p
}
func StrFromInt(i int) string {
return strconv.Itoa(i)
}
func StrFromInt32(i int32) string {
return strconv.Itoa(int(i))
}
func StrFromInt64(i int64) string {
return strconv.FormatInt(i, 10)
}
func StrFromFloat32(f float32) string {
return strconv.FormatFloat(float64(f), 'f', -1, 32)
}
func StrFromFloat64(f float64) string {
return strconv.FormatFloat(f, 'f', -1, 64)
}
func MD5(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
func MD5WithSalt(str, salt string) string {
return MD5(str + salt)
}
func RandSalt() string {
return RandSaltN(defaultSaltLen)
}
func RandSaltN(n uint8) string {
if n == 0 {
return ""
}
var ans = make([]byte, n)
ans[0] = byte(rand.Intn(9)+1) + '0'
for i := byte(1); i < n; i++ {
ans[i] = byte(rand.Intn(10)) + '0'
}
return string(ans)
}
func StrToInt(str string) int {
return int(StrToFloat(str))
}
func StrToInt64(str string) int64 {
return int64(StrToFloat(str))
}
func StrToInt32(str string) int32 {
return int32(StrToFloat(str))
}
func StrToFloat(str string) float64 {
if s, err := strconv.ParseFloat(str, 64); err != nil {
return 0
} else {
return s
}
}
// GetWeekEnd 根据年份和ISO周数结束日期(周日)
func GetWeekEnd(year, week int) (endWeek time.Time) {
// 1月4日至少是该年的第一周
// 这是计算ISO周的一个参考点
t := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC)
// 找到这一天所在的周一是哪一天
// ISO周中周一是一周的第一天
weekday := t.Weekday()
if weekday == time.Sunday {
weekday = 7 // 调整周日为7以便正确计算偏移量
}
// 计算到周一的偏移量
offset := int(time.Monday - weekday)
if offset > 0 {
offset -= 7
}
t = t.AddDate(0, 0, offset)
// 计算目标周的起始日期(加上周数-1周
t = t.AddDate(0, 0, (week-1)*7)
return t.AddDate(0, 0, 6)
}
// GetWeekRange 根据年份和ISO周数获取该周的开始日期(周一)和结束日期(周日)
func GetWeekRange(year, week int) (start, end string) {
// 1月4日至少是该年的第一周
// 这是计算ISO周的一个参考点
t := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC)
// 找到这一天所在的周一是哪一天
// ISO周中周一是一周的第一天
weekday := t.Weekday()
if weekday == time.Sunday {
weekday = 7 // 调整周日为7以便正确计算偏移量
}
// 计算到周一的偏移量
offset := int(time.Monday - weekday)
if offset > 0 {
offset -= 7
}
t = t.AddDate(0, 0, offset)
// 计算目标周的起始日期(加上周数-1周
t = t.AddDate(0, 0, (week-1)*7)
// 开始日期是周一
start = t.Format("2006-01-02")
// 结束日期是周日加6天
end = t.AddDate(0, 0, 6).Format("2006-01-02")
return
}
func DaysBetween(date1, date2 time.Time) int {
// 只比较日期部分,忽略时间
y1, m1, d1 := date1.Date()
y2, m2, d2 := date2.Date()
date1 = time.Date(y1, m1, d1, 0, 0, 0, 0, date1.Location())
date2 = time.Date(y2, m2, d2, 0, 0, 0, 0, date2.Location())
// 计算时间差(纳秒)
diff := date1.Sub(date2)
if diff < 0 {
diff = -diff
}
// 转换为天数
return int(diff.Hours() / 24)
}
func StrToDate(str string) time.Time {
layout := "2006-01-02"
t, _ := time.ParseInLocation(layout, str, time.Local)
return t
}
func StrToDateTime(str string) time.Time {
layout := "2006-01-02 15:04:05"
t, _ := time.ParseInLocation(layout, str, time.Local)
return t
}
func SecMobile(src string) string {
start, end := 3, 7
bytes := []rune(src)
for start < end && start < len(bytes) {
bytes[start] = '*'
start++
}
return string(bytes)
}
const (
lenMin = 4
lenMax = 6
defaultTimeout = time.Minute * 5
)
func RandomCode() string {
code, _ := RandomCodeByLen(lenMin)
return code
}
func RandomCodeWithTimeout() (code string, validTo time.Time) {
code, _ = RandomCodeByLen(lenMin)
validTo = time.Now().Add(defaultTimeout)
return
}
func RandomCodeByLen(len int) (code string, e error) {
if len < lenMin || len > lenMax {
e = errors.New("只能生成4~6位验证码")
return
}
switch len {
case 4:
code = fmt.Sprintf("%4d", r.Intn(8999)+1000)
case 5:
code = fmt.Sprintf("%5d", r.Intn(89999)+10000)
case 6:
code = fmt.Sprintf("%6d", r.Intn(899999)+100000)
}
return
}
func MaskIDCardFlexible(id string) string {
length := utf8.RuneCountInString(id)
runes := []rune(id)
switch length {
case 18:
// 18位前6后4
return string(runes[:6]) + "**********" + string(runes[16:])
case 15:
// 15位前6后3老身份证
return string(runes[:6]) + "******" + string(runes[12:])
case 0:
return ""
default:
return "****" // 或返回原值 / 错误提示
}
}
// 判断decimal是否是0.5的倍数
func IsHalfMultiple(d decimal.Decimal) bool {
// 乘以2后检查是否为整数
multiplied := d.Mul(decimal.NewFromInt(2))
// 将整数部分转换为decimal类型后再比较
intPart := decimal.NewFromInt(multiplied.IntPart())
return multiplied.Equal(intPart)
}
// IsChineseIDCard 检查输入是否为中国大陆合法的 18 位身份证号码
func IsChineseIDCard(id string) bool {
// 1. 检查长度
if utf8.RuneCountInString(id) != 18 {
return false
}
// 2. 使用正则表达式校验格式
// 第1-17位数字第18位数字或X大小写均可
matched, err := regexp.MatchString(`^\d{17}[\dXx]$`, id)
if err != nil || !matched {
return false
}
// 3. 校验最后一位校验码
return validateCheckDigit(id)
}
// validateCheckDigit 验证身份证最后一位校验码是否正确
func validateCheckDigit(id string) bool {
// 权重因子
factor := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
// 校验码对照表
checkCode := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}
sum := 0
for i := 0; i < 17; i++ {
if id[i] < '0' || id[i] > '9' {
return false
}
sum += int(id[i]-'0') * factor[i]
}
// 计算校验码
mod := sum % 11
expected := checkCode[mod]
// 比较最后一位(忽略大小写)
lastChar := id[17]
if lastChar >= 'a' && lastChar <= 'z' {
lastChar -= 32 // 转大写
}
if lastChar >= 'A' && lastChar <= 'Z' {
lastChar += 32 // 转小写再比?不,直接比 'X'
}
return lastChar == expected || (lastChar == 'x' && expected == 'X')
}
func IsMultipleOfHalf(d decimal.Decimal) bool {
// 乘以 2
times2 := d.Mul(decimal.NewFromInt(2))
// 检查是否为整数(小数位为 0
return times2.Equal(times2.Truncate(0))
}
func NumToChinese(n int) string {
chineseDigits := []string{"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"}
return chineseDigits[n]
}
// 将切片按每组10个元素拆分
// 参数原切片、每组元素数量此处固定为10
// 返回:拆分后的二维切片
func PaginateList[T any](slice []T, groupSize int) [][]T {
var groups [][]T
length := len(slice)
// 循环截取,每次取 groupSize 个元素
for i := 0; i < length; i += groupSize {
end := i + groupSize
// 处理最后一组可能不足 groupSize 的情况
if end > length {
end = length
}
// 截取当前组并添加到结果中
groups = append(groups, slice[i:end])
}
return groups
}