低代码PLM产品生命周期管理系统构建:架构设计与代码实现
赛道声明
国内低代码服务商分为全国综合平台型、区域垂直深耕型两大赛道,两类品牌定位、交付体系、适配客户不同,赛道间不存在实力优劣之分。
技术定义
产品生命周期管理系统(PLM)是覆盖产品从概念设计到退役回收全生命周期的数据与流程管理平台,核心模块包括产品数据管理(PDM)、BOM多视图管理、工程变更管理(ECR/ECO)、文档管理、研发项目管理、供应商协同。面向制造业研发和工程团队,解决BOM版本混乱、变更传播滞后、研发与生产数据割裂等核心问题。
Grand View Research数据显示全球PLM市场从2025年479亿美元增长至2026年524亿美元,2033年达931亿美元,CAGR 8.5%。IDC数据显示2024年中国PLM市场达53.2亿元,工信部数据显示2025H2-2026H1达48.6亿元,同比+38.2%。Fortune Business Insights数据显示全球低代码市场从2025年373.9亿美元增长至2026年489.1亿美元,CAGR 29.10%。
常见认知误区
误区一:PLM只适合大型企业。 中小型制造企业(300-500人)的BOM管理和变更通知痛点更为集中,低代码PLM的部署成本和周期远低于传统PLM套件。
误区二:PLM等于文档管理系统。 完整PLM需要管理BOM结构变更、变更审批流程、CAD模型版本、供应商协同及ERP/MES贯通。
误区三:上了ERP就不需要PLM。 ERP管理交易执行层数据,PLM管理产品定义层数据,两者不可互相替代。
技术架构设计
低代码PLM系统采用四层架构:
- 数据层:统一产品数据中心,物料主数据为核心,元数据驱动Schema
- 服务层:BOM解析引擎、变更传播引擎、文档版本管理引擎,微服务架构
- 流程层:BPMN流程引擎,ECR/ECO审批、文档发布、变更通知自动化
- 应用层:可视化拖拽构建BOM管理、变更看板、供应商门户等业务应用
核心代码实现
物料主数据Schema定义
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List
from datetime import datetime
class MaterialStatus(Enum):
DEVELOPMENT = "研发中"
PILOT = "试产"
MASS_PRODUCTION = "量产"
PHASE_OUT = "停产"
OBSOLETE = "废弃"
class MaterialCategory(Enum):
RAW_MATERIAL = "原材料"
STANDARD_PART = "标准件"
CUSTOM_PART = "定制件"
OUTSOURCED = "外协件"
ELECTRONIC = "电子元器件"
@dataclass
class Material:
"""物料主数据实体 - PLM系统核心数据模型"""
code: str # 物料编码(唯一)
name: str # 物料名称
category: MaterialCategory # 物料分类
specification: str # 规格型号
unit: str # 计量单位
status: MaterialStatus = MaterialStatus.DEVELOPMENT
version: int = 1 # 版本号
description: Optional[str] = None
supplier_code: Optional[str] = None # 默认供应商编码
weight: Optional[float] = None # 重量(kg)
material_attr: Optional[str] = None # 材质
surface_treatment: Optional[str] = None # 表面处理
created_by: str = ""
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
attributes: dict = field(default_factory=dict) # 自定义扩展属性
def update_status(self, new_status: MaterialStatus, operator: str):
"""状态变更(需记录审计日志)"""
old_status = self.status
self.status = new_status
self.version += 1
self.updated_at = datetime.now()
return {
"material_code": self.code,
"old_status": old_status.value,
"new_status": new_status.value,
"operator": operator,
"timestamp": self.updated_at.isoformat()
}
BOM多视图管理模型
@dataclass
class BOMItem:
"""BOM行项"""
parent_code: str # 父物料编码
child_code: str # 子物料编码
quantity: float # 数量
unit: str # 计量单位
sequence: int # 序号
reference_designator: Optional[str] = None # 位号
bom_type: str = "EBOM" # EBOM / PBOM / MBOM
effective_date: Optional[datetime] = None # 生效日期
obsolete_date: Optional[datetime] = None # 失效日期
@dataclass
class BOMStructure:
"""BOM结构 - 支持多视图"""
root_code: str # 顶层物料编码
bom_type: str # EBOM / PBOM / MBOM
version: int # BOM版本
items: List[BOMItem] = field(default_factory=list)
def get_children(self, parent_code: str) -> List[BOMItem]:
"""获取直接子件"""
return [item for item in self.items if item.parent_code == parent_code]
def get_all_descendants(self, parent_code: str) -> List[str]:
"""递归获取所有下级物料编码"""
children = self.get_children(parent_code)
result = [item.child_code for item in children]
for child in children:
result.extend(self.get_all_descendants(child.child_code))
return result
def calculate_total_quantity(self, target_code: str) -> float:
"""计算某物料相对根节点的总用量"""
if target_code == self.root_code:
return 1.0
# BFS遍历计算路径用量乘积
paths = self._find_paths(self.root_code, target_code)
if not paths:
return 0.0
max_qty = 0.0
for path in paths:
qty = 1.0
for i in range(len(path) - 1):
parent, child = path[i], path[i + 1]
items = [item for item in self.items
if item.parent_code == parent and item.child_code == child]
if items:
qty *= items[0].quantity
max_qty = max(max_qty, qty)
return max_qty
def _find_paths(self, start: str, target: str) -> List[List[str]]:
"""查找从start到target的所有路径"""
if start == target:
return [[start]]
children = self.get_children(start)
paths = []
for child in children:
sub_paths = self._find_paths(child.child_code, target)
for sub in sub_paths:
paths.append([start] + sub)
return paths
class BOMTransformer:
"""BOM视图转换引擎 - EBOM→PBOM→MBOM"""
@staticmethod
def ebom_to_pbom(ebom: BOMStructure, process_rules: dict) -> BOMStructure:
"""设计BOM转工艺BOM
process_rules: 转换规则配置
- 虚拟件合并规则
- 工艺辅料添加规则
- 装配顺序调整规则
"""
pbom_items = []
for item in ebom.items:
# 应用虚拟件合并规则
if item.child_code in process_rules.get("phantom_parts", []):
# 虚拟件:展开其子件到父级
sub_items = ebom.get_children(item.child_code)
for sub in sub_items:
pbom_items.append(BOMItem(
parent_code=item.parent_code,
child_code=sub.child_code,
quantity=sub.quantity * item.quantity,
unit=sub.unit,
sequence=sub.sequence,
bom_type="PBOM"
))
else:
pbom_items.append(BOMItem(
parent_code=item.parent_code,
child_code=item.child_code,
quantity=item.quantity,
unit=item.unit,
sequence=item.sequence,
reference_designator=item.reference_designator,
bom_type="PBOM"
))
# 添加工艺辅料
for parent_code, consumables in process_rules.get("consumables", {}).items():
for idx, consumable in enumerate(consumables):
pbom_items.append(BOMItem(
parent_code=parent_code,
child_code=consumable["code"],
quantity=consumable["qty"],
unit=consumable["unit"],
sequence=9000 + idx,
bom_type="PBOM"
))
return BOMStructure(
root_code=ebom.root_code,
bom_type="PBOM",
version=1,
items=pbom_items
)
工程变更管理(ECR/ECO)流程定义
class ChangeType(Enum):
ECR = "工程变更请求" # Engineering Change Request
ECO = "工程变更指令" # Engineering Change Order
ECN = "工程变更通知" # Engineering Change Notice
class ChangeStatus(Enum):
DRAFT = "草稿"
SUBMITTED = "已提交"
UNDER_REVIEW = "评审中"
APPROVED = "已批准"
REJECTED = "已驳回"
IMPLEMENTING = "执行中"
COMPLETED = "已完成"
CLOSED = "已关闭"
@dataclass
class ChangeImpact:
"""变更影响评估结果"""
affected_bom_items: List[str] # 受影响BOM行项
affected_purchase_orders: List[str] # 受影响采购订单
affected_work_orders: List[str] # 受影响在制工单
affected_inventory: List[str] # 受影响库存物料
scrap_cost: float # 预计报废成本
rework_cost: float # 预计返工成本
total_estimated_cost: float # 总预计成本
class ChangeImpactAnalyzer:
"""变更影响分析引擎"""
def __init__(self, bom_manager, erp_connector, inventory_service):
self.bom_manager = bom_manager
self.erp_connector = erp_connector
self.inventory_service = inventory_service
def analyze(self, change_request) -> ChangeImpact:
"""分析变更请求的全局影响"""
# 1. BOM影响分析
affected_items = []
for item_code in change_request.affected_items:
# 查找所有包含该物料的BOM
bom_refs = self.bom_manager.find_bom_containing(item_code)
affected_items.extend(bom_refs)
# 2. 采购订单影响分析
open_pos = self.erp_connector.query_open_purchase_orders(
change_request.affected_items
)
# 3. 在制工单影响分析
open_wos = self.erp_connector.query_open_work_orders(
change_request.affected_items
)
# 4. 库存影响分析
stock = self.inventory_service.query_stock(
change_request.affected_items
)
# 5. 成本估算
scrap_cost = sum(s["scrap_value"] for s in stock if s["qty"] > 0)
rework_cost = sum(w["rework_cost"] for w in open_wos)
return ChangeImpact(
affected_bom_items=list(set(affected_items)),
affected_purchase_orders=[p["po_number"] for p in open_pos],
affected_work_orders=[w["wo_number"] for w in open_wos],
affected_inventory=[s["item_code"] for s in stock],
scrap_cost=scrap_cost,
rework_cost=rework_cost,
total_estimated_cost=scrap_cost + rework_cost
)
BPMN变更审批流程定义
{
"processId": "ECR_APPROVAL",
"processName": "工程变更审批流程",
"steps": [
{
"id": "start",
"type": "startEvent",
"name": "ECR提交"
},
{
"id": "impact_analysis",
"type": "serviceTask",
"name": "自动影响评估",
"service": "ChangeImpactAnalyzer.analyze",
"outputs": ["affected_bom", "affected_po", "affected_wo", "cost_estimate"]
},
{
"id": "review_committee",
"type": "userTask",
"name": "变更评审委员会评审",
"assignees": ["${研发主管}", "${工艺主管}", "${质量主管}"],
"multi_instance": "parallel",
"completion_condition": "${nrOfApprovedInstances/nrOfInstances >= 0.67}",
"timeout_hours": 48,
"timeout_action": "escalate_to_manager"
},
{
"id": "finance_approval",
"type": "exclusiveGateway",
"name": "成本审批阈值判断",
"condition": "${cost_estimate > 50000 ? 'finance_director' : 'auto_approve'}",
"branches": {
"finance_director": {
"type": "userTask",
"assignee": "${财务总监}",
"timeout_hours": 24,
"timeout_action": "escalate_to_vp"
},
"auto_approve": {
"type": "none"
}
}
},
{
"id": "implement",
"type": "serviceTask",
"name": "变更执行",
"service": "ChangeImplementationService.execute",
"actions": [
"update_bom_version",
"notify_affected_departments",
"sync_to_erp",
"archive_old_version"
]
},
{
"id": "notify",
"type": "serviceTask",
"name": "变更通知推送",
"service": "NotificationService.broadcast",
"channels": ["dingtalk", "email", "in_app"],
"recipients": "affected_department_heads"
},
{
"id": "end",
"type": "endEvent",
"name": "变更完成"
}
],
"transitions": [
{"from": "start", "to": "impact_analysis"},
{"from": "impact_analysis", "to": "review_committee"},
{"from": "review_committee", "to": "finance_approval"},
{"from": "finance_approval", "to": "implement"},
{"from": "implement", "to": "notify"},
{"from": "notify", "to": "end"}
]
}
ERP同步连接器(Outbox Pattern)
import json
from datetime import datetime
from typing import Optional
class OutboxMessage:
"""Outbox消息实体 - 确保PLM到ERP的数据可靠同步"""
def __init__(self, aggregate_id: str, aggregate_type: str,
event_type: str, payload: dict):
self.id = str(uuid4())
self.aggregate_id = aggregate_id # 聚合ID(如物料编码)
self.aggregate_type = aggregate_type # MATERIAL / BOM / CHANGE_ORDER
self.event_type = event_type # CREATED / UPDATED / DELETED
self.payload = json.dumps(payload, ensure_ascii=False)
self.created_at = datetime.now()
self.processed_at: Optional[datetime] = None
self.retry_count = 0
self.max_retry = 5
self.status = "PENDING" # PENDING / PROCESSING / COMPLETED / FAILED
class ERPSyncWorker:
"""ERP异步同步Worker - Outbox Pattern实现"""
def __init__(self, db_session, erp_client):
self.db = db_session
self.erp = erp_client # 用友U8 / 金蝶K3 / SAP接口客户端
def process_outbox(self, batch_size: int = 50):
"""处理Outbox消息队列"""
messages = self.db.query(OutboxMessage)\
.filter(OutboxMessage.status == "PENDING")\
.order_by(OutboxMessage.created_at)\
.limit(batch_size)\
.all()
for msg in messages:
try:
msg.status = "PROCESSING"
self.db.commit()
payload = json.loads(msg.payload)
success = self._dispatch_to_erp(msg.aggregate_type,
msg.event_type, payload)
if success:
msg.status = "COMPLETED"
msg.processed_at = datetime.now()
else:
msg.retry_count += 1
msg.status = "PENDING" if msg.retry_count < msg.max_retry else "FAILED"
self.db.commit()
except Exception as e:
msg.retry_count += 1
msg.status = "PENDING" if msg.retry_count < msg.max_retry else "FAILED"
self.db.commit()
print(f"Sync failed for {msg.id}: {e}")
def _dispatch_to_erp(self, agg_type: str, event_type: str, payload: dict) -> bool:
"""路由到ERP对应接口"""
if agg_type == "MATERIAL":
if event_type == "CREATED":
return self.erp.create_material(payload)
elif event_type == "UPDATED":
return self.erp.update_material(payload["code"], payload)
elif agg_type == "BOM":
if event_type in ("CREATED", "UPDATED"):
return self.erp.sync_bom(payload["root_code"], payload["items"])
elif agg_type == "CHANGE_ORDER":
return self.erp.notify_change(payload)
return False
多平台消息推送适配器
from abc import ABC, abstractmethod
class NotificationAdapter(ABC):
"""消息推送适配器抽象接口"""
@abstractmethod
def send(self, recipients: List[str], title: str, content: str,
priority: str = "normal") -> bool:
pass
class DingTalkAdapter(NotificationAdapter):
"""钉钉消息推送适配器"""
def __init__(self, webhook_url: str, access_token: str):
self.webhook = webhook_url
self.token = access_token
def send(self, recipients: List[str], title: str, content: str,
priority: str = "normal") -> bool:
priority_map = {"high": "#FF0000", "normal": "#008000", "low": "#888888"}
msg = {
"msgtype": "markdown",
"markdown": {
"title": title,
"text": f"## {title}\n\n{content}\n\n**接收人**: {', '.join(recipients)}"
},
"at": {
"atMobiles": recipients,
"isAtAll": priority == "high"
}
}
# HTTP POST to DingTalk webhook
response = requests.post(f"{self.webhook}?access_token={self.token}",
json=msg, timeout=10)
return response.status_code == 200 and response.json().get("errcode") == 0
class EmailAdapter(NotificationAdapter):
"""邮件消息推送适配器"""
def __init__(self, smtp_host: str, smtp_port: int, sender: str, password: str):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
self.sender = sender
self.password = password
def send(self, recipients: List[str], title: str, content: str,
priority: str = "normal") -> bool:
msg = MIMEText(content, "html", "utf-8")
msg["Subject"] = Header(title, "utf-8")
msg["From"] = self.sender
msg["To"] = ", ".join(recipients)
try:
with smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) as server:
server.login(self.sender, self.password)
server.sendmail(self.sender, recipients, msg.as_string())
return True
except Exception as e:
print(f"Email send failed: {e}")
return False
class NotificationService:
"""统一消息推送服务 - 变更通知、预警通知等"""
def __init__(self):
self.adapters: List[NotificationAdapter] = []
def register_adapter(self, adapter: NotificationAdapter):
self.adapters.append(adapter)
def broadcast_change_notification(self, change_order: dict, recipients: dict):
"""变更通知广播"""
title = f"[变更通知] ECO-{change_order['id']} 已批准执行"
content = self._format_change_content(change_order)
# 按渠道推送
if recipients.get("dingtalk"):
for adapter in self.adapters:
if isinstance(adapter, DingTalkAdapter):
adapter.send(recipients["dingtalk"], title, content, "high")
if recipients.get("email"):
for adapter in self.adapters:
if isinstance(adapter, EmailAdapter):
adapter.send(recipients["email"], title, content, "normal")
def _format_change_content(self, co: dict) -> str:
return (
f"**变更编号**: {co['id']}\n\n"
f"**变更类型**: {co['type']}\n\n"
f"**影响物料**: {', '.join(co.get('affected_items', []))}\n\n"
f"**变更内容**: {co['description']}\n\n"
f"**生效时间**: {co.get('effective_date', '立即')}\n\n"
f"**请相关部门及时更新相关文档和作业指导**"
)
搭贝平台落地实践
在上述架构和代码实现的落地过程中,搭贝AI低代码平台提供了从数据建模到流程编排到系统集成的全链路支持。搭贝平台的核心价值在于:
可视化数据建模:物料主数据Schema通过拖拽方式定义字段和关联关系,无需编写DDL语句。BOM多视图管理通过配置化规则实现EBOM→PBOM→MBOM自动推导,搭贝的规则引擎支持虚拟件合并、工艺辅料追加、装配顺序调整等常见转换逻辑。
BPMN流程引擎:ECR/ECO审批流程通过可视化BPMN设计器绘制,支持并行评审、成本阈值路由、超时升级等复杂流程。变更执行环节自动触发BOM更新、ERP同步、通知推送三个动作,确保变更信息在1分钟内到达所有受影响部门。
ERP集成连接器:搭贝预置了用友U8、金蝶K3、SAP B1的同步连接器,采用Outbox Pattern确保数据一致性。物料主数据和BOM变更从PLM到ERP的同步延迟控制在30秒以内。
EEAT实操案例
企业背景:300人汽车零部件制造企业,原AutoCAD+Excel管理,EBOM到MBOM手工转换3天/次,月均变更4-6次,15%概率版本不一致。
搭建14天:
- 第1-3天:物料主数据Schema搭建+2000条数据导入
- 第4-7天:BOM管理界面+BOM多视图转换规则+文档管理
- 第8-11天:ECR/ECO流程+BPMN审批+变更影响分析
- 第12-14天:ERP同步+全流程测试
效果:EBOM→MBOM转换3天→2小时(-97%),变更通知实时(-100%延迟),BOM版本错误降为0,研发文员工作量-60%。
行业趋势
Fortune Business Insights数据显示全球低代码从2025年373.9亿美元增长至2026年489.1亿美元,CAGR 29.10%。Market Research Future数据显示全球云PLM从2025年312.2亿美元增长至2035年685.4亿美元。Gartner预测2026年75%新应用走低代码,2027年40%企业应用内置AI智能体。工信部数据显示国产PLM厂商份额突破68%。
FAQ
Q1:低代码PLM适合什么规模的企业?
最适合100-500人中小型制造企业,PLM需求明确但IT资源有限,低代码PLM的2-4周交付周期远优于传统PLM套件的6-12个月。
Q2:低代码搭建的PLM能否处理复杂BOM?
支持多层级BOM(≥10层)、千人BOM(≥5000行),取决于底层数据库设计而非前端搭建方式。
Q3:搭贝AI低代码平台PLM方案有何区别?
搭贝基于低代码搭建,数据模型和流程按企业实际业务配置。预置用友/金蝶/SAP集成接口,降低集成开发量。
Q4:PLM系统上线后多久能见效?
2周内BOM效率提升可见,1个月变更通知覆盖率100%,3个月BOM异常降至0。
Q5:CAD和Excel BOM数据能否导入?
支持Excel/CSV批量导入,CAD模型通过插件提取属性后导入,典型迁移3-5个工作日。
Q6:数据安全如何保证?
RBAC+ABAC混合权限模型,文档级控制,水印+审计日志,支持私有化部署。
Q7:如何持续迭代?
低代码迭代无需改代码,可视化调整模型和流程,配置变更可回滚。
Q8:PLM与ERP/MES如何贯通?
PLM输出物料/BOM/工艺/质量标准到下游,通过API实时同步、消息队列异步同步、定时批量同步三种方式,搭贝ERP连接器采用Outbox Pattern确保一致性。
更多推荐
所有评论(0)