Skip to content

Commit e862c20

Browse files
committed
refactor(updater, ui): 优化更新流程、移除冗余状态管理
- 重构更新器,移除状态缓存,优化检查流程,改为每次启动时重新检测。 - 优化预览版提示逻辑和界面样式,增加版本号和提示信息的视觉区分。 - 移除`useElectronAPI` Hook,简化 Electron API 访问,直接使用 `window.electronAPI`。 - 调整 Release Notes 生成逻辑,修复检出代码问题,获取完整 git 历史和所有 tags。 - 优化版本忽略功能,`useUpdater`改为单例模式,支持预览版过滤。 - 移除build:parallel脚本中的build:desktop-only任务。 docs(dev): 更新桌面端自动更新系统设计文档 - 新增系统设计文档,描述自动更新系统的架构和界面布局。 - 补充 electron-updater 版本号说明,推荐使用 SemVer 2.0.0 标准。
1 parent ba1b862 commit e862c20

40 files changed

+1299
-3892
lines changed

.github/workflows/release.yml

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
steps:
1818
- name: 检出代码
1919
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0 # 获取完整的git历史
22+
fetch-tags: true # 确保获取所有tags
2023

2124
- name: 安装 pnpm
2225
uses: pnpm/action-setup@v2
@@ -141,6 +144,9 @@ jobs:
141144
steps:
142145
- name: 检出代码
143146
uses: actions/checkout@v4
147+
with:
148+
fetch-depth: 0 # 获取完整的git历史
149+
fetch-tags: true # 确保获取所有tags
144150

145151
- name: 安装 pnpm
146152
uses: pnpm/action-setup@v2
@@ -257,6 +263,9 @@ jobs:
257263
steps:
258264
- name: 检出代码
259265
uses: actions/checkout@v4
266+
with:
267+
fetch-depth: 0 # 获取完整的git历史
268+
fetch-tags: true # 确保获取所有tags
260269

261270
- name: 安装 pnpm
262271
uses: pnpm/action-setup@v2
@@ -376,6 +385,9 @@ jobs:
376385
steps:
377386
- name: 检出代码
378387
uses: actions/checkout@v4
388+
with:
389+
fetch-depth: 0 # 获取完整的git历史
390+
fetch-tags: true # 确保获取所有tags
379391

