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) }