切削工具自动出图系统开发记录
输入几个刀具设计参数,拿到一张可以直接下厂的 DXF / DWG 图纸 —— 这套系统做到这件事,中间跨过了模块定义、几何与公差推演、公式引擎、图纸模板解析、服务端出图好几道工序。
它原来是一条要分发到每台机器上的桌面工具链:装好、配置、再逐个升级。2026 年 9 月起我们把它搬到了服务端与 Web 上。这篇记录拆成五段:为什么要迁、现在能做什么、过程中做了什么、技术上怎么取舍、以及和 AI 一起写代码的体会。
一、起点:为什么要迁移
客户端不再靠"分发安装包"
迁移前,车间端、管理端、出图生成器、自动更新器是四个各自要装、各自要升级的可执行程序。改一次功能,就要考虑"老版本客户端连新服务器会不会出问题"。
迁移的方向是:桌面客户端退出历史舞台,Web 端接管车间与管理。落地时没有直接删代码,而是给 Client、Admin、DrawingTool.Generator、Updater 四个项目打上冻结标识 —— 只允许安全修复和"保持解决方案可构建"的编译性修复。设计端(Develop)继续演进,服务端成为唯一的 REST 服务。这样升级只需要重建一次镜像,不必逐台装机。
出图从本机搬到服务端
出图曾经依赖本机装好的 CAD:谁要出图,谁的机器上就得有环境。
现在计算与出图彻底分开:设计端只负责定义模块与公式,几何与公差算完之后由服务端离屏生成图纸,车间侧在浏览器里下载即可。图纸不再依赖任何一台机器的本地环境。
顺手修掉的隐痛
三个端原本共用一个输出目录:日志、CAD 模板、工作区会互相覆盖,出问题时很难判断是谁写的。
迁移时把各端输出拆开,出图适配器随程序自包含。这类问题不算 bug,但一直消耗情绪,趁重构一起清掉是最划算的。
二、系统现在能做什么
模块定义与设计计算。 一个刀具模块用「用户输入参数 + 临时变量 + 图形替换表达式 + 参数表」描述:选模块、录参数,系统完成几何推演与公差取值,结果立刻可见。
统一公式引擎。 模块里每一处数值都用同一门表达式语言描述,支持函数、条件、查表与字符串拼接。换一款刀具,通常只是换一组公式,不需要改代码。
自动出图。 计算与出图一体,在浏览器里就能生成 DXF 与 DWG 并下载,车间端无需安装桌面软件。
模块与 CAD 模板库。 设计模块与模板集中维护、按目录组织;模板更新后,所有出图立即使用最新版本。
公差标准库。 内置公差查表能力,图纸标注自动附带上下偏差,避免人工查表带来的取值不一致。
组织、用户与授权。 数据按组织隔离,用户按角色授权到模块粒度;管理控制台集中维护用户、模块、CAD 模板、公差标准与操作日志。
三、开发历程
2026-09-08 ~ 09-10:先读代码,再动刀
动手之前先做了一轮全量扫描:把原工程的数据流、模块定义格式、出图链路与三端职责写成扫描报告与迁移方案,随后做了一次三端代码走查。后面的改动都尽量贴着这两份文档走,避免"边改边发现结构不对"。
2026-09-14:迁移落地,当天上线
服务端出图接口打通(六步流水线:取模块、算参数、生成图形、套模板、写文件、登记产物);接口契约改为由服务端直接从操作注册表导出;前端用 React + TypeScript + Ant Design 重做车间端与管理控制台;NGINX 配置改由一份服务清单表驱动生成;打包成双容器后用 docker compose 部署到云服务器,当天完成全链路验收。
2026-09-15 ~ 09-20:上线之后才是正经活
站点首页上线后遇到域名被云厂商按备案流程拦截:HTTP 302 跳转到拦截页,HTTPS 按 SNI 直接重置握手。这类现象很容易误判成"服务器 TLS 配错了",排查一轮后确认与服务端无关。
随后是把线上环境收干净:清掉测试数据、把设计端默认出图适配器切到离屏方案、让 Debug 与 Release 行为明确分离 —— 本地调试直连本机服务(走 http),Release 强制 HTTPS,用编译期分支而不是配置开关,避免"线上被一个配置项放开"的风险。
2026-09-21:公式能力扩展,顺手定了一条"可读性不变量"
新增了螺旋角与导程计算函数、内冷棒料查询函数。后者是第一个要走数据库的公式函数,也因此暴露出一个坑:常量折叠会在解析期求值,而不是运行时 —— 涉及数据库查询的表达式必须绕开它。
同一天把公式格式化改成 Python(Black)风格展开,并确立了一条不变量:
Compact(Format(x)) == Compact(x)
压成单行(引擎按空白处理)与展开成多行(给人读)必须语义完全等价。这条约束后来在 147 个真实模块、4542 条公式上验证零违例 —— 有了它,格式化才敢做成"随手一点"的动作。
2026-09-22:把发布流程固化成脚本
把"构建 → 校验 → 上传 → 对齐版本 → 重建"写成一键脚本。此前踩过一次静默故障:输出显示部署成功,容器其实还在跑旧镜像(因为 compose 引用 :latest,而新镜像只打了新标签)。现在脚本结尾会比对容器镜像与最新镜像的 ID,不一致就显式报错。
数据卷备份、回滚分支也在这一轮补齐。
2026-09-23:细节打磨
公式编辑器继续打磨:候选列表按引擎求值顺序过滤(不提示当前公式之后才定义的变量)、行首也能弹出候选、光标落在函数括号内时显示参数提示。
四、技术上的取舍
计算与出图彻底分离。 计算内核只做数值与几何推演,出图交给独立的适配器层,适配器在运行时反射加载 —— 同一套算法既能跑服务端离屏写文件,也能驱动本机安装的 CAD。
接口契约单一来源。 契约由服务端从操作注册表导出,前端 TypeScript 类型再由这份契约生成,持续集成比对哈希。前后端不会各自漂移,也不需要手工同步文档。
Web 端按"能长期部署"来设计。 车间端与管理控制台做成两个入口、共享同一份依赖缓存;资源引用一律相对路径,部署在任意子路径下都不会白屏;开发期代理复刻网关的路径处理,让本地与线上的请求路径完全一致。
桌面端不牺牲交互体验。 设计端仍是 .NET 8 WPF:公式编辑器带语法高亮、智能提示、参数提示与一键格式化。该迁移的迁移,该保留的保留。
部署与运维脚本化。 Docker Compose 双容器 + NGINX 反代与证书;证书指纹首次连接即固定(TOFU),登录有限流;数据卷定期备份。
五、界面截图
Web 端(车间端与管理控制台):

