输入关键词开始搜索

部署与运维文档

09 — 云融(YunRong)· 部署与运维文档

前置文档05-模块详细设计文档07-GUI设计文档 设计思路00-设计思路文档

版本:v0.1 状态:已发布 最后更新:2025-01


1. 构建指南

1.1 开发环境要求

依赖最低版本Windows 安装Linux 安装
CMake3.20+winget install Kitware.CMakeapt install cmake
C++ 编译器GCC 9+ / MSVC 2019+ / Clang 14+Visual Studio 2022 Build Toolsapt install build-essential
Qt 66.2+winget install Qt.Qt6 或在线安装器apt install qt6-base-dev qt6-charts-dev qt6-websockets-dev
SQLite33.35+vcpkg install sqlite3apt install libsqlite3-dev
nlohmann/json3.10+vcpkg install nlohmann-jsonapt install nlohmann-json3-dev
spdlog1.10+vcpkg install spdlogapt install libspdlog-dev
Google Test1.12+vcpkg install gtestapt install libgtest-dev
Git2.30+winget install Git.Gitapt 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++ 客户端分仓库管理。

依赖最低版本安装
Go1.22+winget install GoLang.Go / apt install golang-go
PostgreSQL14+仅运行时依赖
# 获取源码
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 安装包NSISWiX Toolset注册开始菜单、文件关联、卸载入口
Windows便携版 .zipCMake install() + 7z免安装,适合企业批量部署
Linux.AppImagelinuxdeployqt单文件,跨发行版,无需 root
Linux.debCPackDebian/Ubuntu 系
Linux.rpmCPackFedora/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 运行时数据目录

运行时不写入安装目录,而是使用系统标准数据路径:

数据类型WindowsLinux
用户配置%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逐帧网络数据 dumpWebSocket 每帧内容
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&current=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&current=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.12025-01初稿:构建指南、打包分发、目录约定、配置管理、日志策略、更新机制、崩溃报告