diff --git a/code/go-project/demo-project/.idea/.gitignore b/code/base-project/base-go-v2/.idea/.gitignore
similarity index 100%
rename from code/go-project/demo-project/.idea/.gitignore
rename to code/base-project/base-go-v2/.idea/.gitignore
diff --git a/code/go-project/demo-project/.idea/demo-project.iml b/code/base-project/base-go-v2/.idea/base-go-v2.iml
similarity index 100%
rename from code/go-project/demo-project/.idea/demo-project.iml
rename to code/base-project/base-go-v2/.idea/base-go-v2.iml
diff --git a/code/go-project/demo-project/.idea/modules.xml b/code/base-project/base-go-v2/.idea/modules.xml
similarity index 56%
rename from code/go-project/demo-project/.idea/modules.xml
rename to code/base-project/base-go-v2/.idea/modules.xml
index 298418bb..14cf0c20 100644
--- a/code/go-project/demo-project/.idea/modules.xml
+++ b/code/base-project/base-go-v2/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/code/go-project/base-farmework/.idea/vcs.xml b/code/base-project/base-go-v2/.idea/vcs.xml
similarity index 100%
rename from code/go-project/base-farmework/.idea/vcs.xml
rename to code/base-project/base-go-v2/.idea/vcs.xml
diff --git a/code/base-project/base-go-v2/cmd/app/main.go b/code/base-project/base-go-v2/cmd/app/main.go
new file mode 100644
index 00000000..4879f7a4
--- /dev/null
+++ b/code/base-project/base-go-v2/cmd/app/main.go
@@ -0,0 +1 @@
+package app
diff --git a/code/base-project/base-go-v2/configs/app.yaml b/code/base-project/base-go-v2/configs/app.yaml
new file mode 100644
index 00000000..00d895c5
--- /dev/null
+++ b/code/base-project/base-go-v2/configs/app.yaml
@@ -0,0 +1,17 @@
+server:
+ port: 8085
+
+snowflake:
+ datacenterID: 0
+ workerID: 0
+
+db:
+ host: 117.72.182.135
+ port: 5432
+ user: postgres
+ password: zhang520..
+ dbname: base
+
+jwt:
+ secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
+ expiry: 168h
\ No newline at end of file
diff --git a/code/base-project/base-go-v2/go.mod b/code/base-project/base-go-v2/go.mod
new file mode 100644
index 00000000..925615ca
--- /dev/null
+++ b/code/base-project/base-go-v2/go.mod
@@ -0,0 +1,27 @@
+module base-go-v2
+
+go 1.25.0
+
+require (
+ github.com/andybalholm/brotli v1.2.0 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
+ github.com/klauspost/compress v1.18.1 // indirect
+ github.com/lib/pq v1.10.9 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/sagikazarmark/locafero v0.11.0 // indirect
+ github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
+ github.com/spf13/afero v1.15.0 // indirect
+ github.com/spf13/cast v1.10.0 // indirect
+ github.com/spf13/pflag v1.0.10 // indirect
+ github.com/spf13/viper v1.21.0 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.68.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ go.yaml.in/yaml/v3 v3.0.4 // indirect
+ golang.org/x/sys v0.37.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
+)
diff --git a/code/base-project/base-go-v2/go.sum b/code/base-project/base-go-v2/go.sum
new file mode 100644
index 00000000..7e50566d
--- /dev/null
+++ b/code/base-project/base-go-v2/go.sum
@@ -0,0 +1,43 @@
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
+github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
+github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
+github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
+github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
+github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
+github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
+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=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/code/base-project/base-go-v2/internal/config/app.go b/code/base-project/base-go-v2/internal/config/app.go
new file mode 100644
index 00000000..7017768f
--- /dev/null
+++ b/code/base-project/base-go-v2/internal/config/app.go
@@ -0,0 +1,59 @@
+package config
+
+import (
+ "base-go-v2/internal/types"
+ "github.com/spf13/viper"
+ "time"
+)
+
+// App 全局唯一导出配置实例,初始化默认值
+var App = &types.AppConfig{
+ Server: types.AppConfig{}.Server,
+ JWT: types.AppConfig{}.JWT,
+ Db: types.AppConfig{}.Db,
+ Snowflake: types.AppConfig{}.Snowflake,
+}
+
+// 给部分字段设置默认值
+func init() {
+ App.Server.Port = 8080
+ App.JWT.Secret = "default_secret"
+ App.JWT.Expiry = time.Hour
+
+ // 数据库只给连接池默认值
+ App.Db.MaxOpenConns = 10
+ App.Db.MaxIdleConns = 5
+ App.Db.ConnMaxLifetime = 30 * time.Minute
+
+ App.Snowflake.DatacenterID = 1
+ App.Snowflake.WorkerID = 1
+}
+
+// InitApplicationConfig 初始化配置
+func InitApplicationConfig(configPath string) error {
+ v := viper.New()
+ v.SetConfigFile(configPath)
+ v.SetConfigType("yaml")
+
+ if err := v.ReadInConfig(); err != nil {
+ return err
+ }
+
+ if err := v.UnmarshalKey("server", &App.Server); err != nil {
+ return err
+ }
+
+ if err := v.UnmarshalKey("jwt", &App.JWT); err != nil {
+ return err
+ }
+
+ if err := v.UnmarshalKey("snowflake", &App.Snowflake); err != nil {
+ return err
+ }
+
+ if err := v.UnmarshalKey("db", &App.Db); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/code/base-project/base-go-v2/internal/database/db.go b/code/base-project/base-go-v2/internal/database/db.go
new file mode 100644
index 00000000..4ca69719
--- /dev/null
+++ b/code/base-project/base-go-v2/internal/database/db.go
@@ -0,0 +1,62 @@
+package database
+
+import (
+ "base-go-v2/internal/config"
+ "database/sql"
+ "fmt"
+ "time"
+
+ _ "github.com/lib/pq"
+)
+
+var DB *sql.DB
+
+var defaultPoolConfig = struct {
+ MaxOpenConns int
+ MaxIdleConns int
+ ConnMaxLifetime time.Duration
+}{
+ MaxOpenConns: 10,
+ MaxIdleConns: 5,
+ ConnMaxLifetime: 30 * time.Minute,
+}
+
+func InitDb() error {
+ d := config.Db
+
+ // 构造 DSN
+ dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
+ d.Host, d.Port, d.User, d.Password, d.Dbname)
+
+ db, err := sql.Open("postgres", dsn)
+ if err != nil {
+ return fmt.Errorf("打开数据库失败: %w", err)
+ }
+
+ // 设置连接池(使用默认值,如果用户没传)
+ maxOpenConns := d.MaxOpenConns
+ if maxOpenConns == 0 {
+ maxOpenConns = defaultPoolConfig.MaxOpenConns
+ }
+ db.SetMaxOpenConns(maxOpenConns)
+
+ maxIdleConns := d.MaxIdleConns
+ if maxIdleConns == 0 {
+ maxIdleConns = defaultPoolConfig.MaxIdleConns
+ }
+ db.SetMaxIdleConns(maxIdleConns)
+
+ connMaxLifetime := d.ConnMaxLifetime
+ if connMaxLifetime == 0 {
+ connMaxLifetime = defaultPoolConfig.ConnMaxLifetime
+ }
+ db.SetConnMaxLifetime(connMaxLifetime)
+
+ // 测试连接
+ if err := db.Ping(); err != nil {
+ return fmt.Errorf("数据库连接测试失败: %w", err)
+ }
+
+ DB = db
+ return nil
+}
diff --git a/code/base-project/base-go-v2/internal/types/appConfig.go b/code/base-project/base-go-v2/internal/types/appConfig.go
new file mode 100644
index 00000000..8b5821fd
--- /dev/null
+++ b/code/base-project/base-go-v2/internal/types/appConfig.go
@@ -0,0 +1,28 @@
+package types
+
+import "time"
+
+// AppConfig 统一配置结构体
+type AppConfig struct {
+ Server struct {
+ Port int
+ }
+ JWT struct {
+ Secret string
+ Expiry time.Duration
+ }
+ Db struct {
+ Host string
+ Port int
+ User string
+ Password string
+ Dbname string
+ MaxOpenConns int
+ MaxIdleConns int
+ ConnMaxLifetime time.Duration
+ }
+ Snowflake struct {
+ DatacenterID uint64
+ WorkerID uint64
+ }
+}
diff --git a/code/base-project/base-go-v2/internal/utils/jwt/jwt.go b/code/base-project/base-go-v2/internal/utils/jwt/jwt.go
new file mode 100644
index 00000000..60c751a3
--- /dev/null
+++ b/code/base-project/base-go-v2/internal/utils/jwt/jwt.go
@@ -0,0 +1,75 @@
+package jwt
+
+import (
+ "base-go-v2/internal/config"
+ "errors"
+ "github.com/golang-jwt/jwt/v5"
+ "time"
+)
+
+var (
+ ErrTokenExpired = errors.New("token expired")
+ ErrTokenInvalid = errors.New("token invalid")
+)
+
+// CustomClaims 定义自己的 payload 结构
+type CustomClaims struct {
+ UserID string `json:"user_id"`
+ jwt.RegisteredClaims
+}
+
+// CreateToken 创建一个 JWT token
+// 支持 config.App.JWT.Expiry == -1 永不过期
+func CreateToken(orgID, userID string) (string, error) {
+ claims := CustomClaims{
+ UserID: userID,
+ RegisteredClaims: jwt.RegisteredClaims{
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ },
+ }
+
+ expiry := config.App.JWT.Expiry
+ switch {
+ case expiry > 0:
+ expireAt := time.Now().Add(expiry)
+ claims.ExpiresAt = jwt.NewNumericDate(expireAt)
+ case expiry == -1:
+ // 永不过期,ExpiresAt 设置为 nil
+ claims.ExpiresAt = nil
+ default:
+ // 默认过期时间,例如 1 小时
+ expireAt := time.Now().Add(time.Hour)
+ claims.ExpiresAt = jwt.NewNumericDate(expireAt)
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString([]byte(config.App.JWT.Secret))
+}
+
+// VerifyToken 验证并解析 JWT token
+func VerifyToken(tokenString string) (*CustomClaims, error) {
+ signKey := []byte(config.App.JWT.Secret)
+
+ token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+ // 校验签名算法
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, ErrTokenInvalid
+ }
+ return signKey, nil
+ })
+ if err != nil {
+ return nil, ErrTokenInvalid
+ }
+
+ claims, ok := token.Claims.(*CustomClaims)
+ if !ok || !token.Valid {
+ return nil, ErrTokenInvalid
+ }
+
+ // 检查过期:ExpiresAt 为 nil 时永不过期
+ if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
+ return nil, ErrTokenExpired
+ }
+
+ return claims, nil
+}
diff --git a/code/go-project/base-farmework/.idea/workspace.xml b/code/go-project/base-farmework/.idea/workspace.xml
deleted file mode 100644
index 65206bbd..00000000
--- a/code/go-project/base-farmework/.idea/workspace.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "associatedIndex": 2
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1754667317086
-
-
- 1754667317086
-
-
-
-
-
-
-
-
-
-
- true
-
-
\ No newline at end of file
diff --git a/code/go-project/demo-project/.idea/vcs.xml b/code/go-project/demo-project/.idea/vcs.xml
deleted file mode 100644
index c2365ab1..00000000
--- a/code/go-project/demo-project/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/code/go-project/demo-project/go.mod b/code/go-project/demo-project/go.mod
deleted file mode 100644
index 3d0c6626..00000000
--- a/code/go-project/demo-project/go.mod
+++ /dev/null
@@ -1 +0,0 @@
-module demo-project
diff --git a/code/go-project/demo-project/main.go b/code/go-project/demo-project/main.go
deleted file mode 100644
index df6b8ab2..00000000
--- a/code/go-project/demo-project/main.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package main
-
-import (
- "fmt"
-)
-
-//TIP
To run your code, right-click the code and select Run.
Alternatively, click
-// the icon in the gutter and select the Run menu item from here.
-
-func main() {
- //TIP Press when your caret is at the underlined text
- // to see how GoLand suggests fixing the warning.
Alternatively, if available, click the lightbulb to view possible fixes.
- s := "gopher"
- fmt.Printf("Hello and welcome, %s!\n", s)
-
- for i := 1; i <= 5; i++ {
- //TIP To start your debugging session, right-click your code in the editor and select the Debug option.
We have set one breakpoint
- // for you, but you can always add more by pressing .
- fmt.Println("i =", 100/i)
- }
-}
\ No newline at end of file
diff --git a/notes/后端开发/golang/安装与配置.md b/notes/后端开发/golang/安装与配置.md
index ea252c2c..49b65e81 100644
--- a/notes/后端开发/golang/安装与配置.md
+++ b/notes/后端开发/golang/安装与配置.md
@@ -105,3 +105,23 @@ go env -w GOPATH="D:\devtool\golang\gopath"
+
+
+# 错误
+
+## GoLand运行报错:WARNING: undefined behavior - version of Delve is too old for Go version go1.25.0 (maximum supported version 1.24)
+
+安装:
+
+```bash
+go install github.com/go-delve/delve/cmd/dlv@master
+```
+
+修改 goland 配置:`Help\Edit Custom Properties`,新增配置:
+
+```
+dlv.path=D:\\devtool\\golang\\gopath\\bin\\dlv.exe
+```
+
+这个路径是 `gopath` 配置后的路径,默认在c盘 `dlv.path=C:\\Users\\_user_\\go\\bin\\dlv.exe`
+
diff --git a/notes/后端开发/golang/项目骨架.md b/notes/后端开发/golang/项目骨架.md
index c43d4424..5a8da2e8 100644
--- a/notes/后端开发/golang/项目骨架.md
+++ b/notes/后端开发/golang/项目骨架.md
@@ -12,15 +12,296 @@ myapp/
│ ├── handler/ # HTTP handler(路由处理函数)
│ ├── repository/ # 数据访问层(如数据库、缓存)
│ ├── service/ # 业务逻辑层
-│ └── model/ # 实体结构体、DTO、表结构等
-│
-├── pkg/ # 可被外部引入的公共包
+│ ├── model/ # 实体结构体、DTO、表结构等
│ ├── logger/ # 日志封装
│ ├── utils/ # 工具函数
│ └── middleware/ # HTTP中间件
-│
+
├── go.mod # Go module 定义
├── go.sum # Go module 校验文件
└── README.md # 项目说明
```
+
+
+假设有 **用户服务(8080)** 和 **订单服务(8081)**:
+
+```
+project/
+├── cmd/
+│ ├── user-service/ # main.go,启动用户服务,监听 8080
+│ └── order-service/ # main.go,启动订单服务,监听 8081
+│
+├── internal/ # 内部模块,不对外暴露
+│ ├── user/ # 用户服务模块
+│ │ ├── handler.go # HTTP 接口
+│ │ └── service.go # 业务逻辑
+│ ├── order/ # 订单服务模块
+│ │ ├── handler.go
+│ │ └── service.go
+│ └── database/ # 数据库连接封装
+│
+├── pkg/ # 可复用公共库(跨服务)
+│ ├── logger/ # 日志封装
+│ ├── utils/ # 工具函数
+│ └── types/ # 公共类型定义
+│
+└── configs/ # 各服务配置
+ ├── user.yaml
+ └── order.yaml
+```
+
+
+
+# 项目配置
+
+## 集成 [fasthttp](https://github.com/valyala/fasthttp)
+
+```bash
+go get -u github.com/valyala/fasthttp
+```
+
+测试
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/valyala/fasthttp"
+)
+
+func main() {
+ requestHandler := func(ctx *fasthttp.RequestCtx) {
+ fmt.Fprintf(ctx, "Hello, fasthttp!")
+ }
+
+ fmt.Println("Server is running on :8099")
+ if err := fasthttp.ListenAndServe(":8099", requestHandler); err != nil {
+ fmt.Printf("Error in ListenAndServe: %s\n", err)
+ }
+}
+```
+
+输出以下内容,即代表成功:
+
+```
+Server is running on :8099
+```
+
+
+
+## 集成 [postgres](https://github.com/lib/pq)
+
+```bash
+go get github.com/lib/pq
+```
+
+测试
+
+```go
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+
+ _ "github.com/lib/pq" // pq 驱动
+)
+
+func main() {
+ // PostgreSQL 连接信息
+ dsn := "host=localhost port=5432 user=postgres password=123456 dbname=testdb sslmode=disable"
+
+ // 连接数据库
+ db, err := sql.Open("postgres", dsn)
+ if err != nil {
+ log.Fatal("连接数据库失败:", err)
+ }
+ defer db.Close()
+
+ // 测试连接
+ err = db.Ping()
+ if err != nil {
+ log.Fatal("数据库无法访问:", err)
+ }
+ fmt.Println("成功连接 PostgreSQL!")
+
+ // 测试查询
+ var now string
+ err = db.QueryRow("SELECT NOW()").Scan(&now)
+ if err != nil {
+ log.Fatal("查询失败:", err)
+ }
+ fmt.Println("数据库当前时间:", now)
+}
+```
+
+输出以下内容,即代表成功:
+
+```
+成功连接 PostgreSQL!
+数据库当前时间: 2025-10-31T03:08:00.125282Z
+```
+
+
+
+## 集成 [viper](https://github.com/spf13/viper)
+
+```bash
+go get github.com/spf13/viper
+```
+
+测试:
+
+新建 `application.yaml` :
+
+```yaml
+server:
+ port: 8082
+```
+
+`main.go`:
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/spf13/viper"
+)
+
+func main() {
+ // 设置配置文件路径
+ viper.SetConfigFile("application.yaml")
+
+ // 读取配置文件
+ if err := viper.ReadInConfig(); err != nil {
+ log.Fatalf("读取配置文件失败: %v", err)
+ }
+
+ // 获取配置
+ port := viper.GetInt("server.port")
+ fmt.Println("Server port:", port)
+
+ // 热加载示例
+ viper.WatchConfig()
+ viper.OnConfigChange(func(e fsnotify.Event) {
+ fmt.Println("配置文件修改:", e.Name)
+ newPort := viper.GetInt("server.port")
+ fmt.Println("新的 server.port:", newPort)
+ })
+
+ // 阻塞等待,可以模拟服务运行
+ select {}
+}
+```
+
+输出以下内容,即代表成功:
+
+```
+Server port:8082
+```
+
+
+
+## 集成 [jwt](https://github.com/golang-jwt/jwt)
+
+```bash
+go get -u github.com/golang-jwt/jwt/v5
+```
+
+测试:
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+// 用于签名的密钥(生产环境不要硬编码)
+var jwtKey = []byte("my_secret_key")
+
+func main() {
+ // 用户 ID
+ userID := 123
+
+ // 生成 token
+ tokenString, err := GenerateJWT(userID)
+ if err != nil {
+ log.Fatal("生成 JWT 失败:", err)
+ }
+ fmt.Println("生成的 JWT:", tokenString)
+
+ // 验证 token
+ claims, err := ValidateJWT(tokenString)
+ if err != nil {
+ log.Fatal("验证 JWT 失败:", err)
+ }
+ fmt.Printf("解析后的用户 ID: %v, 过期时间: %v\n", claims.UserID, claims.ExpiresAt.Time)
+}
+
+// 自定义 claims
+type MyClaims struct {
+ UserID int `json:"user_id"`
+ jwt.RegisteredClaims
+}
+
+// 生成 JWT
+func GenerateJWT(userID int) (string, error) {
+ claims := MyClaims{
+ UserID: userID,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), // 1 小时过期
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ Issuer: "myapp",
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString(jwtKey)
+}
+
+// 验证 JWT
+func ValidateJWT(tokenString string) (*MyClaims, error) {
+ claims := &MyClaims{}
+
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ return jwtKey, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ if !token.Valid {
+ return nil, fmt.Errorf("token 无效")
+ }
+
+ return claims, nil
+}
+```
+
+输出以下内容,即代表成功:
+
+```
+生成的 JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsImlzcyI6Im15YXBwIiwiZXhwIjoxNzYxODg1MTQ2LCJpYXQiOjE3NjE4ODE1NDZ9.f7gZg6YmuYRXzaOLhJu4Vg8tXYgueHIixeoh10caSWg
+解析后的用户 ID: 123, 过期时间: 2025-10-31 12:32:26 +0800 CST
+```
+
+
+
+## 集成 [zap](https://pkg.go.dev/go.uber.org/zap)
+
+```bash
+go get -u go.uber.org/zap
+```
+
diff --git a/notes/数据库/postgres/安装与配置.md b/notes/数据库/postgres/安装与配置.md
new file mode 100644
index 00000000..dbd7f7e5
--- /dev/null
+++ b/notes/数据库/postgres/安装与配置.md
@@ -0,0 +1,32 @@
+# 安装
+
+## Docker安装
+
+### 1. 拉取镜像
+
+```
+docker pull postgres
+```
+
+### 2. 创建数据卷
+
+创建一个数据库以持久化PostgreSQL的数据
+
+```bash
+docker volume create postgre-data
+```
+
+### 3. 创建并运行容器
+
+```
+docker run -d \
+ --name postgres \
+ -e POSTGRES_PASSWORD=123456 \
+ -v ./data/postgres:/var/lib/postgresql/data/pgdata \
+ -p 5432:5432 \
+ postgres
+
+```
+
+上述设定了数据卷,以及暴露了端口`5432`,这是PostgreSQL的默认端口。
+