feat: 多租户账号体系 + 前台收藏按钮

- 新增 users 表(user_id 数据隔离,bcrypt 密码)
- 认证: 注册/登录(用户名+密码)/会话绑定用户, 首个用户成为管理员并接管旧数据
- 数据隔离: 笔记/分类/标签/回收站/版本/草稿/图谱/FTS 全部按用户隔离
- 前台: 登录/注册弹窗, 登录后★收藏自己的笔记, 游客只读公开笔记
- 后台: 用户名+密码登录, 每人管理自己的工作区, 越权访问返回404
- 冒烟测试重构+新增多租户隔离用例(78/78)
This commit is contained in:
Your Name
2026-08-11 12:58:12 +08:00
parent 2196189791
commit d9793300f9
16 changed files with 1216 additions and 278 deletions
+15 -5
View File
@@ -8,33 +8,43 @@ import (
)
// Setup 初始化路由
func Setup(r *gin.Engine, noteHandler *handler.NoteHandler, adminHandler *handler.AdminHandler, imageHandler *handler.ImageHandler, cfg *config.Config) *gin.Engine {
func Setup(r *gin.Engine, noteHandler *handler.NoteHandler, authHandler *handler.AuthHandler, adminHandler *handler.AdminHandler, imageHandler *handler.ImageHandler, cfg *config.Config) *gin.Engine {
// 全局中间件
r.Use(middleware.CORS())
r.Use(middleware.CurrentUser()) // 解析当前用户 ID(游客为 0),供公开/混合路由使用
// 静态文件服务(图片)
r.Static("/uploads", cfg.UploadDir)
// ─────────── 公开 API ───────────
// ─────────── 认证接口(多租户账号)───────────
auth := r.Group("/api/auth")
{
auth.POST("/register", authHandler.Register) // 首个用户自动成为 admin
auth.POST("/login", authHandler.Login)
auth.POST("/logout", authHandler.Logout)
auth.GET("/me", authHandler.Me)
}
// ─────────── 公开 API(登录用户看自己的;游客看公开笔记)───────────
api := r.Group("/api")
{
notes := api.Group("/notes")
{
notes.GET("", noteHandler.ListNotes)
notes.GET("/search", noteHandler.SearchNotes)
notes.GET("/:id", noteHandler.GetNote) // 公开安全版:公开无密码的笔记
notes.GET("/:id", noteHandler.GetNote) // 公开安全版:游客只看公开无密码;登录用户看自己
notes.POST("/:id/access", noteHandler.AccessNote) // 密码验证访问
}
api.GET("/categories", noteHandler.GetCategories)
api.GET("/tags", noteHandler.GetTags)
api.GET("/tree", noteHandler.GetPublicTree) // 前台公开
api.GET("/tree", noteHandler.GetPublicTree) // 前台树(登录=自己,游客=公开
// 分享 JSON 接口(公开)
api.GET("/share/:token", noteHandler.GetSharedNote)
}
// ─────────── 管理后台 API(需认证)───────────
// ─────────── 管理(个人工作区)API(需登录)───────────
adminApi := r.Group("/admin/api")
adminApi.Use(middleware.AuthRequired())
{