6.5 上线前一周,测试同学拿着一份 RC2 构建包过来问我:这个包到底是从哪个分支打的?为什么上面说好合入的支付超时修正,在包里面完全找不到痕迹?我打开发布清单一看,瞬间就明白了——标签打在了 release/6.5 合并最新修复之前的一个旧提交上。那一刻我才真正意识到:版本管理流程不是流程文档里的虚词,它直接决定交付物是什么、能不能追溯、出了问题要花多久定位。后来我花了两天时间,把 6.5 这个版本从分支规划、提交规范、基线固化、RC 构建、补丁发布到依赖锁定的全过程重新捋了一遍,也把过程里踩过的坑整理了出来。这篇文章就是那次整理的产物,按“从版本号怎么定,到分支怎么切,从 RC 怎么打标签,到线上出问题如何走补丁流程”的顺序完整走一遍,适合项目里负责发布、做配置管理、刚接触企业级交付流程的工程师,也适合那些想知道“为什么我打的包老是对不上”的开发者。
1. 版本号里的信息量:6.5 到底在表达什么
1.1 语义化版本规则在企业项目里的落地
很多人觉得版本号就是“叫起来方便的一个数字”,实际上在设计版本管理流程时,版本号是第一份正式协议。6.5 这个写法,拆开是主版本号 6 和次版本号 5,语义化版本规范里,第三位才是补丁号。也就是说,6.5 不是小修小补,而是一个引入了新功能、但保持了基础兼容性的中间版本。
我见过一些团队把版本号当成部门编号用,这个月叫 V202406,下个月叫 V202407,结果客户报告问题的时候对外说不清楚是哪个迭代出的包。后续我要求所有对外交付必须遵循固定规则:大功能或破坏性变更升主版本,新功能向后兼容升次版本,缺陷修复升补丁版本。按这套规则,6.5.0 指代正式发布版,6.5.1、6.5.2 就是后续补丁。再往细了走,还可以加上构建元数据,例如 6.5.0+build.1234,用来区分同一天里不同时段的构建。
我还建议产品、研发、测试和运维共用同一套版本命名表,不要研发叫 A、测试叫 B、客户成功叫 C。版本管理流程的第一步不是配工具,而是让所有人对“什么是版本号、这个版本号意味着什么”达成一致。
1.2 版本管理的对象:不只是代码
一说版本管理,很多人第一反应就是“管理代码的提交记录”。真正做过发布的人会发现,代码只是最底层的一部分。6.5 这个版本要正常上线,需要把下面这些内容全部纳入版本管理范围。
| 管理对象 | 说明 | 常见失控现场 |
|---|---|---|
| 源代码 | 各模块源码、构建脚本、自动化测试用例 | 分支合错、提交遗漏 |
| 依赖清单 | go.mod、package-lock.json、requirements.txt 等锁文件 | 依赖版本漂移,本地能编,服务器编不过 |
| 构建产物 | 二进制包、镜像、安装包、前端静态资源 | 线上包和标签对不上,无法复现 |
| 配置文件 | 各环境配置模板、环境变量说明、Nginx/网关配置 | 配置散落在聊天记录里,发布时靠人肉拼 |
| 数据库变更 | 迁移脚本、初始化数据、回滚脚本 | 漏带脚本,上线后表结构对不上 |
| 文档 | 发布说明、变更记录、回滚手册 | 文档和实际行为不一致 |
这个表格看起来啰嗦,但任何一个环节失控,后面排查的时间成本都是指数级上升。比如依赖清单没锁版本,6.5 开发周期里有人升级了一个公共库,测试阶段看着是好的,发布前重新构建时拉到了一个不兼容的新版本,整个环境直接崩掉。这类问题用“查代码提交记录”的方式根本查不出来,因为代码仓库里什么都没变。
1.3 可追溯性铁三角:提交、构建、测试记录必须闭环
版本管理流程设计的核心,不是把文件存起来,而是做到可追溯。我在 6.5 周期里反复强调“铁三角”概念:一个发布包,要能同时回答三个问题——
- 这个包对应源码仓库里的哪一次提交或哪一个标签?
- 这个包是哪一次构建出来的?构建参数和环境是什么?
- 这个包经过了哪些测试?测试是在什么环境、什么时间执行的?
这三个答案必须形成闭环。一个二进制包发到线上后,运维能通过构建号查构建列表,构建列表里存着源码标签和提交号,测试记录里再关联同一构建号。这样发生线上问题时,排查人员不用再问“是谁打的包”,直接顺着链条走就能定位到是代码问题、构建问题还是环境问题。
我在 6.5 发布阶段特意做了一个发布清单模板,每次构建完必须记录:构建号、源码标签、构建时间、产物 SHA256、目标环境、测试结论。这个清单一开始研发觉得是麻烦事,后来变成排查问题的第一部字典。
2. 6.5 开发期间的分支模型、提交流程与多人协作约定
2.1 分支模型选择:SVN 企业节奏与 Git 灵活协作的取舍
6.5 开发启动之前,团队内部发生过一次讨论:继续用 SVN 还是全面切 Git。很多人搜“svn 版本管理下载”是为了找一个可以快速上手的客户端工具,但真正要决定的是分支模型,而不是客户端软件本身。
SVN 的分支模型比较重,却有一个明显优点:目录结构直观,trunk、branches、tags 三层界限清楚。早期团队用 SVN 时,流程是平常在 trunk 上开发,发版前svn copy trunk branches/release-6.5,发布时再svn copy branches/release-6.5 tags/release-6.5.0。缺点是分支合并的成本很高,不适合高频协同开发。
后来团队规模变大,同时推进 6.5 的新功能模块和 6.4 的线上补丁,就切到了 Git 的工作流。Git 的分支模型灵活性更强,但灵活性也意味着约束成本更高——如果每个成员凭自己喜好开分支,仓库会变成一个分叉树。最终我们定下来的是“主干开发 + 发布分支 + 补丁分支”的模型:
- 日常开发基于
main(或trunk)拉功能分支,完成后合并回主线; - 到了 6.5 功能冻结时间点,从
main拉出release/6.5; - 之后的 6.5 RC 构建、缺陷修复、正式发布都围绕
release/6.5进行; - 针对 6.5 的线上补丁,再从
release/6.5派生hotfix/6.5.x。
这样的好处是:主干保持持续集成状态,新功能可以正常往主干合;同时release/6.5进入稳定期,不会因为别人合了无关代码而引入风险。
2.2 分支命名与提交信息规范
分支命名是版本管理流程里最容易被忽略、但回报率最高的一环。我在团队里定下的规则很简单:
- 功能分支:
feature/6.5-简述 - 发布分支:
release/6.5 - 补丁分支:
hotfix/6.5.1-简述 - 标签:
release/6.5.0、release/6.5.0-RC1
这么命名的好处是,在 Jenkins、GitLab CI 或者其他自动化平台上筛选分支时,可以按前缀精确匹配。比如只触发release/*的构建任务,就不会误把开发分支的代码打成正式候选包。
提交信息也做了统一要求。一个合格的提交信息至少要包含:做什么、为什么做、关联的工作项编号。我给了团队一个模板:
feat(支付): 超时订单自动关闭 - 原因:支付回调丢失时订单状态会一直停留 - 关联:TICKET-6392 - 变更:新增超时轮询任务,30 分钟未支付自动关闭一开始有人嫌麻烦,觉得每次提交写这么多是浪费时间。但真的进了 6.5 发布阶段,需要回查“某个修复到底合进来没有”的时候,一段注释清晰的提交历史能节省至少半小时的核对时间。
2.3 保护发布分支与代码评审机制
分支拉好、命名定好,接下来就要解决“谁能合并到发布分支”的问题。我在 GitLab 上对release/6.5开了分支保护规则:禁止直接推送,所有变更必须通过 Merge Request 合入,至少一个负责人批准。这个策略非常容易理解——发布分支是临门一脚的地方,任何人随手git push都可能把一个未经审查的变更带进发布包。
对于 SVN 用户,也可以用权限配置做到类似效果,比如仅允许指定用户对branches/release-6.5和tags目录执行 commit,其他开发者的变更必须先提交到开发分支,再由配置管理员合并。虽然步骤变多,但这也是版本管理流程中“明确责任边界”的体现。
评审机制不能只走形式。我要求每个 MR 里描述风险评估、影响范围、需要补充的测试点。6.5 开发阶段有一次,一位同事的 MR 标题写着“修复订单导出乱码”,看起来人畜无害,打开后却发现他改了公共的 Excel 工具类,影响范围远不止订单模块。如果没有评审拦截,这类变更很可能在发布前最后一刻引爆问题。
3. 进入发布节奏:基线、标签、RC 与构建产物管理
3.1 基线如何固化:从“可以发布”到“只修必须修的”
版本管理里的“基线”,可以理解成一张快照:在某一时刻,代码、依赖、配置、文档被固定下来,作为后续构建和验证的起点。6.5 的功能开发进入尾声时,我组织了一次基线评审,目标是把“开发中的主线”切换成“可发布的稳定线”。
具体操作上,我从main创建了release/6.5分支,然后把基线内容同步到一个共享的发布计划文档里:
- 基线包含模块清单和各模块负责人;
- 基线包含当前已知缺陷列表和修复计划;
- 基线之后只允许合入缺陷修复,新功能需求一律挪到后面版本。
这一步很关键,因为一旦进入基线状态,团队的“新增冲动”必须被拦截住。我常打一个比方:发布阶段就像火箭进入最后倒计时,你可以修推进器的小问题,但不能临时决定再装一个新摄像头上去。
在 Git 里固定基线的方式很简单,就是创建分支并打标签:
git checkout main git pull git checkout -b release/6.5 git push origin release/6.5如果是 SVN,等价操作是:
svn copy trunk branches/release-6.5 -m "6.5 release branch created"3.2 RC 版本与缺陷修复节奏:RC1、RC2 怎么管理
发布分支建好后,6.5 开始进入发布候选周期。RC 全称 Release Candidate,即候选发布版。我的习惯是第一版候选叫 RC1,每修完一批问题再出 RC2、RC3,直到达到发布标准。
每个 RC 版本从release/6.5分支打个新标签,然后触发构建。我们当时的节奏是:
| RC 版本 | 标签 | 内容状态 | 验证范围 |
|---|---|---|---|
| 6.5.0-RC1 | release/6.5.0-RC1 | 所有功能已合入 | 全量功能回归 |
| 6.5.0-RC2 | release/6.5.0-RC2 | 修复 RC1 发现的支付超时问题 | 全量回归 + 重点回归 |
| 6.5.0-RC3 | release/6.5.0-RC3 | 修复 RC2 发现的导出乱码问题 | 全量回归 + 重点回归 + 兼容性验证 |
这里有一个非常容易犯的错误:RC 阶段还在往发布分支里塞新功能。我见过一次失控的场景,发布分支叠加了三个“顺手加的优化”,修复了一个数据库查询慢的问题,马上紧跟着又加入了缓存改造,结果缓存改造引入新的并发问题,整体发布被无限拖后。
在版本管理流程中,RC 阶段唯一的“新东西”应该是缺陷修复。任何非缺陷修复性质的变更,都应当明确拒绝。这个原则要在项目启动时就和产品、测试对齐,否则到了 RC 阶段才来争论“这个需求算不算缺陷”,流程就被打乱了。
3.3 标签规范与构建编号:让每个包都有身份证
标签在版本管理里扮演的是“不可变指针”角色。我强调过很多次:标签一旦打了,就不允许移动或删除。有些团队为了省事,同一个标签反复重打,结果是测试环境用的包和线上包虽然标签相同,内容却完全不一样,追溯链条从这里断掉。
我在 6.5 周期使用的标签命名分三类:
- 功能冻结基线:
base/6.5.0 - 发布候选:
release/6.5.0-RC1 - 正式发布:
release/6.5.0
打标签的同时,构建系统会记录一个不可重复的构建编号。构建编号建议用日期加流水号,比如6.5.0-RC2-202406181030,这样看到编号就能知道是哪一天哪个版本。
我要求每个构建产物内部都写入版本信息。对 Java 项目可以写到MANIFEST.MF或application.yml;对 Go 项目用-ldflags注入构建时间和 git commit;对前端项目可以生成一个version.json。这样即使包已经部署到服务器,运维也能直接读取包内版本信息,不用倒回去重新查流水线记录。
3.4 自动化支撑:人工核对只保留一次
6.5 的版本管理流程里,我最大的优化点是减少人工操作。具体做法是把“打标签、触发构建、上传制品、通知测试”串成一条流水线。比如在 GitLab CI 里,当检测到release/6.5.0-RC*格式的标签被推送,就自动执行构建任务,并把产物上传到制品仓库,同时将构建号回写到发布记录平台。
这个流程背后的逻辑很简单:人只要参与操作,就有操作失误的可能。标签打错分支、构建分支选错、产物传错目录,这些坑我都踩过。自动化不是为了炫技,而是把容易出现低级错误的位置统一管理起来。人工需要做的只剩一件事——确认发布清单里的内容符合发布计划。也就是说,自动化解决“怎么打”的问题,人只决定“打不打”。
4. 发布后的分支管理:缺陷修复、补丁与回滚的完整链路
4.1 hotfix 分支如何创建与回收
6.5 正式发布后,并不意味着release/6.5分支就可以关掉了。线上环境大概率还会出现需要紧急修复的问题,例如数据异常、核心链路不可用。这时候如果直接在release/6.5上改,问题是一旦有人同时合入两个修复,下一次构建可能同时带上两个变更,想单独回滚一个就会很难受。
我的处理方式是为 6.5 的每次线上补丁单独拉一个hotfix/6.5.1-简述分支,示例:
git checkout release/6.5 git pull git checkout -b hotfix/6.5.1-fix-payment-timeout # 修改代码 git commit -m "fix(支付): 修复回调丢失时订单状态卡死" git push origin hotfix/6.5.1-fix-payment-timeout补丁分支验证通过后,合并回release/6.5,再打release/6.5.1标签。同时,把同一个修复也合并到main上,确保下一个大版本不会重新引入这个缺陷。
很多人忽略“把补丁合回主干”这一步。我曾经见过一个项目,线上补丁只改了旧分支,主干一直是缺失这个修复的,等到下个版本发布时同一个问题再次出现,全团队傻眼。版本管理流程里,补丁分支不是一个孤岛,它的生命周期终点是“合并回发布分支 + 合并回主干 + 删除分支”。
4.2 修补版本的流程与回归范围:6.5.1、6.5.2 怎么出
补丁版本发布的频率通常比较高,流程可以比大版本简化,但不能省略关键步骤。6.5.1 的发布流程我定的标准是:
- 从
release/6.5拉hotfix/6.5.1分支; - 修复代码并补充针对性自动化测试;
- 构建补丁版本包,打
release/6.5.1标签; - 执行功能回归测试,回归范围覆盖本次修复影响的模块 + 核心主流程;
- 发布到预发环境验证;
- 发版并同步更新变更记录。
有一个判断标准我经常讲:如果补丁修改的是支付、登录、购物车这类核心流程,哪怕改动只有一行,也必须跑一遍主流程回归。因为修补的目的不只是解决问题,而是要确保不破坏已经存在的功能。这里的回归范围需要研发和测试一起协商,不能测试拍脑袋定,也不能研发一句话说了算。
补丁版本要避免引入重构。6.5.1 阶段发现一个函数写得很难看,想顺手重构,这种事情要果断踩刹车。补丁版本的定位是“最小变更、最快验证、最低风险”,任何重构都应该留到后续的 6.6 或者 7.0 去做。
4.3 回滚不是删除,而是保留现场
发布后如果发现严重问题,第一时间要做的不一定是立刻修好,而是考虑是否需要回滚。版本管理流程中的一个重要设计,就是回滚不能破坏追溯链。
我建议发布时做到两点:一是保留上一个正式版本的构建产物,不要发布新版本后就去清理制品仓库;二是数据库变更脚本必须配套回滚脚本。6.5 上线时我们带了一个数据库字段扩展,如果没有把回滚脚本准备好,一旦发现新版本有问题想回滚,数据库结构可能是回不去的,最终只能在上线状态上硬扛到修复版本出来。
回滚操作本身也要记录到版本管理档案里,包括回滚时间、回滚原因、回滚到了哪个版本。这些记录看起来是给别人看的,但两个月后如果线上再次出现类似问题,回看当时的处理过程,就会少走很多弯路。
如果条件允许,我强烈建议给核心服务加功能开关。功能开关的优先级高于回滚,因为开关可以在不更换构建产物的前提下紧急关闭有问题的功能。6.5 里我们给一个新上线的营销活动做了开关,上线后活动配置出现异常,运营在后台一键关闭,整个回滚过程没有动代码、没有重新发版,风险窗口大大减小。
5. 工具实践:Git/SVN 常用命令、依赖锁定与 Go 多版本管理
5.1 Git 与 SVN 的常用版本管理命令对照
即使团队已经切到 Git,很多人搜“svn 版本管理下载”是因为存量项目还留在 SVN,或者在同一个仓库里同时维护两套工具。我整理了一份常用操作对照表,在 6.5 周期里给团队共用:
| 操作 | Git | SVN |
|---|---|---|
| 拉取最新代码 | git pull | svn update |
| 创建分支 | git branch release/6.5 | svn copy trunk branches/release-6.5 |
| 切换分支 | git checkout release/6.5 | svn switch branches/release-6.5 |
| 发布标签 | git tag -a release/6.5.0 -m "6.5 release" | svn copy branches/release-6.5 tags/release-6.5.0 |
| 合并分支 | git merge hotfix/6.5.1-fix | svn merge branches/hotfix-6.5.1 |
| 查看提交历史 | git log --oneline | svn log |
| 回退本地修改 | git checkout -- file | svn revert file |
SVN 的 tags 目录本质上还是文件拷贝,一不小心就可能被直接修改。如果要保持标签不可变,需要通过仓库钩子脚本禁止对 tags 目录写入。Git 的 tag 本身设计上就鼓励不可变,用起来更省心,但前提依然是“打完标签不要强行覆盖”。
5.2 依赖版本锁定:go.mod、sum 文件和锁文件
6.5 版本开发周期中,团队使用 Go 作为主要后端语言。Go 的依赖管理通过go.mod和go.sum实现,这两个文件本身就是版本管理流程的一部分,而且必须提交到代码仓库。
go.mod记录了模块依赖的版本,go.sum记录了依赖包的哈希校验值。只要这两个文件被提交,构建时就会按固定版本下载,不会因为时间推移拉到一个新版本。
我在 6.5 周期里遇到过一个经典问题:本地go.mod被某个同事用go get -u顺手升级了十几个依赖,提交后合并到主干,结果构建时发现一个底层的网络库升级后不兼容旧版 TLS,测试环境表现不明显,预发环境直接握手失败。后来团队定下了一条规矩:go.mod的变更必须在 MR 描述里明确说明,并且单独标注变更原因;除非是安全修复,涉及大量依赖升级的改动不允许直接合并到发布分支。
很多语言生态也有类似机制。比如 Node 项目的package-lock.json、Python 项目的requirements.txt或poetry.lock,这些都是版本管理流程里不可缺失的“指纹文件”。它们存在的意义只有一个:让 6.5 的团队构建和别人构建、现在构建和三个月后构建,得到完全一致的环境。
5.3 Go 多版本管理的实操方案
开发一个版本跨度较长的项目时,本地、CI 和服务器上需要运行不同的 Go 版本。比如维护 6.5 的老模块可能要求 Go 1.20,而新模块已经切到 Go 1.23,这时就需要一套多版本管理方案。
先从最简单的开始:Go 自带的工具链管理。从 Go 1.21 开始,可以使用GOTOOLCHAIN环境变量来控制构建时使用的 Go 版本。如果go.mod里声明了go 1.20.0,而当前环境的 Go 版本不同,Go 工具链可以自动下载和使用对应版本。
# 查看当前工具链 go env GOTOOLCHAIN # 指定某个版本 go env -w GOTOOLCHAIN=go1.21.13 # 在 go.mod 中声明最低版本 go mod edit -go=1.21.13对于需要手动切换多个 Go 版本的情况,可以用官方提供的golang.org/dl系列工具,例如:
go install golang.org/dl/go1.20.14@latest go1.20.14 download go1.20.14 version也可以使用社区常见的 gvm(Go Version Manager)来统一管理:
gvm install go1.20.14 gvm use go1.20.14但要注意,版本切换工具解决的是“多个 Go 版本共存”的问题,并不能替代版本管理的核心职责。真正决定构建可复现性的,依然是go.mod和go.sum是否提交、CI 环境是否稳定。我在 CI 流水线里会显式指定 Go 版本号,而不是默认用最新版,保证每次构建的编译器环境也是一致的。这个细节很容易被忽略,但实际排查“本地能编、CI 编不过”的时候,第一条就要看两边 Go 版本是否一致。
6. 几回“翻车”换来的版本管理避坑经验
6.1 标签打在旧提交上:一次 RC2 错包事故
回到开头讲的那次事故。6.5 开发进入 RC2 阶段,测试同学在验证支付超时修复时发现代码逻辑不存在。我追查后确认:研发同事在本地打了release/6.5.0-RC2标签,但打标签前没有执行git pull,本地分支还停留在半天前的提交上。他构建时 Jenkins 检测到新标签并触发构建,自动化流程没问题,问题出在标签指向了一个旧提交。
这次事故让我定了一个强制规则:打标签之前必须基于远程最新提交,并且标签操作必须由构建平台或者 CI 脚本执行,而不是靠开发者在本地手动敲git tag。现在团队的流程是,发布人在平台上填写版本号,平台自动执行git fetch origin、确认最新提交、创建标签、触发构建,全程不留人工敲命令的空间。
提示:本地打标签前,先执行
git fetch origin --tags && git pull,再确认当前提交和远程一致,最后才允许创建标签。
6.2 长期分支合并带来的“幽灵冲突”
6.5 开发期间,前端小组从release/6.5拉了一个feature/6.5-refactor-account分支做账户模块重构,整个开发周期长达六周。等它合回发布分支时,账模块虽然没问题,但因为它基于的旧主干已经没有同步支付、订单等多个模块的改动,合并后产生了大量冲突。解决冲突过程中有一处直接把旧版本的支付逻辑带回来了,覆盖了之前修好的问题。
这个教训让我重新规范了分支生命周期:任何功能分支从主干拉出后,超过一周没有合并的,必须每周至少同步一次主线。不是等到功能全做完再回合并,而是保持分支之间的差集尽量小。合并频繁会带来部分工作量,但和发布前一次性解大量冲突相比,这点成本值得。
6.3 排查链路:线上运行的为什么不是我打的标签
另一个印象深刻的问题是线上包和标签对不上。6.5 正式发布后,客户反馈一个老问题没有修复,我拿到的发布记录显示代码已经合入并打了标签。查到最后发现,运维同学部署时从制品仓库拉错了一个名称相近的旧包,而旧包没有清理,文件名和目录结构太相似。
从那以后,我强制在构建流程的环节中加了两道校验:第一,构建产物不再用纯文件名区分,需要附带一个包含构建号的说明文件,并生成 SHA256 校验值;第二,部署平台记录部署的构建号,发布排查时用构建号而不是文件名来追溯。排查的顺序也固定下来:线上包里的版本信息 → 制品仓库里的构建记录 → 构建日志里的源码标签 → 代码提交。按这条链路走,大多数“包不对”的问题都能在十分钟内定位。
6.4 一个可落地的版本管理自检清单
每次版本从开发到发布,我会用下面这份清单做最终检查,建议团队复制到自己的发布计划里使用:
- 版本号是否已经按语义化规则定义,并且产品、研发、测试、运维都知晓?
- 发布分支是否从主干正确创建,名字是否规范?
- 发布分支是否设置了保护权限,禁止直接推送?
- 代码提交是否关联了工作项或者需求编号?
- 依赖锁文件(go.mod、go.sum、package-lock.json 等)是否已提交且没有异常的大范围升级?
- 基线状态之后是否还有新功能合入?
- RC 标签和构建产物是否已上传制品仓库?
- 发布清单是否记录了构建号、源码标签、构建时间、测试结论?
- 数据库变更脚本和回滚脚本是否准备齐全?
- 上一个正式版本的回滚包是否保留?
- 缺陷修复补丁是否同时合回了主干?
- 变更记录和版本说明是否更新?
这些检查项不是流程文档里的装饰,每一条背后都对应一次真实的发布事故或险些事故。我把它们固化在发布任务列表里,上线前逐项勾选,缺一项都要停下来问清楚。
最后一个建议。如果你只能从这篇文章里记住一件事,我希望是:版本管理流程的好坏,不在于用了多先进的工具,而在于出问题时能否用最快的时间回答三个问题——这个版本是怎么来的、它包含了什么、如果不对该怎么退回去。6.5 的整个周期走下来,我发现真正省时间的,不是某一套流程做得多么完美,而是每一个细节都允许你在三十秒内找到答案,而不是靠几个核心成员回忆起“当时应该是这样”。这个习惯,值得放到你下一个版本里去验证。