jdysya 0e248830a9 feat(gateway): 实现用户注册功能并优化登录流程
- 新增用户模型和数据库迁移
- 实现用户注册页面和处理逻辑
- 更新登录页面,使用手机号作为用户名
- 添加密码加密存储
- 优化错误处理和用户提示
2025-02-15 16:09:41 +08:00

279 lines
7.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)
var logger = logrus.New()
// 在全局变量后新增用户模型
type Region struct {
ID uint `gorm:"primary_key"`
Name string `gorm:"not null;unique"`
}
type User struct {
gorm.Model
FullName string `gorm:"not null"`
RegionID uint `gorm:"not null"` // 修改为关联地区ID
Mobile string `gorm:"unique;not null"`
Password string `gorm:"not null"`
Region Region // 关联关系
}
func init() {
// 配置日志格式
logger.SetFormatter(&logrus.JSONFormatter{})
logger.SetOutput(os.Stdout)
}
func main() {
// 初始化数据库
db, err := gorm.Open("sqlite3", "family.db")
if err != nil {
logger.Fatalf("数据库连接失败: %v", err)
}
defer db.Close()
db.AutoMigrate(&Region{}, &User{}) // 同时迁移Region和User表
// 初始化 Gin 引擎
r := gin.Default()
// 配置静态文件服务
r.Static("/assets", "./static/assets")
r.LoadHTMLGlob("templates/*")
// 配置 Session 中间件
store := cookie.NewStore([]byte("secret"))
store.Options(sessions.Options{
MaxAge: 86400 * 7,
HttpOnly: true,
Secure: false, // 如果是HTTPS需要设为true
SameSite: http.SameSiteLaxMode, // 允许跨站携带cookie
})
r.Use(sessions.Sessions("mysession", store))
// 登录页面
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
// 处理登录请求
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username") // 修改表单字段名
password := c.PostForm("password")
var user User
// 电话号作为用户名
if err := db.Where("mobile = ?", username).First(&user).Error; err != nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{"error": "用户不存在或密码错误"})
return
}
// 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{"error": "用户不存在或密码错误"})
return
}
// 保存session保持原有逻辑
session := sessions.Default(c)
session.Set("user", user.ID)
if err := session.Save(); err != nil {
logger.Errorf("Session保存失败: %v", err)
c.HTML(http.StatusInternalServerError, "login.html", gin.H{"error": "登录状态保存失败"})
return
}
c.Redirect(http.StatusSeeOther, "/") // 改用303状态码
})
// 在登录路由后新增注册路由
// 注册页面
r.GET("/register", func(c *gin.Context) {
regions, err := getRegions(db)
if err != nil {
c.HTML(http.StatusInternalServerError, "register.html", gin.H{"error": "系统错误"})
return
}
c.HTML(http.StatusOK, "register.html", gin.H{"regions": regions})
})
// 处理注册请求
r.POST("/register", func(c *gin.Context) {
// 获取地区参数(修改这部分)
regionStr, exists := c.GetPostForm("region")
if !exists {
regions, err := getRegions(db)
if err != nil {
c.HTML(http.StatusInternalServerError, "register.html", gin.H{"error": "系统错误"})
return
}
c.HTML(http.StatusBadRequest, "register.html", gin.H{
"error": "请选择所在地区",
"regions": regions,
"form": gin.H{
"fullname": c.PostForm("fullname"),
"mobile": c.PostForm("mobile"),
},
})
return
}
// 转换地区ID为数字
regionID, err := strconv.ParseUint(regionStr, 10, 32)
if err != nil {
regions, err := getRegions(db)
if err != nil {
c.HTML(http.StatusInternalServerError, "register.html", gin.H{"error": "系统错误"})
return
}
c.HTML(http.StatusBadRequest, "register.html", gin.H{
"error": "无效的地区参数",
"regions": regions,
"form": gin.H{
"fullname": c.PostForm("fullname"),
"mobile": c.PostForm("mobile"),
},
})
return
}
user := User{
FullName: c.PostForm("fullname"),
Mobile: c.PostForm("mobile"),
RegionID: uint(regionID), // 使用转换后的ID
}
// 验证地区是否存在
var region Region
if err := db.First(&region, user.RegionID).Error; err != nil {
regions, err := getRegions(db)
if err != nil {
c.HTML(http.StatusInternalServerError, "register.html", gin.H{"error": "系统错误"})
return
}
c.HTML(http.StatusBadRequest, "register.html", gin.H{
"error": "请选择有效地区",
"regions": regions,
"form": gin.H{
"fullname": c.PostForm("fullname"),
"mobile": c.PostForm("mobile"),
},
})
return
}
// 验证手机号格式
if len(user.Mobile) != 11 {
c.HTML(http.StatusBadRequest, "register.html", gin.H{"error": "手机号格式不正确"})
return
}
// 密码加密
password := c.PostForm("password")
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logger.Errorf("密码加密失败: %v", err)
c.HTML(http.StatusInternalServerError, "register.html", gin.H{"error": "注册失败"})
return
}
user.Password = string(hashedPassword)
// 创建用户
if err := db.Create(&user).Error; err != nil {
logger.Errorf("用户创建失败: %v", err)
errorMsg := "注册失败"
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
errorMsg = "该手机号已注册"
}
c.HTML(http.StatusBadRequest, "register.html", gin.H{"error": errorMsg})
return
}
c.Redirect(http.StatusSeeOther, "/login")
})
// 权限校验中间件
authMiddleware := func(c *gin.Context) {
session := sessions.Default(c)
user := session.Get("user")
if user == nil {
c.Redirect(http.StatusFound, "/login")
c.Abort()
return
}
c.Next()
}
// 文档页面路由
r.Use(authMiddleware)
r.GET("/", func(c *gin.Context) {
// 记录访问痕迹
logAccess(c)
http.ServeFile(c.Writer, c.Request, "./static/index.html")
})
// 在权限校验中间件后添加退出路由
r.GET("/logout", func(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
if err := session.Save(); err != nil {
logger.Errorf("退出登录失败: %v", err)
c.HTML(http.StatusInternalServerError, "error.html", gin.H{"error": "退出登录失败"})
return
}
c.Redirect(http.StatusSeeOther, "/login")
})
// 新增通用静态文件路由(放在其他路由之后)
r.NoRoute(func(c *gin.Context) {
logAccess(c)
filePath := "./static" + c.Request.URL.Path
// 检查文件是否存在
if _, err := os.Stat(filePath); err == nil {
http.ServeFile(c.Writer, c.Request, filePath)
} else {
c.AbortWithStatus(http.StatusNotFound)
}
})
// 启动服务
r.Run(":7070")
}
// 记录访问痕迹
func logAccess(c *gin.Context) {
ip := c.ClientIP()
path := c.Request.URL.Path
method := c.Request.Method
timestamp := time.Now().Format(time.RFC3339)
logger.WithFields(logrus.Fields{
"ip": ip,
"path": path,
"method": method,
"timestamp": timestamp,
}).Info("Page accessed")
}
func getRegions(db *gorm.DB) ([]Region, error) {
var regions []Region
if err := db.Find(&regions).Error; err != nil {
logger.Errorf("获取地区数据失败: %v", err)
return nil, err
}
return regions, nil
}