CI/CD 链路 MES 操作与排障
本文只描述 MES、CI 与 MES 的交互、更新器查询 MES 的契约以及相关部署和排障。Runner 安装、IIS 搭建、主程序关闭协议、备份安装和回滚实现不在本文展开。1. 结论MES 是更新发布链路的“版本元数据和发布状态中心”不保存 ZIP 文件本体。当前有效接口是POST /orbit/openapi/script/添加apiPOST /orbit/openapi/script/查询api3.正式环境必须确认存在UNIQUE(app, branch, version)本次仅完成代码静态核对未连接数据库验证索引。客户端只有在记录同时满足以下条件时才能查到版本release_status active is_enabled 1 is_deleted 0 branch 目标分支 版本、路径、包名、大小和校验值全部合法当前版本身份必须保持一致AppSettings.AppInfo.Version 1.2.3 AppSettings.AppInfo.Branch develop MES version 1.2.3_develop MES numericVersion 1.2.3 MES branch develop2. 当前链路与职责边界2.1 完整链路GitLab CI 查询 MES 最新 active 版本 ↓ 计算版本并编译主程序、更新器 ↓ 生成全量 ZIP 和 SHA256 ↓ package_update 将 ZIP 和校验文件发布到文件服务器并远程验证 ↓ publish_update 调用 添加api(register) ↓ MES 写入 stagedis_enabled0 ↓ verify_update 调用 获取api(activate) ↓ MES 写入 activeis_enabled1 ↓ verify_update 调用 PtsUpdateGet 回查并逐字段核对 ↓ 更新器调用 获取api 获取可用版本 ↓ 下载、校验、安装、失败回滚2.2 职责组件负责不负责MES保存版本元数据和状态按分支返回最新有效版本保存 ZIP、持续检测 ZIP 是否仍可访问2.3 关键边界active表示元数据已激活不保证文件服务器此刻仍在线。SHA256 能验证完整性不能证明发布者身份需要数字签名才能提供真实性保证。MES 脚本会校验 URL 格式和白名单但不会替代 CI/更新器对真实文件的下载与哈希校验。文件服务器可能存在未登记、staged、failed或revoked的包这些包不能仅凭 URL 被客户端采用。3. MES 数据表要求3.1 表名和唯一约束当前脚本固定读写数据表正式环境要求UNIQUE(app,branch,version)该约束用于阻止相同应用、分支和版本的并发重复插入。允许不同分支使用相同数值版本。为 API 幂等和冲突判断提供数据库最终防线。3.2 关键字段字段用途id记录 IDapp固定为项目名version完整版本branchmaster/release/developdownload_urlZIP 完整 URLfile_sizeZIP 字节数必须大于 0当前脚本上限 20GBchecksum32 位 MD5 或 64 位 SHA256新发布使用 SHA256checksum_typeMD5或SHA256package_name带应用、分支、版本和时间戳的 ZIP 名folder_name文件名/branchcommit_sha764 位十六进制 Git SHApipeline_idGitLab Pipeline IDproject_idGitLab Project IDjob_id固定发布身份 Job IDpipeline_urlGitLab Pipeline 审计地址release_statusstaged/active/failed/revokedverified_date激活时间failure_reason失败或撤销原因is_enabled是否允许查询返回is_deleted逻辑删除标记create_* / modify_*创建和修改审计字段3.3 建议的最小字段容量version varchar(64) download_url varchar(1000) description varchar(4000) 或等效文本类型 checksum varchar(64) checksum_type varchar(16) package_name varchar(255) folder_name varchar(255) branch varchar(32) project_id varchar(80) job_id varchar(80) pipeline_id varchar(80) pipeline_url varchar(1000) release_status varchar(32) failure_reason varchar(1000) verified_date datetime nullchecksum若仍为varchar(32)无法保存 SHA256。3.4 上线前只读检查 SQL以下示例按 MySQL 兼容语法编写实际执行前由 DBA 确认数据库类型。检查未删除的重复发布身份SELECTapp,branch,version,COUNT(*)ASduplicate_countFROM数据表WHEREis_deleted0GROUPBYapp,branch,versionHAVINGCOUNT(*)1;检查索引SHOWINDEXFROM数据表;检查某分支可见记录SELECTid,app,version,branch,release_status,is_enabled,is_deleted,package_name,folder_name,download_url,file_size,checksum,checksum_type,pipeline_id,job_id,verified_dateFROM数据表WHEREapp项目名ANDbranchdevelopANDrelease_statusactiveANDis_enabled1ANDis_deleted0;不要在未备份、未确认影响范围时执行清表、物理删除或批量更新。4. 认证和响应判断4.1 获取 Token请求POST /orbit/openapi/auth/获取Token Content-Type: application/json请求体{appId:MES_APP_ID,appSecret:MES_APP_SECRET}后续请求头Token: accessTokenToken 可能位于响应顶层accessToken也可能位于data.accessToken。当前 CI 两种形式都兼容。4.2 业务成功判断当前 CI 会逐层展开最多 8 层data并执行以下规则存在code时只接受0或200。任一层出现statusfalse/0立即失败。任一层出现successfalse/0立即失败。status/success的其他非布尔值视为非法。查询响应必须能找到hasUpdate、updateInfo或latestVersion。datanull视为失败。不要只根据 HTTP 200、中文“成功”消息或记录 ID 判断成功。5.添加api登记和状态流转5.1 接口POST /orbit/openapi/script/添加api Content-Type: application/json支持register 登记为 staged activate 激活为 active fail 标记为 failed revoke 撤销为 revoked5.2 register 请求示例{operation:register,app:项目名,version:1.2.3_develop,branch:develop,downloadUrl:http://xx.xx.x.xxx:5111/xxxxx.zip,description:GitLab CI update package,fileSize:12345678,checksum:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef,checksumType:SHA256,packageName:xxxxxx.zip,folderName:xxx/xxx,commitSha:0123456789abcdef0123456789abcdef01234567,pipelineId:12345,projectId:678,jobId:98765,pipelineUrl:https://xxxxx.com/xxxxx}示例中的 URL、哈希和流水线信息必须替换成真实值文件应先存在并通过 CI 远程验证。5.3 register 校验规则app只能是项目名。branch只能是master/release/develop。version必须为数值版本_branch且后缀与branch一致。当前实际发布使用严格三段数值版本。packageName必须匹配当前带时间戳格式CI 还会校验其中的分支和数值版本与请求一致。folderName必须精确等于文件名/branch。downloadUrl必须来自脚本白名单且路径和包名完全一致。fileSize必须是大于 0 且不超过 20GB 的整数。checksum只能是 32 位 MD5 或 64 位 SHA256新发布使用 SHA256。commitSha必须为 764 位十六进制。pipelineId/projectId/jobId必须为 180 位正整数。pipelineUrl必须为不含账号信息和锚点的 HTTP/HTTPS URL最长 1000 字符。description必须非空且不超过 4000 字符。当前脚本根据checksum长度推导checksum_type。CI 虽然发送checksumType但该请求字段不是服务端最终判断依据。当前 MES 脚本对packageName主要做格式校验没有完整比对文件名中嵌入的分支/版本与请求字段。CI 已执行这层交叉校验手工调用必须遵守第 5 章关系不能依赖 MES 脚本兜底。5.4 幂等和冲突相同app branch version且完整发布身份一致时现有状态为staged或activeregister 按幂等成功返回。现有状态为failed或revoked不能重新登记同版本应使用新版本。以下任一关键字段不同均拒绝覆盖downloadUrl、fileSize、checksum、packageName、folderName、branch、 commitSha、pipelineId、projectId、jobId、pipelineUrl、description典型错误同版本记录已存在但发布内容不一致拒绝覆盖正式处理是发布更高版本不要修改旧记录后强行复用版本号。5.5 状态机操作允许的当前状态目标状态is_enabledregister无同版本记录staged0activatestagedactive1activateactive且身份一致幂等成功1failstaged或activefailed0failfailed幂等成功0revokestaged/active/failedrevoked0revokerevoked幂等成功0约束revoked不能改成failed。只有staged能首次激活。同分支存在相同或更高的有效active版本时较低版本不能激活。activate/fail/revoke必须重新提交完整发布身份不能只传operation version。5.6 成功响应的业务数据Orbit 外层可能包含code/message/data包装。展开后的业务数据形态为{idempotent:false,operation:register,releaseStatus:staged,record:{id:id,version:1.2.3_develop,branch:develop,release_status:staged,is_enabled:0,is_deleted:0}}6.查询api查询最新可用版本6.1 接口和请求POST /orbit/openapi/script/查询api请求示例{app:项目名,version:1.2.2_master,currentBranch:master,branch:develop}字段含义app固定应用标识version当前完整版本推荐数字版本_当前分支currentBranch旧纯数字版本兼容字段新客户端仍可传递branch本次查询或切换的目标分支6.2 查询条件数据库先按以下条件过滤app 请求应用 branch 目标分支 release_status active is_enabled 1 is_deleted 0随后脚本继续校验版本、分支、URL、包名、目录、大小、checksum 和checksum_type。只有全部合法的记录才参与数值版本比较。6.3 有更新响应展开 Orbit 外层包装后的业务数据{contractVersion:2,hasUpdate:true,currentVersion:1.2.2_master,currentBranch:master,targetBranch:develop,latestVersion:1.2.3_develop,latestNumericVersion:1.2.3,updateInfo:{version:1.2.3_develop,numericVersion:1.2.3,branch:develop,downloadUrl:http://xx.xx.x.xxx:5111/xxxxxx.zip,description:GitLab CI update package,fileSize:12345678,checksum:64位sha256,checksumType:SHA256,publishDate:verified_date或publish_date,packageName:xxxxx.zip,folderName:文件名/develop,commitSha:git sha,pipelineId:pipeline id}}6.4 无更新响应{contractVersion:2,hasUpdate:false,currentVersion:1.2.3_develop,currentBranch:develop,targetBranch:develop,latestVersion:1.2.3_develop,latestNumericVersion:1.2.3,updateInfo:null}“没有更新”是正常业务成功不能返回接口失败。6.5 跨分支规则同分支只有服务器数值版本更高时hasUpdatetrue。不同分支目标分支存在合法 active 版本即允许切换即使数值版本更低。示例当前2.0.0_master 目标1.8.0_develop 结果允许切换hasUpdatetrue6.6 warning 含义warning含义业务结果NO_ACTIVE_RELEASE目标分支没有 active 记录成功hasUpdatefalseINVALID_ACTIVE_RELEASE有 active 记录但元数据不合法失败需要修复或撤销数据CLIENT_VERSION_GREATER_THAN_SERVER客户端同分支版本高于服务器成功hasUpdatefalse7. 部署和上线操作流程7.1 上线前准备备份数据表表。备份 MES 上当前已部署的添加api/查询api脚本。检查重复的app branch version。检查字段、长度、非空约束和唯一索引。确认 Runner 能访问 Token、脚本和文件服务器地址。确认 GitLab 敏感变量已 Masked。确认文件服务器上的测试包和 checksum 可访问。7.2 推荐部署顺序完成数据库备份和结构检查。在 DBA 审核后完成必要的数据清理和唯一索引迁移。部署添加脚本.js为添加api。部署查询脚本.js为查询api。使用隔离的develop测试版本执行查询、register、activate、回查、fail/revoke。提交或启用当前.gitlab-ci.yml。完整运行一次developPipeline不只 Retry 后续 Job。使用真实更新器查询并下载测试包。验证跨分支切换、ZIP 身份、checksum、安装和回滚。验收通过后再开放release/master。7.3 为什么顺序不能颠倒先启用 CI、后处理数据库或脚本可能导致新字段不存在或长度不足。同版本并发重复记录。新 CI 调用旧脚本名或旧字段规则。包已上传但登记失败形成孤立文件。记录已登记但无法激活或查询。8. 问题排查8.1 401/403 或 Token 获取失败检查MES_TOKEN_URL是否指向认证接口而不是脚本接口。MES_APP_ID/MES_APP_SECRET是否同时配置或MES_ACCESS_TOKEN是否有效。请求头是否为TokenRunner 到 MES 的网络和端口是否可达。Token 响应是否位于accessToken或data.accessToken。Test-NetConnectionxx.xx.x.xxx-Port 818.2 接口 404 或调用到错误脚本当前脚本名区分如下添加api 获取api检查完整 URL 变量是否覆盖了脚本名变量完整 URL 非空时优先使用。8.3 HTTP 200但 CI 判定失败原因通常是内层statusfalse successfalse datanull code 不是 0/200 响应没有查询契约字段打印完整响应并逐层查看data。不要只看 HTTP 状态码和中文消息。8.4INVALID_ACTIVE_RELEASE说明数据库存在 active/enabled/non-deleted 记录但所有候选记录至少有一项不合法。逐项检查version与branch后缀一致。package_name为当前带时间戳格式。folder_name为文件夹名/branch。download_url主机、端口、目录和文件名精确匹配白名单。file_size 0。checksum 为 32/64 位十六进制。checksum_type与 checksum 长度一致。测试脏数据优先通过revoke撤销。无法调用 API 时先备份再由 DBA 定点处理。8.5 “同版本记录已存在但发布内容不一致”原因相同app branch version的未删除记录已存在但包、URL、哈希、流水线或 Job 身份不同。处理正式发布生成更高版本重新跑完整 Pipeline。测试数据确认无客户端使用后通过 API 撤销是否软删除由 DBA 决定。不要只 Retrypublish_update它仍使用原版本和 artifacts。8.6 “登记版本必须大于 MES 当前最新有效版本”当前发布版本不高于同分支最新 active 版本。重新运行完整 Pipeline让package_update重新查询并生成更高版本。8.7 activate 提示“只有 staged 版本允许激活”查询该版本当前状态active确认是否为相同身份的幂等调用。failed/revoked不能重新激活同版本必须发布新版本。记录不存在检查 app、branch、version 和脚本部署环境。8.8 “存在相同或更高的有效版本拒绝激活”通常是旧 Pipeline 晚到。同分支高版本已 active 时低版本不能覆盖。保留高版本终止旧流水线不要手工降级 active 状态。8.9 MES 登记成功但更新器查不到按顺序检查记录是否仍为staged。verify_update是否成功执行 activate。是否满足active enabled non-deleted。查询branch是否正确。元数据是否触发INVALID_ACTIVE_RELEASE。MES 服务器部署的查询脚本是否为当前版本。登记成功不等于客户端可见。8.10NO_ACTIVE_RELEASE这是正常业务结果。确认目标分支是否尚未发布或已有记录是否仍为 staged/failed/revoked。首次 CI 发布会以INITIAL_CLIENT_VERSION1.0.0为基数生成下一 patch 版本。8.11 checksum 失败检查三处值是否一致MES checksum/checksum_type CI artifacts package-checksum.txt/checksum-type.txtGet-FileHash-LiteralPathC:\path\package.zip-Algorithm SHA256哈希不一致时不要激活重新生成新版本避免覆盖原发布身份。8.12 ZIP 404 或下载失败检查URL 是否为server/文件夹名/branch/packageName。包名是否包含正确分支、版本和时间戳。IIS 物理目录中是否存在文件。外部访问地址与 Runner 本机地址是否一致。防火墙、安全组和 IIS MIME/权限是否允许访问.zip、.sha256。8.13 Retry 后仍使用旧版本这是当前设计。后续 Job 复用package_updateartifacts。需要重新计算版本时重新运行完整 Pipeline。8.14 分支支持不一致如果 CI 允许某分支但 MES 或更新器拒绝检查.gitlab-ci.yml workflow 和分支校验 添加脚本.js 查询脚本.js Config.json SupportedBranches ReleaseVersion 分支解析 更新器分支选择 UI当前只允许master/release/develop。8.15 客户端显示有更新但无法启动安装这可能不是 MES 问题。继续检查客户端.NET 8 Desktop Runtime x64。更新器实际启动路径和版本。ZIP 内根目录和AppSettings.json。主程序状态、文件占用、备份和回滚日志。9. 检查清单9.1 数据库和 MES 部署前[ ] 已备份 数据表。 [ ] 已检查 app branch version 重复记录。 [ ] 已确认 UNIQUE(app, branch, version)。 [ ] checksum 字段可保存 64 位 SHA256。 [ ] description、URL、包名、目录和失败原因字段长度足够。 [ ] 已备份线上 添加/查询 脚本。 [ ] 已确认线上脚本与仓库当前版本一致。9.2 CI 配置[ ] MES_TOKEN_URL 正确。 [ ] MES_SCRIPT_BASE_URL 正确。 [ ] MES_UPDATE_ADD_SCRIPT添加api。 [ ] MES_UPDATE_QUERY_SCRIPT查询api。 [ ] 完整 URL 变量没有意外覆盖脚本名。 [ ] MES_APP_SECRET/MES_ACCESS_TOKEN 已 Masked。 [ ] CHECKSUM_ALGORITHMSHA256。 [ ] UPDATE_APP项目名。 [ ] 文件服务器地址与 MES 脚本白名单一致。9.3 API 冒烟测试[ ] Token 能正常获取。 [ ] 查询api 无 active 版本时返回业务成功、hasUpdatefalse。 [ ] register 后状态为 staged、is_enabled0。 [ ] staged 版本不会被 查询api 返回。 [ ] activate 后状态为 active、is_enabled1。 [ ] active 版本可被查询并返回 contractVersion2。 [ ] 查询返回的版本、分支、URL、大小和 checksum 与登记一致。 [ ] 相同身份 register 重试为幂等成功。 [ ] 相同版本不同身份会被拒绝。 [ ] fail/revoke 后版本不再可见。9.4 Pipeline 验收[ ] package_update 查询 MES 并生成预期版本。 [ ] ZIP 包名符合当前带时间戳格式。 [ ] ZIP 内 AppSettings.Version 为纯数字Branch 单独保存。 [ ] ZIP 和 version.sha256 可从文件服务器下载。 [ ] publish_update 登记 staged。 [ ] verify_update 激活并回查成功。 [ ] 回查逐字段与 artifacts 一致。 [ ] 故意制造验证失败时能标记 failed 并使 Pipeline 失败。9.5 客户端验收[ ] 同分支仅在服务器版本更高时提示更新。 [ ] 跨分支存在 active 版本时允许切换。 [ ] 客户端校验 contractVersion、版本、分支、URL、大小和 checksum。 [ ] ZIP 内 AppSettings 身份与 MES 一致。 [ ] 下载失败或校验失败不会进入半安装状态。 [ ] 客户端已安装 .NET 8 Desktop Runtime x64。9.6 安全和运维[ ] 日志、截图和文档中没有 MES 密钥或 Token。 [ ] 已评估 HTTP 切换 HTTPS 的计划。 [ ] 已规划发布包签名。 [ ] 已监控文件服务器可用性和容量。 [ ] 已制定孤立包、failed/revoked 记录的保留和清理策略。 [ ] 已保存本次数据库、脚本、CI 和客户端验收记录。10. 最终结论文件服务器保存真实更新包MES 保存更新包的身份、可信元数据和发布状态。 只有经过 register → activate并同时满足 active enabled non-deleted 及完整字段校验的记录才能被更新器查询和使用。任何发布问题都应按以下顺序定位版本身份 → 文件服务器真实文件 → MES 登记状态和字段 → PtsUpdateGet 返回契约 → 更新器下载与安装校验