涉及模块: 全局命名、Android 原生、Windows 托盘、Flutter 生命周期
一、产品命名讨论与决策
1.1 背景
项目原名 E2EEPAN(End-to-End Encrypted PAN),名称过于技术化,不利于品牌推广。需要一个更简洁、更有辨识度的名字。
1.2 命名迭代过程
第一轮:VaultS3
最初确定 VaultS3 作为产品名:
- 优点:Vault(保险库)体现安全性,S3 体现存储后端
- 缺点:
- 小写
vaults3不美观(数字+字母混合) - 易联想 HashiCorp Vault(同名产品)
- 代码中
vaults3作为标识符视觉效果差
- 小写
第二轮:探索替代方案
我不喜欢小写+数字的写法,决定重新探索。
约束条件:
- 不造新单词(如 Vaultix、Vaultr 等)
- 与 S3/对象存储有一定关联
- 简短,但不必是单个单词
- 视觉美观
候选方案:
| 名称 | 含义 | 评价 |
|---|---|---|
| Cipher | 密码 | 太通用 |
| CryptStore | 加密存储 | 9字母,略长 |
| Safe3 | 安全+3 | 仍有数字 |
| DataArk | 数据方舟 | 与 S3 关联弱 |
| pbox | Private Box | 与 S3 关联弱 |
| StoreX | 存储X | 简洁,Store 体现存储 |
最终决定:StoreX
- Store:直接体现”存储”核心功能
- X:代表加密(eXtended/eXcrypted 的暗示)
- 6 字母,简短
- 大小写混合
StoreX美观,纯小写storex也可接受
二、全局重命名实现
2.1 修改范围
| 类别 | 修改项 | 旧值 | 新值 |
|---|---|---|---|
| Flutter 项目 | pubspec.yaml name | vaults3_client | storex_client |
| pubspec.yaml description | VaultS3 - E2E… | StoreX - E2E… | |
| README.md 标题 | VaultS3 Client | StoreX Client | |
| Android | namespace | com.vaults3.client | com.storex.client |
| applicationId | com.vaults3.client | com.storex.client | |
| AAR 引用 | vaults3-mobile.aar | storex-mobile.aar | |
| MainActivity 包路径 | com/vaults3/client | com/storex/client | |
| MethodChannel 名 | vaults3/core | storex/core | |
| MethodChannel 名 | vaults3/video_thumbnail | storex/video_thumbnail | |
| Flutter 代码 | 数据库文件名 | vaults3.db | storex.db |
| 锁文件名 | vaults3-desktop-core.lock | storex-desktop-core.lock | |
| EXE 文件名 | vaults3-core.exe | storex-core.exe | |
| Bucket 提示 | 例如:vaults3 | 例如:storex | |
| Go 后端 | go.mod 模块名 | vaults3 | storex |
| 所有 import 路径 | vaults3/internal/… | storex/internal/… | |
| 默认 bucket | vaults3 | storex | |
| S3 元数据路径 | .vaults3/ | .storex/ | |
| 文件魔数 | VAULTS31 | STOREX01 | |
| 验证标记 | VAULTS3_VERIFY | STOREX_VERIFY |
2.2 涉及文件
Flutter 端
client/pubspec.yamlclient/README.mdclient/android/app/build.gradle.ktsclient/android/app/src/main/kotlin/com/storex/client/MainActivity.kt(新建)client/lib/core/services/native_core_service.dartclient/lib/core/services/video_thumbnail_service.dartclient/lib/core/database/database.dartclient/lib/core/state/app_state.dartclient/lib/ui/s3_config_page.dart
Go 端
core/go.modcore/cmd/server/main.gocore/mobile/mobile.gocore/internal/config/config.gocore/internal/storage/s3.gocore/internal/crypto/crypto.gocore/internal/api/server.gocore/internal/api/utils.gocore/internal/api/e2e.gocore/internal/api/files.gocore/internal/api/folders.gocore/internal/api/orphan.gocore/internal/api/send.gocore/internal/api/thumbnail.go
2.3 关键技术决策
为什么改 S3 存储路径?
我明确决定开发早期不做数据兼容,直接修改 .vaults3/ 为 .storex/。
受影响的路径:
.storex/salt- 密钥盐值.storex/verify- 密码验证标记.storex/meta.enc- 加密元数据.storex/send/sessions.enc- 发送会话.storex/send/messages/- 发送消息
为什么改文件魔数?
便携式加密文件的魔数从 VAULTS31 改为 STOREX01:
- 8 字节固定长度
- 用于识别文件格式
- 开发早期无需兼容旧格式
三、后台运行功能实现
3.1 问题分析
现状:
- Android:文件界面根目录按返回键 →
SystemNavigator.pop()→ 应用退出 - Android:其他界面按返回键 → 返回上一页 → 应用保持运行
- Windows:点击关闭按钮 → 应用完全退出,无托盘
期望:
- Android:统一行为,返回键将应用放到后台
- Windows:关闭按钮最小化到托盘,托盘菜单可真正退出
3.2 Android 实现
3.2.1 原生端修改
在 MainActivity.kt 的 storex/core MethodChannel 中添加新方法:
"moveToBackground" -> {
moveTaskToBack(true)
result.success(null)
}moveTaskToBack(true) 是 Android Activity 的原生方法,将当前任务移到后台,类似按 Home 键。
3.2.2 Flutter 端修改
NativeCoreService 添加方法:
static Future<void> moveToBackground() async {
await _channel.invokeMethod<void>('moveToBackground');
}files_page.dart 修改返回键处理:
if (state.isAtRoot) {
if (io.Platform.isAndroid) {
NativeCoreService.moveToBackground();
} else {
SystemNavigator.pop();
}
}3.3 Windows 托盘实现
3.3.1 依赖选择
| 库 | 说明 | 选择 |
|---|---|---|
| tray_manager | 托盘管理 | ✓ 使用 |
| window_manager | 窗口管理 | ✓ 使用 |
| system_tray | 另一个托盘库 | 未选用 |
选择 tray_manager + window_manager 组合:
tray_manager:管理托盘图标、菜单、点击事件window_manager:管理窗口显示/隐藏、拦截关闭事件
3.3.2 TrayService 设计
创建 client/lib/core/services/tray_service.dart:
class TrayService with TrayListener, WindowListener {
// 单例模式
static final TrayService _instance = TrayService._();
factory TrayService() => _instance;
Future<void> init() async {
// 1. 初始化窗口管理器
await windowManager.ensureInitialized();
// 2. 设置窗口选项
await windowManager.waitUntilReadyToShow(...);
// 3. 监听窗口事件
windowManager.addListener(this);
// 4. 拦截关闭事件(关键!)
await windowManager.setPreventClose(true);
// 5. 设置托盘图标和菜单
await trayManager.setIcon('windows/runner/resources/app_icon.ico');
await trayManager.setContextMenu(menu);
await trayManager.setToolTip('StoreX');
// 6. 监听托盘事件
trayManager.addListener(this);
}
// 隐藏到托盘
Future<void> hideToTray() async {
await windowManager.hide();
}
// 从托盘恢复
Future<void> showFromTray() async {
await windowManager.show();
await windowManager.focus();
}
// 拦截关闭事件
@override
void onWindowClose() async {
final isPreventClose = await windowManager.isPreventClose();
if (isPreventClose) {
await hideToTray(); // 隐藏而非退出
}
}
// 托盘菜单点击
@override
void onTrayMenuItemClick(MenuItem menuItem) {
switch (menuItem.key) {
case 'show':
showFromTray();
case 'exit':
windowManager.destroy(); // 真正退出
}
}
}3.3.3 关键技术点
setPreventClose(true):拦截窗口关闭事件,让onWindowClose被调用windowManager.hide():隐藏窗口,但进程保持运行windowManager.destroy():真正销毁窗口,退出应用- 托盘图标:使用现有的
app_icon.ico
3.3.4 main.dart 初始化
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// ... 其他初始化 ...
// Windows: 初始化系统托盘
if (io.Platform.isWindows) {
await TrayService().init();
}
// ... 启动应用 ...
}3.4 托盘菜单设计
┌─────────────┐│ 显示窗口 │├─────────────┤│ 退出 │└─────────────┘- 显示窗口:恢复窗口到前台
- 退出:真正关闭应用
3.5 交互流程
Android
在文件根目录按返回键 ↓PopScope.onPopInvokedWithResult 触发 ↓检测 Platform.isAndroid ↓调用 NativeCoreService.moveToBackground() ↓MethodChannel 调用原生 moveTaskToBack(true) ↓应用进入后台(进程保持运行)Windows
点击窗口关闭按钮 (X) ↓WindowListener.onWindowClose 触发 ↓检测 isPreventClose == true ↓调用 windowManager.hide() ↓窗口隐藏,托盘图标保留 ↓可点击托盘图标恢复窗口 ↓或点击托盘菜单"退出"真正退出四、验证结果
4.1 编译检查
# Go 后端
$ go vet ./...
# 无错误
# Flutter 客户端
$ flutter analyze
# 仅 3 个历史遗留的 info 级别警告(use_build_context_synchronously)4.2 后续手动事项
- 构建 AAR:生成后需重命名为
storex-mobile.aar - 构建 EXE:生成后需重命名为
storex-core.exe - 清空旧数据:S3 桶中的
.vaults3/目录、本地vaults3.db文件
五、经验总结
5.1 命名决策
- 产品命名需综合考虑:技术含义、品牌独特性、代码美观性
- 小写+数字组合(如
vaults3)在代码中视觉效果差 - 开发早期不需考虑数据兼容,可大胆重命名
5.2 跨平台后台运行
- Android 使用
moveTaskToBack放后台,而非SystemNavigator.pop退出 - Windows 需要
window_manager拦截关闭 +tray_manager托盘支持 - 托盘退出需显式调用
windowManager.destroy()
5.3 全局重命名
- 涉及:模块名、包名、路径、常量、通道名、文件名
- 开发早期可直接改存储路径,不需兼容层
- 魔数修改需更新版本号(
STOREX01中的01)
六、相关文件索引
| 文件 | 用途 |
|---|---|
client/lib/core/services/tray_service.dart | Windows 托盘服务 |
client/lib/core/services/native_core_service.dart | Android 原生通道 |
client/lib/ui/files_page.dart | 返回键处理 |
client/android/.../MainActivity.kt | Android 原生实现 |
core/internal/api/server.go | 存储路径定义 |
core/internal/crypto/crypto.go | 文件魔数定义 |