380392
- name: 获取版本号和类型
381393
id: version
@@ -430,35 +442,63 @@ jobs:
430442
- name: 生成 Release Notes
431443
id: release_notes
432444
run: |
433-
# 获取上一个tag
434-
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -2 | tail -1)
445+
# 获取当前tag
435446
CURRENT_TAG=${{ steps.version.outputs.version }}
436-
437-
echo "Previous tag: $PREVIOUS_TAG"
438447
echo "Current tag: $CURRENT_TAG"
439448
449+
# 获取所有tags并调试输出
450+
echo "All tags (sorted):"
451+
git tag --sort=-version:refname | head -10
452+
453+
# 改进的上一个tag获取逻辑
454+
# 1. 先尝试获取所有正式版本tag (不包含预览版)
455+
STABLE_TAGS=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$')
456+
echo "Stable tags found: $STABLE_TAGS"
457+
458+
# 2. 获取所有tag (包含预览版)
459+
ALL_TAGS=$(git tag --sort=-version:refname)
460+
461+
# 3. 根据当前tag类型选择合适的比较策略
462+
PREVIOUS_TAG=""
463+
if [[ $CURRENT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
464+
# 当前是正式版,找上一个正式版
465+
PREVIOUS_TAG=$(echo "$STABLE_TAGS" | grep -v "^$CURRENT_TAG$" | head -1)
466+
echo "Current is stable release, previous stable tag: $PREVIOUS_TAG"
467+
else
468+
# 当前是预览版,找上一个任意版本
469+
PREVIOUS_TAG=$(echo "$ALL_TAGS" | grep -v "^$CURRENT_TAG$" | head -1)
470+
echo "Current is prerelease, previous tag: $PREVIOUS_TAG"
471+
fi
472+
440473
# 生成commit历史(处理多行和长commit)
441-
if [ -n "$PREVIOUS_TAG" ]; then
474+
COMMITS=""
475+
if [ -n "$PREVIOUS_TAG" ] && git rev-parse "$PREVIOUS_TAG" >/dev/null 2>&1 && git rev-parse "$CURRENT_TAG" >/dev/null 2>&1; then
476+
echo "Generating commits from $PREVIOUS_TAG to $CURRENT_TAG"
442477
COMMITS=$(git log --pretty=format:"%s|%h" $PREVIOUS_TAG..$CURRENT_TAG | while IFS='|' read -r subject hash; do
443478
# 截断过长的commit message(保留前80个字符)
444479
if [ ${#subject} -gt 80 ]; then
445480
subject="${subject:0:77}..."
446481
fi
447-
echo "- $subject (\`$hash\`)"
482+
echo "- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash}))"
448483
done)
449-
else
450-
COMMITS=$(git log --pretty=format:"%s|%h" $CURRENT_TAG | while IFS='|' read -r subject hash; do
484+
elif git rev-parse "$CURRENT_TAG" >/dev/null 2>&1; then
485+
echo "No previous tag found or invalid, showing commits for current tag"
486+
COMMITS=$(git log --pretty=format:"%s|%h" --max-count=10 | while IFS='|' read -r subject hash; do
451487
# 截断过长的commit message(保留前80个字符)
452488
if [ ${#subject} -gt 80 ]; then
453489
subject="${subject:0:77}..."
454490
fi
455-
echo "- $subject (\`$hash\`)"
491+
echo "- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash}))"
456492
done)
457493
fi
458494
459495
# 检查是否有commits
460496
if [ -z "$COMMITS" ]; then
497+
echo "No commits found, using fallback message"
461498
COMMITS="- 首次发布"
499+
else
500+
echo "Generated commits:"
501+
echo "$COMMITS"
462502
fi
463503
464504
# 限制commits数量(最多显示20个)
@@ -521,7 +561,7 @@ jobs:
521561
- **讨论交流**: [Discussions](https://github.com/${{ github.repository }}/discussions)
522562
523563
---
524-
**提示**: 如果需要查看完整的提交历史,请访问 [GitHub Commits](https://github.com/\${{ github.repository }}/compare/$PREVIOUS_TAG...$CURRENT_TAG)
564+
**提示**: 如果需要查看完整的提交历史,请访问项目的 [GitHub Commits](https://github.com/${{ github.repository }}/commits)
525565
EOF
526566
527567
echo "Generated release notes:"

dev.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,35 @@ ls packages/desktop/dist/
272272
- **正式版本**`v1.0.0`, `v2.1.3` - 遵循语义化版本控制
273273
- **预览版本**`v1.0.0-beta.1`, `v1.0.0-rc.1`, `v1.0.0-alpha.1`
274274

275+
#### ⚠️ electron-updater 版本号注意事项
276+
277+
**重要**:electron-updater 对预发布版本的处理有特殊限制,必须使用正确的版本号格式。
278+
279+
**✅ 推荐格式(符合 SemVer 2.0.0 标准)**
280+
```bash
281+
v1.2.6-alpha.1, v1.2.6-alpha.2, v1.2.6-alpha.3
282+
v1.2.6-beta.1, v1.2.6-beta.2, v1.2.6-beta.3
283+
v1.2.6-rc.1, v1.2.6-rc.2, v1.2.6-rc.3
284+
```
285+
286+
**❌ 避免格式(可能导致 electron-updater 检测问题)**
287+
```bash
288+
v1.2.6-alpha1, v1.2.6-alpha2, v1.2.6-alpha3
289+
v1.2.6-beta1, v1.2.6-beta2, v1.2.6-beta3
290+
v1.2.6-rc1, v1.2.6-rc2, v1.2.6-rc3
291+
```
292+
293+
**问题说明**
294+
- electron-updater 将预发布版本的第一部分(如 `beta2` 中的 `beta`)视为**频道标识符**
295+
- 使用 `beta1`, `beta2`, `beta3` 格式时,可能出现版本检测异常
296+
-`v1.2.6-beta2` 无法正确检测到 `v1.2.6-beta3` 的更新
297+
- 使用点分隔格式 `beta.1`, `beta.2`, `beta.3` 可以避免此问题
298+
299+
**最佳实践**
300+
1. 始终使用点分隔的预发布版本号格式
301+
2. 遵循 `<version>-<stage>.<number>` 的命名规范
302+
3. 如果遇到版本检测问题,考虑跳过问题版本或重新发布
303+
275304
#### 🔄 完整发布流程
276305
1. **开发阶段**:在 `develop` 分支开发新功能
277306
2. **版本准备**:在 `develop` 分支使用 `pnpm version:prepare` 更新版本号
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Electron API 重构与回滚经验记录
2+
3+
## 📅 时间线
4+
- **2025-07-14**: 发现版本检查功能报错 "Failed to check versions"
5+
- **重构提交**: `12f6f49` - "feat(ui): 添加 Electron API Hook并重构更新管理"
6+
- **问题根源**: 过度抽象导致的架构复杂性和 bug
7+
8+
## 🚨 问题描述
9+
10+
### 症状
11+
```
12+
useUpdater.ts:224 [useUpdater] Error checking all versions: Error: Failed to check versions
13+
at g (useUpdater.ts:128:15)
14+
```
15+
16+
### 主进程日志正常
17+
```
18+
[DESKTOP] [2025-07-14 00:20:57] [info] Unified version check completed: { stable: '1.2.5', prerelease: '1.2.5' }
19+
```
20+
21+
### 前端收到的响应
22+
```javascript
23+
{
24+
currentVersion: '1.2.0',
25+
stable: { hasUpdate: true, remoteVersion: '1.2.5', ... },
26+
prerelease: { hasUpdate: true, remoteVersion: '1.2.5', ... }
27+
}
28+
// 但是 response.success 是 undefined
29+
```
30+
31+
## 🔍 根本原因分析
32+
33+
### 重构前(工作正常)
34+
```typescript
35+
// 简单直接
36+
const results = await window.electronAPI!.updater.checkAllVersions()
37+
```
38+
39+
### 重构后(引入问题)
40+
```typescript
41+
// 过度抽象
42+
const { updater } = useElectronAPI()
43+
const response = await updater.checkAllVersions()
44+
if (!response.success) { // response.success 是 undefined
45+
throw new Error(response.error || 'Failed to check versions')
46+
}
47+
const results = response.data
48+
```
49+
50+
### 问题链条
51+
1. **useUpdater.ts** 调用 `getElectronAPI()` 而不是 `useElectronAPI()`
52+
2. **getElectronAPI()** 直接返回 `window.electronAPI`,绕过了包装器
53+
3. **preload.js** 返回 `result.data`(直接数据)
54+
4. **useElectronAPI.ts** 期望 `{success, data, error}` 格式
55+
5. **数据格式不匹配** 导致 `response.success``undefined`
56+
57+
## 🎯 重构的初衷 vs 实际效果
58+
59+
### 初衷
60+
- 避免类型错误和 IDE 警告
61+
- 提供类型安全的 Electron API 访问
62+
63+
### 实际效果
64+
- 引入了过度复杂的抽象层
65+
- 增加了调试难度
66+
- 创造了新的 bug
67+
- 维护成本大幅增加
68+
69+
## 🔄 回滚操作记录
70+
71+
### 1. 删除过度抽象文件
72+
```bash
73+
rm packages/ui/src/composables/useElectronAPI.ts
74+
```
75+
76+
### 2. 回滚 useUpdater.ts
77+
- 移除 `useElectronAPI` 导入
78+
- 将所有 `electronUpdater` 改为 `window.electronAPI.updater`
79+
- 将所有 `electronShell` 改为 `window.electronAPI.shell`
80+
- 将所有 `electronOn/electronOff` 改为 `window.electronAPI.on/off`
81+
- 移除复杂的响应格式检查
82+
83+
### 3. 简化类型定义
84+
```typescript
85+
// packages/ui/src/types/electron.d.ts
86+
interface UpdaterAPI {
87+
checkAllVersions(): Promise<{
88+
currentVersion: string
89+
stable?: { remoteVersion?: string, hasUpdate?: boolean, ... }
90+
prerelease?: { remoteVersion?: string, hasUpdate?: boolean, ... }
91+
}>
92+
installUpdate(): Promise<void>
93+
ignoreVersion(version: string, versionType?: 'stable' | 'prerelease'): Promise<void>
94+
}
95+
96+
interface ShellAPI {
97+
openExternal(url: string): Promise<void>
98+
}
99+
```
100+
101+
### 4. 保持 preload.js 简单
102+
```javascript
103+
checkAllVersions: async () => {
104+
const result = await withTimeout(
105+
ipcRenderer.invoke(IPC_EVENTS.UPDATE_CHECK_ALL_VERSIONS),
106+
60000
107+
);
108+
if (!result.success) {
109+
throw new Error(result.error);
110+
}
111+
return result.data; // 直接返回数据
112+
}
113+
```
114+
115+
## 📚 经验教训
116+
117+
### ❌ 过度工程化的问题
118+
1. **复杂度爆炸**: 为了解决简单问题引入复杂架构
119+
2. **调试困难**: 多层抽象使问题定位变得复杂
120+
3. **维护成本**: 需要维护额外的 Hook、类型定义、包装逻辑
121+
4. **新 bug 源**: 抽象层本身成为 bug 的来源
122+
123+
### ✅ 正确的解决方案
124+
1. **简单的类型定义**: 通过完善 `electron.d.ts` 解决 IDE 警告
125+
2. **直接 API 调用**: 保持代码简洁明了
126+
3. **最小化抽象**: 只在真正需要时才引入抽象
127+
128+
### 🎯 设计原则
129+
1. **KISS 原则**: Keep It Simple, Stupid
130+
2. **YAGNI 原则**: You Aren't Gonna Need It
131+
3. **优先解决核心问题**: 类型安全 ≠ 复杂抽象
132+
4. **渐进式改进**: 从简单开始,必要时再抽象
133+
134+
## 🔧 最佳实践
135+
136+
### 解决 IDE 警告的正确方法
137+
```typescript
138+
// ✅ 正确:完善类型定义
139+
declare global {
140+
interface Window {
141+
electronAPI: {
142+
updater: UpdaterAPI
143+
shell: ShellAPI
144+
on: (event: string, callback: Function) => void
145+
off: (event: string, callback: Function) => void
146+
}
147+
}
148+
}
149+
150+
// ✅ 正确:直接使用
151+
const result = await window.electronAPI.updater.checkAllVersions()
152+
```
153+
154+
### 避免过度抽象
155+
```typescript
156+
// ❌ 错误:不必要的包装
157+
const { updater } = useElectronAPI()
158+
const response = await updater.checkAllVersions()
159+
const result = response.data
160+
161+
// ✅ 正确:直接调用
162+
const result = await window.electronAPI.updater.checkAllVersions()
163+
```
164+
165+
## 🎉 结果
166+
167+
### 回滚后的优势
168+
- **代码行数减少**: 删除了 100+ 行的包装代码
169+
- **调试简化**: 问题直接定位到源头
170+
- **类型安全**: 通过类型定义实现,无运行时开销
171+
- **维护简单**: 减少了抽象层的维护负担
172+
173+
### 性能提升
174+
- **减少函数调用**: 直接 API 调用,无包装开销
175+
- **减少内存占用**: 无额外的包装对象
176+
- **提高可读性**: 代码意图更加明确
177+
178+
## 💡 未来指导原则
179+
180+
1. **先解决问题,再考虑抽象**
181+
2. **类型安全通过类型定义实现,而非运行时包装**
182+
3. **保持 API 调用的直接性和透明性**
183+
4. **抽象必须有明确的价值,而非为了抽象而抽象**
184+
5. **重构前要充分评估复杂度收益比**
185+
186+
---
187+
188+
**教训**: 有时候最好的重构就是不重构。简单的问题用简单的方法解决。

docs/archives/118-desktop-auto-update-system/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@
121121

122122
## 📚 相关文档
123123

124-
- [技术实现详解](./implementation.md) - 详细的技术实现和架构设计,包含深度重构的技术细节
125-
- [开发经验总结](./experience.md) - 可复用的技术经验和避坑指南,包含深度重构的经验教训
126-
- [问题修复记录](./fixes-record.md) - 完整的22个问题的发现、分析、修复过程
124+
- [设计文档](./design.md) - 系统架构设计、UI布局设计和交互流程设计
125+
- [技术实现详解](./implementation.md) - 详细的技术实现和架构设计
126+
- [开发经验总结](./experience.md) - 可复用的技术经验和避坑指南
127+
- [问题修复记录](./fixes-record.md) - 完整的问题发现、分析、修复过程
127128

128129
## ✅ 项目结论
129130

0 commit comments

Comments
 (0)