Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions TeXmacs/misc/themes/liii-night.css
Original file line number Diff line number Diff line change
Expand Up @@ -1018,42 +1018,17 @@ QWidget#centralWidget QWidget#startup-tab-content {
}

QWidget#centralWidget QWidget#startup-tab-file-cards,
QWidget#centralWidget QWidget#startup-tab-category-bar,
QWidget#centralWidget QWidget#startup-tab-template-item,
QWidget#centralWidget QLabel#startup-tab-template-name,
QWidget#centralWidget QLabel#startup-tab-template-info {
background-color: #2c2c2c;
}

/* 页面标题 */
QWidget#centralWidget QLabel#startup-tab-page-title {
font-weight: bold;
color: #ffffff;
background: #2c2c2c;
}

/* 页面描述文字 */
QLabel#startup-tab-page-desc {
color: #aaaaaa;
}

/* 分类按钮 */
QPushButton#startup-tab-category-btn {
background: transparent;
border: none;
color: #aaaaaa;
}

QPushButton#startup-tab-category-btn:hover {
background: #4a4f57;
color: #ffffff;
}

QPushButton#startup-tab-category-btn:checked {
background: #215a6a;
color: white;
}

/* 模板网格容器 */
QWidget#centralWidget QWidget#startup-tab-grid {
background-color: #2c2c2c;
Expand Down
23 changes: 0 additions & 23 deletions TeXmacs/misc/themes/liii.css
Original file line number Diff line number Diff line change
Expand Up @@ -1021,34 +1021,11 @@ QWidget#startup-tab-content {
background-color: #f3f3f3;
}

/* 页面标题 */
QLabel#startup-tab-page-title {
font-weight: bold;
color: #215a6a;
}

/* 页面描述文字 */
QLabel#startup-tab-page-desc {
color: #666666;
}

/* 分类按钮 */
QPushButton#startup-tab-category-btn {
background: transparent;
border: none;
color: #666666;
}

QPushButton#startup-tab-category-btn:hover {
background: #dfdfdf;
color: #333333;
}

QPushButton#startup-tab-category-btn:checked {
background: #215a6a;
color: white;
}

/* 模板网格容器 */
QWidget#startup-tab-grid {
background-color: #f3f3f3;
Expand Down
33 changes: 20 additions & 13 deletions TeXmacs/templates/categories.scm
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,32 @@
(texmacs-module (templates categories))

(tm-define template-default-categories
'(((id . "university-thesis")
(name . "University Thesis")
(icon . "🎓")
(order . 1))
'(((categoryKey . "university-thesis")
(name . "高校论文")
(nameEn . "University Thesis")
(description . "各高校学位论文模板")
(order . 1)
(templateCount . 15))

((id . "lab-report")
(name . "Lab Report")
(icon . "📊")
(order . 2))
((categoryKey . "lab-report")
(name . "实验报告")
(nameEn . "Lab Report")
(description . "各类实验报告模板")
(order . 2)
(templateCount . 10))

((id . "math-modeling")
(name . "Math Modeling")
(icon . "🧪")
(order . 3))))
((categoryKey . "math-modeling")
(name . "数学建模")
(nameEn . "Math Modeling")
(description . "数学建模竞赛论文模板")
(order . 3)
(templateCount . 8))))

