生产环境版本锁定与升级 SOP:从 Ver 号对账到回滚预案
这份文档解决什么问题? 对 ccbpm 最常见的抱怨之一是"代码一直在更新,不稳定"。 但源码显示:更新快和自动升级是同一套机制的两面 ------不是产品缺陷,而是运维策略缺位。 本文把这件事固化成一份可执行的 SOP:该锁的锁住,该升的按流程升。
适用:JFlow2026(Java)、CCFlowForNetcore(.NET Core)、CCFlowVue3(前端)。
0. 先纠正一个认知
"版本不稳定"的真相,来自源码:
| 源码事实 | 位置 | 含义 |
|---|---|---|
| 全局版本号是日期戳 | CCFlow/Components/BP.WF/Upgrader.cs:public static int Ver = 2026122308; |
单调递增,语义是"2026-12-23 第 08 版" |
| 升级是幂等的 | 同文件 UpdataCCFlowVer():if (currDBVer >= Ver) return null; |
库里版本 ≥ 程序版本则直接跳过,不会重复升级 |
| 升级会自动打补丁 | 同文件:CheckPhysicsTable() / UpdataCCFlowVerSQLScript() |
表缺列/结构差异自动补齐 |
| 库版本对账 | SELECT IntVal FROM Sys_Serial WHERE CfgKey='Ver' |
程序版本 ≠ 库版本才触发升级 |
结论:升级机制是健壮的、幂等的、可跳版本升级的。 你真正需要做的不是"阻止升级",而是控制"什么时候升"------也就是锁定版本。
⚠️ 但自动升级不等于可以随意升。生产环境必须走本文流程。
1. 版本锁定:三条铁律
铁律一:生产环境永不跟随 Gitee 主线
- 开源版在 Gitee 定期发布,商业版在内部 SVN 拿研发中最新代码。
- "最新"意味着未经过你的回归测试 。生产只用你验证过的那个版本。
铁律二:以 Ver 号 + Git 提交为唯一标识
不要用"上次拉的那版"这种模糊说法。锁版本 = 锁定三样东西:
| 锁定对象 | 具体锁定 | 记录方式 |
|---|---|---|
| 后端代码 | Git commit hash / SVN revision | 记录到发布单 |
| 后端版本号 | Upgrader.cs 里的 Ver 值(如 2026122308) |
记录到发布单 |
| 前端包 | CCFlowVue3 构建产物(dist)的构建时间 + commit |
记录到发布单 |
跨栈一致性 :Java 与 .NET 分别有各自的
Ver,必须分别记录、分别升级。 前端dist与后端 API 存在契约,升级后端时必须评估前端是否需同步重建。
铁律三:环境隔离,逐级晋级
开发(dev) ──► 测试(test) ──► 预生产(staging) ──► 生产(prod)
跟随最新 锁定候选版 锁定候选版 锁定正式版
任何版本不得跳过 staging 直接上 prod。
2. 上线前的版本锁定操作
2.1 建立"版本资产"记录(每次发布必填)
建议在项目里维护一份 版本发布单.md(或工单系统),每次发布填写:
markdown
## 发布单 vYYYY.MM.DD
- [ ] 后端仓库:JFlow / CCFlowForNetcore
- [ ] 后端 commit:__________________
- [ ] 后端 Ver :__________________ (Upgrader.cs 中读取)
- [ ] 前端 commit:__________________
- [ ] 前端 dist 构建时间:____________
- [ ] 数据库类型与版本:______________
- [ ] 升级脚本执行结果:______________
- [ ] 回归测试结论:__________________
- [ ] 备份文件位置:__________________
2.2 读出版本号(升级前必做)
bash
# 后端 Ver 号
# Java:在 JFlow2026 中查找版本常量
# .NET:CCFlow/Components/BP.WF/Upgrader.cs
grep -n "public static int Ver" Upgrader.cs
# 库里当前版本
SELECT IntVal FROM Sys_Serial WHERE CfgKey='Ver';
规则 :库版本 ≥ 程序版本 时,升级会被自动跳过;库版本 < 程序版本 时才会打补丁。
3. 升级操作 SOP
顺序不可颠倒:备份 → 停服(或灰度)→ 换代码 → 触发升级 → 验证 → 观察。
第 0 步:备份(不可跳过)
| 备份项 | 说明 |
|---|---|
| 全库备份 | 升级会改表结构(CheckPhysicsTable 自动补列/改列),必须可回滚 |
| 应用目录 | 当前运行的程序包(用于回退) |
| 前端 dist | 当前生产包 |
| 配置文件 | application.properties / jflow.properties / app.config |
铁律 :没有可用的全库备份,不得开始升级。
第 1 步:升级代码
Java(JFlow2026):
bash
# 拉到锁定的 commit(不要直接拉主线)
git checkout <锁定的commit>
mvn -pl jflow-web -am package
# 产出 jflow-web/target/jflow-web-*.jar 替换旧包
.NET(CCFlowForNetcore):
bash
git checkout <锁定的commit>
dotnet publish -c Release
前端(CCFlowVue3):
bash
pnpm install
pnpm build # 产出 dist
第 2 步:触发数据库升级
由 admin 登录后台即自动触发(程序 Ver ≠ 库 Ver 时打补丁)。
用 admin 账号登录 → 系统自动比对版本 → 自动执行补丁脚本 → 更新 Sys_Serial
⚠️ SAAS 模式例外 :源码中
UpdataCCFlowVer()对CCBPMRunModel.SAAS直接返回"info@SAAS模式需要手工升级.",SAAS 需人工升级 ,不会自动跑。 参见Upgrader.cs中的分支判断。
第 3 步:验证
| 验证项 | 方法 |
|---|---|
| 版本号已更新 | 再次执行 SELECT IntVal FROM Sys_Serial WHERE CfgKey='Ver',应等于程序 Ver |
| 核心链路可用 | 发起 → 待办 → 审批 → 结束,全链路跑一遍 |
| 升级日志无报错 | 检查应用日志 |
| 历史数据完好 | 抽查历史流程实例、表单、附件可正常打开 |
第 4 步:观察期
升级后 24--48 小时为重点观察期,关注:报错日志、定时任务是否正常、消息是否发出、附件上传下载。
4. 故障处理:自动修复与回滚
4.1 "表缺列"类报错------可能自动愈合
源码注释明确了一个机制:
未走升级登录、只是表缺列时,部分功能第一次报错后
CheckPhysicsTable可能自动补列,刷新再试。
处理顺序:
- 先刷新页面重试一次(可能已自动补列);
- 仍报错 → 用 admin 登录后台,强制走一次升级流程;
- 仍不行 → 查日志定位缺的表/列,手动补;
- 最后手段 → 回滚。
4.2 回滚决策
| 场景 | 动作 |
|---|---|
| 界面报错但核心流程可用 | 观察 + 记录,不急着回滚 |
| 核心流程阻断 | 立即回滚(代码 + 数据库) |
| 数据出现异常写入 | 立即停服回滚,并评估数据修复 |
回滚步骤(与升级相反):
- 停服;
- 恢复升级前的全库备份;
- 换回旧程序包 + 旧 dist + 旧配置;
- 启动验证;
- 记录故障原因,进入下一轮修复。
⚠️ 库结构升级可能不可逆 (如
ALTER TABLE ... MODIFY MyPK BIGINT)。 所以备份是唯一可靠的退路,这也是第 0 步不可跳过的原因。
4.3 已知历史问题的现状
| 历史反馈 | 当前代码状态 | 依据 |
|---|---|---|
| 附件主键重复 | ✅ 已用防御式兜底修复:主键冲突时自动降级为 GUID 重插 | WorkNode.cs:try { athDB_N.DirectInsert(); } catch { athDB_N.setMyPK(DBAccess.GenerGUID()); athDB_N.Insert(); } |
| 表单控件报错 | 用 admin 登录触发升级;或刷新让 CheckPhysicsTable 自动补列 |
Upgrader.cs |
5. 升级前检查清单(Checklist)
升级前逐项打勾,任何一项不满足则不升级:
[ ] 已确认目标版本号(Ver)与 commit,并记录到发布单
[ ] 已在 staging 环境完整验证过该版本
[ ] 已完成全库备份,且验证备份可恢复
[ ] 已备份当前程序包 / dist / 配置文件
[ ] 已评估前端是否需同步重建(API 契约是否变化)
[ ] 已确认当前库版本:SELECT IntVal FROM Sys_Serial WHERE CfgKey='Ver'
[ ] 已确认升级窗口(避开业务高峰)
[ ] 已通知相关干系人 / 值班人员
[ ] 非 SAAS 模式(SAAS 需改走人工升级流程)
升级后逐项打勾:
[ ] 库版本号已等于程序 Ver 号
[ ] 发起 → 待办 → 审批 → 结束 全链路通过
[ ] 历史流程实例可正常打开(含附件)
[ ] 定时任务正常运行(EnableScheduler=1 时验证 HostURL 可达)
[ ] 日志无异常堆栈
[ ] 观察期(24--48h)无新增报错
6. 常见问题 FAQ
Q1:能不能一直不升级? 可以。源码设计是"库版本 ≥ 程序版本就跳过"。如果当前版本已够用,不必频繁升级。 但长期不升级会累积补丁,未来跨越大版本时风险更高------建议定期(如每季度)在 staging 演练一次。
Q2:升级会不会丢数据? 正常不会。升级只做结构补丁 + 数据源初始化 ,不动业务数据。 但 ALTER TABLE 等操作不可逆 ,所以必须备份。
Q3:为什么官方说"版本控制做得差"? 那是商务语境 (商业版走内部 SVN 拿最新代码,服务付费客户),不是工程自评 。 工程上的版本控制是:全局 Ver + Sys_Serial 对账 + 幂等补丁 + 自动补列,比多数产品更严谨。
Q4:多实例部署升级要注意什么?
- 升级时建议停掉多余实例,由单实例触发升级,避免并打补丁;
- 定时任务
EnableScheduler=1只允许一台实例开启,否则重复执行。
Q5:前端要不要每次跟着后端升? 不一定。当后端 API 契约变化时 才需要重建前端。 升级前用"后端 release note / diff API handler"判断,不确定就全量同版本部署(最稳)。
7. 附:核心机制源码索引
| 机制 | 文件 | 关键行/方法 |
|---|---|---|
| 全局版本号 | CCFlow/Components/BP.WF/Upgrader.cs |
public static int Ver = 2026122308; |
| 升级入口 | 同上 | UpdataCCFlowVer() |
| 幂等判断 | 同上 | if (currDBVer >= Ver) return null; |
| SAAS 手工升级分支 | 同上 | Glo.CCBPMRunModel == CCBPMRunModel.SAAS |
| 版本号落库 | 同上 | UPDATE Sys_Serial SET IntVal=? WHERE CfgKey='Ver' |
| 表结构自动补齐 | 同上 | CheckPhysicsTable() |
| 附件主键兜底 | CCFlow/Components/BP.WF/WF/WorkNode.cs |
try { DirectInsert(); } catch { setMyPK(GenerGUID()); Insert(); } |
相关文档:
- 系统自动升级
- JFlow 全局配置
- .NET 全局配置
- 《新手 30 分钟上手路径》
8. 一页速记
锁定:生产只跑验证过的 Ver + commit,永不跟主线
晋级:dev → test → staging → prod,逐级走
升级:备份 → 换代码 → admin 登录触发 → 验证 → 观察 48h
退路:全库备份是唯一可靠回滚手段(表结构变更不可逆)
例外:SAAS 模式必须人工升级
心态:升级机制本身是幂等、健壮的;要控的是"何时升",不是"能不能升"