feat: 多租户账号体系 + 前台收藏按钮
- 新增 users 表(user_id 数据隔离,bcrypt 密码) - 认证: 注册/登录(用户名+密码)/会话绑定用户, 首个用户成为管理员并接管旧数据 - 数据隔离: 笔记/分类/标签/回收站/版本/草稿/图谱/FTS 全部按用户隔离 - 前台: 登录/注册弹窗, 登录后★收藏自己的笔记, 游客只读公开笔记 - 后台: 用户名+密码登录, 每人管理自己的工作区, 越权访问返回404 - 冒烟测试重构+新增多租户隔离用例(78/78)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"note-manager/middleware"
|
||||
"note-manager/service"
|
||||
)
|
||||
|
||||
// AuthHandler 认证处理器(多租户账号:注册/登录/登出/当前用户)
|
||||
type AuthHandler struct {
|
||||
userSvc *service.UserService
|
||||
}
|
||||
|
||||
// NewAuthHandler 创建认证处理器
|
||||
func NewAuthHandler(userSvc *service.UserService) *AuthHandler {
|
||||
return &AuthHandler{userSvc: userSvc}
|
||||
}
|
||||
|
||||
// REGISTER 注册请求
|
||||
type registerReq struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
// Register 注册新用户
|
||||
// 首个注册用户自动成为 admin,并接管历史遗留笔记。
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req registerReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
u, err := h.userSvc.Register(req.Username, req.Password, req.DisplayName)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
// 注册成功即自动登录
|
||||
token, _ := middleware.NewSession(u.ID)
|
||||
secure := c.Request.TLS != nil
|
||||
middleware.SetAuthCookie(c, token, secure)
|
||||
success(c, gin.H{"id": u.ID, "username": u.Username, "display_name": u.DisplayName, "role": u.Role})
|
||||
}
|
||||
|
||||
// Login 登录(用户名 + 密码)
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
// 兼容 form 提交(后台登录页)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
req.Username = c.PostForm("username")
|
||||
req.Password = c.PostForm("password")
|
||||
}
|
||||
if req.Password == "" || req.Username == "" {
|
||||
fail(c, http.StatusBadRequest, "请输入用户名和密码")
|
||||
return
|
||||
}
|
||||
u, err := h.userSvc.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
fail(c, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
token, _ := middleware.NewSession(u.ID)
|
||||
secure := c.Request.TLS != nil
|
||||
middleware.SetAuthCookie(c, token, secure)
|
||||
success(c, gin.H{"id": u.ID, "username": u.Username, "display_name": u.DisplayName, "role": u.Role})
|
||||
}
|
||||
|
||||
// Logout 登出
|
||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
middleware.RevokeSession(middleware.GetAuthToken(c))
|
||||
middleware.ClearAuthCookie(c)
|
||||
success(c, nil)
|
||||
}
|
||||
|
||||
// Me 返回当前登录用户信息(未登录返回 null)
|
||||
func (h *AuthHandler) Me(c *gin.Context) {
|
||||
uid := middleware.GetUserID(c)
|
||||
if uid == 0 {
|
||||
success(c, nil)
|
||||
return
|
||||
}
|
||||
u, err := h.userSvc.GetByID(uid)
|
||||
if err != nil {
|
||||
success(c, nil)
|
||||
return
|
||||
}
|
||||
success(c, gin.H{
|
||||
"id": u.ID,
|
||||
"username": u.Username,
|
||||
"display_name": u.DisplayName,
|
||||
"role": u.Role,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user