feat: 云笔记增强 - 安全加固 + 回收站/版本历史/分享/批量导出 + 前端优化
- 安全: 认证改随机token会话(弃固定cookie), 笔记密码SHA256升级为bcrypt(自动迁移), 堵住GET /api/notes/:id泄露带密码笔记, CORS收紧+SameSite防CSRF, 上传图片内容嗅探 - 回收站: 软删除(deleted_at), 列表/恢复/彻底删除/清空, 目录子树连删连恢复 - 版本历史: note_versions表存快照, 每次保存自动留档, 支持查看/回滚 - 分享: 生成随机token分享链接, 支持过期时间, 公开阅读页share.html - 批量导出: 全部笔记打包zip(按目录结构+front matter) - 前端: 深色模式, Mermaid图表, 待办清单checkbox, 字数统计; 后台新增回收站/历史/分享面板和批量导出按钮 - 新增deploy/note-manager.service systemd单元与smoke_test.py
This commit is contained in:
+39
-23
@@ -15,47 +15,60 @@ func Setup(r *gin.Engine, noteHandler *handler.NoteHandler, adminHandler *handle
|
||||
// 静态文件服务(图片)
|
||||
r.Static("/uploads", cfg.UploadDir)
|
||||
|
||||
// API 路由组
|
||||
// ─────────── 公开 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) // 密码验证访问
|
||||
|
||||
// 需要认证的管理接口
|
||||
notes.POST("", noteHandler.CreateNote)
|
||||
notes.PUT("/:id", noteHandler.UpdateNote)
|
||||
notes.DELETE("/:id", noteHandler.DeleteNote)
|
||||
}
|
||||
|
||||
api.GET("/categories", noteHandler.GetCategories)
|
||||
api.GET("/tags", noteHandler.GetTags)
|
||||
api.GET("/tree", noteHandler.GetPublicTree) // 前台公开树
|
||||
|
||||
// 分享 JSON 接口(公开)
|
||||
api.GET("/share/:token", noteHandler.GetSharedNote)
|
||||
}
|
||||
|
||||
// 管理后台专用 API(需要认证)
|
||||
// ─────────── 管理后台 API(需认证)───────────
|
||||
adminApi := r.Group("/admin/api")
|
||||
adminApi.Use(func(c *gin.Context) {
|
||||
token, err := c.Cookie("admin_token")
|
||||
if err != nil || token != "authenticated" {
|
||||
c.JSON(401, gin.H{"code": 401, "message": "请先登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
adminApi.Use(middleware.AuthRequired())
|
||||
{
|
||||
adminApi.GET("/tree", noteHandler.GetTree) // 后台完整树
|
||||
adminApi.POST("/upload", imageHandler.Upload) // 图片上传
|
||||
adminApi.GET("/export/:id", noteHandler.ExportNote) // 导出笔记
|
||||
adminApi.POST("/import", noteHandler.ImportNotes) // 导入笔记
|
||||
// 笔记写操作
|
||||
adminApi.POST("/notes", noteHandler.CreateNote)
|
||||
adminApi.GET("/notes/:id", noteHandler.GetAdminNote)
|
||||
adminApi.PUT("/notes/:id", noteHandler.UpdateNote)
|
||||
adminApi.DELETE("/notes/:id", noteHandler.DeleteNote)
|
||||
|
||||
// 回收站
|
||||
adminApi.GET("/trash", noteHandler.ListTrash)
|
||||
adminApi.POST("/restore/:id", noteHandler.RestoreNote)
|
||||
adminApi.POST("/purge/:id", noteHandler.PurgeNote)
|
||||
adminApi.POST("/empty-trash", noteHandler.EmptyTrash)
|
||||
|
||||
// 版本历史
|
||||
adminApi.GET("/notes/:id/versions", noteHandler.ListVersions)
|
||||
adminApi.POST("/notes/:id/restore-version", noteHandler.RestoreVersion)
|
||||
|
||||
// 分享
|
||||
adminApi.POST("/notes/:id/share", noteHandler.CreateShare)
|
||||
adminApi.POST("/notes/:id/revoke-share", noteHandler.RevokeShare)
|
||||
|
||||
// 导入导出
|
||||
adminApi.GET("/export/:id", noteHandler.ExportNote)
|
||||
adminApi.GET("/export-all", noteHandler.ExportAll)
|
||||
adminApi.POST("/import", noteHandler.ImportNotes)
|
||||
|
||||
// 完整树 + 图片上传
|
||||
adminApi.GET("/tree", noteHandler.GetTree)
|
||||
adminApi.POST("/upload", imageHandler.Upload)
|
||||
}
|
||||
|
||||
// 后台管理路由
|
||||
// ─────────── 后台管理路由 ───────────
|
||||
admin := r.Group("/admin")
|
||||
{
|
||||
admin.GET("/login", adminHandler.LoginPage)
|
||||
@@ -65,6 +78,9 @@ func Setup(r *gin.Engine, noteHandler *handler.NoteHandler, adminHandler *handle
|
||||
admin.GET("/", adminHandler.IndexPage)
|
||||
}
|
||||
|
||||
// ─────────── 分享阅读页 ───────────
|
||||
r.GET("/share/:token", noteHandler.SharePage)
|
||||
|
||||
// 健康检查
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
|
||||
Reference in New Issue
Block a user