Skip to content

搜索适配 - #496

Closed
dengzhongyuan365-dev wants to merge 18 commits into
linuxdeepin:develop/snipefrom
dengzhongyuan365-dev:feat-tiptap
Closed

dengzhongyuan365-dev wants to merge 18 commits into
linuxdeepin:develop/snipefrom
dengzhongyuan365-dev:feat-tiptap

Conversation

@dengzhongyuan365-dev

Copy link
Copy Markdown
Member

Introduce searchable document segments and normalized text matching.

新增可搜索文档片段模型和文本归一化匹配能力。

Log: 新增搜索文档模型和文本归一化

PMS: TASK-393985

Influence: 搜索逻辑具备统一的数据表示,后续可同时覆盖标题、正文、语音标题、语音转写和图片描述等内容。

Hide the Tiptap editor when search returns no results.
Move the new note entry to the note list header.

搜索无结果时隐藏Tiptap编辑器,避免编辑区域布局异常。
将新建笔记入口移动到笔记列表标题栏。

Log: 修复搜索布局并调整新建笔记入口

PMS: TASK-393985

Influence: 搜索无结果时工具栏不再出现在页面底部,
新建笔记入口位置和显示状态更加稳定。
Delay migration start until the upgrade view can render first.

延迟启动迁移流程,确保升级界面可以先完成首次渲染。

Move the upgrade overlay to the app window and keep the titlebar visible.

将升级覆盖层移到应用窗口层级,并保留标题栏可见。

Log: 修复升级迁移界面显示时机与覆盖范围

PMS: TASK-393985

Influence: 迁移少量或空数据时也能看到升级界面,且迁移期间标题栏保持可见。
Prepare note data APIs for read-only search document extraction.

为只读搜索文档抽取准备 const 安全的数据访问接口。

Log: 调整搜索抽取所需只读接口

PMS: TASK-393985

Influence: 搜索模块可以在不修改笔记数据的前提下读取结构化内容,降低索引构建时的副作用风险。
Introduce searchable document segments and normalized text matching.

新增可搜索文档片段模型和文本归一化匹配能力。

Log: 新增搜索文档模型和文本归一化

PMS: TASK-393985

Influence: 搜索逻辑具备统一的数据表示,后续可同时覆盖标题、正文、语音标题、语音转写和图片描述等内容。
Extract legacy and Tiptap note content into searchable segments.

将旧格式和 Tiptap 笔记内容抽取为统一搜索片段。

Log: 新增笔记搜索文档抽取器

PMS: TASK-393985

Influence: 搜索可识别 Tiptap ProseMirror 正文、语音块标题、语音转写文本以及图片描述,同时保持旧格式兼容。
Add n-gram indexed search with scoring and result snippets.

新增 n-gram 索引搜索,支持评分和结果片段。

Log: 新增笔记搜索索引服务

PMS: TASK-393985

Influence: 搜索从逐条内容扫描升级为索引候选加最终校验,提升搜索性能并为结果排序和片段展示提供基础。
Build search sources and add architecture-level search tests.

接入搜索源码构建,并添加搜索架构层单测。

Log: 添加搜索架构测试

PMS: TASK-393985

Influence: 覆盖搜索抽取、归一化、索引匹配和 Tiptap 内容兼容,降低后续迁移回归风险。
Route note-level search through the shared document extractor.

将单笔记搜索改为复用统一文档抽取器。

Log: 统一单笔记搜索实现

PMS: TASK-393985

Influence: 旧的 VNoteItem::search 与新搜索索引使用一致的抽取逻辑,避免 Summernote 和 Tiptap 搜索结果不一致。
Synchronize native search query changes to the Tiptap runtime.

将宿主搜索词同步到 Tiptap 编辑器运行态。

Log: 添加 Tiptap 搜索状态桥接

PMS: TASK-393985

Influence: Native 搜索框状态可以实时驱动 Tiptap 页面高亮,清空搜索时也能恢复编辑器原始显示。
Use indexed search for result loading and keep indexes fresh.

使用索引搜索加载结果,并在笔记变更时维护索引。

Log: 接入笔记搜索业务流程

PMS: TASK-393985

Influence: 搜索结果现在包含标题高亮、匹配字段、片段和评分;保存、重命名、删除及迁移后会同步更新搜索索引。
Render runtime search highlights without changing document content.

在不修改文档内容的前提下渲染运行态搜索高亮。

Log: 添加 Tiptap 搜索高亮渲染

PMS: TASK-393985

Influence: Tiptap 正文和语音块转写文本可显示搜索命中高亮,不会进入保存内容或 undo/redo 历史。
Add tests for Tiptap search state and highlight rendering.

添加 Tiptap 搜索状态和高亮渲染测试。

Log: 添加 Tiptap 搜索高亮测试

PMS: TASK-393985

Influence: 覆盖搜索命中、清空搜索和运行态高亮行为,保障迁移过程中的前端搜索体验稳定。
Add read-only transcript selection and copy helpers in Tiptap.

