diff --git a/repository/park.go b/repository/park.go index 8aa2126..516552d 100644 --- a/repository/park.go +++ b/repository/park.go @@ -100,6 +100,8 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er defer cancel() timeNow := types.Now() + serial.StringSerialRequestChan <- 1 + code := serial.Prefix("P", <-serial.StringSerialResponseChan) createSql, createArgs, _ := pr.ds. Insert("park"). Cols( @@ -108,7 +110,7 @@ func (pr _ParkRepository) CreatePark(ownerId string, park *model.Park) (bool, er "basic_pooled", "adjust_pooled", "loss_pooled", "public_pooled", "created_at", "last_modified_at", ). Vals(goqu.Vals{ - serial.GeneratePrefixedUniqueSerialString("P"), + code, ownerId, park.Name, tools.PinyinAbbr(park.Name), park.Area, park.TenementQuantity, park.Capacity, park.Category, park.MeterType, park.Region, park.Address, park.Contact, park.Phone, park.Enabled, park.PricePolicy, park.TaxRate, @@ -347,13 +349,15 @@ func (pr _ParkRepository) CreateParkBuilding(pid, name string, floor *string) (b defer cancel() timeNow := types.Now() + serial.StringSerialRequestChan <- 1 + code := serial.Prefix("B", <-serial.StringSerialResponseChan) createSql, createArgs, _ := pr.ds. Insert("park_building"). Cols( "id", "park_id", "name", "floors", "enabled", "created_at", "last_modified_at", ). Vals(goqu.Vals{ - serial.GeneratePrefixedUniqueSerialString("B"), + code, pid, name, floor, true, timeNow, timeNow, }). Prepared(true).ToSQL() diff --git a/service/user.go b/service/user.go index 09587bf..7a18799 100644 --- a/service/user.go +++ b/service/user.go @@ -134,7 +134,8 @@ func (us _UserService) CreateUserAccount(user *model.User, detail *model.UserDet } else { prefix = "S" } - user.Id = serial.GeneratePrefixedUniqueSerialString(prefix) + serial.StringSerialRequestChan <- 1 + user.Id = serial.Prefix(prefix, <-serial.StringSerialResponseChan) detail.Id = user.Id } verifyCode := tools.RandStr(10) diff --git a/tools/serial/algorithm.go b/tools/serial/algorithm.go index ed07449..8bd08ec 100644 --- a/tools/serial/algorithm.go +++ b/tools/serial/algorithm.go @@ -2,75 +2,83 @@ package serial import ( "electricity_bill_calc/config" - "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/types" "fmt" "time" - - "github.com/rueian/rueidis" - "github.com/samber/lo" - "go.uber.org/zap" ) -var log = logger.Named("Algorithm", "Unique Serial") +var ( + log = logger.Named("Algorithm", "Unique Serial") + SerialRequestChan = make(chan int64, 500) + StringSerialRequestChan = make(chan int64, 500) + SerialResponseChan = make(chan int64, 500) + StringSerialResponseChan = make(chan string, 500) +) -// 在防止服务器时间回拨的情况下,生成一个用于生成唯一编号的精简时间戳,该时间戳的起始时间为`2022-02-22 22:22:22.000000 +0800`,时间戳精确到秒。 -func generateTimestamp() int64 { +func init() { + go func() { + var ( + lastTimestamp int64 = 0 + lastSerial int64 = 0 + ) + log.Info("唯一序列号生成服务已经启动。") + + for { + select { + case <-SerialRequestChan: + log.Info("收到生成数字型唯一序列号的请求。") + timestamp := generateTimestamp(lastTimestamp) + if timestamp != lastTimestamp { + lastSerial = 0 + } + lastSerial := lastSerial + 1 + uniqueId := generateSerial(timestamp, lastSerial) + SerialResponseChan <- uniqueId + lastTimestamp = timestamp + case <-StringSerialRequestChan: + log.Info("收到生成字符串型唯一序列号的请求。") + timestamp := generateTimestamp(lastTimestamp) + if timestamp != lastTimestamp { + lastSerial = 0 + } + lastSerial := lastSerial + 1 + uniqueId := generateStringSerial(timestamp, lastSerial) + StringSerialResponseChan <- uniqueId + lastTimestamp = timestamp + } + } + }() +} + +// 生成一个能够对抗服务器时间回拨的时间戳 +func generateTimestamp(base int64) int64 { for { timestamp := types.Timestamp() - cmds := make(rueidis.Commands, 0, 2) - cmds = append(cmds, global.Rd.B().Set().Key("LAST_TIMESTAMP").Value(fmt.Sprintf("%d", timestamp)).Nx().ExSeconds(1).Build()) - cmds = append(cmds, global.Rd.B().Get().Key("LAST_TIMESTAMP").Build()) - results := global.Rd.DoMulti(global.Ctx, cmds...) - store_res, err := lo.Last(results) - if err != nil { - log.Error("从Redis缓存中获取上一次的时间戳失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } - if stored_timestamp, err := store_res.AsInt64(); err == nil && timestamp >= stored_timestamp { + if timestamp >= base { return timestamp - } else { - log.Error("转换从Redis缓存中获取的时间戳失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue } + time.Sleep(1 * time.Second) } } -// 生成一个基于精简改进的雪花算法的数字类型唯一编号。 -func GenerateUniqueSerial() int64 { - for { - timestamp := generateTimestamp() - cmds := make(rueidis.Commands, 0, 2) - cmds = append(cmds, global.Rd.B().Set().Key("SERIAL").Value(fmt.Sprintf("%d", 0)).Nx().ExSeconds(1).Build()) - cmds = append(cmds, global.Rd.B().Incr().Key("SERIAL").Build()) - results := global.Rd.DoMulti(global.Ctx, cmds...) - store_res, err := lo.Last(results) - if err != nil { - log.Error("从Redis缓存中获取上一次的序列号失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } - if stored_serial, err := store_res.AsInt64(); err == nil { - return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (stored_serial & 0xffff_ffff) - } else { - log.Error("转换从Redis缓存中获取的序列号失败。", zap.Error(err)) - time.Sleep(1 * time.Second) - continue - } +// 生成一个唯一的数字型序列号 +func generateSerial(timestamp, serial int64) int64 { + return (timestamp << 20) | ((config.ServiceSettings.HostSerial & 0xffff) << 16) | (serial & 0xffff_ffff) +} + +// 生成一个唯一的字符串型序列号 +func generateStringSerial(timestamp, serial int64) string { + return fmt.Sprintf("%017d", generateSerial(timestamp, serial)) +} + +// 生成一个带前缀字符串的唯一字符串型序列号 +func Prefix(prefix string, serial interface{}) string { + switch serial := serial.(type) { + case int64: + return fmt.Sprintf("%s%017d", prefix, serial) + case string: + return fmt.Sprintf("%s%s", prefix, serial) } -} - -// 生成一个基于精简改进的雪花算法的字符串类型唯一编号。 -func GenerateUniqueSerialString() string { - uniqueId := GenerateUniqueSerial() - return fmt.Sprintf("%d", uniqueId) -} - -// 生成一个带有前缀的基于精简改进的雪花算法的字符串类型唯一编号。 -func GeneratePrefixedUniqueSerialString(prefix string) string { - uniqueId := GenerateUniqueSerial() - return fmt.Sprintf("%s%d", prefix, uniqueId) + return "" }