292 lines
7.7 KiB
Go
292 lines
7.7 KiB
Go
package strutil
|
|
|
|
import (
|
|
"fmt"
|
|
"hash/crc32"
|
|
"math/rand" // TODO use v2 on 1.22+
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/gookit/goutil/x/basefn"
|
|
)
|
|
|
|
// global id:
|
|
//
|
|
// https://github.com/rs/xid
|
|
// https://github.com/satori/go.uuid
|
|
var (
|
|
DefMinInt = 1000
|
|
DefMaxInt = 9999
|
|
)
|
|
|
|
// MicroTimeID generate.
|
|
// - return like: 16074145697981929446(len: 20)
|
|
//
|
|
// Conv Base:
|
|
//
|
|
// mtId := MicroTimeID() // eg: 16935349145643425047 len: 20
|
|
// b16id := Base10Conv(mtId, 16) // eg: eb067252154a9d17 len: 16
|
|
// b32id := Base10Conv(mtId, 32) // eg: em1jia8akl78n len: 13
|
|
// b36id := Base10Conv(mtId, 36) // eg: 3ko088phiuoev len: 13
|
|
// b62id := Base10Conv(mtId, 62) // eg: kb24SKgsQ9V len: 11
|
|
func MicroTimeID() string { return MTimeBaseID(10) }
|
|
|
|
// MicroTimeHexID micro time HEX ID generate.
|
|
//
|
|
// return like: 643d4cec7db9e(len: 13)
|
|
func MicroTimeHexID() string { return MTimeHexID() }
|
|
|
|
// MTimeHexID micro time HEX ID generate.
|
|
//
|
|
// return like: 643d4cec7db9e(len: 13)
|
|
func MTimeHexID() string { return MTimeBaseID(16) }
|
|
|
|
// MTimeBase36 micro time BASE36 id generate.
|
|
func MTimeBase36() string { return MTimeBaseID(36) }
|
|
|
|
// MTimeBaseID micro time BASE id generate. toBase: 2-36
|
|
//
|
|
// Examples:
|
|
// - toBase=16: 643d4cec7db9e(len: 13)
|
|
// - toBase=36: hd312z9ka2(len: 10)
|
|
func MTimeBaseID(toBase int) string {
|
|
// eg: 1763431181849557
|
|
ms := time.Now().UnixMicro()
|
|
// rand 1000 - 9999
|
|
// ri := mathutil.RandomInt(DefMinInt, DefMaxInt)
|
|
ri := 1000 + rand.Int63n(8999)
|
|
|
|
if toBase > 36 {
|
|
return BaseConvInt(uint64(ms)+uint64(ri), toBase)
|
|
}
|
|
return strconv.FormatInt(ms+ri, toBase)
|
|
}
|
|
|
|
// DatetimeNo generate. can use for order-no.
|
|
//
|
|
// - No prefix, return like: 2023041410484904074285478388(len: 28)
|
|
// - With prefix, return like: prefix2023041410484904074285478388(len: 28 + len(prefix))
|
|
func DatetimeNo(prefix string) string { return DateSN(prefix) }
|
|
|
|
// DateSN generate date serial number. PREFIX + yyyyMMddHHmmss + ext(微秒+随机数)
|
|
func DateSN(prefix string) string {
|
|
nt := time.Now()
|
|
pl := len(prefix)
|
|
bs := make([]byte, 0, 28+pl)
|
|
if pl > 0 {
|
|
bs = append(bs, prefix...)
|
|
}
|
|
|
|
// micro datetime
|
|
bs = nt.AppendFormat(bs, "20060102150405.000000")
|
|
bs[14+pl] = '0'
|
|
|
|
// host
|
|
name, err := os.Hostname()
|
|
if err != nil {
|
|
name = "default"
|
|
}
|
|
c32 := crc32.ChecksumIEEE([]byte(name)) // eg: 4006367001
|
|
bs = strconv.AppendUint(bs, uint64(c32%99), 10)
|
|
|
|
// rand 1000 - 9999
|
|
// rs := rand.New(rand.NewSource(nt.UnixNano()))
|
|
bs = strconv.AppendInt(bs, 1000+rand.Int63n(8999), 10)
|
|
|
|
return string(bs)
|
|
}
|
|
|
|
// DateSNOpt 基于时间生成唯一编号
|
|
type DateSNOpt struct {
|
|
Layout string // time layout
|
|
// RandMax int // rand max
|
|
DateLen int // 时间格式长度,后面部分将会进行进制转换 默认 8(yyyyMMdd)
|
|
ConvBase int // DateLen 之后的转换 base 2-64. default 36
|
|
// EnableSeq bool // 需要高并发生成时可以启用自增序号。默认不启用
|
|
SeqMaxVal int // 自增序号最大值,之后后自动重置
|
|
globalSeq int64 // 自增,确保同一时刻生成的编号不重复. EnableSeq=true 时启用
|
|
}
|
|
|
|
// default setting: {时间年到秒14位}
|
|
var defOpt = NewDateSNOpt()
|
|
|
|
// ConfigSNOpt config default date sn option
|
|
func ConfigSNOpt(fn func(opt *DateSNOpt)) {
|
|
fn(defOpt)
|
|
}
|
|
|
|
// NewDateSNOpt create a new DateSNOpt instance.
|
|
func NewDateSNOpt() *DateSNOpt {
|
|
return &DateSNOpt{
|
|
Layout: "20060102150405.000000",
|
|
// RandMax: 8999,
|
|
DateLen: 8,
|
|
ConvBase: 36,
|
|
// EnableSeq: true,
|
|
SeqMaxVal: 8999,
|
|
}
|
|
}
|
|
|
|
// prepare for generate
|
|
func (do *DateSNOpt) prepare() {
|
|
if do.DateLen <= 0 {
|
|
do.DateLen = 8 // default 8 for yyyyMMdd
|
|
}
|
|
if do.ConvBase <= 0 {
|
|
do.ConvBase = 36
|
|
}
|
|
if do.Layout == "" {
|
|
do.Layout = "20060102150405.000000"
|
|
}
|
|
// get sequence max value (default 9999)
|
|
if do.SeqMaxVal <= 0 {
|
|
do.SeqMaxVal = 8999
|
|
}
|
|
}
|
|
|
|
func (do *DateSNOpt) getSeqValue() int64 {
|
|
// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
|
|
// this ensures no collisions in tight loops
|
|
seq := atomic.AddInt64(&do.globalSeq, 1)
|
|
|
|
// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
|
|
if seq > int64(do.SeqMaxVal) {
|
|
// try to reset to 1, other goroutines may have already done it
|
|
atomic.CompareAndSwapInt64(&do.globalSeq, seq, 1)
|
|
seq = seq % int64(do.SeqMaxVal)
|
|
if seq == 0 {
|
|
seq = 1
|
|
}
|
|
}
|
|
return 1000 + seq
|
|
}
|
|
|
|
// GenSN generate date serial number.
|
|
func (do *DateSNOpt) GenSN(prefix string) string {
|
|
pl := len(prefix)
|
|
bs := make([]byte, 0, 22+pl)
|
|
if pl > 0 {
|
|
bs = append(bs, prefix...)
|
|
}
|
|
do.prepare()
|
|
|
|
// get time and format
|
|
nt := time.Now()
|
|
bs = nt.AppendFormat(bs, do.Layout)
|
|
|
|
// remove the dot separator if exists
|
|
dotIdx := -1
|
|
for i := pl; i < len(bs); i++ {
|
|
if bs[i] == '.' {
|
|
dotIdx = i
|
|
break
|
|
}
|
|
}
|
|
if dotIdx > 0 {
|
|
bs = append(bs[:dotIdx], bs[dotIdx+1:]...)
|
|
}
|
|
|
|
// determine date length (default 8 for yyyyMMdd)
|
|
idx := do.DateLen + pl
|
|
// high concurrency mode: sequence is the main differentiator
|
|
extBs := strconv.AppendInt(bs[idx:], do.getSeqValue(), 10)
|
|
if extBs[0] == '0' {
|
|
extBs[0] = '1'
|
|
}
|
|
extInt := SafeUint(string(extBs))
|
|
|
|
// convert extension to target base
|
|
bs = append(bs[:idx], BaseConvInt(extInt, do.ConvBase)...)
|
|
return string(bs)
|
|
}
|
|
|
|
// 确保同一时刻生成的编号不重复 max: 89999
|
|
var globalSeqSnV2 int64 = 0
|
|
|
|
func getSeqValue() int64 {
|
|
// use atomic sequence for guaranteed uniqueness (even without EnableSeq)
|
|
// this ensures no collisions in tight loops
|
|
seq := atomic.AddInt64(&globalSeqSnV2, 1)
|
|
|
|
// auto reset when seq exceeds SeqMaxVal (thread-safe using CAS)
|
|
if seq > 89999 {
|
|
// try to reset to 1, other goroutines may have already done it
|
|
atomic.CompareAndSwapInt64(&globalSeqSnV2, seq, 1)
|
|
seq = seq % 89999
|
|
if seq == 0 {
|
|
seq = 1
|
|
}
|
|
}
|
|
return seq
|
|
}
|
|
|
|
// DateSNv2 generate date serial number.
|
|
// - 2 < extBase <= 36
|
|
// - return: PREFIX + yyyyMMddHHmmss + extBase(6bit micro + 5bit random number)
|
|
//
|
|
// Example:
|
|
// - prefix=P, extBase=16, return: P2023091414361354b4490(len=22)
|
|
// - prefix=P, extBase=36, return: P202309141436131gw3jg(len=21)
|
|
func DateSNv2(prefix string, extBase ...int) string {
|
|
pl := len(prefix)
|
|
bs := make([]byte, 0, 22+pl)
|
|
if pl > 0 {
|
|
bs = append(bs, prefix...)
|
|
}
|
|
|
|
// micro datetime
|
|
nt := time.Now()
|
|
bs = nt.AppendFormat(bs, "20060102150405.000000")
|
|
|
|
// 6bit micro + 5bit rand 10000 - 99999
|
|
// ext := strconv.AppendInt(bs[16+pl:], 10000+rand.Int63n(89999), 10)
|
|
ext := strconv.AppendInt(bs[16+pl:], 10000+getSeqValue(), 10)
|
|
// prefix + yyyyMMddHHmmss + ext(convert to base)
|
|
base := basefn.FirstOr(extBase, 36)
|
|
bs = append(bs[:14+pl], strconv.FormatInt(SafeInt64(string(ext)), base)...)
|
|
|
|
return string(bs)
|
|
}
|
|
|
|
var (
|
|
// key is dateLen + extBase
|
|
dsoMap = make(map[string]*DateSNOpt)
|
|
dsoMutex sync.RWMutex
|
|
)
|
|
|
|
// DateSNv3 generate date serial number.
|
|
// - 2 < extBase <= 64
|
|
// - return: PREFIX + DATETIME(yyyyMMddHHmmss).dateLen + extBase(DATETIME.after+6bit micro + 5bit random number)
|
|
// - dateLen: 为 DATETIME(yyyyMMddHHmmss) 保留的长度,默认为 8(yyyyMMdd) 后面的给 extBase 使用
|
|
//
|
|
// Example:
|
|
// - prefix=P, dateLen=8, extBase=16, return: P202511139vs99gbifnj len: 20
|
|
// - prefix=P, dateLen=6, extBase=36, return: P2025119yn52qhefati len: 19
|
|
// - prefix=P, dateLen=6, extBase=48, return: P202511k9ksgD1fe6x len: 18
|
|
// - prefix=P, dateLen=4, extBase=62, return: P2025aZl8N0y58M7 len: 16
|
|
func DateSNv3(prefix string, dateLen int, extBase ...int) string {
|
|
baseVal := basefn.FirstOr(extBase, 36)
|
|
cacheKey := fmt.Sprintf("%d%d", dateLen, baseVal)
|
|
|
|
dsoMutex.RLock()
|
|
dso, ok := dsoMap[cacheKey]
|
|
dsoMutex.RUnlock()
|
|
|
|
if !ok {
|
|
dsoMutex.Lock()
|
|
// double check,防止在加锁期间其他 goroutine 已经创建
|
|
if dso, ok = dsoMap[cacheKey]; !ok {
|
|
dso = NewDateSNOpt()
|
|
// dso.EnableSeq = true
|
|
dso.DateLen = dateLen
|
|
dso.ConvBase = baseVal
|
|
dsoMap[cacheKey] = dso
|
|
}
|
|
dsoMutex.Unlock()
|
|
}
|
|
return dso.GenSN(prefix)
|
|
}
|