Files
servicebase/pkg/tools/strings.go
2025-11-18 17:48:20 +08:00

333 lines
7.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}