From 70fa16c938cd5d233691dc05ac6ec437e139ca4a Mon Sep 17 00:00:00 2001 From: maxwell Date: Wed, 25 Sep 2024 16:06:22 +0800 Subject: [PATCH] update --- .history/config/config_20240925094043.go | 8 ++ .history/config/config_20240925155958.go | 29 +++++ .history/config/config_20240925160003.go | 29 +++++ .history/handlers/handlers_20240925160117.go | 0 .history/handlers/handlers_20240925160124.go | 102 ++++++++++++++++++ .history/main_20240925093717.go | 35 ++++++ .history/main_20240925155936.go | 61 +++++++++++ .../middleware/auth_20240925143543.go | 0 .history/middleware/auth_20240925160148.go | 23 ++++ .history/middleware/auth_20240925160239.go | 23 ++++ .history/models/user_20240925101022.go | 6 ++ .history/models/user_20240925160020.go | 31 ++++++ .history/models/user_20240925160023.go | 31 ++++++ .../routes/auth_20240925095436.go | 0 .history/routes/auth_20240925152339.go | 82 ++++++++++++++ .history/routes/auth_20240925153641.go | 82 ++++++++++++++ .../routes/storage/mysql_20240925153315.go | 0 .../routes/storage/mysql_20240925153633.go | 0 .../routes/storage/mysql_20240925153638.go | 1 + .../routes/storage/redis_20240925153309.go | 0 .../routes/storage/redis_20240925153620.go | 1 + .../routes/storage/redis_20240925153624.go | 1 + config/config.go | 31 +++++- go.mod | 13 ++- go.sum | 27 +++++ handlers/handlers.go | 102 ++++++++++++++++++ main.go | 60 ++++++++--- middleware/auth.go | 23 ++++ models/user.go | 29 ++++- testfb.log | 18 ---- utils/jwt.go | 26 ----- 31 files changed, 805 insertions(+), 69 deletions(-) create mode 100644 .history/config/config_20240925094043.go create mode 100644 .history/config/config_20240925155958.go create mode 100644 .history/config/config_20240925160003.go create mode 100644 .history/handlers/handlers_20240925160117.go create mode 100644 .history/handlers/handlers_20240925160124.go create mode 100644 .history/main_20240925093717.go create mode 100644 .history/main_20240925155936.go rename middleware/logger.go => .history/middleware/auth_20240925143543.go (100%) create mode 100644 .history/middleware/auth_20240925160148.go create mode 100644 .history/middleware/auth_20240925160239.go create mode 100644 .history/models/user_20240925101022.go create mode 100644 .history/models/user_20240925160020.go create mode 100644 .history/models/user_20240925160023.go rename routes/auth.go => .history/routes/auth_20240925095436.go (100%) create mode 100644 .history/routes/auth_20240925152339.go create mode 100644 .history/routes/auth_20240925153641.go create mode 100644 .history/routes/storage/mysql_20240925153315.go create mode 100644 .history/routes/storage/mysql_20240925153633.go create mode 100644 .history/routes/storage/mysql_20240925153638.go create mode 100644 .history/routes/storage/redis_20240925153309.go create mode 100644 .history/routes/storage/redis_20240925153620.go create mode 100644 .history/routes/storage/redis_20240925153624.go create mode 100644 handlers/handlers.go create mode 100644 middleware/auth.go delete mode 100644 testfb.log delete mode 100644 utils/jwt.go diff --git a/.history/config/config_20240925094043.go b/.history/config/config_20240925094043.go new file mode 100644 index 0000000..a2427e4 --- /dev/null +++ b/.history/config/config_20240925094043.go @@ -0,0 +1,8 @@ +package config + +const ( + //日志文件路径 + LogFilePath = "testfb.log" + //地址+端口 + Port = ":3030" +) diff --git a/.history/config/config_20240925155958.go b/.history/config/config_20240925155958.go new file mode 100644 index 0000000..ddfa586 --- /dev/null +++ b/.history/config/config_20240925155958.go @@ -0,0 +1,29 @@ +package config + +import ( + "github.com/gofiber/storage/redis/v3" +) + +type Config struct { + DBConnString string + RedisAddr string + JWTSecret string +} + +func New() *Config { + return &Config{ + DBConnString: "root:password@tcp(localhost:3306)/testfb?charset=utf8mb4&parseTime=True&loc=Local", + RedisAddr: "localhost:6379", + JWTSecret: "your-secret-key", + } +} + +func InitRedis(addr string) *redis.Storage { + return redis.New(redis.Config{ + Host: addr, + Port: 6379, + Username: "", + Password: "", + Database: 0, + }) +} diff --git a/.history/config/config_20240925160003.go b/.history/config/config_20240925160003.go new file mode 100644 index 0000000..ddfa586 --- /dev/null +++ b/.history/config/config_20240925160003.go @@ -0,0 +1,29 @@ +package config + +import ( + "github.com/gofiber/storage/redis/v3" +) + +type Config struct { + DBConnString string + RedisAddr string + JWTSecret string +} + +func New() *Config { + return &Config{ + DBConnString: "root:password@tcp(localhost:3306)/testfb?charset=utf8mb4&parseTime=True&loc=Local", + RedisAddr: "localhost:6379", + JWTSecret: "your-secret-key", + } +} + +func InitRedis(addr string) *redis.Storage { + return redis.New(redis.Config{ + Host: addr, + Port: 6379, + Username: "", + Password: "", + Database: 0, + }) +} diff --git a/.history/handlers/handlers_20240925160117.go b/.history/handlers/handlers_20240925160117.go new file mode 100644 index 0000000..e69de29 diff --git a/.history/handlers/handlers_20240925160124.go b/.history/handlers/handlers_20240925160124.go new file mode 100644 index 0000000..7d3b47f --- /dev/null +++ b/.history/handlers/handlers_20240925160124.go @@ -0,0 +1,102 @@ +package handlers + +import ( + "time" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/storage/redis/v3" + "github.com/golang-jwt/jwt/v4" + "gorm.io/gorm" + + "testfb/models" +) + +func Login(db *gorm.DB, redisClient *redis.Storage) fiber.Handler { + return func(c *fiber.Ctx) error { + var input struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.BodyParser(&input); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"}) + } + + var user models.User + if err := db.Where("username = ?", input.Username).First(&user).Error; err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"}) + } + + if err := user.ComparePassword(input.Password); err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"}) + } + + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["user_id"] = user.ID + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + + t, err := token.SignedString([]byte("your-secret-key")) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not login"}) + } + + err = redisClient.Set(t, user.ID, 24*time.Hour) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not store token"}) + } + + return c.JSON(fiber.Map{"token": t}) + } +} + +func GetCurrentUser(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + userID := c.Locals("user_id").(uint) + + var user models.User + if err := db.First(&user, userID).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + return c.JSON(user) + } +} + +func UpdateCurrentUser(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + userID := c.Locals("user_id").(uint) + + var input models.User + if err := c.BodyParser(&input); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"}) + } + + var user models.User + if err := db.First(&user, userID).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + user.Email = input.Email + user.Phone = input.Phone + + if err := db.Save(&user).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not update user"}) + } + + return c.JSON(user) + } +} + +func GetUserByID(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + id := c.Params("id") + + var user models.User + if err := db.First(&user, id).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + return c.JSON(user) + } +} diff --git a/.history/main_20240925093717.go b/.history/main_20240925093717.go new file mode 100644 index 0000000..14b65b4 --- /dev/null +++ b/.history/main_20240925093717.go @@ -0,0 +1,35 @@ +package main + +import ( + "log" + "os" + + "testfb/config" + "testfb/routes" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +func main() { + //创建日志文件 + f, err := os.OpenFile(config.LogFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalln("日志文件创建失败:", err) + } + defer f.Close() + + app := fiber.New() + + app.Use(logger.New(logger.Config{ + Output: f, + })) + + //注册路由 + routes.SetupRoutes(app) + // 启动服务 + log.Println("服务启动成功,监听端口:", config.Port) + if err := app.Listen(config.Port); err != nil { + log.Fatalln("服务启动失败:", err) + } +} diff --git a/.history/main_20240925155936.go b/.history/main_20240925155936.go new file mode 100644 index 0000000..86d0c32 --- /dev/null +++ b/.history/main_20240925155936.go @@ -0,0 +1,61 @@ +package main + +import ( + "log" + "os" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "gorm.io/driver/mysql" + "gorm.io/gorm" + + "testfb/config" + "testfb/handlers" + "testfb/middleware" + "testfb/models" +) + +func main() { + // Initialize configuration + cfg := config.New() + + // Set up logging + logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("Error opening log file: %v", err) + } + defer logFile.Close() + + // Initialize database + db, err := gorm.Open(mysql.Open(cfg.DBConnString), &gorm.Config{}) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + // Auto-migrate the users table + err = db.AutoMigrate(&models.User{}) + if err != nil { + log.Fatalf("Failed to auto-migrate: %v", err) + } + + // Initialize Redis client + redisClient := config.InitRedis(cfg.RedisAddr) + + // Create Fiber app + app := fiber.New() + + // Set up middleware + app.Use(logger.New(logger.Config{ + Format: "[${time}] ${status} - ${latency} ${method} ${path}\n", + Output: logFile, + })) + + // Set up routes + app.Post("/login", handlers.Login(db, redisClient)) + app.Get("/user", middleware.AuthMiddleware(redisClient), handlers.GetCurrentUser(db)) + app.Put("/user", middleware.AuthMiddleware(redisClient), handlers.UpdateCurrentUser(db)) + app.Get("/users/:id", middleware.AuthMiddleware(redisClient), handlers.GetUserByID(db)) + + // Start server + log.Fatal(app.Listen(":8080")) +} diff --git a/middleware/logger.go b/.history/middleware/auth_20240925143543.go similarity index 100% rename from middleware/logger.go rename to .history/middleware/auth_20240925143543.go diff --git a/.history/middleware/auth_20240925160148.go b/.history/middleware/auth_20240925160148.go new file mode 100644 index 0000000..bc32fec --- /dev/null +++ b/.history/middleware/auth_20240925160148.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/storage/redis/v3" +) + +func AuthMiddleware(redisClient *redis.Storage) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Get("Authorization") + if token == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing authorization token"}) + } + + userID, err := redisClient.Get(token) + if err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"}) + } + + c.Locals("user_id", userID) + return c.Next() + } +} diff --git a/.history/middleware/auth_20240925160239.go b/.history/middleware/auth_20240925160239.go new file mode 100644 index 0000000..bc32fec --- /dev/null +++ b/.history/middleware/auth_20240925160239.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/storage/redis/v3" +) + +func AuthMiddleware(redisClient *redis.Storage) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Get("Authorization") + if token == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing authorization token"}) + } + + userID, err := redisClient.Get(token) + if err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"}) + } + + c.Locals("user_id", userID) + return c.Next() + } +} diff --git a/.history/models/user_20240925101022.go b/.history/models/user_20240925101022.go new file mode 100644 index 0000000..bdf13b8 --- /dev/null +++ b/.history/models/user_20240925101022.go @@ -0,0 +1,6 @@ +package models + +type User struct { + Username string `json:"username" validate:"required,alphanum,min=3,max=20"` + Password string `json:"password" validate:"required,min=8,max=20"` +} diff --git a/.history/models/user_20240925160020.go b/.history/models/user_20240925160020.go new file mode 100644 index 0000000..b0e76d0 --- /dev/null +++ b/.history/models/user_20240925160020.go @@ -0,0 +1,31 @@ +package models + +import ( + "time" + + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + Username string `gorm:"unique" json:"username"` + Password string `json:"-"` + Email string `json:"email"` + Phone string `json:"phone"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (u *User) BeforeCreate(tx *gorm.DB) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + u.Password = string(hashedPassword) + return nil +} + +func (u *User) ComparePassword(password string) error { + return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) +} diff --git a/.history/models/user_20240925160023.go b/.history/models/user_20240925160023.go new file mode 100644 index 0000000..b0e76d0 --- /dev/null +++ b/.history/models/user_20240925160023.go @@ -0,0 +1,31 @@ +package models + +import ( + "time" + + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + Username string `gorm:"unique" json:"username"` + Password string `json:"-"` + Email string `json:"email"` + Phone string `json:"phone"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (u *User) BeforeCreate(tx *gorm.DB) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + u.Password = string(hashedPassword) + return nil +} + +func (u *User) ComparePassword(password string) error { + return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) +} diff --git a/routes/auth.go b/.history/routes/auth_20240925095436.go similarity index 100% rename from routes/auth.go rename to .history/routes/auth_20240925095436.go diff --git a/.history/routes/auth_20240925152339.go b/.history/routes/auth_20240925152339.go new file mode 100644 index 0000000..eddbcf9 --- /dev/null +++ b/.history/routes/auth_20240925152339.go @@ -0,0 +1,82 @@ +package routes + +import ( + "log" + "testfb/utils" + + "github.com/gofiber/fiber/v2" +) + +// 注册路由 +func SetupRoutes(app *fiber.App) { + app.Post("/login", login) +} + +// 处理登录 +func login(c *fiber.Ctx) error { + //请求参数是JSON格式,解析JSON数据 + var data map[string]string + if err := c.BodyParser(&data); err != nil { + log.Println(err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "message": "Invalid request", + }) + } + //把JSON数据中的用户名和密码取出来 + if data["username"] == "" || data["password"] == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "message": "Invalid request", + }) + } + username := data["username"] + password := data["password"] + log.Printf("Login request for user: %s", username) + log.Printf("Password: %s", password) + //验证用户名和密码 + if utils.ValiddateUser(username, password) { + token, err := utils.GenerateJWT(username) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "message": "Cannot get token, Internal server error", + }) + } + log.Println("Token generated for user:", username) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "token": token, + }) + } + log.Printf("Invalid username or password for user: %s", username) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Invalid username or password", + }) +} + +// 得到用户信息。GET /userInfo +// 验证token。GET /validateToken +func userInfo(c *fiber.Ctx) error { + // 从请求头中获取token + token := c.Get("Authorization") + if token == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Unauthorized", + }) + } + // 验证token + username, err := utils.ValidateJWT(token) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Unauthorized", + }) + } + // 得到用户信息 + user, err := utils.GetUser(username) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "message": "Internal server error", + }) + } + return c.Status(fiber.StatusOK).JSON(user) +} diff --git a/.history/routes/auth_20240925153641.go b/.history/routes/auth_20240925153641.go new file mode 100644 index 0000000..59f7c31 --- /dev/null +++ b/.history/routes/auth_20240925153641.go @@ -0,0 +1,82 @@ +package routes + +import ( + "log" + "testfb/utils" + + "github.com/gofiber/fiber/v2" +) + +// 注册路由 +func SetupRoutes(app *fiber.App) { + app.Post("/login", login) +} + +// 处理登录 +func login(c *fiber.Ctx) error { + //请求参数是JSON格式,解析JSON数据 + var data map[string]string + if err := c.BodyParser(&data); err != nil { + log.Println(err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "message": "Invalid request", + }) + } + //把JSON数据中的用户名和密码取出来 + if data["username"] == "" || data["password"] == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "message": "Invalid request", + }) + } + username := data["username"] + password := data["password"] + log.Printf("Login request for user: %s", username) + log.Printf("Password: %s", password) + //验证用户名和密码 + if utils.ValiddateUser(username, password) { + token, err := utils.GenerateJWT(username) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "message": "Cannot get token, Internal server error", + }) + } + log.Println("Token generated for user:", username) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "token": token, + }) + } + log.Printf("Invalid username or password for user: %s", username) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Invalid username or password", + }) +} + +// 得到用户信息。GET /userInfo +// 验证token。GET /validateToken +func userInfo(c *fiber.Ctx) error { + // 从请求头中获取token + token := c.Get("Authorization") + if token == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Unauthorized", + }) + } + // 验证token + username, err := utils.ValidateToken(token) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Unauthorized", + }) + } + // 得到用户信息 + user, err := utils.GetUser(username) + if err != nil { + log.Println(err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "message": "Internal server error", + }) + } + return c.Status(fiber.StatusOK).JSON(user) +} diff --git a/.history/routes/storage/mysql_20240925153315.go b/.history/routes/storage/mysql_20240925153315.go new file mode 100644 index 0000000..e69de29 diff --git a/.history/routes/storage/mysql_20240925153633.go b/.history/routes/storage/mysql_20240925153633.go new file mode 100644 index 0000000..e69de29 diff --git a/.history/routes/storage/mysql_20240925153638.go b/.history/routes/storage/mysql_20240925153638.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/.history/routes/storage/mysql_20240925153638.go @@ -0,0 +1 @@ +package storage diff --git a/.history/routes/storage/redis_20240925153309.go b/.history/routes/storage/redis_20240925153309.go new file mode 100644 index 0000000..e69de29 diff --git a/.history/routes/storage/redis_20240925153620.go b/.history/routes/storage/redis_20240925153620.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/.history/routes/storage/redis_20240925153620.go @@ -0,0 +1 @@ +package storage diff --git a/.history/routes/storage/redis_20240925153624.go b/.history/routes/storage/redis_20240925153624.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/.history/routes/storage/redis_20240925153624.go @@ -0,0 +1 @@ +package storage diff --git a/config/config.go b/config/config.go index a2427e4..ddfa586 100644 --- a/config/config.go +++ b/config/config.go @@ -1,8 +1,29 @@ package config -const ( - //日志文件路径 - LogFilePath = "testfb.log" - //地址+端口 - Port = ":3030" +import ( + "github.com/gofiber/storage/redis/v3" ) + +type Config struct { + DBConnString string + RedisAddr string + JWTSecret string +} + +func New() *Config { + return &Config{ + DBConnString: "root:password@tcp(localhost:3306)/testfb?charset=utf8mb4&parseTime=True&loc=Local", + RedisAddr: "localhost:6379", + JWTSecret: "your-secret-key", + } +} + +func InitRedis(addr string) *redis.Storage { + return redis.New(redis.Config{ + Host: addr, + Port: 6379, + Username: "", + Password: "", + Database: 0, + }) +} diff --git a/go.mod b/go.mod index 0635c42..2760356 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,27 @@ go 1.22.6 require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gofiber/fiber/v2 v2.52.5 // indirect + github.com/gofiber/storage/redis/v3 v3.1.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/redis/go-redis/v9 v9.5.3 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/gorm v1.25.12 // indirect ) diff --git a/go.sum b/go.sum index 3887e01..4a4d0a8 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,23 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/storage/redis/v3 v3.1.2 h1:qYHSRbkRQCD9HovLOOoswe+DoGF28/hwD4d8kmxDNcs= +github.com/gofiber/storage/redis/v3 v3.1.2/go.mod h1:bwSKrd5Ux2blqXVT8tWOYTmZbFDMZR8dztn7rarDZiU= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -15,6 +27,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= +github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -23,7 +37,20 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/handlers/handlers.go b/handlers/handlers.go new file mode 100644 index 0000000..7d3b47f --- /dev/null +++ b/handlers/handlers.go @@ -0,0 +1,102 @@ +package handlers + +import ( + "time" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/storage/redis/v3" + "github.com/golang-jwt/jwt/v4" + "gorm.io/gorm" + + "testfb/models" +) + +func Login(db *gorm.DB, redisClient *redis.Storage) fiber.Handler { + return func(c *fiber.Ctx) error { + var input struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.BodyParser(&input); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"}) + } + + var user models.User + if err := db.Where("username = ?", input.Username).First(&user).Error; err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"}) + } + + if err := user.ComparePassword(input.Password); err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"}) + } + + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["user_id"] = user.ID + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + + t, err := token.SignedString([]byte("your-secret-key")) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not login"}) + } + + err = redisClient.Set(t, user.ID, 24*time.Hour) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not store token"}) + } + + return c.JSON(fiber.Map{"token": t}) + } +} + +func GetCurrentUser(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + userID := c.Locals("user_id").(uint) + + var user models.User + if err := db.First(&user, userID).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + return c.JSON(user) + } +} + +func UpdateCurrentUser(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + userID := c.Locals("user_id").(uint) + + var input models.User + if err := c.BodyParser(&input); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"}) + } + + var user models.User + if err := db.First(&user, userID).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + user.Email = input.Email + user.Phone = input.Phone + + if err := db.Save(&user).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not update user"}) + } + + return c.JSON(user) + } +} + +func GetUserByID(db *gorm.DB) fiber.Handler { + return func(c *fiber.Ctx) error { + id := c.Params("id") + + var user models.User + if err := db.First(&user, id).Error; err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + return c.JSON(user) + } +} diff --git a/main.go b/main.go index 14b65b4..f7c98b6 100644 --- a/main.go +++ b/main.go @@ -4,32 +4,58 @@ import ( "log" "os" - "testfb/config" - "testfb/routes" - "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" + "gorm.io/driver/mysql" + "gorm.io/gorm" + + "testfb/config" + "testfb/handlers" + "testfb/middleware" + "testfb/models" ) func main() { - //创建日志文件 - f, err := os.OpenFile(config.LogFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - log.Fatalln("日志文件创建失败:", err) - } - defer f.Close() + // Initialize configuration + cfg := config.New() + // Set up logging + logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("Error opening log file: %v", err) + } + defer logFile.Close() + + // Initialize database + db, err := gorm.Open(mysql.Open(cfg.DBConnString), &gorm.Config{}) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + // Auto-migrate the users table + err = db.AutoMigrate(&models.User{}) + if err != nil { + log.Fatalf("Failed to auto-migrate: %v", err) + } + + // Initialize Redis client + redisClient := config.InitRedis(cfg.RedisAddr) + + // Create Fiber app app := fiber.New() + // Set up middleware app.Use(logger.New(logger.Config{ - Output: f, + Format: "[${time}] ${status} - ${latency} ${method} ${path}\n", + Output: logFile, })) - //注册路由 - routes.SetupRoutes(app) - // 启动服务 - log.Println("服务启动成功,监听端口:", config.Port) - if err := app.Listen(config.Port); err != nil { - log.Fatalln("服务启动失败:", err) - } + // Set up routes + app.Post("/login", handlers.Login(db, redisClient)) + app.Get("/user", middleware.AuthMiddleware(redisClient), handlers.GetCurrentUser(db)) + app.Put("/user", middleware.AuthMiddleware(redisClient), handlers.UpdateCurrentUser(db)) + app.Get("/users/:id", middleware.AuthMiddleware(redisClient), handlers.GetUserByID(db)) + + // Start server + log.Fatal(app.Listen(":7777")) } diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..bc32fec --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/storage/redis/v3" +) + +func AuthMiddleware(redisClient *redis.Storage) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Get("Authorization") + if token == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing authorization token"}) + } + + userID, err := redisClient.Get(token) + if err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"}) + } + + c.Locals("user_id", userID) + return c.Next() + } +} diff --git a/models/user.go b/models/user.go index bdf13b8..b0e76d0 100644 --- a/models/user.go +++ b/models/user.go @@ -1,6 +1,31 @@ package models +import ( + "time" + + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + type User struct { - Username string `json:"username" validate:"required,alphanum,min=3,max=20"` - Password string `json:"password" validate:"required,min=8,max=20"` + ID uint `gorm:"primarykey" json:"id"` + Username string `gorm:"unique" json:"username"` + Password string `json:"-"` + Email string `json:"email"` + Phone string `json:"phone"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (u *User) BeforeCreate(tx *gorm.DB) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + u.Password = string(hashedPassword) + return nil +} + +func (u *User) ComparePassword(password string) error { + return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) } diff --git a/testfb.log b/testfb.log deleted file mode 100644 index e2030d1..0000000 --- a/testfb.log +++ /dev/null @@ -1,18 +0,0 @@ -09:42:05 | 404 | 107.091µs | 127.0.0.1 | POST | / | Cannot POST / -09:43:05 | 404 | 9.871µs | 127.0.0.1 | POST | / | Cannot POST / -09:43:20 | 404 | 9.475µs | 127.0.0.1 | POST | / | Cannot POST / -09:43:33 | 404 | 9.491µs | 127.0.0.1 | POST | / | Cannot POST / -09:43:49 | 400 | 207.706µs | 127.0.0.1 | POST | /login | - -09:43:55 | 401 | 80.159µs | 127.0.0.1 | POST | /login | - -09:45:52 | 401 | 65.554µs | 127.0.0.1 | POST | /login | - -09:47:38 | 401 | 155.702µs | 127.0.0.1 | POST | /login | - -09:47:48 | 400 | 90.98µs | 127.0.0.1 | POST | /login | - -09:48:00 | 400 | 64.331µs | 127.0.0.1 | POST | /login | - -09:48:06 | 401 | 81.624µs | 127.0.0.1 | POST | /login | - -09:52:52 | 401 | 188.948µs | 127.0.0.1 | POST | /login | - -09:54:47 | 200 | 242.308µs | 127.0.0.1 | POST | /login | - -09:55:40 | 400 | 72.042µs | 127.0.0.1 | POST | /login | - -09:55:47 | 200 | 126.783µs | 127.0.0.1 | POST | /login | - -09:55:52 | 200 | 82.231µs | 127.0.0.1 | POST | /login | - -09:55:56 | 401 | 83.999µs | 127.0.0.1 | POST | /login | - -09:56:01 | 200 | 71.148µs | 127.0.0.1 | POST | /login | - diff --git a/utils/jwt.go b/utils/jwt.go deleted file mode 100644 index a6b53d8..0000000 --- a/utils/jwt.go +++ /dev/null @@ -1,26 +0,0 @@ -package utils - -import ( - "time" - - "github.com/golang-jwt/jwt/v4" -) - -// 密钥 (签发JWT) -var jwtSecret = []byte("mysecretkey") - -// 模拟用户验证 -func ValiddateUser(username, password string) bool { - return username == "admin" && password == "123456" -} - -// 生成JWT -func GenerateJWT(username string) (string, error) { - // 设置过期时间 - claims := jwt.MapClaims{ - "username": username, - "exp": time.Now().Add(time.Hour * 72).Unix(), - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(jwtSecret) -}