(tm-define (template-get-category-name category-id)
(:synopsis "Get the display name for a category")
(let ((cat (list-find template-default-categories
(lambda (c) (equal? (assoc-ref c 'id) category-id)))))
(lambda (c)
(equal? (assoc-ref c 'categoryKey) category-id)))))
(if cat
(assoc-ref cat 'name)
category-id)))
Expand Down
267 changes: 267 additions & 0 deletions devel/1012.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# [1012] TemplateCenter 启动流程重构

## 1 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 2 任务相关的代码文件
- `src/Mogan/TemplateCenter/template_api.cpp` - API 客户端,解析 liiistem.cn 响应
- `src/Mogan/TemplateCenter/template_api.hpp` - API 客户端头文件
- `src/Mogan/TemplateCenter/template_cache.cpp` - 缓存读写逻辑
- `src/Mogan/TemplateCenter/template_cache.hpp` - 缓存头文件
- `src/Mogan/TemplateCenter/template_manager.cpp` - 模板管理器主逻辑
- `src/Mogan/TemplateCenter/template_manager.hpp` - 模板管理器头文件
- `src/Plugins/Qt/QTMStartupTabWidget.cpp` - 启动页左侧导航栏(动态分类按钮)
- `src/Plugins/Qt/QTMTemplatePage.cpp` - 模板页(模板卡片网格)
- `src/Plugins/Qt/QTMTemplateOpener.cpp` - 模板打开器(MD5 校验入口)
- `TeXmacs/templates/categories.scm` - 默认分类 Scheme 配置

## 3 如何测试

### 3.1 确定性测试(单元测试)

#### TemplateCache 测试

```bash
./bin/test_only template_cache_test
```

测试覆盖:
- 分类缓存的保存与加载(含字段映射)
- 损坏缓存文件自动清理
- 模板注册、查询、删除
- 缓存大小计算
- 清空缓存

#### TemplateAPI 解析测试

```bash
./bin/test_only template_api_parser_test
```

测试覆盖:
- categories 响应解析与排序
- templates 字段映射(categoryKey、url、pdfUrl、fileMd5 等)
- created_at/updated_at 字段回退兼容
- 空数组/非数组异常处理

#### TemplateAPI 集成测试

```bash
./bin/test_only template_api_integration_test
```

测试覆盖:
- 分类获取成功/失败/离线阻断
- 模板获取成功/失败
- 下载成功/失败/取消
- 下载进度信号
- HTTP 错误码处理(404 等)
- 并发下载
- 析构时清理活跃请求

#### StartupTabWidget 测试

```bash
./bin/test_only startup_tab_widget_test
```

测试覆盖:
- 页面构造与初始化
- templatesLoaded 信号触发网格刷新
- setCategory 不崩溃
- refreshGrid 不崩溃
- setCategory 带 displayName
- resizeEvent 不崩溃

### 3.2 非确定性测试(文档验证)

#### 首次启动(无缓存)

**前置条件**:删除缓存目录
位于`$TEXMACS_HOME_PATH/system/template_cache`

**验证步骤**:
1. 启动 Mogan STEM
2. 进入启动页左侧"模板"分类导航
3. 预期显示默认分类:高校论文、实验报告、数学建模
4. 点击"高校论文"
5. 右侧模板卡片网格为空或显示加载状态
6. 等待网络响应后显示该分类模板

#### 二次启动(有缓存)

**前置条件**:正常退出后再次启动

**验证步骤**:
1. 启动 Mogan STEM
2. 进入启动页左侧"模板"分类导航
3. 预期分类列表立即可见(毫秒级,从缓存加载)
4. 点击已缓存过的分类
5. 预期模板卡片立即显示(从缓存加载)

#### 网络恢复自动刷新

**前置条件**:断开网络后启动,再恢复网络

**验证步骤**:
1. 断开网络,启动 Mogan STEM
2. 显示缓存的分类和模板(如有)
3. 恢复网络连接
4. 预期后台自动请求远程分类
5. 如有更新,左侧导航栏自动刷新

#### MD5 校验

**前置条件**:已下载过某个模板

**验证步骤**:
1. 找到已下载模板的缓存文件(`$TEXMACS_HOME_PATH/system/templates/xxx.tmu`)
2. 用文本编辑器修改文件内容(破坏 MD5)
3. 再次点击该模板
4. 预期自动清理损坏缓存,重新下载
5. 终端日志
- 校验结果一致:
```bash
[Template] "elegantbook" MD5 verified: "fc2ad78a8bd9f84617ccf224baf72503"
```
- 校验结果不一致:
```bash
[Template] "elegantbook" MD5 mismatch (expected: "fc2ad78a8bd9f84617ccf224baf72503" actual: "ac101be348f706af9f63835f6da17632" ), clearing cache
[Template] Removed cached template file: "C:/Users/26221/AppData/Roaming/moganlab/system/templates/elegantbook.tmu"
```

## 4 提交说明

提交前执行以下最少步骤:

```bash
xmake b stem
./bin/test_only template_cache_test
./bin/test_only template_api_parser_test
./bin/test_only template_api_integration_test
./bin/test_only startup_tab_widget_test
```

## 5 What

1. **API 从静态 JSON 改为 POST 分页接口**:liiistem.cn 原提供 `templates.json` 静态文件(GET 请求),现改为 `/api/v1/doc/template/categories` + `/api/v1/doc/template/list` 两个 POST 接口,支持分类列表和分页模板列表
2. **UI 分类导航重构**:将分类按钮从 TemplatePage 内部移到 StartupTabWidget 左侧边栏,实现动态加载
3. **按需加载模板**:点击分类后才请求该分类的模板列表,避免一次性加载全部模板
4. **MD5 文件校验**:下载后计算 MD5,`QTMTemplateOpener` 打开模板时通过 `verifyLocalTemplate` 验证文件完整性
5. **代码审查修复**:
- `computeFileMd5` 改为 64KB 分块读取,避免大文件 OOM
- `isTemplateAvailableLocally` 恢复为纯查询(const),MD5 校验拆分到 `verifyLocalTemplate`
- `QTMTemplateOpener` 使用 `verifyLocalTemplate` 触发 MD5 校验
- 增量更新检测改为 `pendingIncrementalCategoryId_` 显式标记
- `updatedAt` 字段支持 `updateTime`/`updated_at` 回退
- 补充中文注释和单元测试

## 6 Why

1. **静态 JSON 无法扩展**:`templates.json` 随着模板增多体积越来越大,改为 POST 分页接口可按需加载
2. **启动性能优化**:首次打开模板页时不需要等待全部模板加载,分类按需加载
3. **离线可用**:缓存机制确保无网络时仍能展示分类和已下载模板
4. **代码质量**:review 中发现 `const_cast`、脆弱的增量检测逻辑、大文件 MD5 OOM 风险等问题需要修复

## 7 How

### 7.1 启动流程

```
[启动]
|
v
[TemplateManager::initialize]
|
+-- 初始化缓存
+-- 加载分类(关键分支)
| 有缓存? 从 cache 加载
| 无缓存? 从 Scheme 文件加载默认分类
+-- 加载模板缓存(有缓存时恢复)
+-- 后台请求远程分类
|
v
[UI 显示左侧分类导航]
```

### 7.2 有缓存 vs 无缓存

| 阶段 | 有缓存 | 无缓存 |
|---|---|---|
| 分类来源 | `cache/categories.json` | `TeXmacs/templates/categories.scm` |
| 模板来源 | `cache/metadata.json` + `cache/index.json` | 无,点击分类后网络加载 |
| 显示速度 | 毫秒级 | 毫秒级(分类),模板需网络请求 |
| 网络请求 | 后台刷新分类 | 后台刷新分类 |

### 7.3 点击分类后的流程

```
[点击分类按钮]
|
v
[StartupTabWidget]
记录当前分类
通知 TemplatePage 显示该分类
通知 TemplateManager 请求该分类模板
|
+-->[TemplatePage] 显示分类标题
| 如果已缓存模板,直接显示
|
+-->[TemplateManager]
检查是否已获取过(避免重复请求)
POST /api/v1/doc/template/list
参数: { "categoryKey": "xxx" }
响应成功后更新模板列表
通知 TemplatePage 刷新
```

### 7.4 增量更新检测

旧方案通过分析返回数据推断是否为增量请求,逻辑脆弱。

新方案显式标记:

```cpp
// 发起请求时记录
pendingIncrementalCategoryId_ = categoryId;
api_->fetchTemplates(categoryId);

// 响应处理时直接使用
bool incremental = !pendingIncrementalCategoryId_.isEmpty();
mergeMetadata(remoteMetadata, incremental);
```

### 7.5 MD5 校验

```
[下载完成]
|
v
[计算文件 MD5]
|
v
[写入缓存索引]
|
v
[后续检查]
MD5 匹配 -> 可用
MD5 不匹配 -> 删除缓存,重新下载
```

### 7.6 关键状态变量

| 变量 | 含义 |
|---|---|
| `categoriesFetched_` | 是否已成功获取过远程分类 |
| `fetchedCategories_` | 本会话中已获取过模板的分类 ID 集合 |
| `pendingIncrementalCategoryId_` | 当前正在请求的分类 ID(用于增量更新标记) |
| `isRefreshingCategories_` | 是否正在请求分类(防重复) |
| `isRefreshingTemplates_` | 是否正在请求模板(防重复) |

### 7.7 接口设计

| 方法 | 语义 | 副作用 |
|---|---|---|
| `isTemplateAvailableLocally()` | 纯查询:检查文件是否存在 | 无(const) |
| `verifyLocalTemplate()` | 完整校验:MD5 匹配 + 损坏清理 | 清理缓存、重置状态 |
| `localTemplatePath()` | 获取本地路径 | 无 |
Loading
Loading