v2版本
This commit is contained in:
163
internal/utils/ip_location.go
Normal file
163
internal/utils/ip_location.go
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* package utils
|
||||
* 作用:IP归属地查询工具
|
||||
* 说明:使用第三方API查询IP归属地信息
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IPLocationInfo IP归属地信息
|
||||
type IPLocationInfo struct {
|
||||
Country string `json:"country"` // 国家
|
||||
Region string `json:"region"` // 省份
|
||||
City string `json:"city"` // 城市
|
||||
ISP string `json:"isp"` // 运营商
|
||||
FullLocation string `json:"full_location"` // 完整归属地
|
||||
}
|
||||
|
||||
// 缓存结构(简单内存缓存)
|
||||
var (
|
||||
ipCache = make(map[string]*IPLocationInfo)
|
||||
cacheTimeout = 24 * time.Hour
|
||||
)
|
||||
|
||||
/**
|
||||
* GetIPLocation
|
||||
* 功能:获取IP归属地信息
|
||||
* @param ip IP地址
|
||||
* @returns 归属地信息字符串
|
||||
*/
|
||||
func GetIPLocation(ip string) string {
|
||||
// 排除本地IP
|
||||
if isLocalIP(ip) {
|
||||
return "本地"
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if info, ok := ipCache[ip]; ok {
|
||||
return info.FullLocation
|
||||
}
|
||||
|
||||
// 查询IP归属地
|
||||
info := queryIPLocation(ip)
|
||||
if info != nil {
|
||||
// 构建完整归属地字符串
|
||||
parts := []string{}
|
||||
if info.Country != "" {
|
||||
parts = append(parts, info.Country)
|
||||
}
|
||||
if info.Region != "" {
|
||||
parts = append(parts, info.Region)
|
||||
}
|
||||
if info.City != "" {
|
||||
parts = append(parts, info.City)
|
||||
}
|
||||
if info.ISP != "" {
|
||||
parts = append(parts, info.ISP)
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
info.FullLocation = strings.Join(parts, " ")
|
||||
} else {
|
||||
info.FullLocation = "未知"
|
||||
}
|
||||
|
||||
// 存入缓存
|
||||
ipCache[ip] = info
|
||||
return info.FullLocation
|
||||
}
|
||||
|
||||
return "未知"
|
||||
}
|
||||
|
||||
/**
|
||||
* isLocalIP
|
||||
* 功能:判断是否为本地IP
|
||||
*/
|
||||
func isLocalIP(ip string) bool {
|
||||
// 本地回环地址
|
||||
if ip == "127.0.0.1" || ip == "localhost" || ip == "::1" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 内网地址
|
||||
if strings.HasPrefix(ip, "192.168.") ||
|
||||
strings.HasPrefix(ip, "10.") ||
|
||||
strings.HasPrefix(ip, "172.16.") ||
|
||||
strings.HasPrefix(ip, "172.17.") ||
|
||||
strings.HasPrefix(ip, "172.18.") ||
|
||||
strings.HasPrefix(ip, "172.19.") ||
|
||||
strings.HasPrefix(ip, "172.20.") ||
|
||||
strings.HasPrefix(ip, "172.21.") ||
|
||||
strings.HasPrefix(ip, "172.22.") ||
|
||||
strings.HasPrefix(ip, "172.23.") ||
|
||||
strings.HasPrefix(ip, "172.24.") ||
|
||||
strings.HasPrefix(ip, "172.25.") ||
|
||||
strings.HasPrefix(ip, "172.26.") ||
|
||||
strings.HasPrefix(ip, "172.27.") ||
|
||||
strings.HasPrefix(ip, "172.28.") ||
|
||||
strings.HasPrefix(ip, "172.29.") ||
|
||||
strings.HasPrefix(ip, "172.30.") ||
|
||||
strings.HasPrefix(ip, "172.31.") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* queryIPLocation
|
||||
* 功能:查询IP归属地(使用ip-api.com免费API)
|
||||
*/
|
||||
func queryIPLocation(ip string) *IPLocationInfo {
|
||||
// 使用ip-api.com免费API(限制:每分钟45次请求)
|
||||
url := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN&fields=status,message,country,regionName,city,isp", ip)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"regionName"`
|
||||
City string `json:"city"`
|
||||
ISP string `json:"isp"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if result.Status != "success" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &IPLocationInfo{
|
||||
Country: result.Country,
|
||||
Region: result.Region,
|
||||
City: result.City,
|
||||
ISP: result.ISP,
|
||||
}
|
||||
}
|
||||
|
||||
166
internal/utils/jwt.go
Normal file
166
internal/utils/jwt.go
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* package utils
|
||||
*
|
||||
* JWT Token生成和验证工具包
|
||||
*
|
||||
* 功能概述:
|
||||
* 1. 生成JWT Token(包含用户ID和过期时间)
|
||||
* 2. 解析JWT Token(验证签名和过期时间)
|
||||
* 3. 验证Token有效性(提取用户ID)
|
||||
*
|
||||
* 使用场景:
|
||||
* - 用户登录后生成Token
|
||||
* - API请求时验证Token
|
||||
* - 从Token中提取用户信息
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// jwtSecret JWT签名密钥(从配置文件读取)
|
||||
var jwtSecret []byte
|
||||
|
||||
/**
|
||||
* init
|
||||
*
|
||||
* 功能:初始化JWT签名密钥
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 从配置文件读取JWT密钥
|
||||
* 2. 如果配置文件中没有,使用默认密钥(仅用于开发环境)
|
||||
* 3. 将密钥转换为字节数组存储
|
||||
*
|
||||
* 注意:生产环境必须使用配置文件中的强随机密钥
|
||||
*/
|
||||
func init() {
|
||||
secret := viper.GetString("jwt.secret")
|
||||
if secret == "" {
|
||||
secret = "xk-websocket-secret-key-2025" // 默认密钥,生产环境应使用配置
|
||||
}
|
||||
jwtSecret = []byte(secret)
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims
|
||||
*
|
||||
* JWT Token的载荷结构
|
||||
*
|
||||
* 字段说明:
|
||||
* - UserID: 用户ID(业务数据)
|
||||
* - RegisteredClaims: JWT标准声明(过期时间、签发时间等)
|
||||
*/
|
||||
type Claims struct {
|
||||
UserID string `json:"user_id"` // 用户ID
|
||||
jwt.RegisteredClaims // JWT标准声明
|
||||
}
|
||||
|
||||
/**
|
||||
* GenerateToken
|
||||
*
|
||||
* 功能:生成JWT Token
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 设置Token过期时间(默认7天)
|
||||
* 2. 创建Claims对象,包含用户ID和标准声明
|
||||
* 3. 使用HS256算法签名Token
|
||||
* 4. 返回Token字符串
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @returns token字符串和错误
|
||||
*/
|
||||
func GenerateToken(userID string) (string, error) {
|
||||
// 步骤1: 设置Token过期时间(7天后过期)
|
||||
expirationTime := time.Now().Add(7 * 24 * time.Hour)
|
||||
|
||||
// 步骤2: 创建Claims对象,包含用户ID和标准声明
|
||||
claims := &Claims{
|
||||
UserID: userID, // 业务数据:用户ID
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expirationTime), // 过期时间
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
|
||||
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间(立即生效)
|
||||
},
|
||||
}
|
||||
|
||||
// 步骤3: 使用HS256算法创建Token并签名
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString(jwtSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 步骤4: 返回Token字符串
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* ParseToken
|
||||
*
|
||||
* 功能:解析JWT Token
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 创建空的Claims对象
|
||||
* 2. 使用密钥解析Token并验证签名
|
||||
* 3. 检查Token是否有效(签名正确、未过期)
|
||||
* 4. 返回Claims对象
|
||||
*
|
||||
* @param tokenString token字符串
|
||||
* @returns Claims和错误
|
||||
*/
|
||||
func ParseToken(tokenString string) (*Claims, error) {
|
||||
// 步骤1: 创建空的Claims对象
|
||||
claims := &Claims{}
|
||||
|
||||
// 步骤2: 解析Token并验证签名
|
||||
// 使用密钥验证Token的签名是否有效
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwtSecret, nil // 返回签名密钥
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err // 解析失败(签名错误、格式错误等)
|
||||
}
|
||||
|
||||
// 步骤3: 检查Token是否有效(签名正确、未过期)
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
// 步骤4: 返回解析后的Claims对象
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* ValidateToken
|
||||
*
|
||||
* 功能:验证Token有效性并提取用户ID
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 调用ParseToken解析Token
|
||||
* 2. 如果解析成功,从Claims中提取用户ID
|
||||
* 3. 返回用户ID
|
||||
*
|
||||
* 使用场景:
|
||||
* - 中间件中验证Token
|
||||
* - API处理器中获取当前用户ID
|
||||
*
|
||||
* @param tokenString token字符串
|
||||
* @returns 用户ID和错误
|
||||
*/
|
||||
func ValidateToken(tokenString string) (string, error) {
|
||||
// 步骤1: 解析Token
|
||||
claims, err := ParseToken(tokenString)
|
||||
if err != nil {
|
||||
return "", err // Token无效或已过期
|
||||
}
|
||||
|
||||
// 步骤2: 从Claims中提取用户ID
|
||||
return claims.UserID, nil
|
||||
}
|
||||
|
||||
82
internal/utils/password.go
Normal file
82
internal/utils/password.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* package utils
|
||||
*
|
||||
* 密码加密和验证工具包
|
||||
*
|
||||
* 功能概述:
|
||||
* 1. 使用bcrypt算法加密密码(单向哈希,不可逆)
|
||||
* 2. 验证明文密码与加密密码是否匹配
|
||||
*
|
||||
* 安全特性:
|
||||
* - 使用bcrypt算法,自动加盐
|
||||
* - 计算成本可调,防止暴力破解
|
||||
* - 相同密码每次加密结果不同(因为盐值随机)
|
||||
*/
|
||||
package utils
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
/**
|
||||
* HashPassword
|
||||
*
|
||||
* 功能:使用bcrypt算法加密密码
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 将明文密码转换为字节数组
|
||||
* 2. 使用bcrypt算法生成哈希值(自动加盐)
|
||||
* 3. 将哈希值转换为字符串返回
|
||||
*
|
||||
* 特点:
|
||||
* - 每次加密结果不同(因为盐值随机)
|
||||
* - 使用默认计算成本(10轮)
|
||||
* - 单向加密,不可逆
|
||||
*
|
||||
* 使用场景:
|
||||
* - 用户注册时加密密码
|
||||
* - 用户修改密码时加密新密码
|
||||
*
|
||||
* @param password 明文密码
|
||||
* @returns 加密后的密码哈希字符串和错误
|
||||
*/
|
||||
func HashPassword(password string) (string, error) {
|
||||
// 步骤1-2: 使用bcrypt算法生成密码哈希
|
||||
// bcrypt.DefaultCost = 10,表示进行2^10=1024轮哈希计算
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 步骤3: 将哈希值转换为字符串返回
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckPassword
|
||||
*
|
||||
* 功能:验证明文密码与加密密码是否匹配
|
||||
*
|
||||
* 步骤:
|
||||
* 1. 从加密密码中提取盐值
|
||||
* 2. 使用相同的盐值对明文密码进行哈希
|
||||
* 3. 比较两个哈希值是否相同
|
||||
*
|
||||
* 特点:
|
||||
* - 即使密码相同,每次加密的哈希值也不同
|
||||
* - 但可以通过CompareHashAndPassword正确验证
|
||||
* - 验证过程是安全的,不会泄露密码信息
|
||||
*
|
||||
* 使用场景:
|
||||
* - 用户登录时验证密码
|
||||
* - 修改密码时验证旧密码
|
||||
*
|
||||
* @param password 明文密码
|
||||
* @param hash 加密后的密码哈希
|
||||
* @returns 是否匹配(true=匹配,false=不匹配)
|
||||
*/
|
||||
func CheckPassword(password, hash string) bool {
|
||||
// 步骤1-3: 比较明文密码的哈希值与存储的哈希值
|
||||
// 如果匹配,err为nil,返回true;否则返回false
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
172
internal/utils/response.go
Normal file
172
internal/utils/response.go
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* package utils
|
||||
* 作用:提供统一的API响应工具函数
|
||||
* 说明:所有响应统一返回HTTP 200状态码,错误通过code字段标识
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"xk-websocket-v2/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// 业务状态码常量
|
||||
const (
|
||||
CodeSuccess = 0 // 成功
|
||||
CodeBadRequest = 400 // 参数错误
|
||||
CodeUnauthorized = 401 // 未认证
|
||||
CodeForbidden = 403 // 无权限
|
||||
CodeNotFound = 404 // 资源不存在
|
||||
CodeInternalError = 500 // 服务器错误
|
||||
)
|
||||
|
||||
// 响应类型常量
|
||||
const (
|
||||
TypeSuccess = "success"
|
||||
TypeError = "error"
|
||||
)
|
||||
|
||||
/**
|
||||
* getECS
|
||||
* 作用:获取服务器标识
|
||||
*/
|
||||
func getECS() string {
|
||||
ecs := viper.GetString("server.ecs")
|
||||
if ecs == "" {
|
||||
return "localhost"
|
||||
}
|
||||
return ecs
|
||||
}
|
||||
|
||||
/**
|
||||
* formatDuration
|
||||
* 作用:格式化响应时间为 "XX ms" 格式
|
||||
*/
|
||||
func formatDuration(d time.Duration) string {
|
||||
ms := d.Milliseconds()
|
||||
return strconv.FormatInt(ms, 10) + " ms"
|
||||
}
|
||||
|
||||
/**
|
||||
* Response
|
||||
* 作用:统一的响应函数,所有响应都通过此函数返回
|
||||
* 说明:统一返回HTTP 200状态码
|
||||
*/
|
||||
func Response(c *gin.Context, code int, message string, result interface{}, responseType string) {
|
||||
// 从Context获取请求开始时间
|
||||
startTime, exists := c.Get("request_start_time")
|
||||
var duration time.Duration
|
||||
if exists {
|
||||
duration = time.Since(startTime.(time.Time))
|
||||
} else {
|
||||
duration = 0
|
||||
}
|
||||
|
||||
response := model.ApiResponse{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Result: result,
|
||||
Type: responseType,
|
||||
InterfaceInfo: model.InterfaceInfo{
|
||||
ResultTime: formatDuration(duration),
|
||||
Ecs: getECS(),
|
||||
},
|
||||
}
|
||||
|
||||
// 统一返回HTTP 200状态码
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Success
|
||||
* 作用:成功响应(无数据)
|
||||
*/
|
||||
func Success(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "操作成功"
|
||||
}
|
||||
Response(c, CodeSuccess, message, nil, TypeSuccess)
|
||||
}
|
||||
|
||||
/**
|
||||
* SuccessWithData
|
||||
* 作用:成功响应(带数据)
|
||||
*/
|
||||
func SuccessWithData(c *gin.Context, data interface{}, message string) {
|
||||
if message == "" {
|
||||
message = "获取成功"
|
||||
}
|
||||
Response(c, CodeSuccess, message, data, TypeSuccess)
|
||||
}
|
||||
|
||||
/**
|
||||
* Error
|
||||
* 作用:错误响应(自定义状态码和消息)
|
||||
*/
|
||||
func Error(c *gin.Context, code int, message string) {
|
||||
if message == "" {
|
||||
message = "操作失败"
|
||||
}
|
||||
Response(c, code, message, nil, TypeError)
|
||||
}
|
||||
|
||||
/**
|
||||
* BadRequest
|
||||
* 作用:参数错误响应(code=400)
|
||||
*/
|
||||
func BadRequest(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "参数错误"
|
||||
}
|
||||
Response(c, CodeBadRequest, message, nil, TypeError)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unauthorized
|
||||
* 作用:未认证响应(code=401)
|
||||
*/
|
||||
func Unauthorized(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "未认证"
|
||||
}
|
||||
Response(c, CodeUnauthorized, message, nil, TypeError)
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbidden
|
||||
* 作用:无权限响应(code=403)
|
||||
*/
|
||||
func Forbidden(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "无权限"
|
||||
}
|
||||
Response(c, CodeForbidden, message, nil, TypeError)
|
||||
}
|
||||
|
||||
/**
|
||||
* NotFound
|
||||
* 作用:资源不存在响应(code=404)
|
||||
*/
|
||||
func NotFound(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "资源不存在"
|
||||
}
|
||||
Response(c, CodeNotFound, message, nil, TypeError)
|
||||
}
|
||||
|
||||
/**
|
||||
* InternalError
|
||||
* 作用:服务器错误响应(code=500)
|
||||
*/
|
||||
func InternalError(c *gin.Context, message string) {
|
||||
if message == "" {
|
||||
message = "服务器错误"
|
||||
}
|
||||
Response(c, CodeInternalError, message, nil, TypeError)
|
||||
}
|
||||
|
||||
182
internal/utils/snowflake.go
Normal file
182
internal/utils/snowflake.go
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* package utils
|
||||
* 作用:雪花ID生成器,生成全局唯一的ID
|
||||
* 说明:使用Twitter的雪花算法,生成64位整数ID
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 时间戳占用位数(41位,可以使用69年)
|
||||
timestampBits = 41
|
||||
// 数据中心ID占用位数(5位,最多32个数据中心)
|
||||
datacenterIDBits = 5
|
||||
// 机器ID占用位数(5位,每个数据中心最多32台机器)
|
||||
machineIDBits = 5
|
||||
// 序列号占用位数(12位,每毫秒最多4096个ID)
|
||||
sequenceBits = 12
|
||||
|
||||
// 最大值
|
||||
maxDatacenterID = -1 ^ (-1 << datacenterIDBits)
|
||||
maxMachineID = -1 ^ (-1 << machineIDBits)
|
||||
maxSequence = -1 ^ (-1 << sequenceBits)
|
||||
|
||||
// 位移
|
||||
machineIDShift = sequenceBits
|
||||
datacenterIDShift = sequenceBits + machineIDBits
|
||||
timestampShift = sequenceBits + machineIDBits + datacenterIDBits
|
||||
|
||||
// 起始时间戳(2024-01-01 00:00:00)
|
||||
epoch int64 = 1704067200000
|
||||
)
|
||||
|
||||
// Snowflake 雪花ID生成器
|
||||
type Snowflake struct {
|
||||
mutex sync.Mutex
|
||||
datacenterID int64
|
||||
machineID int64
|
||||
sequence int64
|
||||
lastStamp int64
|
||||
}
|
||||
|
||||
var (
|
||||
// 全局雪花ID生成器实例
|
||||
globalSnowflake *Snowflake
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
/**
|
||||
* InitSnowflake
|
||||
* 功能:初始化全局雪花ID生成器
|
||||
* @param datacenterID 数据中心ID(0-31)
|
||||
* @param machineID 机器ID(0-31)
|
||||
*/
|
||||
func InitSnowflake(datacenterID, machineID int64) error {
|
||||
if datacenterID < 0 || datacenterID > maxDatacenterID {
|
||||
return errors.New("datacenter ID must be between 0 and 31")
|
||||
}
|
||||
if machineID < 0 || machineID > maxMachineID {
|
||||
return errors.New("machine ID must be between 0 and 31")
|
||||
}
|
||||
|
||||
once.Do(func() {
|
||||
globalSnowflake = &Snowflake{
|
||||
datacenterID: datacenterID,
|
||||
machineID: machineID,
|
||||
sequence: 0,
|
||||
lastStamp: -1,
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
* NextID
|
||||
* 功能:生成下一个ID
|
||||
* @returns 64位整数ID
|
||||
*/
|
||||
func NextID() (int64, error) {
|
||||
if globalSnowflake == nil {
|
||||
// 默认使用datacenterID=1, machineID=1
|
||||
if err := InitSnowflake(1, 1); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return globalSnowflake.nextID()
|
||||
}
|
||||
|
||||
/**
|
||||
* nextID
|
||||
* 功能:生成下一个ID(内部方法)
|
||||
*/
|
||||
func (s *Snowflake) nextID() (int64, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 如果当前时间小于上次时间,说明时钟回拨
|
||||
if now < s.lastStamp {
|
||||
return 0, errors.New("clock moved backwards")
|
||||
}
|
||||
|
||||
// 如果是同一毫秒内
|
||||
if now == s.lastStamp {
|
||||
s.sequence = (s.sequence + 1) & maxSequence
|
||||
// 序列号溢出,等待下一毫秒
|
||||
if s.sequence == 0 {
|
||||
now = s.waitNextMillis(s.lastStamp)
|
||||
}
|
||||
} else {
|
||||
// 新的毫秒,序列号重置
|
||||
s.sequence = 0
|
||||
}
|
||||
|
||||
s.lastStamp = now
|
||||
|
||||
// 生成ID
|
||||
id := ((now - epoch) << timestampShift) |
|
||||
(s.datacenterID << datacenterIDShift) |
|
||||
(s.machineID << machineIDShift) |
|
||||
s.sequence
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* waitNextMillis
|
||||
* 功能:等待下一毫秒
|
||||
*/
|
||||
func (s *Snowflake) waitNextMillis(lastStamp int64) int64 {
|
||||
now := time.Now().UnixMilli()
|
||||
for now <= lastStamp {
|
||||
now = time.Now().UnixMilli()
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
/**
|
||||
* NextIDString
|
||||
* 功能:生成下一个ID(字符串格式)
|
||||
*/
|
||||
func NextIDString() (string, error) {
|
||||
id, err := NextID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return int64ToString(id), nil
|
||||
}
|
||||
|
||||
/**
|
||||
* int64ToString
|
||||
* 功能:将int64转换为字符串
|
||||
*/
|
||||
func int64ToString(id int64) string {
|
||||
if id == 0 {
|
||||
return "0"
|
||||
}
|
||||
negative := id < 0
|
||||
if negative {
|
||||
id = -id
|
||||
}
|
||||
|
||||
var result []byte
|
||||
for id > 0 {
|
||||
result = append([]byte{byte('0' + id%10)}, result...)
|
||||
id /= 10
|
||||
}
|
||||
|
||||
if negative {
|
||||
result = append([]byte{'-'}, result...)
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user