PCB 拼版系统近五日迭代复盘:租户隔离、横直料重构与拼版引擎打磨
2026/8/7 7:31:05 网站建设 项目流程

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{privatestaticfinalThreadLocal<String>CURRENT_TENANT=newThreadLocal<>();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{StringtenantId=TenantContext.get();if(tenantId!=null){Sessionsession=entityManager.unwrap(Session.class);session.enableFilter("tenantFilter").setParameter("tenantId",tenantId);}returnpjp.proceed();}}

踩的一个大坑

功能上线后发现一个诡异的现象:新租户登录后居然能看到 default 租户的数据。

排查下来,根因出在list()方法上——它没有加@Transactional。没有事务注解,AOP 切不到这个方法,Hibernate 过滤器没激活,SQL 查出来的是全量数据。

// 问题代码@OverridepublicList<Board>list(){returnboardRepository.findAll();// SELECT * FROM board,跨租户了!}// 修完后@Override@Transactional(readOnly=true)publicList<Board>list(){returnboardRepository.findAll();// SELECT * FROM board WHERE tenant_id = ?}

解决方式是对所有 Service 的list()getById()history()等查询方法统一加上@Transactional(readOnly = true)。同时加了一道兜底——SSO 没返回 tenantId 时默认设为'default',避免空值绕过过滤器。前端顶部导航栏也加了当前租户名称的标签,方便排查。

JVM 方面顺手调了一下,堆内存限制到-Xmx1638m(2G 的 80%),K8s 的 limit 对齐到 2Gi,开了ExitOnOutOfMemoryError防止 OOM 后进程僵死。


二、横直料和花式拼的重写

这是这周改动最密集、也是来回最多的一块。

先搞清楚两个概念

之前花式拼和横直料混在一起,变量名也互相套用,调试的时候经常搞混。这次做了彻底的分离:

  • 花式拼工作在 WPnl 内部,影响的是 SET 在 WPnl 上的排列方式。包括错位拼(奇数行偏移半宽)和旋转交替(相邻 SET 一个正常方向、一个转 90°)。开关是enableStaggered
  • 横直料工作在 Sheet 层面,影响的是 WPnl 在大板上的排列方式。部分行的 WPnl 保持正常方向,部分行旋转 90°,目标是把大板尽量填满。开关是enableHvMaterial

为什么推倒重来

横直料第一版其实已经写完了,后端生成逻辑加前端定制化的开料图渲染(正常区和旋转区分开画、余料分级标注),代码量也不小。

但测试的时候发现一个硬伤——某些板材尺寸下,开料图上的 WPnl 会画出大板边界。根因是切割行/列数用的是 WPnl 的最小尺寸算的,而实际占位应该取正常方向和旋转方向的最大尺寸。这个 bug 本质上是数据结构设计的问题,修的话要改好几个地方的坐标计算。

当时做了一个判断:与其在这个架构上打补丁,不如撤回重来。于是git revert了整块代码,保留 DTO 里的字段做框架占位,然后重新设计了数据流。

重写后的版本,花式拼放在tryCompactPlaceoptimizeBySizeTable两条路径里分别实现——紧凑路径里枚举正常行和旋转行的组合,表驱动路径里反算 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询