09 — 云融(YunRong)· 部署与运维文档
前置文档:05-模块详细设计文档、07-GUI设计文档
设计思路:00-设计思路文档
版本:v0.1
状态:已发布
最后更新:2025-01
1. 构建指南
1.1 开发环境要求
| 依赖 | 最低版本 | Windows 安装 | Linux 安装 |
|---|
| CMake | 3.20+ | winget install Kitware.CMake | apt install cmake |
| C++ 编译器 | GCC 9+ / MSVC 2019+ / Clang 14+ | Visual Studio 2022 Build Tools | apt install build-essential |
| Qt 6 | 6.2+ | winget install Qt.Qt6 或在线安装器 | apt install qt6-base-dev qt6-charts-dev qt6-websockets-dev |
| SQLite3 | 3.35+ | vcpkg install sqlite3 | apt install libsqlite3-dev |
| nlohmann/json | 3.10+ | vcpkg install nlohmann-json | apt install nlohmann-json3-dev |
| spdlog | 1.10+ | vcpkg install spdlog | apt install libspdlog-dev |
| Google Test | 1.12+ | vcpkg install gtest | apt install libgtest-dev |
| Git | 2.30+ | winget install Git.Git | apt install git |
1.2 获取源码
git clone https://github.com/your-org/yunrong.git
cd yunrong
git submodule update --init --recursive # 如有第三方 submodule
1.3 一键构建(CMake Presets)
# 列出可用预设
cmake --list-presets
# Debug 构建
cmake --preset debug
cmake --build --preset debug -j$(nproc)
# Release 构建
cmake --preset release
cmake --build --preset release -j$(nproc)
# 运行测试
ctest --preset test
CMakePresets.json
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTS": "ON",
"ENABLE_COVERAGE": "ON"
}
},
{
"name": "release",
"displayName": "Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTS": "OFF"
}
}
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" },
{ "name": "release", "configurePreset": "release" }
],
"testPresets": [
{
"name": "test",
"configurePreset": "debug",
"output": { "outputOnFailure": true }
}
]
}
1.4 平台特定构建选项
# Linux: 指定 Qt 安装路径
cmake -B build -DCMAKE_PREFIX_PATH=/opt/Qt/6.5.0/gcc_64
# Windows: 指定 vcpkg 工具链
cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
# 静态链接 (Linux)
cmake -B build -DBUILD_STATIC=ON
# 跳过 GUI 构建 (仅构建 core 库,用于 CI/服务端)
cmake -B build -DBUILD_GUI=OFF
1.5 Go 后端构建
Go 后端为独立项目,与 C++ 客户端分仓库管理。
| 依赖 | 最低版本 | 安装 |
|---|
| Go | 1.22+ | winget install GoLang.Go / apt install golang-go |
| PostgreSQL | 14+ | 仅运行时依赖 |
# 获取源码
git clone https://github.com/your-org/yunrong-server.git
cd yunrong-server
# 下载依赖
go mod download
# 开发模式运行
go run ./cmd/server -config config/dev.yaml
# 编译
go build -o bin/server ./cmd/server
# 运行测试
go test ./...
2. 打包与分发
2.1 打包策略
| 平台 | 格式 | 工具 | 说明 |
|---|
| Windows | .exe 安装包 | NSIS 或 WiX Toolset | 注册开始菜单、文件关联、卸载入口 |
| Windows | 便携版 .zip | CMake install() + 7z | 免安装,适合企业批量部署 |
| Linux | .AppImage | linuxdeployqt | 单文件,跨发行版,无需 root |
| Linux | .deb | CPack | Debian/Ubuntu 系 |
| Linux | .rpm | CPack | Fedora/RHEL 系 |
2.2 CMake 安装规则
# 顶层 CMakeLists.txt
include(GNUInstallDirs)
include(CPack)
# 安装目标
install(TARGETS yunrong_client
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
BUNDLE DESTINATION .
)
# 配置文件模板
install(FILES config/default.json
DESTINATION ${CMAKE_INSTALL_DATADIR}/yunrong/config
)
# 图标
install(FILES resources/icons/app.png
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps
RENAME yunrong.png
)
# Linux: .desktop 文件
if(UNIX AND NOT APPLE)
install(FILES resources/yunrong.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
)
endif()
2.3 Windows NSIS 安装脚本骨架
; installer.nsi
!define PRODUCT_NAME "YunRong"
!define PRODUCT_VERSION "0.1.0"
!define PRODUCT_PUBLISHER "YourOrg"
OutFile "${PRODUCT_NAME}-${PRODUCT_VERSION}-setup.exe"
InstallDir "$PROGRAMFILES64\${PRODUCT_PUBLISHER}\${PRODUCT_NAME}"
Section "Install"
SetOutPath "$INSTDIR"
File /r "dist\*.*"
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\yunrong_client.exe"
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\yunrong_client.exe"
WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd
2.4 Linux AppImage 生成
# 1. 安装到临时目录
cmake --install build/release --prefix AppDir/usr
# 2. 复制依赖
linuxdeployqt AppDir/usr/share/applications/yunrong.desktop \
-qmake=/opt/Qt/6.5.0/gcc_64/bin/qmake \
-appimage
# 3. 输出: YunRong-x86_64.AppImage
2.5 版本号策略
采用语义化版本 (SemVer):MAJOR.MINOR.PATCH
0.1.0 — 开发预览版
0.2.0 — 新增文件传输功能
1.0.0 — 首个正式发布版
1.0.1 — Bug 修复
1.1.0 — 新增报表导出功能
版本号嵌入二进制:
# cmake/Version.cmake
execute_process(COMMAND git describe --tags --always --dirty
OUTPUT_VARIABLE GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
target_compile_definitions(yunrong_client PRIVATE
APP_VERSION="${PROJECT_VERSION}"
GIT_VERSION="${GIT_VERSION}"
)
3. 目录结构约定
3.1 安装后目录布局
# Windows
C:\Program Files\YourOrg\YunRong\
├── yunrong_client.exe # 主程序
├── Qt6*.dll # Qt 运行时
├── plugins\ # Qt 插件
│ ├── platforms\qwindows.dll
│ ├── imageformats\
│ └── sqldrivers\qsqlite.dll
├── config\
│ └── default.json # 默认配置
├── resources\
│ ├── icons\
│ └── styles\
└── uninstall.exe
# Linux
/opt/yunrong/
├── bin/yunrong_client
├── lib/ # 私有库
├── share/
│ ├── config/default.json
│ ├── icons/
│ └── applications/
└── doc/
3.2 运行时数据目录
运行时不写入安装目录,而是使用系统标准数据路径:
| 数据类型 | Windows | Linux |
|---|
| 用户配置 | %APPDATA%\YunRong\config.json | ~/.config/yunrong/config.json |
| 本地数据库 | %LOCALAPPDATA%\YunRong\data.db | ~/.local/share/yunrong/data.db |
| 日志文件 | %LOCALAPPDATA%\YunRong\logs\ | ~/.local/share/yunrong/logs/ |
| 缓存/缩略图 | %LOCALAPPDATA%\YunRong\cache\ | ~/.cache/yunrong/ |
| 下载文件 | %USERPROFILE%\Downloads\ | ~/Downloads/ |
| 崩溃转储 | %LOCALAPPDATA%\YunRong\crash\ | ~/.local/share/yunrong/crash/ |
// 路径获取实现
class PlatformPaths {
public:
static std::string configDir() {
#ifdef PLATFORM_WINDOWS
return getEnv("APPDATA") + "/YunRong";
#else
return getEnv("HOME") + "/.config/yunrong";
#endif
}
static std::string dataDir() { /* ... */ }
static std::string logDir() { /* ... */ }
};
4. 配置管理
4.1 配置层级与优先级
高优先级 (用户修改)
~/.config/yunrong/config.json ← 用户偏好,GUI 设置页面写入
│ 覆盖
/opt/yunrong/share/config/default.json ← 管理员预设,安装时部署
│ 覆盖
低优先级 (硬编码)
C++ 代码中的默认值 ← 编译期常量,最后的兜底
4.2 配置文件格式
{
"_comment": "YunRong 用户配置 v1",
"server": {
"url": "https://collab.example.com",
"ws_url": "wss://collab.example.com/ws",
"timeout_sec": 30
},
"ui": {
"theme": "system",
"font_size": 14,
"language": "zh_CN",
"sidebar_width": 280,
"info_panel_visible": true
},
"notify": {
"sound_enabled": true,
"desktop_enabled": true,
"do_not_disturb": false,
"dnd_start": "22:00",
"dnd_end": "08:00"
},
"file": {
"download_dir": "",
"upload_limit_kbps": 0,
"auto_open_after_download": false
},
"network": {
"proxy_type": "none",
"proxy_host": "",
"proxy_port": 0,
"proxy_username": "",
"proxy_password_encrypted": ""
},
"advanced": {
"log_level": "info",
"log_max_size_mb": 50,
"log_max_files": 5,
"db_cache_mb": 64,
"message_retention_days": 30
}
}
4.3 配置验证与迁移
class ConfigValidator {
public:
// 启动时调用:检查配置完整性,填充缺失的默认值
static void validateAndRepair(json& config) {
// 确保每个 section 存在
for (auto& section : {"server", "ui", "notify", "file", "network", "advanced"}) {
if (!config.contains(section)) {
config[section] = json::object();
}
}
// 确保每个关键 key 存在
setDefault(config["server"], "timeout_sec", 30);
setDefault(config["ui"], "theme", "system");
setDefault(config["ui"], "font_size", 14);
// ...
}
// 版本升级时迁移旧配置格式
static json migrate(const json& oldConfig, int fromVersion, int toVersion);
private:
template<typename T>
static void setDefault(json& obj, const std::string& key, const T& value) {
if (!obj.contains(key)) obj[key] = value;
}
};
5. 日志策略
5.1 日志级别
| 级别 | 用途 | 示例 |
|---|
trace | 逐帧网络数据 dump | WebSocket 每帧内容 |
debug | 开发诊断信息 | 状态机转移、SQL 执行 |
info | 关键操作记录 | 登录成功、连接建立、文件传输完成 |
warn | 可恢复的异常 | 重连尝试、Token 即将过期、磁盘空间低 |
error | 不可恢复的错误 | 数据库损坏、TLS 握手失败 |
critical | 即将崩溃 | 未捕获异常、内存耗尽 |
5.2 Logger 配置
// infra/logger.cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
void initLogger(const std::string& logDir, const std::string& level) {
auto logPath = logDir + "/yunrong.log";
// 控制台 Sink (开发模式彩色输出)
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
consoleSink->set_level(spdlog::level::from_str(level));
// 文件 Sink (滚动: 单文件最大 50MB, 保留 5 个)
auto fileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
logPath, 50 * 1024 * 1024, 5
);
fileSink->set_level(spdlog::level::trace); // 文件记录全量
auto logger = std::make_shared<spdlog::logger>(
"yunrong", spdlog::sinks_init_list{consoleSink, fileSink}
);
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::warn); // warn 及以上立即刷盘
spdlog::register_logger(logger);
}
5.3 日志使用规范
// 模块化 Logger
#define LOG_NET spdlog::get("yunrong") // 网络模块统一使用
#define LOG_DB spdlog::get("yunrong") // 可通过 logger name 区分
// 使用
LOG_NET->info("WS connected to {}", url);
LOG_NET->warn("Heartbeat timeout, attempt {}/3", failCount);
LOG_NET->error("TLS handshake failed: {}", ec.message());
LOG_DB->debug("INSERT message: msg_id={}, conv={}", msgId, convId);
LOG_DB->error("SQLite corruption detected, attempting recovery");
6. 更新机制
6.1 自动更新设计
客户端启动
│
├── 1. 后台 HTTP GET /api/v1/version/latest?platform=win¤t=0.1.0
│ Response: { "latest": "0.2.0", "url": "https://.../setup-0.2.0.exe",
│ "sha256": "abc...", "changelog": "...", "force": false }
│
├── 2. 比较版本号
│ latest > current?
│ ├── 否 → 无操作
│ └── 是 → 步骤 3
│
├── 3. force == true?
│ ├── 是 → 强制更新弹窗(不可关闭)
│ └── 否 → 提示更新弹窗(可稍后提醒)
│
├── 4. 用户确认 → 后台下载安装包
│ ├── HTTP GET (Range 断点续传)
│ ├── SHA-256 校验
│ └── 校验通过 → 步骤 5
│
└── 5. 安装
├── Windows: 启动 NSIS 安装包 /Q (静默)
├── Linux AppImage: 替换旧 .AppImage 文件
└── 安装完成 → 提示重启应用
6.2 版本检查 API
GET /api/v1/version/latest?platform=windows¤t=0.1.0
Response 200:
{
"code": 0,
"data": {
"latest": "0.2.0",
"url": "https://dl.example.com/releases/0.2.0/YunRong-0.2.0-setup.exe",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 52428800,
"changelog": "- 新增文件断点续传\n- 修复消息重复问题\n- 性能优化",
"force": false,
"min_required": "0.1.0"
}
}
7. 崩溃报告
7.1 崩溃捕获
// app/main.cpp
#ifdef PLATFORM_WINDOWS
#include <windows.h>
#include <dbghelp.h>
LONG WINAPI unhandledExceptionFilter(EXCEPTION_POINTERS* exp) {
// 生成 MiniDump
auto dumpPath = PlatformPaths::crashDir() + "/crash_" +
std::to_string(time(nullptr)) + ".dmp";
HANDLE hFile = CreateFileA(dumpPath.c_str(), GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION mei;
mei.ThreadId = GetCurrentThreadId();
mei.ExceptionPointers = exp;
mei.ClientPointers = FALSE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
hFile, MiniDumpNormal, &mei, nullptr, nullptr);
CloseHandle(hFile);
}
// 记录崩溃日志
spdlog::get("yunrong")->critical("Application crashed. Dump: {}", dumpPath);
// 下次启动时询问是否上报
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
7.2 崩溃报告隐私约束
| 包含 | 不包含 |
|---|
| 堆栈跟踪、寄存器状态 | 消息内容、联系人信息 |
| 崩溃时加载的模块列表 | 用户 Token、密码 |
| OS 版本、应用版本 | 文件内容 |
| 崩溃前 100 条日志 | 网络请求 body |
8. 运维监控
8.1 客户端健康自检
应用启动时执行自检,结果记录到日志:
struct HealthCheckResult {
bool sqliteOk;
bool configOk;
bool diskSpaceOk;
bool networkOk;
std::string summary;
};
HealthCheckResult runHealthCheck() {
HealthCheckResult result{};
// 1. SQLite 可读写
result.sqliteOk = testSqliteReadWrite();
// 2. 配置文件可解析
result.configOk = testConfigParsable();
// 3. 磁盘空间 ≥ 100MB
result.diskSpaceOk = getAvailableDiskSpace(dataDir()) > 100 * 1024 * 1024;
// 4. 网络可达 (可选,可延迟到实际使用时)
result.networkOk = true; // 允许离线使用
return result;
}
8.2 服务端运维要点(概要)
虽然本项目仅实现客户端,但部署时需要关注服务端侧:
| 关注点 | 说明 |
|---|
| PostgreSQL 连接池 | 服务端使用 pgbouncer 管理连接池,避免客户端数量激增时 PG 连接耗尽 |
| WebSocket 连接数 | 单服务器节点的 WS 连接上限(通常 10k-50k),超出需水平扩展 |
| 消息分区清理 | messages 表按月分区,超过保留期(如 2 年)的月份可直接 DROP PARTITION |
| 文件存储 | 上传文件存储于对象存储(MinIO / S3),PG 仅存元数据 |
| 日志聚合 | 服务端日志接入 ELK / Loki,客户端崩溃报告单独收集 |
9. 安全运维清单
| 检查项 | 说明 | 当前状态 |
|---|
| TLS 证书有效期监控 | 证书过期前 30 天告警 | 服务端职责 |
| SQLite 文件加密 | 生产环境使用 SQLCipher 加密 | 设计已就绪 |
| Token 安全存储 | 使用系统凭据管理器,不存明文 | 设计已就绪 |
| 更新包签名校验 | 自动更新前校验 SHA-256 + GPG 签名 | 待实现 |
| 敏感日志脱敏 | Token/密码不出现在日志中 | 开发时遵守 |
| 最小权限运行 | Linux 下不以 root 运行 | 打包时保证 |
附录 A — 快速参考
开发者常用命令
# 构建
cmake --preset debug && cmake --build --preset debug -j$(nproc)
# 运行
./build/debug/src/app/yunrong_client
# 测试
ctest --preset test
# 格式化 (clang-format)
find src/ -name '*.cpp' -o -name '*.h' | xargs clang-format -i
# 静态分析 (clang-tidy)
cmake -DCMAKE_CXX_CLANG_TIDY=clang-tidy -B build/tidy
cmake --build build/tidy
# 打包
cpack -G NSIS # Windows 安装包
cpack -G DEB # Linux .deb
目录速查
| 用途 | 路径 |
|---|
| 源码 | src/ |
| 测试 | tests/ |
| 构建产物 | build/ |
| 文档 | doc/ |
| Mock Server 场景 | tests/scenarios/ |
| 安装包输出 | build/packages/ |
附录 B — 文档修订记录
| 版本 | 日期 | 作者 | 变更说明 |
|---|
| v0.1 | 2025-01 | — | 初稿:构建指南、打包分发、目录约定、配置管理、日志策略、更新机制、崩溃报告 |