
PCB 拼版系统近五日迭代复盘租户隔离、横直料重构与拼版引擎打磨最近一周主要干了四件事给系统搭了套多租户数据隔离、把横直料和花式拼的算法整个推倒重写了一版、PCS 到 SET 的枚举逻辑从固定配置改成了全组合搜索、以及前端 UI 做了一轮精修。这篇文章是这段时间的复盘重点聊聊几个踩过的坑和做过的技术决策。一、多租户数据隔离为什么需要系统要对接多家 PCB 工厂每家工厂的板材库、拼版方案、单板数据必须互不可见。最笨的办法是在每个查询里手动加WHERE tenant_id ?但这在几十个方法里极易漏掉而且每次新增查询都要记得加。方案选择用了 Hibernate 原生的多租户过滤器 Spring AOP ThreadLocal 的组合核心思路是让租户过滤对业务代码完全透明——写findAll()就够了不用每次都传 tenantId。整条链路是这样的请求进来时AuthInterceptor从 SSO Token 里解析出 tenantId存到TenantContext一个 ThreadLocal。进入 Service 层后TenantFilterAspect检测到Transactional方法自动激活 Hibernate 的租户过滤器。之后这条事务里所有的 SQL ——不管是findById还是自定义 JPQL——Hibernate 会自动在 SQL 语句上追加租户条件。请求结束后TenantContext.clear()清理掉防止线程池复用的时候串数据。数据库层面所有业务表加了一个tenant_id字段默认值是default。publicclassTenantContext{privatestaticfinalThreadLocalStringCURRENT_TENANTnewThreadLocal();publicstaticvoidset(StringtenantId){CURRENT_TENANT.set(tenantId);}publicstaticStringget(){returnCURRENT_TENANT.get();}publicstaticvoidclear(){CURRENT_TENANT.remove();}}AspectpublicclassTenantFilterAspect{Around(annotation(org.springframework.transaction.annotation.Transactional))publicObjectenableTenantFilter(ProceedingJoinPointpjp)throwsThrowable{StringtenantIdTenantContext.get();if(tenantId!null){SessionsessionentityManager.unwrap(Session.class);session.enableFilter(tenantFilter).setParameter(tenantId,tenantId);}returnpjp.proceed();}}踩的一个大坑功能上线后发现一个诡异的现象新租户登录后居然能看到 default 租户的数据。排查下来根因出在list()方法上——它没有加Transactional。没有事务注解AOP 切不到这个方法Hibernate 过滤器没激活SQL 查出来的是全量数据。// 问题代码OverridepublicListBoardlist(){returnboardRepository.findAll();// SELECT * FROM board跨租户了}// 修完后OverrideTransactional(readOnlytrue)publicListBoardlist(){returnboardRepository.findAll();// SELECT * FROM board WHERE tenant_id ?}解决方式是对所有 Service 的list()、getById()、history()等查询方法统一加上Transactional(readOnly true)。同时加了一道兜底——SSO 没返回 tenantId 时默认设为default避免空值绕过过滤器。前端顶部导航栏也加了当前租户名称的标签方便排查。JVM 方面顺手调了一下堆内存限制到-Xmx1638m2G 的 80%K8s 的 limit 对齐到 2Gi开了ExitOnOutOfMemoryError防止 OOM 后进程僵死。二、横直料和花式拼的重写这是这周改动最密集、也是来回最多的一块。先搞清楚两个概念之前花式拼和横直料混在一起变量名也互相套用调试的时候经常搞混。这次做了彻底的分离花式拼工作在 WPnl 内部影响的是 SET 在 WPnl 上的排列方式。包括错位拼奇数行偏移半宽和旋转交替相邻 SET 一个正常方向、一个转 90°。开关是enableStaggered。横直料工作在 Sheet 层面影响的是 WPnl 在大板上的排列方式。部分行的 WPnl 保持正常方向部分行旋转 90°目标是把大板尽量填满。开关是enableHvMaterial。为什么推倒重来横直料第一版其实已经写完了后端生成逻辑加前端定制化的开料图渲染正常区和旋转区分开画、余料分级标注代码量也不小。但测试的时候发现一个硬伤——某些板材尺寸下开料图上的 WPnl 会画出大板边界。根因是切割行/列数用的是 WPnl 的最小尺寸算的而实际占位应该取正常方向和旋转方向的最大尺寸。这个 bug 本质上是数据结构设计的问题修的话要改好几个地方的坐标计算。当时做了一个判断与其在这个架构上打补丁不如撤回重来。于是git revert了整块代码保留 DTO 里的字段做框架占位然后重新设计了数据流。重写后的版本花式拼放在tryCompactPlace和optimizeBySizeTable两条路径里分别实现——紧凑路径里枚举正常行和旋转行的组合表驱动路径里反算 SET 在目标 WPnl 尺寸内的最优排列。横直料则集中在 Sheet 层分区排布。去重的 key 也加了:STAG和:HV后缀避免不同策略产生的方案被误去重掉。两个值得记的 Bug花式拼功能完善后又踩了俩坑。第一个minUtilization提前 return。tryCompactPlace里纯单向方案利用率不达标时直接return null了问题在于横直料和花式拼的代码写在这个 return 的下边——纯单向不达标根本没机会执行优化策略。开关明明是开着的但策略被静默跳过了。排查这个花了不少时间因为现象是横直料没有效果根本不会往利用率的条件判断上去想。修法是把这个 return 下移只包裹纯单向的 variant 生成横直料和花式拼自己独立判断利用率。第二个折断边颠倒引起的 SET 图重叠。autoReverseBreakaway会交换上下、左右的折断边来寻找更紧凑的 SET 配置。问题是交换之后生成了新的折断边组合但 SET 的实际宽高没有同步更新导致前端渲染时 PCS 位置用的折断边值跟实际容器尺寸不一致SET 图上出现了重叠。修法是交换折断边时同步更新对应的宽高。三、PCS→SET 枚举引擎PCS 到 SET 这一层之前只按固定的行列数算一个配置。比如 100×150 的 PCS固定 4×3 就出一个 460×540 的 SET。实际场景里PCS 的排列组合远不止一种。不同的行列数会对应不同的 SET 宽高而 SET 尺寸又直接影响后续 WPnl 的利用率。改为枚举所有合法的行列组合后同样的 PCS 可能产生十几种不同的 SET 尺寸全部受 SET 最小/最大尺寸约束过滤取利用率最高的进入下游管线。折断边的自动颠倒也在这一层起作用——上下折断边互换会产生不同的 SET 长度配合行列枚举可以覆盖更多有效组合。PDF 导出这边做了一些配套优化SET 图的每个 PCS 中央画了三角旗方向标识和 Canvas 上的 F 字母标记对应的逻辑大小拼的 A/B 区域在排版图上左右并排以及解决了一个老问题——嵌入了微软雅黑字体文件到 Docker 镜像PDF 里的中文终于不是方框了。四、一些架构层面的修正开关逻辑的调整WPnl 尺寸序列、阻抗条、铜箔开料、PCS→SET 枚举这四个高级功能之前的实现方式是开关关了就把对应数据删掉。这导致用户关掉开关再打开自己填的数据全没了。而且前端的删除行为和后端的判断逻辑不一致经常出现数据传过去了但因为开关状态不匹配而被后端忽略的情况。改成前后端各管各的——前端无论开关状态都原样发送数据后端新增对应的enableXxx字段来判断是否启用。开关只是控制用不用这个功能不应该干预数据本身。参数模板的完善参数模板里很多字段之前没有映射到单拼页面——留边范围、WPnl 尺寸限制、花式拼/大小拼/横直料的开关状态、AB 拼的旋转镜像参数、铜箔开料的留边和间距、PCS 四边折断边、阻抗条列表等等。这次一次补全了选了模板之后页面上所有控件自动填充不用再手动改参数。五、前端 UI 的调整单拼页做了一次比较大的布局调整。参数面板之前是平铺的折叠起来占空间展开就挤到右边的图。现在改成卡片式带阴影和圆角折叠时彻底隐藏为三图区域腾空间。Set 宽度和长度从参数组里拆出来放到了尺寸留边区更符合填写逻辑。三图的顺序调成了 SET 图左→ 拼版图中→ 开料图右从左到右刚好是从 PCS 级到工作板级再到大板级逻辑上顺了很多。编辑器加了中裁设置和交替拼。中裁支持水平/垂直方向加槽宽和行数留边不够的时候会给红色警告。交替拼可以按行或列统一对偶数行做旋转或镜像。Canvas 编辑模式也新增了裁切线的渲染——半透明底色加双向箭头引线。全局样式统一了一轮——顶部导航高度缩小、表格表头字重减了一点、卡片阴影微调、加了骨架屏 token 和空的空状态样式。不显眼但整体会整洁不少。这五天做的都不是那种一眼就能看到的大功能更多是打磨和补漏——把租户隔离跑通、把横直料的逻辑推倒理顺、把 PCS 层从不灵活的单一模式改成完整的搜索空间。拼版引擎的竞争力最终不靠单个惊艳算法而是靠这些细节堆出来的。关于我惠州市浅草软件科技有限公司专注智能制造领域的排样优化算法研发。本文所述为实际落地的工业软件系统欢迎技术交流。产品详情https://qiancaosoft.com/products/pcb-optimization