From 91ae4cb4742528cf77e22ca235e69764ef4e278a Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Fri, 31 Oct 2025 17:28:45 +0800 Subject: [PATCH] u --- .../base-go-v2}/.idea/.gitignore | 0 .../base-go-v2/.idea/base-go-v2.iml} | 0 .../base-go-v2}/.idea/modules.xml | 2 +- .../base-go-v2}/.idea/vcs.xml | 0 code/base-project/base-go-v2/cmd/app/main.go | 1 + code/base-project/base-go-v2/configs/app.yaml | 17 ++ code/base-project/base-go-v2/go.mod | 27 ++ code/base-project/base-go-v2/go.sum | 43 +++ .../base-go-v2/internal/config/app.go | 59 ++++ .../base-go-v2/internal/database/db.go | 62 ++++ .../base-go-v2/internal/types/appConfig.go | 28 ++ .../base-go-v2/internal/utils/jwt/jwt.go | 75 +++++ .../base-farmework/.idea/workspace.xml | 115 ------- code/go-project/demo-project/.idea/vcs.xml | 6 - code/go-project/demo-project/go.mod | 1 - code/go-project/demo-project/main.go | 21 -- notes/后端开发/golang/安装与配置.md | 20 ++ notes/后端开发/golang/项目骨架.md | 289 +++++++++++++++++- notes/数据库/postgres/安装与配置.md | 32 ++ 19 files changed, 650 insertions(+), 148 deletions(-) rename code/{go-project/demo-project => base-project/base-go-v2}/.idea/.gitignore (100%) rename code/{go-project/demo-project/.idea/demo-project.iml => base-project/base-go-v2/.idea/base-go-v2.iml} (100%) rename code/{go-project/demo-project => base-project/base-go-v2}/.idea/modules.xml (56%) rename code/{go-project/base-farmework => base-project/base-go-v2}/.idea/vcs.xml (100%) create mode 100644 code/base-project/base-go-v2/cmd/app/main.go create mode 100644 code/base-project/base-go-v2/configs/app.yaml create mode 100644 code/base-project/base-go-v2/go.mod create mode 100644 code/base-project/base-go-v2/go.sum create mode 100644 code/base-project/base-go-v2/internal/config/app.go create mode 100644 code/base-project/base-go-v2/internal/database/db.go create mode 100644 code/base-project/base-go-v2/internal/types/appConfig.go create mode 100644 code/base-project/base-go-v2/internal/utils/jwt/jwt.go delete mode 100644 code/go-project/base-farmework/.idea/workspace.xml delete mode 100644 code/go-project/demo-project/.idea/vcs.xml delete mode 100644 code/go-project/demo-project/go.mod delete mode 100644 code/go-project/demo-project/main.go create mode 100644 notes/数据库/postgres/安装与配置.md 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 - - - - - - - - - - - - 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的默认端口。 +