first commit
This commit is contained in:
332
pkg/tools/strings.go
Normal file
332
pkg/tools/strings.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user