279 lines
7.2 KiB
Go
279 lines
7.2 KiB
Go
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(®ion, 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(®ions).Error; err != nil {
|
||
logger.Errorf("获取地区数据失败: %v", err)
|
||
return nil, err
|
||
}
|
||
return regions, nil
|
||
}
|