package service import ( "crypto/rand" "encoding/hex" "errors" "fmt" "strconv" "time" "gorm.io/gorm" "note-manager/model" "note-manager/repository" ) // NoteService 笔记业务逻辑层 type NoteService struct { repo *repository.NoteRepository pageSize int } // NewNoteService 创建服务实例 func NewNoteService(repo *repository.NoteRepository, pageSize int) *NoteService { return &NoteService{repo: repo, pageSize: pageSize} } // CreateNote 创建笔记或目录 func (s *NoteService) CreateNote(req model.NoteCreateRequest) (*model.Note, error) { note := &model.Note{ Title: req.Title, Content: req.Content, Category: req.Category, Tags: req.Tags, IsFolder: req.IsFolder, SortOrder: req.SortOrder, IsPublic: true, // 默认公开 } if req.Password != "" { note.Password = model.HashPassword(req.Password) } if req.IsPinned != nil { note.IsPinned = *req.IsPinned } if req.IsFavorite != nil { note.IsFavorite = *req.IsFavorite } if req.IsPublic != nil { note.IsPublic = *req.IsPublic } if req.ParentID != nil { note.ParentID = *req.ParentID } if err := s.repo.Create(note); err != nil { return nil, fmt.Errorf("创建笔记失败: %w", err) } // 创建时保存第一版历史 _, _ = s.repo.SaveVersion(note) return note, nil } // GetNote 获取单条笔记(后台管理使用,任意笔记) func (s *NoteService) GetNote(id uint) (*model.Note, error) { note, err := s.repo.GetByID(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("笔记不存在") } return nil, err } return note, nil } // GetNoteContent 获取笔记内容(需密码验证,用于前台展示) // 返回 (note, 是否需要升级密码哈希, err) func (s *NoteService) GetNoteContent(id uint, password string) (*model.Note, bool, error) { note, err := s.repo.GetByID(id) if err != nil { return nil, false, errors.New("笔记不存在") } // 检查密码 if note.Password != "" { ok, upgrade := model.CheckPassword(password, note.Password) if !ok { return nil, false, errors.New("密码错误") } return note, upgrade, nil } return note, false, nil } // UpdateNote 更新笔记或目录(保存更新前快照到版本历史) func (s *NoteService) UpdateNote(id uint, req model.NoteUpdateRequest) (*model.Note, error) { note, err := s.repo.GetByID(id) if err != nil { return nil, errors.New("笔记不存在") } // 记录旧状态,判断是否产生实质内容变化 oldContent := note.Content oldTitle := note.Title if req.Title != nil { note.Title = *req.Title } if req.Content != nil { note.Content = *req.Content } if req.Category != nil { note.Category = *req.Category } if req.Tags != nil { note.Tags = *req.Tags } if req.IsPinned != nil { note.IsPinned = *req.IsPinned } if req.IsFavorite != nil { note.IsFavorite = *req.IsFavorite } if req.IsPublic != nil { note.IsPublic = *req.IsPublic } if req.ParentID != nil { note.ParentID = *req.ParentID } if req.IsFolder != nil { note.IsFolder = *req.IsFolder } if req.SortOrder != nil { note.SortOrder = *req.SortOrder } if req.RemovePassword != nil && *req.RemovePassword { note.Password = "" } else if req.Password != nil && *req.Password != "" { note.Password = model.HashPassword(*req.Password) } if err := s.repo.Update(note); err != nil { return nil, fmt.Errorf("更新笔记失败: %w", err) } // 若内容或标题发生变化,保存历史版本 if oldContent != note.Content || oldTitle != note.Title { _, _ = s.repo.SaveVersion(note) } return note, nil } // DeleteNote 软删除笔记或目录(目录会软删除所有子项) func (s *NoteService) DeleteNote(id uint) error { note, err := s.repo.GetByID(id) if err != nil { return errors.New("笔记不存在") } if note.IsFolder { return s.repo.DeleteWithChildren(id) } return s.repo.Delete(id) } // GetAllTree 获取所有笔记和目录的树形结构(管理后台用) func (s *NoteService) GetAllTree() ([]model.NoteListItem, error) { return s.repo.GetAllTree() } // GetPublicTree 获取公开笔记的树形结构(前台用) func (s *NoteService) GetPublicTree() ([]model.NoteListItem, error) { return s.repo.GetPublicTree() } // ListNotes 获取笔记列表 func (s *NoteService) ListNotes(pageStr, pageSizeStr, category, tag string, pinned, favorite *bool) ([]model.NoteListItem, int64, int, error) { page := parseInt(pageStr, 1) pageSize := parseInt(pageSizeStr, s.pageSize) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = s.pageSize } items, total, err := s.repo.List(repository.ListQuery{ Page: page, PageSize: pageSize, Category: category, Tag: tag, Pinned: pinned, Favorite: favorite, }) if err != nil { return nil, 0, 0, fmt.Errorf("获取笔记列表失败: %w", err) } totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } return items, total, totalPages, nil } // GetByParentID 获取指定目录下的所有项目 func (s *NoteService) GetByParentID(parentID uint) ([]model.NoteListItem, error) { return s.repo.GetByParentID(parentID) } // SearchNotes 搜索笔记 func (s *NoteService) SearchNotes(keyword, pageStr, pageSizeStr string) ([]model.NoteListItem, int64, int, error) { if keyword == "" { return nil, 0, 0, errors.New("搜索关键词不能为空") } page := parseInt(pageStr, 1) pageSize := parseInt(pageSizeStr, s.pageSize) items, total, err := s.repo.Search(keyword, page, pageSize) if err != nil { return nil, 0, 0, fmt.Errorf("搜索笔记失败: %w", err) } totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } return items, total, totalPages, nil } // GetCategories 获取所有分类 func (s *NoteService) GetCategories() ([]string, error) { return s.repo.GetCategories() } // GetTags 获取所有标签 func (s *NoteService) GetTags() ([]string, error) { return s.repo.GetTags() } // ─────────────── 回收站 ─────────────── // ListTrash 获取回收站列表 func (s *NoteService) ListTrash() ([]model.NoteListItem, error) { return s.repo.ListTrash() } // RestoreNote 从回收站恢复笔记或目录(整棵子树) func (s *NoteService) RestoreNote(id uint) error { note, err := s.repo.GetByIDIncludingDeleted(id) if err != nil { return errors.New("记录不存在") } if note.IsFolder { return s.repo.RestoreSubtree(id) } return s.repo.Restore(id) } // PurgeNote 彻底删除笔记或目录(不可恢复) func (s *NoteService) PurgeNote(id uint) error { note, err := s.repo.GetByIDIncludingDeleted(id) if err != nil { return errors.New("记录不存在") } if note.IsFolder { if err := s.repo.HardDeleteWithChildren(id); err != nil { return err } } else { if err := s.repo.HardDelete(id); err != nil { return err } } // 清理版本历史 _ = s.repo.DeleteVersions(id) return nil } // EmptyTrash 清空回收站 func (s *NoteService) EmptyTrash() error { trash, err := s.repo.ListTrash() if err != nil { return err } for _, item := range trash { if err := s.PurgeNote(item.ID); err != nil { return err } } return nil } // ─────────────── 版本历史 ─────────────── // ListVersions 获取笔记版本列表 func (s *NoteService) ListVersions(noteID uint) ([]model.NoteVersion, error) { return s.repo.ListVersions(noteID) } // RestoreVersion 将笔记恢复到指定版本 func (s *NoteService) RestoreVersion(noteID, versionID uint) (*model.Note, error) { version, err := s.repo.GetVersion(versionID) if err != nil { return nil, errors.New("版本不存在") } note, err := s.repo.GetByID(noteID) if err != nil { return nil, errors.New("笔记不存在") } // 保存当前状态为历史版本(防止覆盖) _, _ = s.repo.SaveVersion(note) // 回滚 note.Title = version.Title note.Content = version.Content note.Category = version.Category note.Tags = version.Tags if err := s.repo.Update(note); err != nil { return nil, fmt.Errorf("恢复版本失败: %w", err) } return note, nil } // ─────────────── 分享 ─────────────── // CreateShare 创建/更新分享令牌 func (s *NoteService) CreateShare(noteID uint, expireHours int) (*model.Note, error) { note, err := s.repo.GetByID(noteID) if err != nil { return nil, errors.New("笔记不存在") } if note.IsFolder { return nil, errors.New("目录不能分享") } note.ShareToken = randomToken(32) if expireHours > 0 { t := time.Now().Add(time.Duration(expireHours) * time.Hour) note.ShareExpireAt = &t } else { note.ShareExpireAt = nil } if err := s.repo.Update(note); err != nil { return nil, fmt.Errorf("创建分享失败: %w", err) } return note, nil } // RevokeShare 撤销分享 func (s *NoteService) RevokeShare(noteID uint) error { note, err := s.repo.GetByID(noteID) if err != nil { return errors.New("笔记不存在") } note.ShareToken = "" note.ShareExpireAt = nil return s.repo.Update(note) } // GetSharedNote 通过令牌获取分享笔记(校验过期时间) func (s *NoteService) GetSharedNote(token string) (*model.Note, error) { note, err := s.repo.GetByShareToken(token) if err != nil { return nil, err } if note.ShareExpireAt != nil && time.Now().After(*note.ShareExpireAt) { return nil, errors.New("分享链接已过期") } // 若有密码则需校验(在 handler 层处理) _ = s.repo.IncrementVisitByToken(token) return note, nil } // UpgradePasswordHash 将旧 SHA-256 密码哈希升级为 bcrypt func (s *NoteService) UpgradePasswordHash(id uint, password string) error { note, err := s.repo.GetByID(id) if err != nil { return err } note.Password = model.HashPassword(password) return s.repo.Update(note) } // randomToken 生成安全的随机令牌 func randomToken(bytesLen int) string { b := make([]byte, bytesLen) if _, err := rand.Read(b); err != nil { // 兜底使用时间戳(几乎不会发生) return fmt.Sprintf("%d", time.Now().UnixNano()) } return hex.EncodeToString(b) } func parseInt(s string, defaultVal int) int { if s == "" { return defaultVal } v, err := strconv.Atoi(s) if err != nil { return defaultVal } return v }