This commit is contained in:
oneao committed 2025-10-31 17:28:45 +08:00
1 parent 200f72eb86
commit 91ae4cb474
19 files changed
+650 -148

No files matched your search

File renamed without changes.
@@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/demo-project.iml" filepath="$PROJECT_DIR$/.idea/demo-project.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/base-go-v2.iml" filepath="$PROJECT_DIR$/.idea/base-go-v2.iml" />
</modules>
</component>
</project>
File renamed without changes.
@@ -0,0 +1 @@
package app
@@ -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
+27
View File
@@ -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
)
+43
View File
@@ -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=
@@ -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
}
@@ -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
}
@@ -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
}
}
@@ -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
}
-115
View File
@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="4636f4cb-d351-4483-ab41-f250e36fd785" name="更改" comment="">
<change beforePath="$PROJECT_DIR$/../../base-project/base-vue/test.html" beforeDir="false" afterPath="$PROJECT_DIR$/../../base-project/base-vue/test.html" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" url="file://$PROJECT_DIR$/../../../../devtool/golang/go1.24.6" />
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../../.." />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 2
}</component>
<component name="ProjectId" id="310iJEfnQcn2Pqx78c3M6FhCNFP" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"DefaultGoTemplateProperty": "Go File",
"Go Build.go build base-framework/cmd/app.executor": "Run",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.GoLinterPluginOnboarding": "true",
"RunOnceActivity.GoLinterPluginStorageMigration": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.go.formatter.settings.were.checked": "true",
"RunOnceActivity.go.migrated.go.modules.settings": "true",
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
"git-widget-placeholder": "master",
"go.import.settings.migrated": "true",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "C:/Users/Administrator",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.goide.configuration.GoLibrariesConfigurableProvider"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\workspace\code\go-project\base-farmework\pkg\configs" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\workspace\code\go-project\base-farmework\pkg" />
<recent name="D:\workspace\code\go-project\base-farmework\pkg\config" />
<recent name="D:\workspace\code\go-project\base-farmework\configs" />
</key>
</component>
<component name="RunManager">
<configuration name="go build base-framework/cmd/app" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<module name="base-farmework" />
<working_directory value="$PROJECT_DIR$" />
<kind value="PACKAGE" />
<package value="base-framework/cmd/app" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/cmd/app/main.go" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Go Build.go build base-framework/cmd/app" />
</list>
</recent_temporary>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-gosdk-3b128438d3f6-07d2d2d66b1e-org.jetbrains.plugins.go.sharedIndexes.bundled-GO-251.27812.54" />
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-GO-251.27812.54" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="4636f4cb-d351-4483-ab41-f250e36fd785" name="更改" comment="" />
<created>1754667317086</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1754667317086</updated>
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VgoProject">
<environment>
<map>
<entry key="GOPROXY" value="https://goproxy.cn,direct" />
</map>
</environment>
<settings-migrated>true</settings-migrated>
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
-1
View File
@@ -1 +0,0 @@
module demo-project
-21
View File
@@ -1,21 +0,0 @@
package main
import (
"fmt"
)
//TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
func main() {
//TIP <p>Press <shortcut actionId="ShowIntentionActions"/> when your caret is at the underlined text
// to see how GoLand suggests fixing the warning.</p><p>Alternatively, if available, click the lightbulb to view possible fixes.</p>
s := "gopher"
fmt.Printf("Hello and welcome, %s!\n", s)
for i := 1; i <= 5; i++ {
//TIP <p>To start your debugging session, right-click your code in the editor and select the Debug option.</p> <p>We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.</p>
fmt.Println("i =", 100/i)
}
}
@@ -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`
+285 -4
View File
@@ -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
```
@@ -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的默认端口。