添加只读语音转写文本选择和复制辅助逻辑。

Log: 修复转写文本前端选择复制

PMS: TASK-393985

Influence: 语音转写文本可以像普通文本一样选择和复制,同时保持语音块结构只读,避免破坏 ProseMirror 文档。
Allow Tiptap read-only DOM text to write to the system clipboard.

允许 Tiptap 只读 DOM 文本写入系统剪贴板。

Log: 添加转写文本剪贴板桥接

PMS: TASK-393985

Influence: 语音转写复制不再完全依赖 WebEngine 原生 Copy,提升 Ctrl+C 和右键复制在 QtWebEngine 下的稳定性。
Route shortcut and context-menu copy through transcript-aware paths.

将快捷键和右键复制路由到转写文本感知通道。

Log: 修复转写文本快捷键和右键复制

PMS: TASK-393985

Influence: Ctrl+C 和右键菜单复制语音转写文本均可正常写入剪贴板,右键无选区时也能复制当前转写内容。
Rebuild bundled editor assets after search and copy updates.

在搜索和复制修复后重建编辑器打包产物。

Log: 重建 Tiptap 编辑器产物

PMS: TASK-393985

Influence: 桌面端内置资源包含最新搜索高亮和语音转写复制逻辑。
Rename the migration upgrade view QML type to avoid runtime conflicts.

重命名迁移升级页 QML 类型,避免运行时解析到错误组件。

Log: 修复语音记事本 AT 启动后找不到初始记事本的问题

Influence: 仅影响迁移升级覆盖层的 QML 类型名,普通启动和 AT fixture 加载已验证。
Clear stale QML disk cache in AT setup and disable QML disk cache for the launched app.

在AT启动脚本中清理旧QML磁盘缓存,并禁用本次应用进程的QML缓存。

Log: 修复线上机器命中旧QML缓存导致应用未启动的问题

Influence: 仅影响语音记事本AT启动前的本应用缓存清理,不改变业务逻辑。

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @dengzhongyuan365-dev, your pull request is larger than the review limit of 150,000 diff characters

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dengzhongyuan365-dev

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 88 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
PR linuxdeepin/deepin-voice-note#496
标题 搜索适配
作者 dengzhongyuan365-dev
提交数 18
修改文件 28
关联任务 TASK-393985
审查结论 代码审查通过
评分详情 本次PR实现了完整的笔记搜索功能适配,包括N-gram索引搜索引擎、文档抽取器策略模式、搜索高亮和语音转写文本复制。代码结构清晰,线程安全设计合理,HTML转义处理到位。未发现安全漏洞。存在少量性能优化空间和代码质量改进点。

📋 变更概要

本次PR实现了 deepin-voice-note 的搜索功能全面适配,主要包含:

  1. 新增搜索子系统 (src/search/):NoteSearchService 单例服务、SearchIndexManager N-gram倒排索引、SearchDocumentExtractor 策略模式文档抽取器、SearchTextNormalizer 文本归一化
  2. 搜索业务接入:VNoteMainManager 使用索引搜索替代线性遍历、保存/重命名/删除/迁移时同步更新索引
  3. Tiptap 搜索高亮:搜索词同步到 Tiptap 编辑器运行态,不修改文档内容
  4. 语音转写文本复制:通过专用通道实现只读 DOM 文本剪贴板复制
  5. 迁移视图重构:UpgradeView 重命名为 MigrationUpgradeView 避免 QML 类型冲突
  6. UI 调整:新建笔记按钮从标题栏移至笔记列表头部
  7. 测试覆盖:新增搜索架构单元测试和 TiptapChannel 桥接测试

🔍 详细分析

1. 语法逻辑 ✅ (22/25分)

评价: 语法正确,逻辑清晰 ✅ 通过

潜在问题:

  1. src/gui/mainwindow/WebEngineView.qml - triggerTiptapWebAction 函数中使用魔法数字(5=Copy, 7=Undo, 8=Redo)表示 QWebEnginePage::WebAction 值。虽然有注释说明,但缺乏常量定义,后续维护可能出错。
  2. src/common/VNoteMainManager.cpp - loadSearchNotes 函数中 buildAllNotesLookupNoteSearchService::search(内部调用 refreshIndex)都会遍历所有笔记,存在双重遍历。虽不导致逻辑错误,但增加了不必要的计算。

