Files
note-manager/model/note.go
T
Your Name b93c6f1e16 feat: v3 增强 - FTS5全文搜索+双向链接/知识图谱+自动保存草稿+标签管理+实时预览编辑器+PWA
- FTS5中文分词搜索(需-tags=sqlite_fts5)
- [[wiki链接]]反向链接+SVG知识图谱
- 自动保存草稿(不触发版本历史)
- 标签重命名/合并/删除+使用统计
- 后台编辑实时预览+格式工具栏
- PWA manifest+service worker+移动端适配
- 冒烟测试扩至66例全通过
2026-08-11 12:38:41 +08:00

157 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import (
"crypto/sha256"
"encoding/hex"
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// bcryptCost 密码哈希成本因子
const bcryptCost = 10
// HashPassword 生成密码哈希(优先使用 bcrypt,向后兼容旧的 SHA-256
// 返回值格式:new(bcrypt)哈希以 $2 开头;旧的 SHA-256 以 64 位十六进制开头。
func HashPassword(password string) string {
if password == "" {
return ""
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
if err != nil {
// 极端情况下 fallback 到 sha256
s := sha256.Sum256([]byte(password))
return hex.EncodeToString(s[:])
}
return string(hash)
}
// IsBcryptHash 判断哈希是否为 bcrypt 格式
func IsBcryptHash(hash string) bool {
return len(hash) >= 4 && hash[:4] == "$2a$" || (len(hash) >= 4 && hash[:4] == "$2b$") || (len(hash) >= 4 && hash[:4] == "$2y$")
}
// CheckPassword 验证密码(兼容 bcrypt 与旧 SHA-256 哈希)
// 第二个返回值表示是否应当把存储的哈希升级为 bcrypt(旧 sha256 命中时返回 true
func CheckPassword(password, hash string) (bool, bool) {
if hash == "" {
return true, false
}
if password == "" {
return false, false
}
if IsBcryptHash(hash) {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil, false
}
// 旧的 SHA-256 哈希
s := sha256.Sum256([]byte(password))
if hex.EncodeToString(s[:]) == hash {
// 命中旧哈希,建议升级为 bcrypt
return true, true
}
return false, false
}
// Note 笔记模型(也用于目录)
type Note struct {
ID uint `json:"id" gorm:"primaryKey"`
Title string `json:"title" gorm:"size:255;not null"`
Content string `json:"content" gorm:"type:text"`
DraftContent string `json:"draft_content,omitempty" gorm:"type:text"` // 未保存的草稿(自动保存用)
Category string `json:"category" gorm:"size:100;index"`
Tags string `json:"tags" gorm:"type:text"` // JSON 数组格式存储
Password string `json:"-" gorm:"size:255"` // 访问密码(哈希存储)
IsPinned bool `json:"is_pinned" gorm:"default:false"`
IsFavorite bool `json:"is_favorite" gorm:"default:false"`
IsPublic bool `json:"is_public"` // 是否公开
ParentID uint `json:"parent_id" gorm:"default:0;index"` // 父级目录 ID0 表示根目录
IsFolder bool `json:"is_folder" gorm:"default:false"` // 是否为文件夹
SortOrder int `json:"sort_order" gorm:"default:0"` // 排序顺序
ShareToken string `json:"-" gorm:"size:64;index"` // 分享令牌,空 = 未分享
ShareExpireAt *time.Time `json:"share_expire_at,omitempty"` // 分享过期时间,nil = 永久
VisitCount int `json:"visit_count" gorm:"default:0"` // 浏览次数
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` // 软删除
}
// NoteListItem 笔记列表响应(不含内容和密码)
type NoteListItem struct {
ID uint `json:"id"`
Title string `json:"title"`
Category string `json:"category"`
Tags string `json:"tags"`
HasPassword bool `json:"has_password"` // 是否有密码保护
IsPinned bool `json:"is_pinned"`
IsFavorite bool `json:"is_favorite"`
IsPublic bool `json:"is_public"`
ParentID uint `json:"parent_id"`
IsFolder bool `json:"is_folder"`
SortOrder int `json:"sort_order"`
ShareToken string `json:"share_token,omitempty"` // 分享令牌(后台完整列表需要)
ShareExpireAt *time.Time `json:"share_expire_at,omitempty"`
VisitCount int `json:"visit_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// NoteCreateRequest 创建笔记请求
type NoteCreateRequest struct {
Title string `json:"title" binding:"required"`
Content string `json:"content"`
Category string `json:"category"`
Tags string `json:"tags"`
Password string `json:"password"`
IsPinned *bool `json:"is_pinned"`
IsFavorite *bool `json:"is_favorite"`
IsPublic *bool `json:"is_public"`
ParentID *uint `json:"parent_id"`
IsFolder bool `json:"is_folder"`
SortOrder int `json:"sort_order"`
}
// NoteUpdateRequest 更新笔记请求
type NoteUpdateRequest struct {
Title *string `json:"title"`
Content *string `json:"content"`
Category *string `json:"category"`
Tags *string `json:"tags"`
Password *string `json:"password"`
RemovePassword *bool `json:"remove_password"` // 是否移除密码
IsPinned *bool `json:"is_pinned"`
IsFavorite *bool `json:"is_favorite"`
IsPublic *bool `json:"is_public"`
ParentID *uint `json:"parent_id"`
IsFolder *bool `json:"is_folder"`
SortOrder *int `json:"sort_order"`
}
// NoteAccessRequest 笔记访问请求(验证密码)
type NoteAccessRequest struct {
Password string `json:"password"`
}
// NoteVersion 笔记版本历史
type NoteVersion struct {
ID uint `json:"id" gorm:"primaryKey"`
NoteID uint `json:"note_id" gorm:"index"`
Title string `json:"title"`
Content string `json:"content" gorm:"type:text"`
Category string `json:"category"`
Tags string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
}
// TableName 指定版本表名
func (NoteVersion) TableName() string {
return "note_versions"
}
// TagUsage 标签使用统计
type TagUsage struct {
Name string `json:"name"`
Count int `json:"count"`
}