package routers import ( "errors" "fmt" "ops/models" "path" "strconv" "time" "github.com/gin-gonic/gin" "github.com/mitchellh/mapstructure" ) type TabUser struct { ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键 Name string `gorm:"size:100;uniqueIndex"` // 唯一约束索引 Email string `gorm:"size:255;index"` // 字符串长度限制100 索引 Pass string `gorm:"size:128"` // 建议存储哈希后的密码 Type string `gorm:"size:64;default:user"` // Salt string `gorm:"size:64;"` Date time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 默认当前时间 } type TabUserGroups struct { ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键 Name string `gorm:"size:100;uniqueIndex"` // 唯一约束索引 Email string `gorm:"size:255;index"` // 字符串长度限制100 索引 Type string `gorm:"size:64;default:usergroup"` // Date time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 默认当前时间 } type TabUserGroupBinds struct { ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键 UserID uint `gorm:"index"` GroupID uint `gorm:"index"` Date time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 默认当前时间 } type TabUserInfo struct { ID uint `gorm:"primaryKey;autoIncrement"` UserID uint `gorm:"not null;uniqueIndex"` FirstName string `gorm:"size:50;null"` Username string `gorm:"size:30;null"` Birthdate time.Time `gorm:"type:datetime;null"` Gender string `gorm:"type:char(1);check:gender IN ('M', 'F', 'U');default:'U'"` AvatarPath string `gorm:"size:255"` Region string `gorm:"size:50"` Language string `gorm:"size:10;default:'zh-CN'"` CreatedAt time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP;column:created_at"` } // var def_user_info = User_info{ // ID:0, // UserID:0, // } type TabUserCookie struct { ID uint `gorm:"primaryKey;autoIncrement"` UserID uint `gorm:"not null"` Name string `gorm:"size:255;not null;index"` Value string `gorm:"size:255;not null;index"` ExpiresAt time.Time `gorm:"type:datetime;index"` CreatedAt time.Time `gorm:"type:datetime;not null;default:CURRENT_TIMESTAMP"` UpdatedAt time.Time `gorm:"type:datetime;index;not null;default:CURRENT_TIMESTAMP"` Remember bool `gorm:"default:false"` } // TabUserLoginFailLog 用户登录失败日志表 type TabUserLoginFailLog struct { ID uint `gorm:"primaryKey;autoIncrement"` Username string `gorm:"size:100;index"` // 尝试登录的用户名 UserID uint `gorm:"index;default:0"` // 用户ID(如果用户存在) IP string `gorm:"size:64"` // 登录IP UserAgent string `gorm:"size:512"` // User-Agent Reason string `gorm:"size:64"` // 失败原因:password_error / user_not_found Count int `gorm:"default:1"` // 连续失败次数 CreatedAt time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 首次失败时间 UpdatedAt time.Time `gorm:"type:datetime;index;default:CURRENT_TIMESTAMP"` // 最后失败时间 } var ( sysUserGroup TabUserGroups sysAdmins []uint ) func updateSysAdminsCash() { // 查询 admins 用户组的 ID var adminGroup TabUserGroups if models.DB.Where("name = ?", "admins").First(&adminGroup).Error != nil { // admins 组不存在,清空缓存 sysAdmins = []uint{} return } // 查询 admins 组的所有成员 var binds []TabUserGroupBinds if models.DB.Where("group_id = ?", adminGroup.ID).Find(&binds).Error != nil { sysAdmins = []uint{} return } // 更新缓存 newAdmins := make([]uint, 0, len(binds)) for _, bind := range binds { newAdmins = append(newAdmins, bind.UserID) } sysAdmins = newAdmins } // recordLoginFail 记录登录失败日志,更新连续失败次数 func recordLoginFail(ctx *gin.Context, username string, userID uint, reason string) { // 获取客户端IP和UserAgent ip := ctx.ClientIP() userAgent := ctx.GetHeader("User-Agent") // 查找是否有该用户最近的失败记录(24小时内) var existingLog TabUserLoginFailLog err := models.DB.Where("username = ? AND reason = ? AND updated_at > ?", username, reason, time.Now().Add(-24*time.Hour)). Order("id DESC").First(&existingLog).Error if err == nil { // 存在最近失败的记录,更新次数 models.DB.Model(&existingLog).Updates(map[string]interface{}{ "count": existingLog.Count + 1, "ip": ip, "user_agent": userAgent, "updated_at": time.Now(), }) } else { // 不存在,创建新记录 newLog := TabUserLoginFailLog{ Username: username, UserID: userID, IP: ip, UserAgent: userAgent, Reason: reason, Count: 1, CreatedAt: time.Now(), UpdatedAt: time.Now(), } models.DB.Create(&newLog) } } func HashUserPass(user *TabUser) { switch models.ConfigsUser.PassHashType { case "text": break case "md5": user.Pass = models.Md5Str(user.Pass) case "md5salt": if user.Salt == "" { user.Salt = models.RandStr32() } user.Pass = models.Md5Str(models.Md5Str(user.Pass) + user.Salt) } } func CheckCookiesAndUpdate(cookie *TabUserCookie) bool { if !models.IsExpired(cookie.ExpiresAt) { if cookie.Remember { cookiewhere := TabUserCookie{ ID: cookie.ID, } cookieupdata := TabUserCookie{ UpdatedAt: time.Now(), ExpiresAt: time.Now().Add(time.Duration(models.ConfigsUser.CookieTimeout) * time.Second), } models.DB.Where(&cookiewhere).Updates(&cookieupdata) } return true } else { //以过期 return false } //return false } func ApiUserInit() { //用户模块初始化init fmt.Println("users init") // 自动创建表结构 models.DB.AutoMigrate(&TabUser{}) models.DB.AutoMigrate(&TabUserGroups{}) models.DB.AutoMigrate(&TabUserGroupBinds{}) models.DB.AutoMigrate(&TabUserInfo{}) models.DB.AutoMigrate(&TabUserCookie{}) models.DB.AutoMigrate(&TabUserLoginFailLog{}) //创建admin用户 var user TabUser user.Name = "admin" if models.DB.Where(&user).First(&user).Error == nil { } else { //fmt.Println("用户不存在") //对密码加盐 user.Salt = models.RandStr32() user.Pass = "adminpassword" HashUserPass(&user) models.DB.Create(&user) // 传入指针 } //创建admin group var usergroup TabUserGroups usergroup.Name = "admins" if models.DB.Where(&usergroup).First(&usergroup).Error == nil { } else { //fmt.Println("用户组不存在") models.DB.Create(&usergroup) // 传入指针 } //创建用户与用户组绑定 var usergroupbind TabUserGroupBinds usergroupbind.UserID = user.ID usergroupbind.GroupID = usergroup.ID if models.DB.Where(&usergroupbind).First(&usergroupbind).Error == nil { } else { models.DB.Create(&usergroupbind) // 传入指针 } //更新系统管理员列表 updateSysAdminsCash() } type From_user_add struct { Useremail string `json:"useremail"` Username string `json:"username"` Userpass string `json:"userpass"` } type From_user_login struct { Username string `json:"username"` Password string `json:"password"` Remember bool `json:"remember"` } type From_user_updateinfo struct { Username string `json:"username"` Remark string `json:"remark"` Birthday string `json:"birthday"` } type From_user_changeemail struct { Newemail string `json:"newemail"` } type From_user_changepass struct { Oldpass string `json:"oldpass"` Newpass string `json:"newpass"` } func AuthenticationAuthorityFromCookie(c string) (*TabUser, error) { if c != "" { cookie := TabUserCookie{ Value: c, } if models.DB.Where(&cookie).First(&cookie).Error == nil { //找到cookie,验证cookie有效性,以及更新cookie if CheckCookiesAndUpdate(&cookie) { //cookie有效 //载入user user := TabUser{ ID: cookie.UserID, } models.DB.Where(&user).First(&user) return &user, nil } else { return nil, errors.New("cookie 过期") } } else { return nil, errors.New("cookie Not Fund") } } else { return nil, errors.New("cookie 参数错误") } } func GetUserInfoFromUserID(userID uint) *TabUserInfo { //通过id获取用户info if userID <= 0 { return nil } //先查询用户是否存在 var user TabUser user.ID = userID if models.DB.Where(&user).First(&user).Error == nil { var userinfo TabUserInfo userinfo.UserID = user.ID if models.DB.Where(&userinfo).First(&userinfo).Error == nil { return &userinfo } else { //无记录,创建一条 userinfo.Username = user.Name userinfo.FirstName = user.Email userinfo.Birthdate = (time.Now()) models.DB.Create(&userinfo) return &userinfo } } return nil } func AuthenticationAuthority(ctx *gin.Context) (bool, TabUser, map[string]interface{}) { data, cookieval := SeparateData(ctx) //fmt.Println("cookieis" + cookieval) var user TabUser if cookieval != "" { user_, error := AuthenticationAuthorityFromCookie(cookieval) if error == nil { user = *user_ return true, user, data } else { return false, user, nil } } else { //ReturnJson(ctx, "userCookieError", nil) return false, user, nil } } func ApiUser(r *gin.RouterGroup) { r.GET("/test", func(ctx *gin.Context) { ReturnJson(ctx, "apiOK", nil) }) r.POST("/test", func(ctx *gin.Context) { ReturnJson(ctx, "apiOK", nil) }) //get获取用户info r.GET("/getuserinfo/:id", func(ctx *gin.Context) { idStr := ctx.Param("id") id, err := strconv.Atoi(idStr) var redata map[string]interface{} = make(map[string]interface{}) if err == nil { userinfo := GetUserInfoFromUserID(uint(id)) if userinfo != nil { redata["userinfo"] = *userinfo } } ReturnJson(ctx, "apiOK", redata) }) //修改用户密码 r.POST("/changePassword", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if isAuth { var jsonData From_user_changepass if err := mapstructure.Decode(data, &jsonData); err == nil { //验证旧密码 //fmt.Println(user) //转换旧密码 olduser := TabUser{ Pass: jsonData.Oldpass, Salt: user.Salt, } HashUserPass(&olduser) if olduser.Pass == user.Pass { //旧密码正确,更新新密码 var userupdate TabUser userupdate.Pass = jsonData.Newpass userupdate.Salt = models.RandStr32() HashUserPass(&userupdate) models.DB.Model(&user).Updates(&userupdate) ReturnJson(ctx, "apiOK", nil) } else { //旧密码错误 ReturnJson(ctx, "userPassIncorrect", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } }) //更新用户邮箱 r.POST("/changeEmail", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if isAuth { var jsonData From_user_changeemail if err := mapstructure.Decode(data, &jsonData); err == nil { //判断新邮箱格式 if models.IsEmailValid(jsonData.Newemail) { var userupdate TabUser userupdate.Email = jsonData.Newemail models.DB.Model(&user).Updates(&userupdate) ReturnJson(ctx, "apiOK", nil) } else { ReturnJson(ctx, "userEmailFormatError", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } }) //修改用户头像 r.POST("/updateAvatar", func(ctx *gin.Context) { cookie := ctx.PostForm("cookie") user, err := AuthenticationAuthorityFromCookie(cookie) if err == nil { file, err := ctx.FormFile("file") if err == nil { if file.Filename != "" { //限制文件大小 if file.Size > 512 { //头像裁剪过限制1M应该差不多 if file.Size < 1048576 { //判断mime mimeType, err := models.GetFileMime(file) if err == nil { file_extname := models.ConfigsFile.AllowImageMime[mimeType] if file_extname != "" { //haxi文件 file_hashi_name, err := models.SHA256HashFile(file) if err == nil { dst := path.Join(models.ConfigsFile.Pahts["avatar"], file_hashi_name+file_extname) var is_save_ok = false //判断文件是否存在避免重复保存 if models.FileExists(dst) { //fmt.Println("文件存在") is_save_ok = true ReturnJson(ctx, "apiOK", nil) } else { //fmt.Println("文件no存在") ferr := ctx.SaveUploadedFile(file, dst) if ferr == nil { //文件保存成功 //fmt.Print("save_ok") is_save_ok = true ReturnJson(ctx, "apiOK", nil) } else { //fmt.Print(ferr) ReturnJson(ctx, "postErr", nil) } } if is_save_ok { //修改数据库内容 var user_info_fund TabUserInfo user_info_fund.UserID = user.ID var user_update_avatar TabUserInfo user_update_avatar.AvatarPath = file_hashi_name + file_extname //先查找是否有记录 if models.DB.Where(&user_info_fund).First(&user_info_fund).Error == nil { //有记录,更新 models.DB.Model(&user_info_fund).Updates(&user_update_avatar) } else { //无记录,创建 user_update_avatar.UserID = user.ID models.DB.Create(&user_update_avatar) } } } else { ReturnJson(ctx, "postErr", nil) } } else { ReturnJson(ctx, "file_mime_err", nil) } } else { ReturnJson(ctx, "postErr", nil) } } else { ReturnJson(ctx, "file_size_err", nil) } } else { ReturnJson(ctx, "file_size_err", nil) } } else { ReturnJson(ctx, "file_name_err", nil) } } else { ReturnJson(ctx, "file_get_err", nil) } } else { ReturnJson(ctx, "userCookieError", nil) } }) //更新用户info r.POST("/updateInfo", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if isAuth { var jsonData From_user_updateinfo if err := mapstructure.Decode(data, &jsonData); err == nil { // fmt.Println("updateinfo data is", jsonData) // fmt.Println(user) t, err := time.Parse("2006-01-02", jsonData.Birthday) if err == nil { var userinfo TabUserInfo userinfo.UserID = user.ID var userinfoupdate TabUserInfo userinfoupdate.UserID = user.ID userinfoupdate.CreatedAt = time.Now() userinfoupdate.Username = jsonData.Username userinfoupdate.Birthdate = t userinfoupdate.FirstName = jsonData.Remark //先查找是否有记录 if models.DB.Where(&userinfo).First(&userinfo).Error == nil { //有记录,更新 models.DB.Model(&userinfo).Updates(&userinfoupdate) } else { //无记录,创建 models.DB.Create(&userinfoupdate) // 传入指针 } ReturnJson(ctx, "apiOK", nil) } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } }) //通过cookie获取用户info r.POST("/getinfo", func(ctx *gin.Context) { isAuth, user, _ := AuthenticationAuthority(ctx) if isAuth { //载入用户info var redata map[string]interface{} = make(map[string]interface{}) info := GetUserInfoFromUserID(user.ID) redata["userInfo"] = *info user.Pass = "" user.Salt = "" redata["user"] = user // 只返回当前用户是否为系统管理员,不暴露完整列表 isSysAdmin := false for _, adminID := range sysAdmins { if adminID == user.ID { isSysAdmin = true break } } redata["isSysAdmin"] = isSysAdmin // 获取用户加入的群组列表 var binds []TabUserGroupBinds models.DB.Where("user_id = ?", user.ID).Find(&binds) var groups []map[string]interface{} for _, bind := range binds { var group TabUserGroups if models.DB.Where("id = ?", bind.GroupID).First(&group).Error == nil { groups = append(groups, map[string]interface{}{ "id": group.ID, "name": group.Name, "type": group.Type, }) } } redata["groups"] = groups ReturnJson(ctx, "apiOK", redata) } }) //用户登陆 r.POST("/login", func(ctx *gin.Context) { var loginuser From_user_login data, _ := SeparateData(ctx) if data != nil { if err := mapstructure.Decode(data, &loginuser); err == nil { if loginuser.Username != "" && loginuser.Password != "" { //传入的数据都ok,获取用户信息 getuser := TabUser{ Name: loginuser.Username, } if models.DB.Where(&getuser).First(&getuser).Error == nil { //倒入数据 user := TabUser{ Pass: loginuser.Password, //密码明文 Salt: getuser.Salt, //保存的盐制 } //哈希密 HashUserPass(&user) if user.Pass == getuser.Pass { //用户密码正确,生成cookie cookie := TabUserCookie{ UserID: getuser.ID, Name: "login", Value: models.RandStr32(), CreatedAt: time.Now(), UpdatedAt: time.Now(), ExpiresAt: time.Now().Add(time.Duration(models.ConfigsUser.CookieTimeout) * time.Second), //计算过期时间, Remember: loginuser.Remember, } models.DB.Create(&cookie) // 传入指针 // 登录成功,清除该用户的失败记录 models.DB.Where("username = ?", loginuser.Username).Delete(&TabUserLoginFailLog{}) redata := map[string]interface{}{ "cookie": cookie, } //登录成功,记录日志 //recordLoginFail(ctx, loginuser.Username, getuser.ID, "logined") ReturnJson(ctx, "apiOK", redata) } else { // 密码错误,记录失败日志 recordLoginFail(ctx, loginuser.Username, getuser.ID, "password_error") ReturnJson(ctx, "userPassIncorrect", nil) } } else { //用户不存在,记录失败日志 recordLoginFail(ctx, loginuser.Username, 0, "user_not_found") ReturnJson(ctx, "userNameNoFund", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "postErr", nil) } }) //用户注册 r.POST("/register", func(ctx *gin.Context) { //转换传进来的数据 var jsonData From_user_add data, _ := SeparateData(ctx) if data != nil { if err := mapstructure.Decode(data, &jsonData); err == nil { //转换字段 newUser := TabUser{ Name: jsonData.Username, Email: jsonData.Useremail, Pass: jsonData.Userpass, // 实际应替换为哈希值 Date: time.Now(), // Date 字段无需赋值,数据库会自动填充默认值 } if newUser.Name != "" && newUser.Pass != "" && newUser.Email != "" { //用户名是唯一的,先读取是否有这个用户名 var user TabUser user.Name = newUser.Name if models.DB.Where(&user).First(&user).Error == nil { //fmt.Println("找到用户:", user.ID) ReturnJson(ctx, "userNameDup", nil) } else { //fmt.Println("用户不存在") //对密码加盐 newUser.Salt = models.RandStr32() //对用户的密码进行哈希替换 HashUserPass(&newUser) models.DB.Create(&newUser) // 传入指针 //创建用户后写一个log ReturnJson(ctx, "apiOK", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "postErr", nil) } }) }