u
This commit is contained in:
1 parent
428d370874
commit
b408533fe7
15 files changed
+710
-97
No files matched your search
Whitespace-only changes.
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"base-framework/internal/app"
|
||||
"base-framework/pkg/config"
|
||||
"base-framework/pkg/logx"
|
||||
"base-framework/pkg/utils/snowflake"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -19,7 +21,14 @@ func main() {
|
||||
log.Fatalf("加载数据库配置失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 初始化日志
|
||||
logx.InitLogger()
|
||||
logx.Logger.Info("项目启动成功...", logx.Uint64("id", snowflake.NextID()))
|
||||
logx.Logger.Info("项目启动成功2...")
|
||||
|
||||
logx.Logger.Error("测试错误")
|
||||
|
||||
// 初始化路由
|
||||
r := app.InitAppRouter()
|
||||
|
||||
err = http.ListenAndServe(":"+strconv.Itoa(config.Server.Port), r)
|
||||
@@ -27,34 +36,3 @@ func main() {
|
||||
log.Fatalf("server start failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//// 连接字符串格式
|
||||
//connStr := "host=117.72.182.135 port=5432 user=postgres password=zhang520.. dbname=test sslmode=disable"
|
||||
//
|
||||
//db, err := sql.Open("postgres", connStr)
|
||||
//if err != nil {
|
||||
// log.Fatal("打开数据库失败:", err)
|
||||
//}
|
||||
//defer db.Close()
|
||||
//
|
||||
//// 设置连接池参数(可选)
|
||||
//db.SetMaxOpenConns(20)
|
||||
//db.SetMaxIdleConns(5)
|
||||
//db.SetConnMaxLifetime(0)
|
||||
//
|
||||
//// 测试连接
|
||||
//err = db.Ping()
|
||||
//if err != nil {
|
||||
// log.Fatal("数据库连接失败:", err)
|
||||
//}
|
||||
//
|
||||
//fmt.Println("数据库连接成功!")
|
||||
//
|
||||
//// 查询示例
|
||||
//var version string
|
||||
//err = db.QueryRow("SELECT version()").Scan(&version)
|
||||
//if err != nil {
|
||||
// log.Fatal(err)
|
||||
//}
|
||||
//fmt.Println("PostgreSQL version:", version)
|
||||
@@ -1,5 +1,20 @@
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
jwt:
|
||||
secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
|
||||
expiry: 168h
|
||||
|
||||
log:
|
||||
dir: "logs" # 日志根目录
|
||||
level: "debug" # 全局最低日志级别
|
||||
format: "console" # 输出格式,可选 json 或 console
|
||||
daily: true # 是否按天滚动
|
||||
maxDays: 30 # 保留天数,0表示无限
|
||||
caller: true # 是否记录调用文件行号
|
||||
levelDirs: false # 是否按级别目录
|
||||
console: true # 是否打印到控制台
|
||||
|
||||
snowflake:
|
||||
datacenterID: 0
|
||||
workerID: 0
|
||||
@@ -19,7 +19,8 @@ require (
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -45,6 +45,10 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{"level":"ERROR","ts":"2025-08-15 14:58:22","caller":"app/main.go:29","msg":"测试错误..."}
|
||||
2025-08-15 15:01:57 ERROR app/main.go:29 测试错误...
|
||||
ERROR app/main.go:29 测试错误...
|
||||
2025-08-15 15:21:51.058 ERROR app/main.go:29 测试错误...
|
||||
ERROR app/main.go:29 测试错误...
|
||||
ERROR app/main.go:29 测试错误...
|
||||
ERROR app/main.go:29 测试错误...
|
||||
2025-08-15 15:26:50 ERROR app/main.go:29 测试错误...
|
||||
2025-08-15 15:27:34 ERROR app/main.go:29 测试错误...
|
||||
2025-08-15 15:31:13.135 ERROR app/main.go:29 测试错误...
|
||||
2025-08-15 15:34:43.374 ERROR 15832 --- app/main.go:29 测试错误...
|
||||
2025-08-15 15:35:09.214 ERROR 3820 --- app/main.go:29 测试错误...
|
||||
2025-08-15 15:44:29.342 ERROR 5068 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 15:50:13.811 ERROR 10980 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 15:50:21.047 ERROR 14588 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 15:50:44.946 ERROR 4664 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 15:50:53.440 ERROR 16332 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 16:00:58.286 ERROR 5928 --- app/main.go:29 : 测试错误...
|
||||
2025-08-15 16:04:36.926 ERROR 1616 --- app/main.go:29: 测试错误...
|
||||
2025-08-15 16:15:11.023 ERROR 3956 --- app/main.go:29: 测试错误
|
||||
{"level":"ERROR","ts":"2025-08-15 16:15:30.845","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:16:36.736","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:22:15.508","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:22:27.021","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:22:46.606","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:22:54.954","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:24:12.726","caller":"app/main.go:29","msg":"测试错误"}
|
||||
{"level":"ERROR","ts":"2025-08-15 17:24:34.956","caller":"app/main.go:29","msg":"测试错误"}
|
||||
2025-08-15 17:24:58.277 ERROR 14832 app/main.go:29 测试错误
|
||||
2025-08-15 17:26:22.912 ERROR 6312 --- app/main.go:29 测试错误
|
||||
2025-08-15 17:26:33.441 ERROR 1184 --- app/main.go:29 测试错误
|
||||
2025-08-15 17:26:41.369 ERROR 7592 --- app/main.go:29 测试错误
|
||||
@@ -0,0 +1,64 @@
|
||||
{"level":"INFO","ts":"2025-08-15 14:58:22","caller":"app/main.go:26","msg":"项目启动成功...","id":346910475188961280}
|
||||
{"level":"INFO","ts":"2025-08-15 14:58:22","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
2025-08-15 15:01:57 INFO app/main.go:26 项目启动成功... {"id": 346911374758117376}
|
||||
2025-08-15 15:01:57 INFO app/main.go:27 项目启动成功2...
|
||||
INFO app/main.go:26 项目启动成功... {"id": 346914331130068992}
|
||||
INFO app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:21:51.058 INFO app/main.go:26 项目启动成功... {"id": 346916382815813632}
|
||||
2025-08-15 15:21:51.058 INFO app/main.go:27 项目启动成功2...
|
||||
INFO app/main.go:26 项目启动成功... {"id": 346917491865288704}
|
||||
INFO app/main.go:27 项目启动成功2...
|
||||
INFO app/main.go:26 项目启动成功... {"id": 346917578448306176}
|
||||
INFO app/main.go:27 项目启动成功2...
|
||||
INFO app/main.go:26 项目启动成功... {"id": 346917609146417152}
|
||||
INFO app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:26:50 INFO app/main.go:26 项目启动成功... {"id": 346917637277614080}
|
||||
2025-08-15 15:26:50 INFO app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:27:34 INFO app/main.go:26 项目启动成功... {"id": 346917823945113600}
|
||||
2025-08-15 15:27:34 INFO app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:31:13.135 INFO app/main.go:26 项目启动成功... {"id": 346918740337623040}
|
||||
2025-08-15 15:31:13.135 INFO app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:34:43.373 INFO 15832 --- app/main.go:26 项目启动成功... {"id": 346919622139707392}
|
||||
2025-08-15 15:34:43.374 INFO 15832 --- app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:35:09.214 INFO 3820 --- app/main.go:26 项目启动成功... {"id": 346919730524717056}
|
||||
2025-08-15 15:35:09.214 INFO 3820 --- app/main.go:27 项目启动成功2...
|
||||
2025-08-15 15:44:29.342 INFO 5068 --- app/main.go:26 : 项目启动成功... {"id": 346922079871827968}
|
||||
2025-08-15 15:44:29.342 INFO 5068 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 15:50:13.811 INFO 10980 --- app/main.go:26 : 项目启动成功... {"id": 346923524679532544}
|
||||
2025-08-15 15:50:13.811 INFO 10980 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 15:50:21.046 INFO 14588 --- app/main.go:26 : 项目启动成功... {"id": 346923555025321984}
|
||||
2025-08-15 15:50:21.047 INFO 14588 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 15:50:44.946 INFO 4664 --- app/main.go:26 : 项目启动成功... {"id": 346923655269187584}
|
||||
2025-08-15 15:50:44.946 INFO 4664 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 15:50:53.440 INFO 16332 --- app/main.go:26 : 项目启动成功... {"id": 346923690895605760}
|
||||
2025-08-15 15:50:53.440 INFO 16332 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 16:00:58.286 INFO 5928 --- app/main.go:26 : 项目启动成功... {"id": 346926227803602944}
|
||||
2025-08-15 16:00:58.286 INFO 5928 --- app/main.go:27 : 项目启动成功2...
|
||||
2025-08-15 16:04:36.925 INFO 1616 --- app/main.go:26: 项目启动成功... {"id": 346927144842035200}
|
||||
2025-08-15 16:04:36.926 INFO 1616 --- app/main.go:27: 项目启动成功2...
|
||||
2025-08-15 16:15:11.023 INFO 3956 --- app/main.go:26: 项目启动成功... {"id": 346929804441812992}
|
||||
2025-08-15 16:15:11.023 INFO 3956 --- app/main.go:27: 项目启动成功2...
|
||||
{"level":"INFO","ts":"2025-08-15 16:15:30.845","caller":"app/main.go:26","msg":"项目启动成功...","id":346929887581306880}
|
||||
{"level":"INFO","ts":"2025-08-15 16:15:30.845","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:16:36.735","caller":"app/main.go:26","msg":"项目启动成功...","id":346945263438397440}
|
||||
{"level":"INFO","ts":"2025-08-15 17:16:36.735","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:15.508","caller":"app/main.go:26","msg":"项目启动成功...","id":346946684351152128}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:15.508","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:27.021","caller":"app/main.go:26","msg":"项目启动成功...","id":346946732644368384}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:27.021","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:46.606","caller":"app/main.go:26","msg":"项目启动成功...","id":346946814789812224}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:46.606","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:54.953","caller":"app/main.go:26","msg":"项目启动成功...","id":346946849799667712}
|
||||
{"level":"INFO","ts":"2025-08-15 17:22:54.954","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:24:12.726","caller":"app/main.go:26","msg":"项目启动成功...","id":346947176003272704}
|
||||
{"level":"INFO","ts":"2025-08-15 17:24:12.726","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
{"level":"INFO","ts":"2025-08-15 17:24:34.956","caller":"app/main.go:26","msg":"项目启动成功...","id":346947269242650624}
|
||||
{"level":"INFO","ts":"2025-08-15 17:24:34.956","caller":"app/main.go:27","msg":"项目启动成功2..."}
|
||||
2025-08-15 17:24:58.277 INFO 14832 app/main.go:26 项目启动成功... {"id": 346947367058014208}
|
||||
2025-08-15 17:24:58.277 INFO 14832 app/main.go:27 项目启动成功2...
|
||||
2025-08-15 17:26:22.912 INFO 6312 --- app/main.go:26 项目启动成功... {"id": 346947722042933248}
|
||||
2025-08-15 17:26:22.912 INFO 6312 --- app/main.go:27 项目启动成功2...
|
||||
2025-08-15 17:26:33.441 INFO 1184 --- app/main.go:26 项目启动成功... {"id": 346947766204760064}
|
||||
2025-08-15 17:26:33.441 INFO 1184 --- app/main.go:27 项目启动成功2...
|
||||
2025-08-15 17:26:41.368 INFO 7592 --- app/main.go:26 项目启动成功... {"id": 346947799453007872}
|
||||
2025-08-15 17:26:41.369 INFO 7592 --- app/main.go:27 项目启动成功2...
|
||||
Whitespace-only changes.
@@ -1,48 +1,56 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"base-framework/pkg/types"
|
||||
"github.com/spf13/viper"
|
||||
"time"
|
||||
)
|
||||
|
||||
// server 服务器配置结构体(包内私有)
|
||||
type server struct {
|
||||
Port int
|
||||
}
|
||||
|
||||
// jwt 配置结构体(包内私有)
|
||||
type jwt struct {
|
||||
Secret string
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
// 全局导出变量,指向私有结构体实例
|
||||
// 全局导出变量,指向 types 包中的配置实例
|
||||
var (
|
||||
Server *server
|
||||
JWT *jwt
|
||||
Server *types.Server
|
||||
JWT *types.Jwt
|
||||
Log *types.Log
|
||||
Snowflake *types.Snowflake
|
||||
)
|
||||
|
||||
// InitApplicationConfig 初始化配置,传入配置文件路径
|
||||
func InitApplicationConfig(configPath string) (error, error) {
|
||||
func InitApplicationConfig(configPath string) (err error, err2 error) {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(configPath)
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return err, nil
|
||||
return err, err2
|
||||
}
|
||||
|
||||
var s server
|
||||
if err := v.UnmarshalKey("server", &s); err != nil {
|
||||
return err, nil
|
||||
// server 配置
|
||||
var server types.Server
|
||||
if err := v.UnmarshalKey("server", &server); err != nil {
|
||||
return err, err2
|
||||
}
|
||||
Server = &s
|
||||
|
||||
var j jwt
|
||||
if err := v.UnmarshalKey("jwt", &j); err != nil {
|
||||
return err, nil
|
||||
// jwt 配置
|
||||
var jwt types.Jwt
|
||||
if err := v.UnmarshalKey("jwt", &jwt); err != nil {
|
||||
return err, err2
|
||||
}
|
||||
JWT = &j
|
||||
|
||||
return nil, nil
|
||||
// 初始化日志模块
|
||||
var log types.Log
|
||||
if err := v.UnmarshalKey("log", &log); err != nil {
|
||||
return err, err2
|
||||
}
|
||||
|
||||
// 初始化日志模块
|
||||
var snowflake types.Snowflake
|
||||
if err := v.UnmarshalKey("snowflake", &snowflake); err != nil {
|
||||
return err, err2
|
||||
}
|
||||
|
||||
Server = &server
|
||||
JWT = &jwt
|
||||
Log = &log
|
||||
Snowflake = &snowflake
|
||||
|
||||
return nil, err2
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"base-framework/pkg/types"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -13,18 +14,6 @@ import (
|
||||
_ "github.com/lib/pq" // PostgreSQL 驱动,根据实际数据库替换
|
||||
)
|
||||
|
||||
// DBConfig 数据库配置结构体
|
||||
type DBConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Dbname string
|
||||
MaxOpenConns int // 最大打开连接数
|
||||
MaxIdleConns int // 最大空闲连接数
|
||||
ConnMaxLifetime time.Duration // 连接最大生命周期
|
||||
}
|
||||
|
||||
// 默认连接池参数
|
||||
const (
|
||||
defaultMaxOpenConns = 10
|
||||
@@ -33,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
dbConfigs map[string]DBConfig // 配置副本
|
||||
dbConfigs map[string]types.DBConfig // 配置副本
|
||||
dataSources = make(map[string]*sql.DB) // 连接池
|
||||
configsDSN = make(map[string]string) // key -> dsn
|
||||
mu sync.RWMutex
|
||||
@@ -83,7 +72,7 @@ func unmarshalConfigs() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
temp := make(map[string]DBConfig)
|
||||
temp := make(map[string]types.DBConfig)
|
||||
if err := v.Unmarshal(&temp); err != nil {
|
||||
return fmt.Errorf("解析数据库配置失败: %w", err)
|
||||
}
|
||||
@@ -92,11 +81,11 @@ func unmarshalConfigs() error {
|
||||
}
|
||||
|
||||
// GetDBConfigs 并发安全地返回当前数据库配置副本
|
||||
func GetDBConfigs() map[string]DBConfig {
|
||||
func GetDBConfigs() map[string]types.DBConfig {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
res := make(map[string]DBConfig, len(dbConfigs))
|
||||
res := make(map[string]types.DBConfig, len(dbConfigs))
|
||||
for k, v := range dbConfigs {
|
||||
res[k] = v
|
||||
}
|
||||
@@ -104,13 +93,13 @@ func GetDBConfigs() map[string]DBConfig {
|
||||
}
|
||||
|
||||
// buildDSN 构建 PostgreSQL DSN
|
||||
func buildDSN(cfg DBConfig) string {
|
||||
func buildDSN(cfg types.DBConfig) string {
|
||||
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Dbname)
|
||||
}
|
||||
|
||||
// ReloadDataSources 支持增量更新数据源,并验证数据库连接
|
||||
func ReloadDataSources(newConfigs map[string]DBConfig) error {
|
||||
func ReloadDataSources(newConfigs map[string]types.DBConfig) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
@@ -171,7 +160,7 @@ func ReloadDataSources(newConfigs map[string]DBConfig) error {
|
||||
}
|
||||
|
||||
// applyDBConfig 设置 sql.DB 连接池参数,并应用默认值
|
||||
func applyDBConfig(db *sql.DB, cfg DBConfig) {
|
||||
func applyDBConfig(db *sql.DB, cfg types.DBConfig) {
|
||||
maxOpen := cfg.MaxOpenConns
|
||||
if maxOpen <= 0 {
|
||||
maxOpen = defaultMaxOpenConns
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// String 类型字段
|
||||
func String(key, val string) zap.Field { return zap.String(key, val) }
|
||||
func Stringp(key string, val *string) zap.Field { return zap.Stringp(key, val) }
|
||||
func Strings(key string, val []string) zap.Field { return zap.Strings(key, val) }
|
||||
|
||||
func Bool(key string, val bool) zap.Field { return zap.Bool(key, val) }
|
||||
func Boolp(key string, val *bool) zap.Field { return zap.Boolp(key, val) }
|
||||
func Bools(key string, val []bool) zap.Field { return zap.Bools(key, val) }
|
||||
|
||||
func Int(key string, val int) zap.Field { return zap.Int(key, val) }
|
||||
func Intp(key string, val *int) zap.Field { return zap.Intp(key, val) }
|
||||
func Ints(key string, val []int) zap.Field { return zap.Ints(key, val) }
|
||||
|
||||
func Int64(key string, val int64) zap.Field { return zap.Int64(key, val) }
|
||||
func Int64p(key string, val *int64) zap.Field { return zap.Int64p(key, val) }
|
||||
func Int64s(key string, val []int64) zap.Field { return zap.Int64s(key, val) }
|
||||
|
||||
func Int32(key string, val int32) zap.Field { return zap.Int32(key, val) }
|
||||
func Int32p(key string, val *int32) zap.Field { return zap.Int32p(key, val) }
|
||||
func Int32s(key string, val []int32) zap.Field { return zap.Int32s(key, val) }
|
||||
|
||||
func Int16(key string, val int16) zap.Field { return zap.Int16(key, val) }
|
||||
func Int16p(key string, val *int16) zap.Field { return zap.Int16p(key, val) }
|
||||
func Int16s(key string, val []int16) zap.Field { return zap.Int16s(key, val) }
|
||||
|
||||
func Int8(key string, val int8) zap.Field { return zap.Int8(key, val) }
|
||||
func Int8p(key string, val *int8) zap.Field { return zap.Int8p(key, val) }
|
||||
func Int8s(key string, val []int8) zap.Field { return zap.Int8s(key, val) }
|
||||
|
||||
func Uint(key string, val uint) zap.Field { return zap.Uint(key, val) }
|
||||
func Uintp(key string, val *uint) zap.Field { return zap.Uintp(key, val) }
|
||||
func Uints(key string, val []uint) zap.Field { return zap.Uints(key, val) }
|
||||
|
||||
func Uint64(key string, val uint64) zap.Field { return zap.Uint64(key, val) }
|
||||
func Uint64p(key string, val *uint64) zap.Field { return zap.Uint64p(key, val) }
|
||||
func Uint64s(key string, val []uint64) zap.Field { return zap.Uint64s(key, val) }
|
||||
|
||||
func Uint32(key string, val uint32) zap.Field { return zap.Uint32(key, val) }
|
||||
func Uint32p(key string, val *uint32) zap.Field { return zap.Uint32p(key, val) }
|
||||
func Uint32s(key string, val []uint32) zap.Field { return zap.Uint32s(key, val) }
|
||||
|
||||
func Uint16(key string, val uint16) zap.Field { return zap.Uint16(key, val) }
|
||||
func Uint16p(key string, val *uint16) zap.Field { return zap.Uint16p(key, val) }
|
||||
func Uint16s(key string, val []uint16) zap.Field { return zap.Uint16s(key, val) }
|
||||
|
||||
func Uint8(key string, val uint8) zap.Field { return zap.Uint8(key, val) }
|
||||
func Uint8p(key string, val *uint8) zap.Field { return zap.Uint8p(key, val) }
|
||||
|
||||
func Uintptr(key string, val uintptr) zap.Field { return zap.Uintptr(key, val) }
|
||||
func Uintptrp(key string, val *uintptr) zap.Field { return zap.Uintptrp(key, val) }
|
||||
func Uintptrs(key string, val []uintptr) zap.Field { return zap.Uintptrs(key, val) }
|
||||
|
||||
func Float32(key string, val float32) zap.Field { return zap.Float32(key, val) }
|
||||
func Float32p(key string, val *float32) zap.Field { return zap.Float32p(key, val) }
|
||||
func Float32s(key string, val []float32) zap.Field { return zap.Float32s(key, val) }
|
||||
|
||||
func Float64(key string, val float64) zap.Field { return zap.Float64(key, val) }
|
||||
func Float64p(key string, val *float64) zap.Field { return zap.Float64p(key, val) }
|
||||
func Float64s(key string, val []float64) zap.Field { return zap.Float64s(key, val) }
|
||||
|
||||
func Complex64(key string, val complex64) zap.Field { return zap.Complex64(key, val) }
|
||||
func Complex64p(key string, val *complex64) zap.Field { return zap.Complex64p(key, val) }
|
||||
func Complex64s(key string, val []complex64) zap.Field { return zap.Complex64s(key, val) }
|
||||
|
||||
func Complex128(key string, val complex128) zap.Field { return zap.Complex128(key, val) }
|
||||
func Complex128p(key string, val *complex128) zap.Field { return zap.Complex128p(key, val) }
|
||||
func Complex128s(key string, val []complex128) zap.Field { return zap.Complex128s(key, val) }
|
||||
|
||||
func Binary(key string, val []byte) zap.Field { return zap.Binary(key, val) }
|
||||
|
||||
func Time(key string, val time.Time) zap.Field { return zap.Time(key, val) }
|
||||
func Timep(key string, val *time.Time) zap.Field { return zap.Timep(key, val) }
|
||||
func Times(key string, val []time.Time) zap.Field { return zap.Times(key, val) }
|
||||
|
||||
func Duration(key string, val time.Duration) zap.Field { return zap.Duration(key, val) }
|
||||
func Durationp(key string, val *time.Duration) zap.Field { return zap.Durationp(key, val) }
|
||||
func Durations(key string, val []time.Duration) zap.Field { return zap.Durations(key, val) }
|
||||
|
||||
func Any(key string, val any) zap.Field { return zap.Any(key, val) }
|
||||
@@ -0,0 +1,212 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"base-framework/pkg/config"
|
||||
"base-framework/pkg/types"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
var (
|
||||
Logger *zap.Logger
|
||||
|
||||
once sync.Once
|
||||
currentDate string
|
||||
mu sync.Mutex
|
||||
|
||||
infoFile *os.File
|
||||
warnFile *os.File
|
||||
errorFile *os.File
|
||||
|
||||
infoCore zapcore.Core
|
||||
warnCore zapcore.Core
|
||||
errorCore zapcore.Core
|
||||
)
|
||||
|
||||
const logTmFmtWithMS = "2006-01-02 15:04:05.000"
|
||||
|
||||
// 彩色日志级别
|
||||
var levelColor = map[zapcore.Level]string{
|
||||
zapcore.DebugLevel: "\033[36mDEBUG\033[0m",
|
||||
zapcore.InfoLevel: "\033[32mINFO\033[0m",
|
||||
zapcore.WarnLevel: "\033[33mWARN\033[0m",
|
||||
zapcore.ErrorLevel: "\033[31mERROR\033[0m",
|
||||
zapcore.DPanicLevel: "\033[35mDPANIC\033[0m",
|
||||
zapcore.PanicLevel: "\033[35mPANIC\033[0m",
|
||||
zapcore.FatalLevel: "\033[35mFATAL\033[0m",
|
||||
}
|
||||
|
||||
// defaultLogConfig 返回默认日志配置
|
||||
func defaultLogConfig() *types.Log {
|
||||
return &types.Log{
|
||||
Dir: "logs",
|
||||
Level: "debug",
|
||||
Format: "json",
|
||||
Daily: true,
|
||||
MaxDays: 30,
|
||||
Caller: true,
|
||||
LevelDirs: true,
|
||||
Console: true,
|
||||
}
|
||||
}
|
||||
|
||||
// InitLogger 初始化全局 Logger
|
||||
func InitLogger() {
|
||||
once.Do(func() {
|
||||
if config.Log == nil {
|
||||
config.Log = defaultLogConfig()
|
||||
}
|
||||
createDirIfNotExist(config.Log.Dir)
|
||||
switchDate(time.Now().Format("2006-01-02"))
|
||||
})
|
||||
}
|
||||
|
||||
// 切换日志文件
|
||||
func switchDate(today string) {
|
||||
currentDate = today
|
||||
|
||||
// 关闭旧文件
|
||||
if infoFile != nil {
|
||||
_ = infoFile.Close()
|
||||
}
|
||||
if warnFile != nil {
|
||||
_ = warnFile.Close()
|
||||
}
|
||||
if errorFile != nil {
|
||||
_ = errorFile.Close()
|
||||
}
|
||||
|
||||
// 文件路径
|
||||
infoPath, warnPath, errorPath := config.Log.Dir, config.Log.Dir, config.Log.Dir
|
||||
if config.Log.LevelDirs {
|
||||
infoPath = filepath.Join(config.Log.Dir, "info")
|
||||
warnPath = filepath.Join(config.Log.Dir, "warn")
|
||||
errorPath = filepath.Join(config.Log.Dir, "error")
|
||||
createDirIfNotExist(infoPath)
|
||||
createDirIfNotExist(warnPath)
|
||||
createDirIfNotExist(errorPath)
|
||||
} else {
|
||||
createDirIfNotExist(config.Log.Dir)
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
infoFile = getOrCreateFile(filepath.Join(infoPath, fmt.Sprintf("info-%s.log", today)))
|
||||
warnFile = getOrCreateFile(filepath.Join(warnPath, fmt.Sprintf("warn-%s.log", today)))
|
||||
errorFile = getOrCreateFile(filepath.Join(errorPath, fmt.Sprintf("error-%s.log", today)))
|
||||
|
||||
// 文件编码器
|
||||
var fileEncoder zapcore.Encoder
|
||||
if config.Log.Format == "console" {
|
||||
fileEncoder = newConsoleEncoder(false)
|
||||
} else {
|
||||
fileEncoder = newJSONEncoder()
|
||||
}
|
||||
|
||||
// 文件核心
|
||||
infoCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl <= zapcore.InfoLevel
|
||||
}))
|
||||
warnCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl == zapcore.WarnLevel
|
||||
}))
|
||||
errorCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.ErrorLevel
|
||||
}))
|
||||
|
||||
cores := []zapcore.Core{infoCore, warnCore, errorCore}
|
||||
|
||||
// 开启本地console输出
|
||||
if config.Log.Console {
|
||||
consoleCore := zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.DebugLevel
|
||||
}))
|
||||
cores = append(cores, consoleCore)
|
||||
}
|
||||
|
||||
Logger = zap.New(zapcore.NewTee(cores...))
|
||||
if config.Log.Caller {
|
||||
Logger = Logger.WithOptions(zap.AddCaller())
|
||||
} else {
|
||||
Logger = Logger.WithOptions(zap.AddCallerSkip(1))
|
||||
}
|
||||
|
||||
Logger = Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
|
||||
if config.Log.Daily {
|
||||
checkDateSwitch()
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func newConsoleEncoder(enableColor bool) zapcore.Encoder {
|
||||
cfg := zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
LevelKey: "level",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format(logTmFmtWithMS))
|
||||
},
|
||||
EncodeLevel: func(lvl zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
|
||||
if enableColor {
|
||||
color, ok := levelColor[lvl]
|
||||
if !ok {
|
||||
color = lvl.CapitalString()
|
||||
}
|
||||
enc.AppendString(color)
|
||||
} else {
|
||||
enc.AppendString(lvl.CapitalString())
|
||||
}
|
||||
},
|
||||
EncodeCaller: func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
|
||||
pid := os.Getpid()
|
||||
enc.AppendString(fmt.Sprintf("%d --- %s", pid, caller.TrimmedPath()))
|
||||
},
|
||||
}
|
||||
|
||||
return zapcore.NewConsoleEncoder(cfg)
|
||||
}
|
||||
|
||||
func newJSONEncoder() zapcore.Encoder {
|
||||
cfg := zap.NewProductionEncoderConfig()
|
||||
cfg.TimeKey = "ts"
|
||||
cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format(logTmFmtWithMS))
|
||||
}
|
||||
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
cfg.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
return zapcore.NewJSONEncoder(cfg)
|
||||
}
|
||||
|
||||
func checkDateSwitch() {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if today != currentDate {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if today != currentDate {
|
||||
switchDate(today)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createDirIfNotExist(dir string) {
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
}
|
||||
}
|
||||
|
||||
func getOrCreateFile(file string) *os.File {
|
||||
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -2,15 +2,29 @@ package middleware
|
||||
|
||||
import (
|
||||
"base-framework/pkg/reponse"
|
||||
"strings"
|
||||
|
||||
"base-framework/pkg/router"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 仅初始化一次的项目信息
|
||||
var (
|
||||
initOnce sync.Once
|
||||
projectBase string // 例:base-farmework
|
||||
projectAbsPrefix string // 例:/abs/path/to/base-farmework/
|
||||
projectAbsPrefixLower string // 小写版,用于不区分大小写前缀比较(Windows 安全)
|
||||
)
|
||||
|
||||
// Error 中间件:捕获 c.Errors() 并返回统一的 ErrorInfo
|
||||
func Error() router.HandlerFunc {
|
||||
// 注册时预热(只做一次)
|
||||
initOnce.Do(initProjectInfo)
|
||||
|
||||
return func(c *router.Context) {
|
||||
c.Next() // 执行后续 Handler
|
||||
c.Next()
|
||||
|
||||
errs := c.Errors()
|
||||
if len(errs) == 0 {
|
||||
@@ -25,8 +39,8 @@ func Error() router.HandlerFunc {
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
Message: lastErr.Error(),
|
||||
Stack: stack, // 完整堆栈
|
||||
Location: location, // 业务 Handler 文件行号
|
||||
Stack: stack,
|
||||
Location: location,
|
||||
}
|
||||
|
||||
reponse.Error(c).ErrorInfo(info).Send()
|
||||
@@ -34,34 +48,111 @@ func Error() router.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// extractBusinessLocation:获取 AddError 后的业务 Handler 文件行号
|
||||
// 仅在注册时执行:确定 go.mod 根目录并缓存前缀
|
||||
func initProjectInfo() {
|
||||
// 以当前源文件为起点向上找 go.mod
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
if root, ok := findGoModRoot(file); ok {
|
||||
projectBase = filepath.Base(root)
|
||||
|
||||
p := filepath.ToSlash(root)
|
||||
if !strings.HasSuffix(p, "/") {
|
||||
p += "/"
|
||||
}
|
||||
projectAbsPrefix = p
|
||||
projectAbsPrefixLower = strings.ToLower(p)
|
||||
}
|
||||
}
|
||||
|
||||
// extractBusinessLocation:获取 AddError 后的业务 Handler 文件行(仅返回 <project>/<subpath>.go:<line>)
|
||||
func extractBusinessLocation(stack string) string {
|
||||
lines := strings.Split(stack, "\n")
|
||||
foundAddError := false
|
||||
countGo := 0
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
for _, raw := range lines {
|
||||
line := strings.TrimSpace(raw)
|
||||
|
||||
if strings.Contains(line, "AddError") {
|
||||
foundAddError = true
|
||||
continue
|
||||
}
|
||||
if foundAddError && strings.Contains(line, ".go:") {
|
||||
if !foundAddError {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, ".go:") {
|
||||
countGo++
|
||||
if countGo == 2 { // 第2个 .go: 行就是业务 Handler
|
||||
// 去掉 +0x偏移
|
||||
if idx := strings.Index(line, " +"); idx != -1 {
|
||||
line = line[:idx]
|
||||
if countGo == 2 {
|
||||
// 去掉 " +0x..." 偏移
|
||||
if i := strings.Index(line, " +"); i != -1 {
|
||||
line = line[:i]
|
||||
}
|
||||
// 保留 base-farmework 开头的路径
|
||||
slash := strings.Index(line, "base-farmework")
|
||||
if slash != -1 {
|
||||
line = line[slash:]
|
||||
}
|
||||
return line
|
||||
return trimToProject(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 将绝对路径裁剪为 "<project>/<subpath>.go:<line>"
|
||||
func trimToProject(goLine string) string {
|
||||
// 统一斜杠
|
||||
line := strings.ReplaceAll(goLine, "\\", "/")
|
||||
|
||||
// 用最后一个冒号分离(避免 Windows 盘符)
|
||||
idx := strings.LastIndex(line, ":")
|
||||
if idx == -1 || idx+1 >= len(line) {
|
||||
return line
|
||||
}
|
||||
filePath, lineNo := line[:idx], line[idx+1:]
|
||||
|
||||
// 优先:前缀裁剪(O(1)),比 filepath.Rel 更快
|
||||
if projectAbsPrefix != "" {
|
||||
fp := filepath.ToSlash(filePath)
|
||||
fpl := strings.ToLower(fp)
|
||||
if strings.HasPrefix(fpl, projectAbsPrefixLower) {
|
||||
rel := fp[len(projectAbsPrefix):]
|
||||
return projectBase + "/" + rel + ":" + lineNo
|
||||
}
|
||||
}
|
||||
|
||||
// 其次:internal/cmd/pkg 作为锚点
|
||||
segs := strings.Split(filePath, "/")
|
||||
for i, s := range segs {
|
||||
if s == "internal" || s == "cmd" || s == "pkg" {
|
||||
if i > 0 {
|
||||
return strings.Join(segs[i-1:], "/") + ":" + lineNo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
// 启发式:目录名包含 '-' 或 '_',当作仓库名
|
||||
for i, s := range segs {
|
||||
if strings.ContainsAny(s, "-_") {
|
||||
return strings.Join(segs[i:], "/") + ":" + lineNo
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:末尾 4 级
|
||||
start := len(segs) - 4
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
return strings.Join(segs[start:], "/") + ":" + lineNo
|
||||
}
|
||||
|
||||
// 从给定文件路径向上查找包含 go.mod 的目录(仅注册时调用一次)
|
||||
func findGoModRoot(filePath string) (string, bool) {
|
||||
dir := filepath.Clean(filepath.Dir(filepath.FromSlash(filePath)))
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||
return dir, true
|
||||
}
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return "", false
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
// Server 服务器配置结构体(包内私有)
|
||||
type Server struct {
|
||||
Port int
|
||||
}
|
||||
|
||||
// Jwt 配置结构体(包内私有)
|
||||
type Jwt struct {
|
||||
Secret string
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Dir string // 日志根目录
|
||||
Level string // 全局最低日志级别
|
||||
Format string // 输出格式,可选 json 或 console
|
||||
Daily bool // 是否按天滚动
|
||||
MaxDays int // 保留天数,0表示无限
|
||||
Caller bool // 是否记录调用文件行号
|
||||
LevelDirs bool // 是否按级别目录
|
||||
Console bool
|
||||
}
|
||||
|
||||
// DBConfig 数据库配置结构体
|
||||
type DBConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Dbname string
|
||||
MaxOpenConns int // 最大打开连接数
|
||||
MaxIdleConns int // 最大空闲连接数
|
||||
ConnMaxLifetime time.Duration // 连接最大生命周期
|
||||
}
|
||||
|
||||
type Snowflake struct {
|
||||
DatacenterID uint64
|
||||
WorkerID uint64
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package snowflake
|
||||
|
||||
import (
|
||||
"base-framework/pkg/config"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
datacenterBits = uint64(5)
|
||||
workerBits = uint64(5)
|
||||
sequenceBits = uint64(12)
|
||||
|
||||
maxSequence = -1 ^ (-1 << sequenceBits)
|
||||
workerShift = sequenceBits
|
||||
datacenterShift = sequenceBits + workerBits
|
||||
timestampShift = sequenceBits + workerBits + datacenterBits
|
||||
|
||||
epoch = int64(1672531200000) // 2023-01-01
|
||||
)
|
||||
|
||||
type Snowflake struct {
|
||||
lastStamp uint64 // 上一次时间戳 (毫秒)
|
||||
sequence uint64 // 序列号
|
||||
dcID uint64
|
||||
workerID uint64
|
||||
}
|
||||
|
||||
var (
|
||||
sfInstance *Snowflake
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// lazy 初始化实例
|
||||
func initInstance() {
|
||||
var dcID, wID uint64
|
||||
if config.Snowflake != nil {
|
||||
dcID = config.Snowflake.DatacenterID
|
||||
wID = config.Snowflake.WorkerID
|
||||
}
|
||||
sfInstance = &Snowflake{
|
||||
dcID: dcID,
|
||||
workerID: wID,
|
||||
}
|
||||
}
|
||||
|
||||
// NextID 生成全局唯一 ID,无锁版本
|
||||
func NextID() uint64 {
|
||||
once.Do(initInstance)
|
||||
|
||||
for {
|
||||
now := uint64(time.Now().UnixNano() / 1e6)
|
||||
|
||||
last := atomic.LoadUint64(&sfInstance.lastStamp)
|
||||
seq := atomic.LoadUint64(&sfInstance.sequence)
|
||||
|
||||
if now < last {
|
||||
// 系统时钟回拨,等待
|
||||
now = waitNextMillis(last)
|
||||
}
|
||||
|
||||
if now == last {
|
||||
seq = (seq + 1) & uint64(maxSequence)
|
||||
if seq == 0 {
|
||||
now = waitNextMillis(last)
|
||||
}
|
||||
} else {
|
||||
seq = 0
|
||||
}
|
||||
|
||||
if atomic.CompareAndSwapUint64(&sfInstance.lastStamp, last, now) {
|
||||
atomic.StoreUint64(&sfInstance.sequence, seq)
|
||||
id := ((now - uint64(epoch)) << timestampShift) |
|
||||
(sfInstance.dcID << datacenterShift) |
|
||||
(sfInstance.workerID << workerShift) |
|
||||
seq
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待下一个毫秒
|
||||
func waitNextMillis(last uint64) uint64 {
|
||||
now := uint64(time.Now().UnixNano() / 1e6)
|
||||
for now <= last {
|
||||
now = uint64(time.Now().UnixNano() / 1e6)
|
||||
}
|
||||
return now
|
||||
}
|
||||
Reference in new issue
Block a user