亮点:

  • selectionTouches 的 Range 边界比较修复正确(旧代码的交集判断逻辑有误,新代码已修正为正确的 Range 交集判断:r.end > cr.start && r.start < cr.end
  • QTimer::singleShot(0, this, lambda) 延迟启动迁移编排器的逻辑合理,确保 UpgradeView 完成首帧绘制
  • TiptapChannelBridge::setSearchQuery 正确处理了归一化比较和编辑器重建后重新应用搜索高亮的场景
  • 线程锁使用正确:buildAllNotesLookuprefreshFromNotes 都正确使用 lockForRead()

2. 代码质量 ✅ (22/25分)

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:

  1. src/gui/mainwindow/WebEngineView.qml - WebAction 魔法数字(5/7/8)在多处使用,虽注释说明但未提取为常量
  2. src/search/searchindexmanager.cpp - fingerprintForNote 使用 \x1f(单元分隔符)拼接多个字段,若字段本身包含该字符可能产生碰撞。实际风险极低但缺乏防御性处理

亮点:

  • 采用策略模式(ISearchDocumentExtractor)支持多种笔记格式抽取(Tiptap/LegacyHtml/LegacyNoteDatas/PlainText),扩展性好
  • SearchTextNormalizer 统一文本归一化逻辑(NFKC + CaseFold + 空白压缩)
  • 新增了完整的搜索架构单元测试(ut_searcharchitecture.cpp,6个测试用例)和 TiptapChannel 桥接测试
  • 注释覆盖率高,中英文注释并行,设计意图说明清晰
  • Utils::createRichText 改进:正确处理多次匹配和 HTML 转义,修复了旧版本仅替换首个匹配且未转义的问题

3. 代码性能 ✅ (17/20分)

评价: 算法复杂度合理 ✅ 通过

潜在问题:

  1. src/search/notesearchservice.cpp - NoteSearchService::search 每次搜索都调用 refreshIndex(noteAll) 遍历所有笔记。虽然指纹缓存避免了不必要的重新索引,但全量遍历本身在笔记数量大时仍有开销。
  2. src/common/VNoteMainManager.cpp - buildAllNotesLookup 在每次搜索时构建全量 QHash<int, VNoteItem*> 查找表,与 refreshIndex 形成双重遍历。
  3. src/search/searchtextnormalizer.cpp - normalize 函数对输入字符串进行多次转换,产生多个中间字符串副本。

亮点:

  • N-gram(3-gram)倒排索引设计合理,支持快速候选过滤
  • 指纹缓存机制有效避免重复索引(fingerprintForNote 对比,相同则跳过)
  • QReadWriteLock 读写分离锁设计合理,搜索时使用读锁不阻塞并发搜索
  • 搜索结果排序考虑了评分、置顶、修改时间和 noteId 多维度
  • updateNote 先用读锁检查指纹,再决定是否获取写锁更新,避免不必要的写锁竞争

4. 代码安全 ✅ (28/30分)

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个

安全分析:

安全设计良好,未发现安全漏洞。具体安全检查结果如下:

  1. XSS 防护 ✅ - Utils::createRichTextSearchIndexManager::highlightedHtml 均正确使用 toHtmlEscaped() 对用户内容进行 HTML 转义,防止 XSS 攻击
  2. SQL 注入 ✅ - 搜索系统使用内存索引,无 SQL 查询,无注入风险
  3. 命令注入 ✅ - 无 shell 命令执行
  4. 敏感信息 ✅ - 无硬编码密钥、密码或 Token
  5. 线程安全 ✅ - 通过 QReadWriteLock 保障并发访问安全
  6. 剪贴板操作 ✅ - 使用 QMimeData::setText() 安全接口
  7. JavaScript 执行 ✅ - WebEngineView 中的 runJavaScript 调用使用内部全局函数,不涉及用户输入拼接

防御性编程建议:

  • src/handler/web_engine_handler.cpp - copyTiptapTranscriptTextToClipboard 接收来自 JS 端的文本并写入系统剪贴板,建议添加文本长度上限校验,防止异常超长文本导致内存问题

💡 改进建议代码示例

// 建议1: WebEngineView.qml 中使用常量替代魔法数字
// 在文件顶部或全局定义:
// readonly property int WebAction_Copy: 5
// readonly property int WebAction_Undo: 7
// readonly property int WebAction_Redo: 8

// 建议2: SearchIndexManager 避免每次搜索全量遍历
// 可在 NoteSearchService 中维护脏标记,仅在笔记变更时刷新索引:
class NoteSearchService : public QObject {
    // ...
private:
    bool m_indexDirty = true;
    void markDirty() { m_indexDirty = true; }
};

QList<SearchResult> NoteSearchService::search(VNOTE_ALL_NOTES_MAP *noteAll, const QString &query)
{
    if (m_indexDirty) {
        refreshIndex(noteAll);
        m_indexDirty = false;
    }
    return m_index.search(query);
}

// 建议3: fingerprintForNote 使用长度前缀编码防止分隔符碰撞
QString SearchIndexManager::fingerprintForNote(const VNoteItem *note)
{
    // 使用 JSON 序列化或长度前缀编码替代简单分隔符
    QJsonObject obj;
    obj["id"] = note->noteId;
    obj["folder"] = static_cast<int>(note->folderId);
    obj["title"] = note->noteTitle;
    obj["html"] = note->htmlCode;
    obj["meta"] = note->metaDataConstRef().toString();
    return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}

本报告由 AI 代码审查工具自动生成 | 扫描时间: 2026-08-29 14:30:00

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants