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"` // 父级目录 ID,0 表示根目录 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"` }