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 (
|
import (
|
||||||
"base-framework/internal/app"
|
"base-framework/internal/app"
|
||||||
"base-framework/pkg/config"
|
"base-framework/pkg/config"
|
||||||
|
"base-framework/pkg/logx"
|
||||||
|
"base-framework/pkg/utils/snowflake"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -19,7 +21,14 @@ func main() {
|
|||||||
log.Fatalf("加载数据库配置失败: %v", err)
|
log.Fatalf("加载数据库配置失败: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 初始化日志
|
||||||
|
logx.InitLogger()
|
||||||
|
logx.Logger.Info("项目启动成功...", logx.Uint64("id", snowflake.NextID()))
|
||||||
|
logx.Logger.Info("项目启动成功2...")
|
||||||
|
|
||||||
|
logx.Logger.Error("测试错误")
|
||||||
|
|
||||||
|
// 初始化路由
|
||||||
r := app.InitAppRouter()
|
r := app.InitAppRouter()
|
||||||
|
|
||||||
err = http.ListenAndServe(":"+strconv.Itoa(config.Server.Port), r)
|
err = http.ListenAndServe(":"+strconv.Itoa(config.Server.Port), r)
|
||||||
@@ -27,34 +36,3 @@ func main() {
|
|||||||
log.Fatalf("server start failed: %v", err)
|
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:
|
server:
|
||||||
port: 8082
|
port: 8082
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
|
secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
|
||||||
expiry: 168h
|
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/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.9.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/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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/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 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
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 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"base-framework/pkg/types"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// server 服务器配置结构体(包内私有)
|
// 全局导出变量,指向 types 包中的配置实例
|
||||||
type server struct {
|
|
||||||
Port int
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwt 配置结构体(包内私有)
|
|
||||||
type jwt struct {
|
|
||||||
Secret string
|
|
||||||
Expiry time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局导出变量,指向私有结构体实例
|
|
||||||
var (
|
var (
|
||||||
Server *server
|
Server *types.Server
|
||||||
JWT *jwt
|
JWT *types.Jwt
|
||||||
|
Log *types.Log
|
||||||
|
Snowflake *types.Snowflake
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitApplicationConfig 初始化配置,传入配置文件路径
|
// InitApplicationConfig 初始化配置,传入配置文件路径
|
||||||
func InitApplicationConfig(configPath string) (error, error) {
|
func InitApplicationConfig(configPath string) (err error, err2 error) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.SetConfigFile(configPath)
|
v.SetConfigFile(configPath)
|
||||||
v.SetConfigType("yaml")
|
v.SetConfigType("yaml")
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
return err, nil
|
return err, err2
|
||||||
}
|
}
|
||||||
|
|
||||||
var s server
|
// server 配置
|
||||||
if err := v.UnmarshalKey("server", &s); err != nil {
|
var server types.Server
|
||||||
return err, nil
|
if err := v.UnmarshalKey("server", &server); err != nil {
|
||||||
|
return err, err2
|
||||||
}
|
}
|
||||||
Server = &s
|
|
||||||
|
|
||||||
var j jwt
|
// jwt 配置
|
||||||
if err := v.UnmarshalKey("jwt", &j); err != nil {
|
var jwt types.Jwt
|
||||||
return err, nil
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"base-framework/pkg/types"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -13,18 +14,6 @@ import (
|
|||||||
_ "github.com/lib/pq" // PostgreSQL 驱动,根据实际数据库替换
|
_ "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 (
|
const (
|
||||||
defaultMaxOpenConns = 10
|
defaultMaxOpenConns = 10
|
||||||
@@ -33,7 +22,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dbConfigs map[string]DBConfig // 配置副本
|
dbConfigs map[string]types.DBConfig // 配置副本
|
||||||
dataSources = make(map[string]*sql.DB) // 连接池
|
dataSources = make(map[string]*sql.DB) // 连接池
|
||||||
configsDSN = make(map[string]string) // key -> dsn
|
configsDSN = make(map[string]string) // key -> dsn
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -83,7 +72,7 @@ func unmarshalConfigs() error {
|
|||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
temp := make(map[string]DBConfig)
|
temp := make(map[string]types.DBConfig)
|
||||||
if err := v.Unmarshal(&temp); err != nil {
|
if err := v.Unmarshal(&temp); err != nil {
|
||||||
return fmt.Errorf("解析数据库配置失败: %w", err)
|
return fmt.Errorf("解析数据库配置失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -92,11 +81,11 @@ func unmarshalConfigs() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDBConfigs 并发安全地返回当前数据库配置副本
|
// GetDBConfigs 并发安全地返回当前数据库配置副本
|
||||||
func GetDBConfigs() map[string]DBConfig {
|
func GetDBConfigs() map[string]types.DBConfig {
|
||||||
mu.RLock()
|
mu.RLock()
|
||||||
defer mu.RUnlock()
|
defer mu.RUnlock()
|
||||||
|
|
||||||
res := make(map[string]DBConfig, len(dbConfigs))
|
res := make(map[string]types.DBConfig, len(dbConfigs))
|
||||||
for k, v := range dbConfigs {
|
for k, v := range dbConfigs {
|
||||||
res[k] = v
|
res[k] = v
|
||||||
}
|
}
|
||||||
@@ -104,13 +93,13 @@ func GetDBConfigs() map[string]DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildDSN 构建 PostgreSQL DSN
|
// 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",
|
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)
|
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Dbname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadDataSources 支持增量更新数据源,并验证数据库连接
|
// ReloadDataSources 支持增量更新数据源,并验证数据库连接
|
||||||
func ReloadDataSources(newConfigs map[string]DBConfig) error {
|
func ReloadDataSources(newConfigs map[string]types.DBConfig) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
@@ -171,7 +160,7 @@ func ReloadDataSources(newConfigs map[string]DBConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applyDBConfig 设置 sql.DB 连接池参数,并应用默认值
|
// applyDBConfig 设置 sql.DB 连接池参数,并应用默认值
|
||||||
func applyDBConfig(db *sql.DB, cfg DBConfig) {
|
func applyDBConfig(db *sql.DB, cfg types.DBConfig) {
|
||||||
maxOpen := cfg.MaxOpenConns
|
maxOpen := cfg.MaxOpenConns
|
||||||
if maxOpen <= 0 {
|
if maxOpen <= 0 {
|
||||||
maxOpen = defaultMaxOpenConns
|
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 (
|
import (
|
||||||
"base-framework/pkg/reponse"
|
"base-framework/pkg/reponse"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"base-framework/pkg/router"
|
"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
|
// Error 中间件:捕获 c.Errors() 并返回统一的 ErrorInfo
|
||||||
func Error() router.HandlerFunc {
|
func Error() router.HandlerFunc {
|
||||||
|
// 注册时预热(只做一次)
|
||||||
|
initOnce.Do(initProjectInfo)
|
||||||
|
|
||||||
return func(c *router.Context) {
|
return func(c *router.Context) {
|
||||||
c.Next() // 执行后续 Handler
|
c.Next()
|
||||||
|
|
||||||
errs := c.Errors()
|
errs := c.Errors()
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
@@ -25,8 +39,8 @@ func Error() router.HandlerFunc {
|
|||||||
Method: c.Request.Method,
|
Method: c.Request.Method,
|
||||||
Path: c.Request.URL.Path,
|
Path: c.Request.URL.Path,
|
||||||
Message: lastErr.Error(),
|
Message: lastErr.Error(),
|
||||||
Stack: stack, // 完整堆栈
|
Stack: stack,
|
||||||
Location: location, // 业务 Handler 文件行号
|
Location: location,
|
||||||
}
|
}
|
||||||
|
|
||||||
reponse.Error(c).ErrorInfo(info).Send()
|
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 {
|
func extractBusinessLocation(stack string) string {
|
||||||
lines := strings.Split(stack, "\n")
|
lines := strings.Split(stack, "\n")
|
||||||
foundAddError := false
|
foundAddError := false
|
||||||
countGo := 0
|
countGo := 0
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, raw := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line := strings.TrimSpace(raw)
|
||||||
|
|
||||||
if strings.Contains(line, "AddError") {
|
if strings.Contains(line, "AddError") {
|
||||||
foundAddError = true
|
foundAddError = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if foundAddError && strings.Contains(line, ".go:") {
|
if !foundAddError {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(line, ".go:") {
|
||||||
countGo++
|
countGo++
|
||||||
if countGo == 2 { // 第2个 .go: 行就是业务 Handler
|
if countGo == 2 {
|
||||||
// 去掉 +0x偏移
|
// 去掉 " +0x..." 偏移
|
||||||
if idx := strings.Index(line, " +"); idx != -1 {
|
if i := strings.Index(line, " +"); i != -1 {
|
||||||
line = line[:idx]
|
line = line[:i]
|
||||||
}
|
}
|
||||||
// 保留 base-farmework 开头的路径
|
return trimToProject(line)
|
||||||
slash := strings.Index(line, "base-farmework")
|
}
|
||||||
if slash != -1 {
|
}
|
||||||
line = line[slash:]
|
}
|
||||||
}
|
return ""
|
||||||
return line
|
}
|
||||||
|
|
||||||
|
// 将绝对路径裁剪为 "<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