diff --git a/go-gin-demo/context_demo.go b/Web开发/01go-gin-demo/context_demo.go
similarity index 100%
rename from go-gin-demo/context_demo.go
rename to Web开发/01go-gin-demo/context_demo.go
diff --git a/go-gin-demo/go.mod b/Web开发/01go-gin-demo/go.mod
similarity index 100%
rename from go-gin-demo/go.mod
rename to Web开发/01go-gin-demo/go.mod
diff --git a/go-gin-demo/go.sum b/Web开发/01go-gin-demo/go.sum
similarity index 100%
rename from go-gin-demo/go.sum
rename to Web开发/01go-gin-demo/go.sum
diff --git a/go-gin-demo/handler_demo.go b/Web开发/01go-gin-demo/handler_demo.go
similarity index 100%
rename from go-gin-demo/handler_demo.go
rename to Web开发/01go-gin-demo/handler_demo.go
diff --git a/go-gin-demo/main.go b/Web开发/01go-gin-demo/main.go
similarity index 100%
rename from go-gin-demo/main.go
rename to Web开发/01go-gin-demo/main.go
diff --git a/Web开发/01go-gin-demo/params_form_demo.go b/Web开发/01go-gin-demo/params_form_demo.go
new file mode 100644
index 0000000..4c0448c
--- /dev/null
+++ b/Web开发/01go-gin-demo/params_form_demo.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ r := gin.Default()
+
+ r.POST("/login", func(c *gin.Context) {
+ username := c.PostForm("username") // 获取表单参数
+ password := c.PostForm("password")
+
+ // 简单验证
+ if username == "" || password == "" {
+ c.JSON(400, gin.H{"error": "username and password required"})
+ return
+ }
+
+ c.JSON(200, gin.H{
+ "message": "Login successful",
+ "username": username,
+ })
+ })
+
+ r.Run(":8080")
+}
diff --git a/go-gin-demo/params_json_demo.go b/Web开发/01go-gin-demo/params_json_demo.go
similarity index 100%
rename from go-gin-demo/params_json_demo.go
rename to Web开发/01go-gin-demo/params_json_demo.go
diff --git a/Web开发/01go-gin-demo/params_path_demo.go b/Web开发/01go-gin-demo/params_path_demo.go
new file mode 100644
index 0000000..b7cd950
--- /dev/null
+++ b/Web开发/01go-gin-demo/params_path_demo.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ r := gin.Default()
+
+ // 单个参数
+ r.GET("/users/:id", func(c *gin.Context) {
+ id := c.Param("id") // 获取参数
+ c.JSON(200, gin.H{"user_id": id})
+ })
+
+ // 多个参数
+ r.GET("/users/:uid/posts/:pid", func(c *gin.Context) {
+ uid := c.Param("uid")
+ pid := c.Param("pid")
+ c.JSON(200, gin.H{"uid": uid, "pid": pid})
+ })
+
+ r.Run(":8080")
+}
diff --git a/Web开发/01go-gin-demo/params_query_demo.go b/Web开发/01go-gin-demo/params_query_demo.go
new file mode 100644
index 0000000..68cf24c
--- /dev/null
+++ b/Web开发/01go-gin-demo/params_query_demo.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ r := gin.Default()
+
+ r.GET("/search", func(c *gin.Context) {
+ keyword := c.Query("q") // 获取查询参数
+ limit := c.DefaultQuery("limit", "10") // 带默认值
+ c.JSON(200, gin.H{
+ "keyword": keyword,
+ "limit": limit,
+ })
+ })
+
+ r.Run(":8080")
+}
diff --git a/go-gin-demo/routes_demo.go b/Web开发/01go-gin-demo/routes_demo.go
similarity index 100%
rename from go-gin-demo/routes_demo.go
rename to Web开发/01go-gin-demo/routes_demo.go
diff --git a/go-gin-demo/user_manager.go b/Web开发/01go-gin-demo/user_manager.go
similarity index 100%
rename from go-gin-demo/user_manager.go
rename to Web开发/01go-gin-demo/user_manager.go
diff --git a/go-gin-middleware/go.mod b/Web开发/02go-gin-middleware/go.mod
similarity index 100%
rename from go-gin-middleware/go.mod
rename to Web开发/02go-gin-middleware/go.mod
diff --git a/go-gin-middleware/go.sum b/Web开发/02go-gin-middleware/go.sum
similarity index 100%
rename from go-gin-middleware/go.sum
rename to Web开发/02go-gin-middleware/go.sum
diff --git a/go-gin-middleware/middleware_abort.go b/Web开发/02go-gin-middleware/middleware_abort.go
similarity index 100%
rename from go-gin-middleware/middleware_abort.go
rename to Web开发/02go-gin-middleware/middleware_abort.go
diff --git a/go-gin-middleware/middleware_auth.go b/Web开发/02go-gin-middleware/middleware_auth.go
similarity index 100%
rename from go-gin-middleware/middleware_auth.go
rename to Web开发/02go-gin-middleware/middleware_auth.go
diff --git a/go-gin-middleware/middleware_basic.go b/Web开发/02go-gin-middleware/middleware_basic.go
similarity index 100%
rename from go-gin-middleware/middleware_basic.go
rename to Web开发/02go-gin-middleware/middleware_basic.go
diff --git a/go-gin-middleware/middleware_complete.go b/Web开发/02go-gin-middleware/middleware_complete.go
similarity index 100%
rename from go-gin-middleware/middleware_complete.go
rename to Web开发/02go-gin-middleware/middleware_complete.go
diff --git a/go-gin-middleware/middleware_cors.go b/Web开发/02go-gin-middleware/middleware_cors.go
similarity index 100%
rename from go-gin-middleware/middleware_cors.go
rename to Web开发/02go-gin-middleware/middleware_cors.go
diff --git a/go-gin-middleware/middleware_cors_custom.go b/Web开发/02go-gin-middleware/middleware_cors_custom.go
similarity index 100%
rename from go-gin-middleware/middleware_cors_custom.go
rename to Web开发/02go-gin-middleware/middleware_cors_custom.go
diff --git a/go-gin-middleware/middleware_logger.go b/Web开发/02go-gin-middleware/middleware_logger.go
similarity index 100%
rename from go-gin-middleware/middleware_logger.go
rename to Web开发/02go-gin-middleware/middleware_logger.go
diff --git a/go-gin-middleware/middleware_recovery.go b/Web开发/02go-gin-middleware/middleware_recovery.go
similarity index 100%
rename from go-gin-middleware/middleware_recovery.go
rename to Web开发/02go-gin-middleware/middleware_recovery.go
diff --git a/go-gin-middleware/middleware_token.go b/Web开发/02go-gin-middleware/middleware_token.go
similarity index 100%
rename from go-gin-middleware/middleware_token.go
rename to Web开发/02go-gin-middleware/middleware_token.go
diff --git a/go-gorm-demo/association_methods.go b/Web开发/03go-gorm-demo/association_methods.go
similarity index 100%
rename from go-gorm-demo/association_methods.go
rename to Web开发/03go-gorm-demo/association_methods.go
diff --git a/go-gorm-demo/crud_create.go b/Web开发/03go-gorm-demo/crud_create.go
similarity index 100%
rename from go-gorm-demo/crud_create.go
rename to Web开发/03go-gorm-demo/crud_create.go
diff --git a/go-gorm-demo/crud_delete.go b/Web开发/03go-gorm-demo/crud_delete.go
similarity index 100%
rename from go-gorm-demo/crud_delete.go
rename to Web开发/03go-gorm-demo/crud_delete.go
diff --git a/go-gorm-demo/crud_read.go b/Web开发/03go-gorm-demo/crud_read.go
similarity index 100%
rename from go-gorm-demo/crud_read.go
rename to Web开发/03go-gorm-demo/crud_read.go
diff --git a/go-gorm-demo/crud_update.go b/Web开发/03go-gorm-demo/crud_update.go
similarity index 100%
rename from go-gorm-demo/crud_update.go
rename to Web开发/03go-gorm-demo/crud_update.go
diff --git a/Web开发/03go-gorm-demo/db_connect.go b/Web开发/03go-gorm-demo/db_connect.go
new file mode 100644
index 0000000..5594b01
--- /dev/null
+++ b/Web开发/03go-gorm-demo/db_connect.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func main() {
+ // 连接 SQLite(文件数据库)
+ db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
+ if err != nil {
+ panic("failed to connect database")
+ }
+
+ fmt.Println("Database connected successfully!")
+
+ // 获取底层的 *sql.DB
+ sqlDB, err := db.DB()
+ if err != nil {
+ panic(err)
+ }
+
+ // 设置连接池
+ sqlDB.SetMaxIdleConns(10)
+ sqlDB.SetMaxOpenConns(100)
+
+ fmt.Println("Connection pool configured")
+}
diff --git a/go-gorm-demo/go.mod b/Web开发/03go-gorm-demo/go.mod
similarity index 100%
rename from go-gorm-demo/go.mod
rename to Web开发/03go-gorm-demo/go.mod
diff --git a/go-gorm-demo/go.sum b/Web开发/03go-gorm-demo/go.sum
similarity index 100%
rename from go-gorm-demo/go.sum
rename to Web开发/03go-gorm-demo/go.sum
diff --git a/go-gorm-demo/model_basic.go b/Web开发/03go-gorm-demo/model_basic.go
similarity index 100%
rename from go-gorm-demo/model_basic.go
rename to Web开发/03go-gorm-demo/model_basic.go
diff --git a/go-gorm-demo/preload_strategies.go b/Web开发/03go-gorm-demo/preload_strategies.go
similarity index 100%
rename from go-gorm-demo/preload_strategies.go
rename to Web开发/03go-gorm-demo/preload_strategies.go
diff --git a/go-gorm-demo/query_advanced.go b/Web开发/03go-gorm-demo/query_advanced.go
similarity index 100%
rename from go-gorm-demo/query_advanced.go
rename to Web开发/03go-gorm-demo/query_advanced.go
diff --git a/go-gorm-demo/raw_sql.go b/Web开发/03go-gorm-demo/raw_sql.go
similarity index 100%
rename from go-gorm-demo/raw_sql.go
rename to Web开发/03go-gorm-demo/raw_sql.go
diff --git a/go-gorm-demo/relation_has_many.go b/Web开发/03go-gorm-demo/relation_has_many.go
similarity index 100%
rename from go-gorm-demo/relation_has_many.go
rename to Web开发/03go-gorm-demo/relation_has_many.go
diff --git a/go-gorm-demo/relation_has_one.go b/Web开发/03go-gorm-demo/relation_has_one.go
similarity index 100%
rename from go-gorm-demo/relation_has_one.go
rename to Web开发/03go-gorm-demo/relation_has_one.go
diff --git a/go-gorm-demo/relation_many_to_many.go b/Web开发/03go-gorm-demo/relation_many_to_many.go
similarity index 100%
rename from go-gorm-demo/relation_many_to_many.go
rename to Web开发/03go-gorm-demo/relation_many_to_many.go
diff --git a/go-gorm-demo/test.db b/Web开发/03go-gorm-demo/test.db
similarity index 100%
rename from go-gorm-demo/test.db
rename to Web开发/03go-gorm-demo/test.db
diff --git a/go-gorm-demo/transaction_basic.go b/Web开发/03go-gorm-demo/transaction_basic.go
similarity index 100%
rename from go-gorm-demo/transaction_basic.go
rename to Web开发/03go-gorm-demo/transaction_basic.go
diff --git a/go-gorm-demo/transaction_rollback.go b/Web开发/03go-gorm-demo/transaction_rollback.go
similarity index 100%
rename from go-gorm-demo/transaction_rollback.go
rename to Web开发/03go-gorm-demo/transaction_rollback.go
diff --git a/go-gorm-demo/transaction_savepoint.go b/Web开发/03go-gorm-demo/transaction_savepoint.go
similarity index 100%
rename from go-gorm-demo/transaction_savepoint.go
rename to Web开发/03go-gorm-demo/transaction_savepoint.go
diff --git a/Web开发/04go-viper-demo/01viper_basic.go b/Web开发/04go-viper-demo/01viper_basic.go
new file mode 100644
index 0000000..71f0b44
--- /dev/null
+++ b/Web开发/04go-viper-demo/01viper_basic.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/viper"
+)
+
+func main() {
+ // 1. 设置配置文件名和路径
+ viper.SetConfigName("config") // 配置文件名(不含扩展名)
+ viper.SetConfigType("yaml") // 配置文件类型
+ viper.AddConfigPath(".") // 配置文件路径
+
+ // 2. 读取配置文件
+ if err := viper.ReadInConfig(); err != nil {
+ panic(fmt.Errorf("Fatal error config file: %s", err))
+ }
+
+ fmt.Println("Config file loaded successfully!")
+
+ // 3. 读取单个配置项
+ appName := viper.GetString("app.name")
+ appPort := viper.GetInt("app.port")
+ fmt.Printf("App Name: %s\n", appName)
+ fmt.Printf("App Port: %d\n", appPort)
+
+ // 4. 读取嵌套配置
+ dbHost := viper.GetString("database.host")
+ dbPort := viper.GetInt("database.port")
+ fmt.Printf("Database: %s:%d\n", dbHost, dbPort)
+
+ // 5. 读取所有配置
+ allSettings := viper.AllSettings()
+ fmt.Printf("All settings: %v\n", allSettings)
+}
diff --git a/Web开发/04go-viper-demo/02viper_struct.go b/Web开发/04go-viper-demo/02viper_struct.go
new file mode 100644
index 0000000..c067e62
--- /dev/null
+++ b/Web开发/04go-viper-demo/02viper_struct.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/viper"
+)
+
+// 配置结构体
+type Config struct {
+ App AppConfig `mapstructure:"app"`
+ Database DatabaseConfig `mapstructure:"database"`
+ Redis RedisConfig `mapstructure:"redis"`
+}
+
+type AppConfig struct {
+ Name string `mapstructure:"name"`
+ Version string `mapstructure:"version"`
+ Port int `mapstructure:"port"`
+}
+
+type DatabaseConfig struct {
+ Host string `mapstructure:"host"`
+ Port int `mapstructure:"port"`
+ Username string `mapstructure:"username"`
+ Password string `mapstructure:"password"`
+ DBName string `mapstructure:"dbname"`
+}
+
+type RedisConfig struct {
+ Host string `mapstructure:"host"`
+ Port int `mapstructure:"port"`
+ Password string `mapstructure:"password"`
+ DB int `mapstructure:"db"`
+}
+
+func main() {
+ // 读取配置文件
+ viper.SetConfigName("config")
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath(".")
+
+ if err := viper.ReadInConfig(); err != nil {
+ panic(err)
+ }
+
+ // 将配置绑定到结构体
+ var config Config
+ if err := viper.Unmarshal(&config); err != nil {
+ panic(err)
+ }
+
+ // 使用配置
+ fmt.Printf("App: %s v%s\n", config.App.Name, config.App.Version)
+ fmt.Printf("Port: %d\n", config.App.Port)
+ fmt.Printf("Database: %s@%s:%d/%s\n",
+ config.Database.Username,
+ config.Database.Host,
+ config.Database.Port,
+ config.Database.DBName,
+ )
+ fmt.Printf("Redis: %s:%d (DB %d)\n",
+ config.Redis.Host,
+ config.Redis.Port,
+ config.Redis.DB,
+ )
+}
diff --git a/Web开发/04go-viper-demo/03viper_env.go b/Web开发/04go-viper-demo/03viper_env.go
new file mode 100644
index 0000000..c43accd
--- /dev/null
+++ b/Web开发/04go-viper-demo/03viper_env.go
@@ -0,0 +1,100 @@
+// package main
+//
+// import (
+//
+// "fmt"
+// "os"
+//
+// "github.com/spf13/viper"
+//
+// )
+//
+// func main() {
+// // 方案:先设置环境变量,再设置前缀和绑定
+// // 1. 先设置环境变量(模拟)
+// os.Setenv("MYAPP_PORT", "9000")
+// os.Setenv("MYAPP_DEBUG", "true")
+// os.Setenv("MYAPP_DB_HOST", "192.168.1.100")
+//
+// // 2. 设置环境变量前缀
+// viper.SetEnvPrefix("MYAPP")
+//
+// // 3. 设置默认值
+// viper.SetDefault("port", 8080)
+// viper.SetDefault("debug", false)
+// viper.SetDefault("db.host", "localhost")
+//
+// // 4. 绑定环境变量 - 显式指定完整的环境变量名(不加前缀)
+// viper.BindEnv("port", "MYAPP_PORT")
+// viper.BindEnv("debug", "MYAPP_DEBUG")
+// viper.BindEnv("db.host", "MYAPP_DB_HOST") // 直接指定完整的环境变量名
+//
+// // 5. 读取配置
+// port := viper.GetInt("port")
+// debug := viper.GetBool("debug")
+// dbHost := viper.GetString("db.host")
+//
+// fmt.Printf("Port: %d (from env)\n", port)
+// fmt.Printf("Debug: %v (from env)\n", debug)
+// fmt.Printf("DB Host: %s (from env)\n", dbHost)
+//
+// // 6. 启用自动环境变量绑定
+// viper.AutomaticEnv()
+//
+// // 设置自定义环境变量后获取
+// os.Setenv("MYAPP_CUSTOM_KEY", "custom_value")
+// // 注意:AutomaticEnv 后,MYAPP_CUSTOM_KEY 会映射到 custom_key
+// customKey := viper.GetString("custom_key")
+// fmt.Printf("Custom Key: %s\n", customKey)
+//
+// // 验证环境变量是否真的设置成功
+// fmt.Println("\n=== 验证环境变量 ===")
+// fmt.Printf("MYAPP_PORT: %s\n", os.Getenv("MYAPP_PORT"))
+// fmt.Printf("MYAPP_DEBUG: %s\n", os.Getenv("MYAPP_DEBUG"))
+// fmt.Printf("MYAPP_DB_HOST: %s\n", os.Getenv("MYAPP_DB_HOST"))
+// }
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/spf13/viper"
+)
+
+func main() {
+ // 1. 设置环境变量前缀
+ viper.SetEnvPrefix("MYAPP") // 自动添加前缀
+ viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+
+ // 2. 绑定环境变量
+ viper.BindEnv("port") // 绑定 MYAPP_PORT
+ viper.BindEnv("debug") // 绑定 MYAPP_DEBUG
+ viper.BindEnv("db.host") // 绑定 MYAPP_DB_HOST
+
+ // 3. 设置默认值
+ viper.SetDefault("port", 8080)
+ viper.SetDefault("debug", false)
+
+ // 4. 设置环境变量(模拟)
+ os.Setenv("MYAPP_PORT", "9000")
+ os.Setenv("MYAPP_DEBUG", "true")
+ os.Setenv("MYAPP_DB_HOST", "192.168.1.100")
+
+ // 5. 读取配置(优先级:环境变量 > 配置文件 > 默认值)
+ port := viper.GetInt("port")
+ debug := viper.GetBool("debug")
+ dbHost := viper.GetString("db.host")
+
+ fmt.Printf("Port: %d (from env)\n", port)
+ fmt.Printf("Debug: %v (from env)\n", debug)
+ fmt.Printf("DB Host: %s (from env)\n", dbHost)
+
+ // 6. 自动绑定所有环境变量
+ viper.AutomaticEnv()
+
+ os.Setenv("MYAPP_CUSTOM_KEY", "custom_value")
+ customKey := viper.GetString("custom.key") // 自动转换 CUSTOM_KEY -> custom.key
+ fmt.Printf("Custom Key: %s\n", customKey)
+}
diff --git a/Web开发/04go-viper-demo/04viper_multi_env.go b/Web开发/04go-viper-demo/04viper_multi_env.go
new file mode 100644
index 0000000..a6f6f8f
--- /dev/null
+++ b/Web开发/04go-viper-demo/04viper_multi_env.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "fmt"
+ "github.com/spf13/viper"
+ "os"
+)
+
+type Config struct {
+ App struct {
+ Name string `mapstructure:"name"`
+ Port int `mapstructure:"port"`
+ Debug bool `mapstructure:"debug"`
+ } `mapstructure:"app"`
+ Database struct {
+ Host string `mapstructure:"host"`
+ Port int `mapstructure:"port"`
+ Username string `mapstructure:"username"`
+ Password string `mapstructure:"password"`
+ DBName string `mapstructure:"dbname"`
+ } `mapstructure:"database"`
+}
+
+func LoadConfig(env string) (*Config, error) {
+ // 根据环境加载配置文件
+ configName := fmt.Sprintf("config.%s", env)
+
+ viper.SetConfigName(configName)
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath(".")
+
+ if err := viper.ReadInConfig(); err != nil {
+ return nil, err
+ }
+
+ var config Config
+ if err := viper.Unmarshal(&config); err != nil {
+ return nil, err
+ }
+
+ return &config, nil
+}
+
+func main() {
+ // 从环境变量读取环境名称
+ env := os.Getenv("APP_ENV")
+ if env == "" {
+ env = "dev" // 默认开发环境
+ }
+
+ fmt.Printf("Loading config for environment: %s\n", env)
+
+ config, err := LoadConfig(env)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("App: %s\n", config.App.Name)
+ fmt.Printf("Port: %d\n", config.App.Port)
+ fmt.Printf("Debug: %v\n", config.App.Debug)
+ fmt.Printf("Database: %s@%s:%d/%s\n",
+ config.Database.Username,
+ config.Database.Host,
+ config.Database.Port,
+ config.Database.DBName,
+ )
+}
diff --git a/Web开发/04go-viper-demo/05viper_watch.go b/Web开发/04go-viper-demo/05viper_watch.go
new file mode 100644
index 0000000..d13db47
--- /dev/null
+++ b/Web开发/04go-viper-demo/05viper_watch.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/spf13/viper"
+)
+
+func main() {
+ // 读取配置文件
+ viper.SetConfigName("config")
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath(".")
+
+ if err := viper.ReadInConfig(); err != nil {
+ panic(err)
+ }
+
+ fmt.Println("Initial config loaded")
+ fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
+ fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
+
+ // 监听配置文件变化
+ viper.WatchConfig()
+ viper.OnConfigChange(func(in fsnotify.Event) {
+ fmt.Println("\n--- Config file changed! ---")
+ fmt.Printf("Event: %s\n", in.Op.String())
+ fmt.Printf("File: %s\n", in.Name)
+ fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
+ fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
+ })
+
+ fmt.Println("\nWatching for config changes... (modify config.yaml to see changes)")
+ fmt.Println("Press Ctrl+C to exit")
+
+ // 保持程序运行
+ select {}
+}
diff --git a/Web开发/04go-viper-demo/06viper_json.go b/Web开发/04go-viper-demo/06viper_json.go
new file mode 100644
index 0000000..cc0dbf1
--- /dev/null
+++ b/Web开发/04go-viper-demo/06viper_json.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/viper"
+)
+
+func main() {
+ viper.SetConfigName("config")
+ viper.SetConfigType("json")
+ viper.AddConfigPath(".")
+
+ if err := viper.ReadInConfig(); err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
+ fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
+}
diff --git a/Web开发/04go-viper-demo/07viper_toml.go b/Web开发/04go-viper-demo/07viper_toml.go
new file mode 100644
index 0000000..a06310e
--- /dev/null
+++ b/Web开发/04go-viper-demo/07viper_toml.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/spf13/viper"
+)
+
+func main() {
+ // 直接指定完整文件路径(避免和其他 config.* 文件冲突)
+ viper.SetConfigFile("./myconfig.toml")
+
+ if err := viper.ReadInConfig(); err != nil {
+ panic(err)
+ }
+
+ // 打印实际读取的配置文件
+ fmt.Printf("Using config file: %s\n\n", viper.ConfigFileUsed())
+
+ fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
+ fmt.Printf("App Port: %d\n", viper.GetInt("app.port"))
+}
diff --git a/Web开发/04go-viper-demo/config.dev.yaml b/Web开发/04go-viper-demo/config.dev.yaml
new file mode 100644
index 0000000..041b618
--- /dev/null
+++ b/Web开发/04go-viper-demo/config.dev.yaml
@@ -0,0 +1,11 @@
+app:
+ name: MyApp-Dev
+ port: 8080
+ debug: true
+
+database:
+ host: localhost
+ port: 3306
+ username: root
+ password: password
+ dbname: dev_db
diff --git a/Web开发/04go-viper-demo/config.json b/Web开发/04go-viper-demo/config.json
new file mode 100644
index 0000000..788e5f4
--- /dev/null
+++ b/Web开发/04go-viper-demo/config.json
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "name": "MyApp",
+ "port": 8080
+ },
+ "database": {
+ "host": "localhost",
+ "port": 3306
+ }
+}
diff --git a/Web开发/04go-viper-demo/config.prod.yaml b/Web开发/04go-viper-demo/config.prod.yaml
new file mode 100644
index 0000000..1cd62bc
--- /dev/null
+++ b/Web开发/04go-viper-demo/config.prod.yaml
@@ -0,0 +1,11 @@
+app:
+ name: MyApp-Prod
+ port: 80
+ debug: false
+
+database:
+ host: prod.database.com
+ port: 3306
+ username: prod_user
+ password: prod_password
+ dbname: prod_db
diff --git a/Web开发/04go-viper-demo/config.yaml b/Web开发/04go-viper-demo/config.yaml
new file mode 100644
index 0000000..c12f74a
--- /dev/null
+++ b/Web开发/04go-viper-demo/config.yaml
@@ -0,0 +1,17 @@
+app:
+ name: MangMang
+ version: 1.0.0
+ port: 9999
+
+database:
+ host: localhost
+ port: 3306
+ username: root
+ password: password
+ dbname: testdb
+
+redis:
+ host: localhost
+ port: 6379
+ password: ""
+ db: 0
diff --git a/Web开发/04go-viper-demo/go.mod b/Web开发/04go-viper-demo/go.mod
new file mode 100644
index 0000000..1942eb4
--- /dev/null
+++ b/Web开发/04go-viper-demo/go.mod
@@ -0,0 +1,26 @@
+module go-viper-demo
+
+go 1.22.2
+
+require (
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spf13/afero v1.11.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/viper v1.18.2 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.9.0 // indirect
+ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/Web开发/04go-viper-demo/go.sum b/Web开发/04go-viper-demo/go.sum
new file mode 100644
index 0000000..3fadd40
--- /dev/null
+++ b/Web开发/04go-viper-demo/go.sum
@@ -0,0 +1,52 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/Web开发/04go-viper-demo/myconfig.toml b/Web开发/04go-viper-demo/myconfig.toml
new file mode 100644
index 0000000..9e46bea
--- /dev/null
+++ b/Web开发/04go-viper-demo/myconfig.toml
@@ -0,0 +1,7 @@
+[app]
+name = "MyApp"
+port = 8080
+
+[database]
+host = "localhost"
+port = 3306
diff --git a/Web开发/05go-zap-demo/01zap_basic.go b/Web开发/05go-zap-demo/01zap_basic.go
new file mode 100644
index 0000000..e2bbad5
--- /dev/null
+++ b/Web开发/05go-zap-demo/01zap_basic.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "go.uber.org/zap"
+)
+
+func main() {
+ // 1. 创建默认 Logger(开发模式)
+ logger, _ := zap.NewDevelopment()
+ defer logger.Sync() // 刷新缓冲区
+
+ // 2. 基础日志
+ logger.Info("Hello Zap!")
+ logger.Warn("This is a warning")
+ logger.Error("This is an error")
+
+ // 3. 结构化日志(带字段)
+ logger.Info("User logged in",
+ zap.String("username", "alice"),
+ zap.Int("user_id", 123),
+ zap.Bool("is_admin", true),
+ )
+
+ // 4. 不同日志级别
+ logger.Debug("Debug message")
+ logger.Info("Info message")
+ logger.Warn("Warn message")
+ logger.Error("Error message")
+ // logger.Fatal("Fatal message") // 会退出程序
+ // logger.Panic("Panic message") // 会触发 panic
+}
diff --git a/Web开发/05go-zap-demo/02zap_production.go b/Web开发/05go-zap-demo/02zap_production.go
new file mode 100644
index 0000000..6b44c99
--- /dev/null
+++ b/Web开发/05go-zap-demo/02zap_production.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "go.uber.org/zap"
+)
+
+func main() {
+ // 创建生产模式 Logger(JSON 格式,性能更高)
+ logger, _ := zap.NewProduction()
+ defer logger.Sync()
+
+ logger.Info("Application started",
+ zap.String("version", "1.0.0"),
+ zap.Int("port", 8080),
+ )
+
+ logger.Error("Failed to process request",
+ zap.String("method", "GET"),
+ zap.String("path", "/api/users"),
+ zap.Int("status_code", 500),
+ )
+}
diff --git a/Web开发/05go-zap-demo/03zap_custom.go b/Web开发/05go-zap-demo/03zap_custom.go
new file mode 100644
index 0000000..4b266ef
--- /dev/null
+++ b/Web开发/05go-zap-demo/03zap_custom.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+func main() {
+ // 1. 自定义配置
+ config := zap.Config{
+ Level: zap.NewAtomicLevelAt(zap.InfoLevel), // 日志级别
+ Development: false, // 生产模式
+ Encoding: "json", // 输出格式:json/console
+ OutputPaths: []string{"stdout", "app.log"}, // 输出到控制台和文件
+ ErrorOutputPaths: []string{"stderr"},
+ EncoderConfig: zapcore.EncoderConfig{
+ TimeKey: "time",
+ LevelKey: "level",
+ NameKey: "logger",
+ CallerKey: "caller",
+ MessageKey: "msg",
+ StacktraceKey: "stacktrace",
+ LineEnding: zapcore.DefaultLineEnding,
+ EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写级别名
+ EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 时间格式
+ EncodeDuration: zapcore.SecondsDurationEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder, // 短路径
+ },
+ }
+
+ // 2. 创建 Logger
+ logger, _ := config.Build()
+ defer logger.Sync()
+
+ // 3. 使用 Logger
+ logger.Info("Custom logger initialized",
+ zap.String("env", "production"),
+ zap.Int("workers", 10),
+ )
+
+ logger.Warn("Low disk space",
+ zap.Int64("available_mb", 500),
+ zap.Int64("threshold_mb", 1000),
+ )
+
+ logger.Error("Database connection failed",
+ zap.String("host", "localhost"),
+ zap.Int("port", 3306),
+ zap.Error(nil),
+ )
+}
diff --git a/Web开发/05go-zap-demo/04zap_rotate.go b/Web开发/05go-zap-demo/04zap_rotate.go
new file mode 100644
index 0000000..81f3b11
--- /dev/null
+++ b/Web开发/05go-zap-demo/04zap_rotate.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+func main() {
+ // 1. 配置日志轮转
+ logRotate := &lumberjack.Logger{
+ Filename: "./logs/app.log", // 日志文件路径
+ MaxSize: 10, // 每个日志文件最大 10MB
+ MaxBackups: 5, // 保留最近 5 个备份
+ MaxAge: 30, // 保留 30 天
+ Compress: true, // 压缩旧日志
+ }
+
+ // 2. 创建 WriteSyncer
+ writeSyncer := zapcore.AddSync(logRotate)
+
+ // 3. 配置 Encoder
+ encoderConfig := zap.NewProductionEncoderConfig()
+ encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
+ encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
+
+ // 4. 创建 Core
+ core := zapcore.NewCore(
+ zapcore.NewJSONEncoder(encoderConfig),
+ writeSyncer,
+ zap.InfoLevel,
+ )
+
+ // 5. 创建 Logger
+ logger := zap.New(core, zap.AddCaller())
+ defer logger.Sync()
+
+ // 6. 使用 Logger
+ for i := 0; i < 100; i++ {
+ logger.Info("Processing request",
+ zap.Int("request_id", i),
+ zap.String("method", "GET"),
+ zap.String("path", "/api/users"),
+ )
+ }
+
+ logger.Info("Log rotation configured successfully")
+}
diff --git a/Web开发/05go-zap-demo/05zap_multi_output.go b/Web开发/05go-zap-demo/05zap_multi_output.go
new file mode 100644
index 0000000..db96189
--- /dev/null
+++ b/Web开发/05go-zap-demo/05zap_multi_output.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "os"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+func main() {
+ // 1. 配置 Encoder
+ encoderConfig := zap.NewProductionEncoderConfig()
+ encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
+ encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 带颜色的级别
+
+ // 2. 控制台输出(console 格式)
+ consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
+ consoleOutput := zapcore.AddSync(os.Stdout)
+
+ // 3. 文件输出(json 格式)
+ fileEncoder := zapcore.NewJSONEncoder(encoderConfig)
+ file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+ fileOutput := zapcore.AddSync(file)
+
+ // 4. 创建多输出 Core
+ core := zapcore.NewTee(
+ zapcore.NewCore(consoleEncoder, consoleOutput, zap.InfoLevel),
+ zapcore.NewCore(fileEncoder, fileOutput, zap.InfoLevel),
+ )
+
+ // 5. 创建 Logger
+ logger := zap.New(core, zap.AddCaller())
+ defer logger.Sync()
+
+ // 6. 使用 Logger
+ logger.Info("Application started")
+ logger.Warn("This is a warning")
+ logger.Error("This is an error")
+}
diff --git a/Web开发/05go-zap-demo/06zap_sugar.go b/Web开发/05go-zap-demo/06zap_sugar.go
new file mode 100644
index 0000000..8f54f63
--- /dev/null
+++ b/Web开发/05go-zap-demo/06zap_sugar.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "go.uber.org/zap"
+)
+
+func main() {
+ logger, _ := zap.NewProduction()
+ defer logger.Sync()
+
+ // 1. 获取 Sugar Logger
+ sugar := logger.Sugar()
+
+ // 2. 使用格式化字符串(类似 fmt.Printf)
+ sugar.Infof("User %s logged in with ID %d", "alice", 123)
+ sugar.Warnf("Low disk space: %d MB available", 500)
+ sugar.Errorf("Failed to connect to %s:%d", "localhost", 3306)
+
+ // 3. 使用键值对
+ sugar.Infow("User logged in",
+ "username", "alice",
+ "user_id", 123,
+ "is_admin", true,
+ )
+
+ // 4. 简洁日志
+ sugar.Info("Simple info message")
+ sugar.Warn("Simple warn message")
+}
diff --git a/Web开发/05go-zap-demo/07zap_dynamic_level.go b/Web开发/05go-zap-demo/07zap_dynamic_level.go
new file mode 100644
index 0000000..1248be5
--- /dev/null
+++ b/Web开发/05go-zap-demo/07zap_dynamic_level.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "time"
+
+ "go.uber.org/zap"
+)
+
+func main() {
+ // 1. 创建可调整的日志级别
+ atom := zap.NewAtomicLevelAt(zap.InfoLevel)
+
+ // 2. 配置 Logger
+ config := zap.Config{
+ Level: atom,
+ Development: false,
+ Encoding: "json",
+ OutputPaths: []string{"stdout"},
+ ErrorOutputPaths: []string{"stderr"},
+ EncoderConfig: zap.NewProductionEncoderConfig(),
+ }
+
+ logger, _ := config.Build()
+ defer logger.Sync()
+
+ // 3. 初始日志级别为 Info
+ logger.Debug("Debug message - 1") // 不会输出
+ logger.Info("Info message - 1") // 会输出
+
+ // 4. 动态调整为 Debug 级别
+ time.Sleep(1 * time.Second)
+ atom.SetLevel(zap.DebugLevel)
+ logger.Info("Log level changed to Debug")
+
+ logger.Debug("Debug message - 2") // 现在会输出
+ logger.Info("Info message - 2") // 会输出
+
+ // 5. 动态调整为 Warn 级别
+ time.Sleep(1 * time.Second)
+ atom.SetLevel(zap.WarnLevel)
+ logger.Info("Log level changed to Warn")
+
+ logger.Info("Info message - 3") // 不会输出
+ logger.Warn("Warn message - 3") // 会输出
+}
diff --git a/Web开发/05go-zap-demo/08zap_global.go b/Web开发/05go-zap-demo/08zap_global.go
new file mode 100644
index 0000000..b6c3f3b
--- /dev/null
+++ b/Web开发/05go-zap-demo/08zap_global.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "go.uber.org/zap"
+)
+
+func main() {
+ // 1. 创建 Logger
+ logger, _ := zap.NewProduction()
+ defer logger.Sync()
+
+ // 2. 设置为全局 Logger
+ zap.ReplaceGlobals(logger)
+
+ // 3. 在任何地方使用全局 Logger
+ doSomething()
+ doAnotherThing()
+}
+
+func doSomething() {
+ // 直接使用全局 Logger
+ zap.L().Info("Doing something",
+ zap.String("function", "doSomething"),
+ )
+}
+
+func doAnotherThing() {
+ // 使用全局 Sugar Logger
+ zap.S().Infow("Doing another thing",
+ "function", "doAnotherThing",
+ )
+}
diff --git a/Web开发/05go-zap-demo/09zap_performance.go b/Web开发/05go-zap-demo/09zap_performance.go
new file mode 100644
index 0000000..735f587
--- /dev/null
+++ b/Web开发/05go-zap-demo/09zap_performance.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+func main() {
+ // 1. 使用 Sampling 采样(减少高频日志)
+ config := zap.NewProductionConfig()
+ config.Sampling = &zap.SamplingConfig{
+ Initial: 100, // 前 100 条日志全部记录
+ Thereafter: 100, // 之后每 100 条记录 1 条
+ }
+
+ logger, _ := config.Build()
+ defer logger.Sync()
+
+ // 2. 预分配字段(避免重复创建)
+ baseFields := []zapcore.Field{
+ zap.String("service", "user-service"),
+ zap.String("version", "1.0.0"),
+ }
+
+ // 3. 使用 With 创建子 Logger
+ userLogger := logger.With(baseFields...)
+
+ // 4. 高效记录日志
+ for i := 0; i < 1000; i++ {
+ userLogger.Info("Processing request",
+ zap.Int("request_id", i),
+ )
+ }
+}
diff --git a/Web开发/05go-zap-demo/app.log b/Web开发/05go-zap-demo/app.log
new file mode 100644
index 0000000..6b26b72
--- /dev/null
+++ b/Web开发/05go-zap-demo/app.log
@@ -0,0 +1,6 @@
+{"level":"info","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:36","msg":"Custom logger initialized","env":"production","workers":10}
+{"level":"warn","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:41","msg":"Low disk space","available_mb":500,"threshold_mb":1000}
+{"level":"error","time":"2025-12-30T10:08:51.909+0800","caller":"05go-zap-demo/03zap_custom.go:46","msg":"Database connection failed","host":"localhost","port":3306,"stacktrace":"main.main\n\t/home/liumangmang/GolandProjects/learn-golang/Web开发/05go-zap-demo/03zap_custom.go:46\nruntime.main\n\t/usr/lib/go-1.22/src/runtime/proc.go:271"}
+{"level":"\u001b[34mINFO\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:35","msg":"Application started"}
+{"level":"\u001b[33mWARN\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:36","msg":"This is a warning"}
+{"level":"\u001b[31mERROR\u001b[0m","ts":"2025-12-30T10:12:45.344+0800","caller":"05go-zap-demo/05zap_multi_output.go:37","msg":"This is an error"}
diff --git a/Web开发/05go-zap-demo/go.mod b/Web开发/05go-zap-demo/go.mod
new file mode 100644
index 0000000..dbc458e
--- /dev/null
+++ b/Web开发/05go-zap-demo/go.mod
@@ -0,0 +1,9 @@
+module go-zap-demo
+
+go 1.22.2
+
+require (
+ go.uber.org/multierr v1.10.0 // indirect
+ go.uber.org/zap v1.26.0 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
+)
diff --git a/Web开发/05go-zap-demo/go.sum b/Web开发/05go-zap-demo/go.sum
new file mode 100644
index 0000000..d2eae48
--- /dev/null
+++ b/Web开发/05go-zap-demo/go.sum
@@ -0,0 +1,6 @@
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
diff --git a/Web开发/05go-zap-demo/logs/app.log b/Web开发/05go-zap-demo/logs/app.log
new file mode 100644
index 0000000..c4559a6
--- /dev/null
+++ b/Web开发/05go-zap-demo/logs/app.log
@@ -0,0 +1,101 @@
+{"level":"INFO","ts":"2025-12-30T10:11:01.060+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":0,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":1,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":2,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":3,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":4,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":5,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":6,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":7,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":8,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":9,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":10,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":11,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":12,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":13,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":14,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":15,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":16,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":17,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":18,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":19,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":20,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":21,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":22,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":23,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":24,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":25,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":26,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":27,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":28,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":29,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":30,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":31,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":32,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":33,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":34,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":35,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":36,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":37,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":38,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":39,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":40,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":41,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":42,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":43,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":44,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":45,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":46,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":47,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":48,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":49,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":50,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":51,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":52,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":53,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":54,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":55,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":56,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":57,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":58,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":59,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":60,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":61,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":62,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":63,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":64,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":65,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":66,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":67,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":68,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":69,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":70,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":71,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":72,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":73,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":74,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":75,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":76,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":77,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":78,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":79,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":80,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":81,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":82,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":83,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":84,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":85,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":86,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":87,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":88,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":89,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":90,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":91,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":92,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":93,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":94,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":95,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":96,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":97,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":98,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:40","msg":"Processing request","request_id":99,"method":"GET","path":"/api/users"}
+{"level":"INFO","ts":"2025-12-30T10:11:01.061+0800","caller":"05go-zap-demo/04zap_rotate.go:47","msg":"Log rotation configured successfully"}
diff --git a/Web开发/06go-auth-api/.idea/.gitignore b/Web开发/06go-auth-api/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/Web开发/06go-auth-api/.idea/06go-auth-api.iml b/Web开发/06go-auth-api/.idea/06go-auth-api.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/06go-auth-api.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Web开发/06go-auth-api/.idea/dataSources.xml b/Web开发/06go-auth-api/.idea/dataSources.xml
new file mode 100644
index 0000000..61a06b0
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/dataSources.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/auth.db
+ $ProjectFileDir$
+
+
+ file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar
+
+
+ file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
+
+
+
+
+
\ No newline at end of file
diff --git a/Web开发/06go-auth-api/.idea/data_source_mapping.xml b/Web开发/06go-auth-api/.idea/data_source_mapping.xml
new file mode 100644
index 0000000..013ec61
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/data_source_mapping.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Web开发/06go-auth-api/.idea/modules.xml b/Web开发/06go-auth-api/.idea/modules.xml
new file mode 100644
index 0000000..b1dbaf2
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Web开发/06go-auth-api/.idea/vcs.xml b/Web开发/06go-auth-api/.idea/vcs.xml
new file mode 100644
index 0000000..b2bdec2
--- /dev/null
+++ b/Web开发/06go-auth-api/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Web开发/06go-auth-api/21综合实战项目.md b/Web开发/06go-auth-api/21综合实战项目.md
new file mode 100644
index 0000000..831bd76
--- /dev/null
+++ b/Web开发/06go-auth-api/21综合实战项目.md
@@ -0,0 +1,826 @@
+---
+title: 综合实战项目
+icon: mdi:shield-account
+date: 2025-12-23
+category:
+ - Go
+ - 后端
+ - 工程化
+ - 实战项目
+tag:
+ - API
+ - 用户认证
+ - 密码加密
+ - JWT
+ - 综合实战
+---
+
+整合 Gin、GORM、Viper、Zap 等框架,开发一个完整的用户注册/登录 API。这个项目包含用户认证、密码加密、JWT 令牌和数据库操作,是学习 Go Web 开发的完美案例。
+
+
+
+---
+
+# Go 综合实战:用户注册/登录 API 完整指南
+
+这是一个完整的 Web 应用示例,整合了前面学到的所有知识:Gin、GORM、Viper、Zap、中间件等。
+
+---
+
+## 一、项目结构
+
+```
+go-auth-api/
+├── config/
+│ ├── app.yaml
+│ ├── app.dev.yaml
+│ └── app.prod.yaml
+├── logs/
+│ └── app.log
+├── main.go
+├── config.go
+├── logger.go
+├── db.go
+├── models.go
+├── handlers.go
+├── middleware.go
+├── jwt.go
+├── utils.go
+└── go.mod
+```
+
+---
+
+## 二、项目初始化
+
+### 2.1 创建项目
+
+```bash
+cd ~/GolandProjects
+mkdir go-auth-api && cd go-auth-api
+go mod init go-auth-api
+
+# 安装依赖
+go get -u github.com/gin-gonic/gin
+go get -u gorm.io/gorm
+go get -u gorm.io/driver/sqlite
+go get -u github.com/spf13/viper
+go get -u go.uber.org/zap
+go get -u github.com/golang-jwt/jwt/v4
+go get -u golang.org/x/crypto
+go get -u gopkg.in/natefinch/lumberjack.v2
+```
+
+> **版本说明**:代码兼容 Go 1.18+ 版本(建议 1.22+ 体验最佳性能)
+
+### 2.2 创建目录
+
+```bash
+mkdir -p config logs
+```
+
+---
+
+## 三、配置文件
+
+### 3.1 config/app.yaml
+
+```yaml
+app:
+ name: AuthAPI
+ version: 1.0.0
+ port: 8080
+ env: dev
+
+database:
+ driver: sqlite
+ path: auth.db
+
+jwt:
+ secret: your-secret-key-change-in-production
+ expire: 86400 # 24 小时
+
+password:
+ bcrypt_cost: 10
+
+logging:
+ level: info
+ format: json
+```
+
+### 3.2 config/app.prod.yaml
+
+```yaml
+app:
+ port: 80
+ env: prod
+
+logging:
+ level: warn
+```
+
+---
+
+## 四、模型定义(models.go)
+
+```go
+package main
+
+import (
+ "time"
+ "gorm.io/gorm"
+)
+
+type User struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Name string `gorm:"size:100;not null" json:"name"`
+ Email string `gorm:"size:100;unique;not null" json:"email"`
+ Password string `gorm:"size:255;not null" json:"-"` // 不在 JSON 中显示
+ Phone string `gorm:"size:20" json:"phone,omitempty"`
+ Age int `json:"age,omitempty"`
+ Active bool `gorm:"default:true" json:"active"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt gorm.DeletedAt `json:"-"`
+}
+
+func (User) TableName() string {
+ return "users"
+}
+
+// 请求体
+type RegisterRequest struct {
+ Name string `json:"name" binding:"required"`
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required,min=6"`
+ Phone string `json:"phone" binding:"omitempty,len=11"`
+}
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required"`
+}
+
+type LoginResponse struct {
+ Token string `json:"token"`
+ User User `json:"user"`
+}
+```
+
+---
+
+## 五、数据库初始化(db.go)
+
+```go
+package main
+
+import (
+ "fmt"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+var DB *gorm.DB
+
+func InitDB(cfg *Config) error {
+ var dsn string
+
+ if cfg.Database.Driver == "sqlite" {
+ dsn = cfg.Database.Path
+ }
+
+ var err error
+ DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
+ if err != nil {
+ return fmt.Errorf("failed to connect database: %w", err)
+ }
+
+ // 自动迁移
+ if err = DB.AutoMigrate(&User{}); err != nil {
+ return fmt.Errorf("failed to migrate database: %w", err)
+ }
+
+ Logger.Info("Database initialized successfully")
+ return nil
+}
+```
+
+---
+
+## 六、JWT 和密码工具(jwt.go + utils.go)
+
+### 6.1 jwt.go
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+ "github.com/golang-jwt/jwt/v4"
+)
+
+type Claims struct {
+ UserID uint `json:"user_id"`
+ Email string `json:"email"`
+ jwt.RegisteredClaims
+}
+
+func GenerateToken(userID uint, email string, secret string, expire int64) (string, error) {
+ claims := Claims{
+ UserID: userID,
+ Email: email,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString([]byte(secret))
+ if err != nil {
+ return "", err
+ }
+
+ return tokenString, nil
+}
+
+func VerifyToken(tokenString string, secret string) (*Claims, error) {
+ claims := &Claims{}
+
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+ }
+ return []byte(secret), nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ if !token.Valid {
+ return nil, fmt.Errorf("invalid token")
+ }
+
+ return claims, nil
+}
+```
+
+### 6.2 utils.go
+
+```go
+package main
+
+import (
+ "golang.org/x/crypto/bcrypt"
+)
+
+// 密码加密
+func HashPassword(password string) (string, error) {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
+ if err != nil {
+ return "", err
+ }
+ return string(hash), nil
+}
+
+// 验证密码
+func VerifyPassword(hashedPassword, password string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+ return err == nil
+}
+
+// 检查邮箱是否已注册
+func EmailExists(email string) bool {
+ var count int64
+ DB.Model(&User{}).Where("email = ?", email).Count(&count)
+ return count > 0
+}
+```
+
+---
+
+## 七、中间件(middleware.go)
+
+```go
+package main
+
+import (
+ "strings"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// 请求日志中间件
+func LoggingMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ Logger.Info("Request",
+ zap.String("method", c.Request.Method),
+ zap.String("path", c.Request.URL.Path),
+ zap.String("ip", c.ClientIP()),
+ )
+
+ c.Next()
+
+ Logger.Info("Response",
+ zap.String("path", c.Request.URL.Path),
+ zap.Int("status", c.Writer.Status()),
+ )
+ }
+}
+
+// JWT 认证中间件
+func AuthMiddleware(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ authHeader := c.GetHeader("Authorization")
+
+ if authHeader == "" {
+ c.JSON(401, gin.H{"error": "Missing authorization header"})
+ c.Abort()
+ return
+ }
+
+ parts := strings.SplitN(authHeader, " ", 2)
+ if len(parts) != 2 || parts[0] != "Bearer" {
+ c.JSON(401, gin.H{"error": "Invalid authorization header"})
+ c.Abort()
+ return
+ }
+
+ claims, err := VerifyToken(parts[1], cfg.JWT.Secret)
+ if err != nil {
+ Logger.Error("Token verification failed", zap.Error(err))
+ c.JSON(401, gin.H{"error": "Invalid token"})
+ c.Abort()
+ return
+ }
+
+ // 将用户信息存储在上下文中
+ c.Set("user_id", claims.UserID)
+ c.Set("email", claims.Email)
+
+ c.Next()
+ }
+}
+
+// CORS 中间件
+func CORSMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+ c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+
+ if c.Request.Method == "OPTIONS" {
+ c.AbortWithStatus(204)
+ return
+ }
+
+ c.Next()
+ }
+}
+```
+
+---
+
+## 八、业务逻辑处理器(handlers.go)
+
+```go
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// 注册用户
+func Register(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ var req RegisterRequest
+
+ if err := c.ShouldBindJSON(&req); err != nil {
+ Logger.Error("Invalid registration request", zap.Error(err))
+ c.JSON(400, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 检查邮箱是否已存在
+ if EmailExists(req.Email) {
+ c.JSON(400, gin.H{"error": "Email already registered"})
+ return
+ }
+
+ // 密码加密
+ hashedPassword, err := HashPassword(req.Password)
+ if err != nil {
+ Logger.Error("Failed to hash password", zap.Error(err))
+ c.JSON(500, gin.H{"error": "Internal server error"})
+ return
+ }
+
+ // 创建用户
+ user := User{
+ Name: req.Name,
+ Email: req.Email,
+ Password: hashedPassword,
+ Phone: req.Phone,
+ }
+
+ if err := DB.Create(&user).Error; err != nil {
+ Logger.Error("Failed to create user", zap.Error(err))
+ c.JSON(500, gin.H{"error": "Failed to register user"})
+ return
+ }
+
+ Logger.Info("User registered successfully", zap.String("email", user.Email))
+
+ c.JSON(201, gin.H{
+ "message": "User registered successfully",
+ "user_id": user.ID,
+ })
+ }
+}
+
+// 用户登录
+func Login(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ var req LoginRequest
+
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(400, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 查找用户
+ var user User
+ if err := DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
+ Logger.Warn("User not found", zap.String("email", req.Email))
+ c.JSON(401, gin.H{"error": "Invalid email or password"})
+ return
+ }
+
+ // 验证密码
+ if !VerifyPassword(user.Password, req.Password) {
+ Logger.Warn("Invalid password", zap.String("email", req.Email))
+ c.JSON(401, gin.H{"error": "Invalid email or password"})
+ return
+ }
+
+ // 生成 JWT token
+ token, err := GenerateToken(user.ID, user.Email, cfg.JWT.Secret, int64(cfg.JWT.Expire))
+ if err != nil {
+ Logger.Error("Failed to generate token", zap.Error(err))
+ c.JSON(500, gin.H{"error": "Failed to generate token"})
+ return
+ }
+
+ Logger.Info("User logged in successfully", zap.String("email", user.Email))
+
+ c.JSON(200, LoginResponse{
+ Token: token,
+ User: User{
+ ID: user.ID,
+ Name: user.Name,
+ Email: user.Email,
+ Phone: user.Phone,
+ Age: user.Age,
+ },
+ })
+ }
+}
+
+// 获取用户信息
+func GetProfile(c *gin.Context) {
+ userID, _ := c.Get("user_id")
+
+ var user User
+ if err := DB.First(&user, userID.(uint)).Error; err != nil {
+ c.JSON(404, gin.H{"error": "User not found"})
+ return
+ }
+
+ c.JSON(200, user)
+}
+
+// 更新用户信息
+func UpdateProfile(c *gin.Context) {
+ userID, _ := c.Get("user_id")
+
+ var req struct {
+ Name string `json:"name"`
+ Phone string `json:"phone"`
+ Age int `json:"age"`
+ }
+
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(400, gin.H{"error": err.Error()})
+ return
+ }
+
+ if err := DB.Model(&User{}).Where("id = ?", userID.(uint)).Updates(req).Error; err != nil {
+ Logger.Error("Failed to update user", zap.Error(err))
+ c.JSON(500, gin.H{"error": "Failed to update profile"})
+ return
+ }
+
+ Logger.Info("User profile updated", zap.Uint("user_id", userID.(uint)))
+ c.JSON(200, gin.H{"message": "Profile updated successfully"})
+}
+
+// 健康检查
+func HealthCheck(c *gin.Context) {
+ c.JSON(200, gin.H{
+ "status": "ok",
+ "app": "AuthAPI",
+ })
+}
+```
+
+---
+
+## 九、配置加载(config.go)
+
+```go
+package main
+
+import (
+ "fmt"
+ "os"
+ "github.com/spf13/viper"
+)
+
+type Config struct {
+ App struct {
+ Name string `mapstructure:"name"`
+ Version string `mapstructure:"version"`
+ Port int `mapstructure:"port"`
+ Env string `mapstructure:"env"`
+ } `mapstructure:"app"`
+
+ Database struct {
+ Driver string `mapstructure:"driver"`
+ Path string `mapstructure:"path"`
+ } `mapstructure:"database"`
+
+ JWT struct {
+ Secret string `mapstructure:"secret"`
+ Expire int `mapstructure:"expire"`
+ } `mapstructure:"jwt"`
+
+ Logging struct {
+ Level string `mapstructure:"level"`
+ Format string `mapstructure:"format"`
+ } `mapstructure:"logging"`
+}
+
+var GlobalConfig *Config
+
+func LoadConfig() (*Config, error) {
+ env := os.Getenv("GO_ENV")
+ if env == "" {
+ env = "dev"
+ }
+
+ viper.SetConfigName("app")
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath("./config")
+
+ if err := viper.ReadInConfig(); err != nil {
+ return nil, fmt.Errorf("failed to read config: %w", err)
+ }
+
+ viper.SetConfigName("app." + env)
+ viper.MergeInConfig()
+
+ var cfg Config
+ if err := viper.Unmarshal(&cfg); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal config: %w", err)
+ }
+
+ GlobalConfig = &cfg
+ return &cfg, nil
+}
+```
+
+---
+
+## 十、日志初始化(logger.go)
+
+```go
+package main
+
+import (
+ "os"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+var Logger *zap.Logger
+
+func InitLogger(env string) error {
+ var level zapcore.Level
+
+ switch env {
+ case "prod":
+ level = zapcore.WarnLevel
+ case "test":
+ level = zapcore.DebugLevel
+ default:
+ level = zapcore.InfoLevel
+ }
+
+ logFile := &lumberjack.Logger{
+ Filename: "logs/app.log",
+ MaxSize: 100,
+ MaxBackups: 10,
+ MaxAge: 7,
+ Compress: true,
+ }
+
+ encoderConfig := zapcore.EncoderConfig{
+ TimeKey: "ts",
+ LevelKey: "level",
+ MessageKey: "msg",
+ CallerKey: "caller",
+ EncodeLevel: zapcore.LowercaseLevelEncoder,
+ EncodeTime: zapcore.ISO8601TimeEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder,
+ }
+
+ core := zapcore.NewCore(
+ zapcore.NewJSONEncoder(encoderConfig),
+ zapcore.NewMultiWriteSyncer(
+ zapcore.AddSync(os.Stdout),
+ zapcore.AddSync(logFile),
+ ),
+ level,
+ )
+
+ Logger = zap.New(core, zap.AddCaller())
+ zap.ReplaceGlobals(Logger)
+
+ return nil
+}
+```
+
+---
+
+## 十一、主程序(main.go)
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ // 加载配置
+ cfg, err := LoadConfig()
+ if err != nil {
+ panic(err)
+ }
+
+ // 初始化日志
+ if err = InitLogger(cfg.App.Env); err != nil {
+ panic(err)
+ }
+ defer Logger.Sync()
+
+ // 初始化数据库
+ if err = InitDB(cfg); err != nil {
+ Logger.Fatal("Failed to initialize database", zap.Error(err))
+ }
+
+ // 创建 Gin 应用
+ r := gin.Default()
+
+ // 应用中间件
+ r.Use(LoggingMiddleware())
+ r.Use(CORSMiddleware())
+
+ // 公开路由
+ public := r.Group("/api")
+ {
+ public.GET("/health", HealthCheck)
+ public.POST("/register", Register(cfg))
+ public.POST("/login", Login(cfg))
+ }
+
+ // 受保护的路由
+ protected := r.Group("/api")
+ protected.Use(AuthMiddleware(cfg))
+ {
+ protected.GET("/profile", GetProfile)
+ protected.PUT("/profile", UpdateProfile)
+ }
+
+ // 启动服务器
+ addr := fmt.Sprintf(":%d", cfg.App.Port)
+ Logger.Info("Server starting",
+ zap.String("app", cfg.App.Name),
+ zap.Int("port", cfg.App.Port),
+ zap.String("env", cfg.App.Env),
+ )
+
+ if err = r.Run(addr); err != nil {
+ Logger.Fatal("Server error", zap.Error(err))
+ }
+}
+```
+
+---
+
+## 十二、API 使用示例
+
+### 12.1 注册用户
+
+```bash
+curl -X POST http://localhost:8080/api/register \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Alice",
+ "email": "alice@example.com",
+ "password": "password123",
+ "phone": "13800138000"
+ }'
+
+# 响应
+{
+ "message": "User registered successfully",
+ "user_id": 1
+}
+```
+
+### 12.2 用户登录
+
+```bash
+curl -X POST http://localhost:8080/api/login \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "alice@example.com",
+ "password": "password123"
+ }'
+
+# 响应
+{
+ "token": "eyJhbGciOiJIUzI1NiIs...",
+ "user": {
+ "id": 1,
+ "name": "Alice",
+ "email": "alice@example.com",
+ "phone": "13800138000"
+ }
+}
+```
+
+### 12.3 获取用户信息(需要认证)
+
+```bash
+curl -X GET http://localhost:8080/api/profile \
+ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
+
+# 响应
+{
+ "id": 1,
+ "name": "Alice",
+ "email": "alice@example.com",
+ "phone": "13800138000",
+ "age": 0,
+ "active": true,
+ "created_at": "2025-12-23T14:30:45Z"
+}
+```
+
+---
+
+## 十三、项目验收清单
+
+- ✅ 用户注册(密码加密、邮箱验证)
+- ✅ 用户登录(JWT 令牌生成)
+- ✅ 用户认证(JWT 验证中间件)
+- ✅ 用户信息管理(获取、更新)
+- ✅ 配置管理(多环境)
+- ✅ 日志记录(结构化、日志轮转)
+- ✅ 错误处理(友好的错误消息)
+- ✅ 代码组织(清晰的文件结构)
+
+---
+
+## 十四、进阶扩展方向
+
+1. **添加邮箱验证** - 发送验证码验证邮箱
+2. **实现刷新令牌** - 增加安全性
+3. **添加速率限制** - 防止暴力破解
+4. **用户权限管理** - 基于角色的访问控制
+5. **社交登录** - 集成 OAuth2(GitHub、Google)
+6. **单元测试** - 为关键业务逻辑编写测试
+7. **Docker 打包** - 容器化部署
+8. **CI/CD 流程** - 自动化测试和部署
+
+---
+
+祝你编码愉快!🚀 这个项目整合了 Go Web 开发的所有核心知识,是学习和面试的好材料。
+
diff --git a/Web开发/06go-auth-api/auth.db b/Web开发/06go-auth-api/auth.db
new file mode 100644
index 0000000..b4af3e6
Binary files /dev/null and b/Web开发/06go-auth-api/auth.db differ
diff --git a/Web开发/06go-auth-api/config.go b/Web开发/06go-auth-api/config.go
new file mode 100644
index 0000000..6df2460
--- /dev/null
+++ b/Web开发/06go-auth-api/config.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/viper"
+)
+
+type Config struct {
+ App struct {
+ Name string `mapstructure:"name"`
+ Version string `mapstructure:"version"`
+ Port int `mapstructure:"port"`
+ Env string `mapstructure:"env"`
+ } `mapstructure:"app"`
+
+ Database struct {
+ Driver string `mapstructure:"driver"`
+ Path string `mapstructure:"path"`
+ } `mapstructure:"database"`
+
+ JWT struct {
+ Secret string `mapstructure:"secret"`
+ Expire int `mapstructure:"expire"`
+ } `mapstructure:"jwt"`
+
+ Logging struct {
+ Level string `mapstructure:"level"`
+ Format string `mapstructure:"format"`
+ } `mapstructure:"logging"`
+}
+
+var GlobalConfig *Config
+
+func LoadConfig() (*Config, error) {
+ env := os.Getenv("APP_ENV")
+ if env == "" {
+ env = "dev"
+ }
+
+ viper.SetConfigName("app")
+ viper.AddConfigPath("./config")
+ viper.SetConfigType("yaml")
+
+ err := viper.ReadInConfig()
+ if err != nil {
+ return nil, fmt.Errorf("读取配置文件失败: %w", err)
+ }
+
+ viper.SetConfigName("app." + env)
+ err = viper.MergeInConfig()
+ var cfg Config
+ err = viper.Unmarshal(&cfg)
+ if err != nil {
+ return nil, fmt.Errorf("解析配置文件失败: %w", err)
+ }
+ GlobalConfig = &cfg
+ return &cfg, nil
+}
diff --git a/Web开发/06go-auth-api/config/app.prod.yaml b/Web开发/06go-auth-api/config/app.prod.yaml
new file mode 100644
index 0000000..b09512f
--- /dev/null
+++ b/Web开发/06go-auth-api/config/app.prod.yaml
@@ -0,0 +1,6 @@
+app:
+ port: 80
+ env: prod
+
+logging:
+ level: warn
diff --git a/Web开发/06go-auth-api/config/app.yaml b/Web开发/06go-auth-api/config/app.yaml
new file mode 100644
index 0000000..985cdec
--- /dev/null
+++ b/Web开发/06go-auth-api/config/app.yaml
@@ -0,0 +1,20 @@
+app:
+ name: AuthAPI
+ version: 1.0.0
+ port: 8088
+ env: dev
+
+database:
+ driver: sqlite
+ path: auth.db
+
+jwt:
+ secret: your-secret-key-change-in-production
+ expire: 86400 # 24 小时
+
+password:
+ bcrypt_cost: 10
+
+logging:
+ level: info
+ format: json
diff --git a/Web开发/06go-auth-api/db.go b/Web开发/06go-auth-api/db.go
new file mode 100644
index 0000000..f76acbb
--- /dev/null
+++ b/Web开发/06go-auth-api/db.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ "fmt"
+
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+var DB *gorm.DB
+
+func InitDB(cfg *Config) error {
+ var dsn string
+ if cfg.Database.Driver == "sqlite" {
+ dsn = cfg.Database.Path
+ }
+
+ var err error
+ DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
+ if err != nil {
+ return fmt.Errorf("连接数据库失败: %w", err)
+ }
+
+ //自动迁移
+ err = DB.AutoMigrate(&User{})
+ if err != nil {
+ return fmt.Errorf("自动迁移数据库失败: %w", err)
+ }
+
+ Logger.Info("数据库初始化成功")
+ return nil
+}
diff --git a/Web开发/06go-auth-api/go.mod b/Web开发/06go-auth-api/go.mod
new file mode 100644
index 0000000..2d2860f
--- /dev/null
+++ b/Web开发/06go-auth-api/go.mod
@@ -0,0 +1,62 @@
+module go-auth-api
+
+go 1.22.2
+
+require (
+ github.com/golang-jwt/jwt/v4 v4.5.2
+ go.uber.org/zap v1.27.0
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1
+ gorm.io/driver/sqlite v1.5.6
+ gorm.io/gorm v1.25.7
+)
+
+require (
+ github.com/bytedance/sonic v1.11.6 // indirect
+ github.com/bytedance/sonic/loader v0.1.1 // indirect
+ github.com/cloudwego/base64x v0.1.4 // indirect
+ github.com/cloudwego/iasm v0.2.0 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-gonic/gin v1.10.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.20.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-sqlite3 v1.14.22 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spf13/afero v1.11.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/viper v1.18.2 // indirect
+ github.com/stretchr/testify v1.9.0 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.12 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ golang.org/x/arch v0.8.0 // indirect
+ golang.org/x/crypto v0.23.0 // indirect
+ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ google.golang.org/protobuf v1.34.1 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/Web开发/06go-auth-api/go.sum b/Web开发/06go-auth-api/go.sum
new file mode 100644
index 0000000..a6ecea5
--- /dev/null
+++ b/Web开发/06go-auth-api/go.sum
@@ -0,0 +1,134 @@
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
+github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.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/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
+gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/Web开发/06go-auth-api/handlers.go b/Web开发/06go-auth-api/handlers.go
new file mode 100644
index 0000000..eb196b5
--- /dev/null
+++ b/Web开发/06go-auth-api/handlers.go
@@ -0,0 +1,159 @@
+package main
+
+import (
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// 注册用户
+func Register(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ var req RegisterRequest
+ err := c.ShouldBindJSON(&req)
+ if err != nil {
+ Logger.Error("参数错误", zap.Error(err))
+ c.JSON(400, gin.H{
+ "message": "参数错误",
+ })
+ return
+ }
+
+ // 验证邮箱
+ if EmailExists(req.Email) {
+ c.JSON(400, gin.H{
+ "message": "邮箱已存在",
+ })
+ return
+ }
+
+ //密码加密
+ hashedPassword, err := HashPassword(req.Password)
+ if err != nil {
+ Logger.Error("密码加密失败", zap.Error(err))
+ c.JSON(500, gin.H{
+ "message": "密码加密失败",
+ })
+ return
+ }
+
+ // 创建用户
+ user := User{
+ Name: req.Name,
+ Email: req.Email,
+ Password: hashedPassword,
+ Phone: req.Phone,
+ }
+
+ err = DB.Create(&user).Error
+ if err != nil {
+ Logger.Error("创建用户失败", zap.Error(err))
+ c.JSON(500, gin.H{
+ "message": "创建用户失败",
+ })
+ return
+ }
+
+ Logger.Info("用户注册成功", zap.String("email", req.Email))
+
+ c.JSON(200, gin.H{
+ "message": "用户注册成功",
+ })
+ }
+}
+
+// 登录
+func Login(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ var req LoginRequest
+
+ err := c.ShouldBindJSON(&req)
+ if err != nil {
+ c.JSON(400, gin.H{"错误": err.Error()})
+ return
+ }
+
+ //查找用户
+ var user User
+ err = DB.Where("email = ?", req.Email).First(&user).Error
+ if err != nil {
+ Logger.Warn("用户不存在", zap.String("email", req.Email))
+ c.JSON(401, gin.H{"错误": "电子邮件或密码无效"})
+ return
+ }
+
+ //验证密码
+ if !VerifyPassword(user.Password, req.Password) {
+ Logger.Warn("密码错误", zap.String("email", req.Email))
+ c.JSON(401, gin.H{"错误": "电子邮件或密码无效"})
+ return
+ }
+
+ //生成token
+ token, err := GenerateToken(user.ID, user.Email, cfg.JWT.Secret, int64(cfg.JWT.Expire))
+
+ if err != nil {
+ Logger.Error("生成token失败", zap.Error(err))
+ c.JSON(500, gin.H{"错误": "生成token失败"})
+ return
+ }
+
+ Logger.Info("用户登录成功", zap.String("email", req.Email))
+
+ c.JSON(200, LoginResponse{
+ Token: token,
+ User: User{
+ ID: user.ID,
+ Name: user.Name,
+ Email: user.Email,
+ Phone: user.Phone,
+ Age: user.Age,
+ },
+ })
+ }
+}
+
+// 获取用户信息
+func GetProfile(c *gin.Context) {
+ userID, _ := c.Get("user_id")
+
+ var user User
+ if err := DB.First(&user, userID.(uint)).Error; err != nil {
+ c.JSON(404, gin.H{"error": "User not found"})
+ return
+ }
+
+ c.JSON(200, user)
+}
+
+// 更新用户信息
+func UpdateProfile(c *gin.Context) {
+ userID, _ := c.Get("user_id")
+
+ var req struct {
+ Name string `json:"name"`
+ Phone string `json:"phone"`
+ Age int `json:"age"`
+ }
+
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(400, gin.H{"error": err.Error()})
+ return
+ }
+
+ if err := DB.Model(&User{}).Where("id = ?", userID.(uint)).Updates(req).Error; err != nil {
+ Logger.Error("Failed to update user", zap.Error(err))
+ c.JSON(500, gin.H{"error": "Failed to update profile"})
+ return
+ }
+
+ Logger.Info("User profile updated", zap.Uint("user_id", userID.(uint)))
+ c.JSON(200, gin.H{"message": "Profile updated successfully"})
+}
+
+// 健康检查
+func HealthCheck(c *gin.Context) {
+ c.JSON(200, gin.H{
+ "status": "ok",
+ "app": "AuthAPI",
+ })
+}
diff --git a/Web开发/06go-auth-api/jwt.go b/Web开发/06go-auth-api/jwt.go
new file mode 100644
index 0000000..c1ffc7f
--- /dev/null
+++ b/Web开发/06go-auth-api/jwt.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/golang-jwt/jwt/v4"
+)
+
+type Claims struct {
+ UserID uint `json:"user_id"`
+ Email string `json:"email"`
+ jwt.RegisteredClaims
+}
+
+func GenerateToken(userID uint, email string, secret string, expire int64) (string, error) {
+ claims := Claims{
+ UserID: userID,
+ Email: email,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ },
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString([]byte(secret))
+ if err != nil {
+ return "", err
+ }
+
+ return tokenString, nil
+}
+
+// VerifyToken 验证JWT令牌的有效性
+// 参数:
+//
+// tokenString - JWT令牌字符串
+// secret - 用于验证令牌的密钥
+//
+// 返回值:
+//
+// *Claims - 解析出的令牌声明信息
+// error - 验证过程中出现的错误
+func VerifyToken(tokenString string, secret string) (*Claims, error) {
+ claims := &Claims{}
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+
+ // 检查签名方法是否为HMAC
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+ }
+ return []byte(secret), nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ // 验证令牌是否有效
+ if !token.Valid {
+ return nil, fmt.Errorf("invalid token")
+ }
+
+ return claims, nil
+
+}
diff --git a/Web开发/06go-auth-api/logger.go b/Web开发/06go-auth-api/logger.go
new file mode 100644
index 0000000..8882476
--- /dev/null
+++ b/Web开发/06go-auth-api/logger.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "os"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+var Logger *zap.Logger
+
+func InitLogger(env string) error {
+ var level zapcore.Level
+
+ switch env {
+ case "prod":
+ level = zapcore.WarnLevel
+ case "test":
+ level = zapcore.DebugLevel
+ default:
+ level = zapcore.InfoLevel
+ }
+
+ logfile := &lumberjack.Logger{
+ Filename: "logs/app.log",
+ MaxSize: 100,
+ MaxBackups: 10,
+ MaxAge: 30,
+ Compress: true,
+ }
+
+ encoderConfig := zapcore.EncoderConfig{
+ TimeKey: "ts",
+ LevelKey: "level",
+ MessageKey: "msg",
+ CallerKey: "caller",
+ EncodeLevel: zapcore.LowercaseLevelEncoder,
+ EncodeTime: zapcore.ISO8601TimeEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder,
+ }
+
+ core := zapcore.NewCore(
+ zapcore.NewJSONEncoder(encoderConfig),
+ zapcore.NewMultiWriteSyncer(
+ zapcore.AddSync(os.Stdout),
+ zapcore.AddSync(logfile),
+ ),
+ level,
+ )
+
+ Logger = zap.New(core, zap.AddCaller())
+ zap.ReplaceGlobals(Logger)
+
+ return nil
+}
diff --git a/Web开发/06go-auth-api/logs/app.log b/Web开发/06go-auth-api/logs/app.log
new file mode 100644
index 0000000..22ccfde
--- /dev/null
+++ b/Web开发/06go-auth-api/logs/app.log
@@ -0,0 +1,24 @@
+{"level":"info","ts":"2025-12-30T17:50:54.962+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
+{"level":"info","ts":"2025-12-30T17:50:54.962+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8080,"env":"dev"}
+{"level":"fatal","ts":"2025-12-30T17:50:54.963+0800","caller":"06go-auth-api/main.go:64","msg":"Server error","error":"listen tcp :8080: bind: address already in use"}
+{"level":"info","ts":"2025-12-30T17:52:46.052+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
+{"level":"info","ts":"2025-12-30T17:52:46.052+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
+{"level":"info","ts":"2025-12-30T17:53:51.134+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"POST","路径":"/api/register","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:53:51.210+0800","caller":"06go-auth-api/handlers.go:56","msg":"用户注册成功","email":"alice@example.com"}
+{"level":"info","ts":"2025-12-30T17:53:51.210+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/register","状态码":200}
+{"level":"info","ts":"2025-12-30T17:54:17.482+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"POST","路径":"/api/login","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:54:17.532+0800","caller":"06go-auth-api/handlers.go:100","msg":"用户登录成功","email":"alice@example.com"}
+{"level":"info","ts":"2025-12-30T17:54:17.532+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/login","状态码":200}
+{"level":"info","ts":"2025-12-30T17:54:53.342+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:55:00.113+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:55:11.577+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:56:24.245+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:56:56.927+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
+{"level":"info","ts":"2025-12-30T17:56:56.927+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
+{"level":"info","ts":"2025-12-30T17:57:06.731+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:58:16.750+0800","caller":"06go-auth-api/db.go:30","msg":"数据库初始化成功"}
+{"level":"info","ts":"2025-12-30T17:58:16.750+0800","caller":"06go-auth-api/main.go:57","msg":"Server starting","app":"AuthAPI","port":8088,"env":"dev"}
+{"level":"info","ts":"2025-12-30T17:58:23.713+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:58:23.714+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/profile","状态码":200}
+{"level":"info","ts":"2025-12-30T17:58:57.676+0800","caller":"06go-auth-api/middleware.go:17","msg":"请求","方法":"GET","路径":"/api/profile","IP":"::1"}
+{"level":"info","ts":"2025-12-30T17:58:57.676+0800","caller":"06go-auth-api/middleware.go:26","msg":"响应","路径":"/api/profile","状态码":401}
diff --git a/Web开发/06go-auth-api/main.go b/Web开发/06go-auth-api/main.go
new file mode 100644
index 0000000..ebaf1fb
--- /dev/null
+++ b/Web开发/06go-auth-api/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+func main() {
+
+ //加载配置
+ cfg, err := LoadConfig()
+ if err != nil {
+ panic(err)
+ }
+
+ //初始化日志
+ err = InitLogger(cfg.App.Env)
+ if err != nil {
+ panic(err)
+ }
+
+ defer Logger.Sync()
+
+ //初始化数据库
+ err = InitDB(cfg)
+ if err != nil {
+ Logger.Fatal("数据库初始化失败", zap.Error(err))
+ }
+
+ //创建Gin应用
+ r := gin.Default()
+
+ //中间件
+ r.Use(LoggingMiddleware())
+ r.Use(CORSMiddleware())
+
+ // 公开路由
+ public := r.Group("/api")
+ {
+ public.GET("/health", HealthCheck)
+ public.POST("/register", Register(cfg))
+ public.POST("/login", Login(cfg))
+ }
+
+ // 受保护的路由
+ protected := r.Group("/api")
+ protected.Use(AuthMiddleware(cfg))
+ {
+ protected.GET("/profile", GetProfile)
+ protected.PUT("/profile", UpdateProfile)
+ }
+
+ // 启动服务器
+ addr := fmt.Sprintf(":%d", cfg.App.Port)
+ Logger.Info("Server starting",
+ zap.String("app", cfg.App.Name),
+ zap.Int("port", cfg.App.Port),
+ zap.String("env", cfg.App.Env),
+ )
+
+ if err = r.Run(addr); err != nil {
+ Logger.Fatal("Server error", zap.Error(err))
+ }
+}
diff --git a/Web开发/06go-auth-api/middleware.go b/Web开发/06go-auth-api/middleware.go
new file mode 100644
index 0000000..27881b6
--- /dev/null
+++ b/Web开发/06go-auth-api/middleware.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// LoggingMiddleware 创建一个请求日志中间件
+// 该中间件会在每个HTTP请求进入时记录请求信息,在响应返回时记录响应信息
+// 参数: 无
+// 返回值: gin.HandlerFunc - Gin框架的中间件处理函数
+func LoggingMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 记录请求日志:方法、路径、客户端IP
+ Logger.Info("请求",
+ zap.String("方法", c.Request.Method),
+ zap.String("路径", c.Request.URL.Path),
+ zap.String("IP", c.ClientIP()),
+ )
+
+ c.Next()
+
+ // 记录响应日志:路径、状态码
+ Logger.Info("响应",
+ zap.String("路径", c.Request.URL.Path),
+ zap.Int("状态码", c.Writer.Status()),
+ )
+ }
+}
+
+// AuthMiddleware JWT 认证中间件
+// 参数: cfg - 配置对象,包含JWT密钥等配置信息
+// 返回值: gin.HandlerFunc - Gin框架的中间件处理函数
+func AuthMiddleware(cfg *Config) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ authHeader := c.GetHeader("Authorization")
+
+ if authHeader == "" {
+ c.JSON(401, gin.H{"错误": "错误的认证头"})
+ c.Abort()
+ return
+ }
+
+ // 解析Authorization头,验证格式为 "Bearer "
+ parts := strings.SplitN(authHeader, " ", 2)
+ if len(parts) != 2 || parts[0] != "Bearer" {
+ c.JSON(401, gin.H{"错误": "错误的认证头"})
+ c.Abort()
+ return
+ }
+
+ // 验证JWT token的有效性
+ claims, err := VerifyToken(parts[1], cfg.JWT.Secret)
+ if err != nil {
+ Logger.Error("验证token失败", zap.Error(err))
+ c.JSON(401, gin.H{"错误": "验证token失败"})
+ c.Abort()
+ return
+ }
+
+ // 将用户信息保存到上下文
+ c.Set("user_id", claims.UserID)
+ c.Set("email", claims.Email)
+ c.Next()
+ }
+}
+
+// CORSMiddleware 创建并返回一个CORS(跨域资源共享)中间件处理器
+// 该中间件用于处理跨域请求,设置相应的响应头信息
+//
+// 参数:
+//
+// 无
+//
+// 返回值:
+//
+// gin.HandlerFunc - Gin框架的中间件处理器函数
+func CORSMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 设置CORS相关响应头
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+ c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+
+ // 处理预检请求(OPTIONS方法)
+ if c.Request.Method == "OPTIONS" {
+ c.AbortWithStatus(204)
+ return
+ }
+ c.Next()
+ }
+}
diff --git a/Web开发/06go-auth-api/models.go b/Web开发/06go-auth-api/models.go
new file mode 100644
index 0000000..66e84b0
--- /dev/null
+++ b/Web开发/06go-auth-api/models.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "time"
+
+ "gorm.io/gorm"
+)
+
+type User struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Name string `gorm:"size:100;not null" json:"name"`
+ Email string `gorm:"size:100;unique;not null" json:"email"`
+ Password string `gorm:"size:255;not null" json:"-"` // 不在 JSON 中显示
+ Phone string `gorm:"size:20" json:"phone,omitempty"`
+ Age int `json:"age,omitempty"`
+ Active bool `gorm:"default:true" json:"active"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt gorm.DeletedAt `json:"-"`
+}
+
+func (User) TableName() string {
+ return "users"
+}
+
+// 请求体
+type RegisterRequest struct {
+ Name string `json:"name" binding:"required"`
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required,min=6"`
+ Phone string `json:"phone" binding:"omitempty,len=11"`
+}
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required,email"`
+ Password string `json:"password" binding:"required"`
+}
+
+type LoginResponse struct {
+ Token string `json:"token"`
+ User User `json:"user"`
+}
diff --git a/Web开发/06go-auth-api/utils.go b/Web开发/06go-auth-api/utils.go
new file mode 100644
index 0000000..3e3fb97
--- /dev/null
+++ b/Web开发/06go-auth-api/utils.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+ "golang.org/x/crypto/bcrypt"
+)
+
+// 密码加密
+func HashPassword(password string) (string, error) {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
+ if err != nil {
+ return "", err
+ }
+
+ return string(hash), nil
+}
+
+// 密码验证
+func VerifyPassword(hashedPassword, password string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+ return err == nil
+}
+
+// 检查邮箱是否注册
+func EmailExists(email string) bool {
+ var count int64
+ DB.Model(&User{}).Where("email = ?", email).Count(&count)
+ return count > 0
+}
diff --git a/go-gin-demo/params_form_demo.go b/go-gin-demo/params_form_demo.go
deleted file mode 100644
index aea3c8c..0000000
--- a/go-gin-demo/params_form_demo.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package main
-
-import (
- "github.com/gin-gonic/gin"
-)
-
-func main() {
- r := gin.Default()
-
- r.POST("/login", func(c *gin.Context) {
- username := c.PostForm("username") // 获取表单参数
- password := c.PostForm("password")
-
- // 简单验证
- if username == "" || password == "" {
- c.JSON(400, gin.H{"error": "username and password required"})
- return
- }
-
- c.JSON(200, gin.H{
- "message": "Login successful",
- "username": username,
- })
- })
-
- r.Run(":8080")
-}
diff --git a/go-gin-demo/params_path_demo.go b/go-gin-demo/params_path_demo.go
deleted file mode 100644
index e2d58c2..0000000
--- a/go-gin-demo/params_path_demo.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package main
-
-import (
- "github.com/gin-gonic/gin"
-)
-
-func main() {
- r := gin.Default()
-
- // 单个参数
- r.GET("/users/:id", func(c *gin.Context) {
- id := c.Param("id") // 获取参数
- c.JSON(200, gin.H{"user_id": id})
- })
-
- // 多个参数
- r.GET("/users/:uid/posts/:pid", func(c *gin.Context) {
- uid := c.Param("uid")
- pid := c.Param("pid")
- c.JSON(200, gin.H{"uid": uid, "pid": pid})
- })
-
- r.Run(":8080")
-}
diff --git a/go-gin-demo/params_query_demo.go b/go-gin-demo/params_query_demo.go
deleted file mode 100644
index 1c0b64c..0000000
--- a/go-gin-demo/params_query_demo.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package main
-
-import (
- "github.com/gin-gonic/gin"
-)
-
-func main() {
- r := gin.Default()
-
- r.GET("/search", func(c *gin.Context) {
- keyword := c.Query("q") // 获取查询参数
- limit := c.DefaultQuery("limit", "10") // 带默认值
- c.JSON(200, gin.H{
- "keyword": keyword,
- "limit": limit,
- })
- })
-
- r.Run(":8080")
-}
diff --git a/go-gorm-demo/db_connect.go b/go-gorm-demo/db_connect.go
deleted file mode 100644
index c05a281..0000000
--- a/go-gorm-demo/db_connect.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package main
-
-import (
- "fmt"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
-)
-
-func main() {
- // 连接 SQLite(文件数据库)
- db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
- if err != nil {
- panic("failed to connect database")
- }
-
- fmt.Println("Database connected successfully!")
-
- // 获取底层的 *sql.DB
- sqlDB, err := db.DB()
- if err != nil {
- panic(err)
- }
-
- // 设置连接池
- sqlDB.SetMaxIdleConns(10)
- sqlDB.SetMaxOpenConns(100)
-
- fmt.Println("Connection pool configured")
-}
diff --git a/hello-go/go.mod b/go基础语法/01hello-go/go.mod
similarity index 100%
rename from hello-go/go.mod
rename to go基础语法/01hello-go/go.mod
diff --git a/hello-go/hello-go b/go基础语法/01hello-go/hello-go
similarity index 100%
rename from hello-go/hello-go
rename to go基础语法/01hello-go/hello-go
diff --git a/hello-go/main.go b/go基础语法/01hello-go/main.go
similarity index 100%
rename from hello-go/main.go
rename to go基础语法/01hello-go/main.go
diff --git a/go-basics/go.mod b/go基础语法/02go-basics/go.mod
similarity index 100%
rename from go-basics/go.mod
rename to go基础语法/02go-basics/go.mod
diff --git a/go-basics/main.go b/go基础语法/02go-basics/main.go
similarity index 100%
rename from go-basics/main.go
rename to go基础语法/02go-basics/main.go
diff --git a/go-slice-map/go.mod b/go基础语法/03go-slice-map/go.mod
similarity index 100%
rename from go-slice-map/go.mod
rename to go基础语法/03go-slice-map/go.mod
diff --git a/go-slice-map/main.go b/go基础语法/03go-slice-map/main.go
similarity index 100%
rename from go-slice-map/main.go
rename to go基础语法/03go-slice-map/main.go
diff --git a/go-struct-methods/go.mod b/go基础语法/04go-struct-methods/go.mod
similarity index 100%
rename from go-struct-methods/go.mod
rename to go基础语法/04go-struct-methods/go.mod
diff --git a/go-struct-methods/main.go b/go基础语法/04go-struct-methods/main.go
similarity index 100%
rename from go-struct-methods/main.go
rename to go基础语法/04go-struct-methods/main.go
diff --git a/go-interfaces/go.mod b/go基础语法/05go-interfaces/go.mod
similarity index 100%
rename from go-interfaces/go.mod
rename to go基础语法/05go-interfaces/go.mod
diff --git a/go-interfaces/main.go b/go基础语法/05go-interfaces/main.go
similarity index 100%
rename from go-interfaces/main.go
rename to go基础语法/05go-interfaces/main.go
diff --git a/go-error-handling/go.mod b/go基础语法/06go-error-handling/go.mod
similarity index 100%
rename from go-error-handling/go.mod
rename to go基础语法/06go-error-handling/go.mod
diff --git a/go-error-handling/main.go b/go基础语法/06go-error-handling/main.go
similarity index 100%
rename from go-error-handling/main.go
rename to go基础语法/06go-error-handling/main.go
diff --git a/go-mini-logger/app.log b/go基础语法/07go-mini-logger/app.log
similarity index 100%
rename from go-mini-logger/app.log
rename to go基础语法/07go-mini-logger/app.log
diff --git a/go-mini-logger/go.mod b/go基础语法/07go-mini-logger/go.mod
similarity index 100%
rename from go-mini-logger/go.mod
rename to go基础语法/07go-mini-logger/go.mod
diff --git a/go-mini-logger/logger.go b/go基础语法/07go-mini-logger/logger.go
similarity index 100%
rename from go-mini-logger/logger.go
rename to go基础语法/07go-mini-logger/logger.go
diff --git a/go-mini-logger/main.go b/go基础语法/07go-mini-logger/main.go
similarity index 100%
rename from go-mini-logger/main.go
rename to go基础语法/07go-mini-logger/main.go
diff --git a/go-goroutine-gpm/go.mod b/go并发模型/01go-goroutine-gpm/go.mod
similarity index 100%
rename from go-goroutine-gpm/go.mod
rename to go并发模型/01go-goroutine-gpm/go.mod
diff --git a/go-goroutine-gpm/main.go b/go并发模型/01go-goroutine-gpm/main.go
similarity index 100%
rename from go-goroutine-gpm/main.go
rename to go并发模型/01go-goroutine-gpm/main.go
diff --git a/go-channel-practice/buffered.go b/go并发模型/02go-channel-practice/buffered.go
similarity index 100%
rename from go-channel-practice/buffered.go
rename to go并发模型/02go-channel-practice/buffered.go
diff --git a/go-channel-practice/directional.go b/go并发模型/02go-channel-practice/directional.go
similarity index 78%
rename from go-channel-practice/directional.go
rename to go并发模型/02go-channel-practice/directional.go
index 6c8b86a..05ecb34 100644
--- a/go-channel-practice/directional.go
+++ b/go并发模型/02go-channel-practice/directional.go
@@ -23,6 +23,6 @@ func consumer(in <-chan int) {
func main() {
ch := make(chan int)
- go producer(ch) // ch 在这里被当作 只发送 channel 使用
- consumer(ch) // ch 在这里被当作 只接收 channel 使用
+ go producer(ch) // ch 在这里被当作 只发送 channel 使用
+ consumer(ch) // ch 在这里被当作 只接收 channel 使用
}
diff --git a/go-channel-practice/go.mod b/go并发模型/02go-channel-practice/go.mod
similarity index 100%
rename from go-channel-practice/go.mod
rename to go并发模型/02go-channel-practice/go.mod
diff --git a/go-channel-practice/pipeline.go b/go并发模型/02go-channel-practice/pipeline.go
similarity index 100%
rename from go-channel-practice/pipeline.go
rename to go并发模型/02go-channel-practice/pipeline.go
diff --git a/go-channel-practice/unbuffered.go b/go并发模型/02go-channel-practice/unbuffered.go
similarity index 100%
rename from go-channel-practice/unbuffered.go
rename to go并发模型/02go-channel-practice/unbuffered.go
diff --git a/go-select-practice/go.mod b/go并发模型/03go-select-practice/go.mod
similarity index 100%
rename from go-select-practice/go.mod
rename to go并发模型/03go-select-practice/go.mod
diff --git a/go-select-practice/select_basic.go b/go并发模型/03go-select-practice/select_basic.go
similarity index 99%
rename from go-select-practice/select_basic.go
rename to go并发模型/03go-select-practice/select_basic.go
index ebe503c..022d42c 100644
--- a/go-select-practice/select_basic.go
+++ b/go并发模型/03go-select-practice/select_basic.go
@@ -31,4 +31,3 @@ func main() {
fmt.Println("main 结束")
}
-
diff --git a/go-select-practice/select_context.go b/go并发模型/03go-select-practice/select_context.go
similarity index 100%
rename from go-select-practice/select_context.go
rename to go并发模型/03go-select-practice/select_context.go
diff --git a/go-select-practice/select_default.go b/go并发模型/03go-select-practice/select_default.go
similarity index 100%
rename from go-select-practice/select_default.go
rename to go并发模型/03go-select-practice/select_default.go
diff --git a/go-select-practice/select_timeout.go b/go并发模型/03go-select-practice/select_timeout.go
similarity index 100%
rename from go-select-practice/select_timeout.go
rename to go并发模型/03go-select-practice/select_timeout.go
diff --git a/go-context-practice/cancel_basic.go b/go并发模型/04go-context-practice/cancel_basic.go
similarity index 100%
rename from go-context-practice/cancel_basic.go
rename to go并发模型/04go-context-practice/cancel_basic.go
diff --git a/go-context-practice/derived_context.go b/go并发模型/04go-context-practice/derived_context.go
similarity index 100%
rename from go-context-practice/derived_context.go
rename to go并发模型/04go-context-practice/derived_context.go
diff --git a/go-context-practice/go.mod b/go并发模型/04go-context-practice/go.mod
similarity index 100%
rename from go-context-practice/go.mod
rename to go并发模型/04go-context-practice/go.mod
diff --git a/go-context-practice/timeout_with_context.go b/go并发模型/04go-context-practice/timeout_with_context.go
similarity index 100%
rename from go-context-practice/timeout_with_context.go
rename to go并发模型/04go-context-practice/timeout_with_context.go
diff --git a/go-sync-practice/go.mod b/go并发模型/05go-sync-practice/go.mod
similarity index 100%
rename from go-sync-practice/go.mod
rename to go并发模型/05go-sync-practice/go.mod
diff --git a/go-sync-practice/mutex_counter.go b/go并发模型/05go-sync-practice/mutex_counter.go
similarity index 100%
rename from go-sync-practice/mutex_counter.go
rename to go并发模型/05go-sync-practice/mutex_counter.go
diff --git a/go-sync-practice/race_counter.go b/go并发模型/05go-sync-practice/race_counter.go
similarity index 100%
rename from go-sync-practice/race_counter.go
rename to go并发模型/05go-sync-practice/race_counter.go
diff --git a/go-sync-practice/rwmutex_cache.go b/go并发模型/05go-sync-practice/rwmutex_cache.go
similarity index 100%
rename from go-sync-practice/rwmutex_cache.go
rename to go并发模型/05go-sync-practice/rwmutex_cache.go
diff --git a/go-sync-practice/waitgroup_like_latch.go b/go并发模型/05go-sync-practice/waitgroup_like_latch.go
similarity index 100%
rename from go-sync-practice/waitgroup_like_latch.go
rename to go并发模型/05go-sync-practice/waitgroup_like_latch.go
diff --git a/go-atomic-cpu/atomic_cas.go b/go并发模型/06go-atomic-cpu/atomic_cas.go
similarity index 100%
rename from go-atomic-cpu/atomic_cas.go
rename to go并发模型/06go-atomic-cpu/atomic_cas.go
diff --git a/go-atomic-cpu/atomic_counter.go b/go并发模型/06go-atomic-cpu/atomic_counter.go
similarity index 100%
rename from go-atomic-cpu/atomic_counter.go
rename to go并发模型/06go-atomic-cpu/atomic_counter.go
diff --git a/go-atomic-cpu/cpu_busy_loop.go b/go并发模型/06go-atomic-cpu/cpu_busy_loop.go
similarity index 100%
rename from go-atomic-cpu/cpu_busy_loop.go
rename to go并发模型/06go-atomic-cpu/cpu_busy_loop.go
diff --git a/go-atomic-cpu/cpu_channel_wait.go b/go并发模型/06go-atomic-cpu/cpu_channel_wait.go
similarity index 100%
rename from go-atomic-cpu/cpu_channel_wait.go
rename to go并发模型/06go-atomic-cpu/cpu_channel_wait.go
diff --git a/go-atomic-cpu/go.mod b/go并发模型/06go-atomic-cpu/go.mod
similarity index 100%
rename from go-atomic-cpu/go.mod
rename to go并发模型/06go-atomic-cpu/go.mod
diff --git a/go-crawler/crawler_basic.go b/go并发模型/07go-crawler/crawler_basic.go
similarity index 100%
rename from go-crawler/crawler_basic.go
rename to go并发模型/07go-crawler/crawler_basic.go
diff --git a/go-crawler/go.mod b/go并发模型/07go-crawler/go.mod
similarity index 100%
rename from go-crawler/go.mod
rename to go并发模型/07go-crawler/go.mod