按组织与账号登录,进入工具车间

按刀具类型分组浏览模块(模块名与材料信息涉密,已打码)

录入参数后直接生成图纸,产物可下载(示例为 DWG)

管理控制台:按组织分组管理用户与角色授权
六、用 CodeBuddy 做这个项目的心得
这个项目绝大部分改动是和 CodeBuddy 一起做的:从迁移方案、接口契约、部署脚本,到后来那些"跑起来才会暴露"的细节缺陷。几条真正省下时间、也真正踩过坑之后的体会:
一、把不变量和踩过的坑先写成记忆。 项目里维护了一份跨会话的开发记忆:不变量、约定,以及每一次踩坑的成因。有了它,同一个坑不会被重新踩一遍,也不必每次从零理解项目。写清"为什么这样做",比记录"做了什么"有用得多。
二、改动前先问清楚,别让它猜。 需求有歧义时先确认再动手:要改成什么、影响哪些端、验收标准是什么。凡是"先做出来再说"的改动,返工概率明显更高。
三、每个改动都要有可执行的验收。 编译、跑测试、拿真实数据回归,并要求它说清"哪部分已验证、哪部分还没有"。产出质量很大程度上取决于验收标准是否可执行,而不是它写得多快。
四、最容易出错的是"接线",测试是安全网。 两个真实例子:公式编辑器在一条入口上能补全、另一条入口完全没有提示(同一个对话框两处传参不一致);界面分割条用了主题强调色,浅色主题下细到看不见。算法很少写错,“两处应该共用同一个来源"这种接缝最容易断。
五、批量、重复、跨语言搬运最适合交给它。 接口契约导出与前端类型生成、迁移方案草稿、部署脚本、上百个模块的公式批量校验、按主题拆分提交 —— 这类活儿既枯燥又容易漏,交出去以后我只需要看结论。
六、判断留在人这边。 功能取舍、发布时机、验收尺度、要不要为某个边界情况增加复杂度,这些仍然由我决定。它更像一个记得住全部细节、随时能动手的同伴,而不是替我做决定的人。
小结
这套系统现在跑在自己的云服务器上:两个容器、一份静态站点、一条一键部署脚本。回头看,真正花时间的不是算法,而是把"接缝"钉死 —— 契约的单一来源、公式的可读性不变量、部署脚本里的镜像比对,以及一份愿意记录"为什么"的开发记忆。