☰
开源BOM管理系统:Excel直连数据库的硬件物料管理方案
2026/10/13 5:51:04 网站建设 项目流程

简介:这是一款面向电子工程师、硬件研发团队及BOM管理初学者的开源物料清单协同管理软件,解决Excel分散维护BOM导致的版本混乱、多人协作困难、数据孤岛等问题。系统基于C#开发,采用集中式数据库架构,支持实时多用户编辑、完整版本控制,并深度集成Ciiva电子元器件搜索API,实现BOM与供应商库存、价格、替代料、技术参数等关键信息的自动同步。资源包共50个文件,含37个核心C#源码文件(如ManufacturerComponent.cs、StockItem.cs等)、5个运行依赖DLL、2个项目配置文件(.csproj)及图标、配置、资源文件等,整体仅315KB,轻量易部署。已有838人学习下载,提供开箱即用的完整可编译VS解决方案(ApiDemoProject),含认证、搜索、比价、库存查询等全链路API调用示例与UI主界面,是理解工业级BOM系统架构与电子元器件数据集成实践的优质入门参考。

1. BOM Management Software:不是Excel插件,而是把散落的BOM表格“焊”进一个可查、可溯、可权限控制的数据库里

你有没有经历过这种场景:三个工程师各自维护一份Excel格式的物料清单(BOM),版本号靠文件名后缀(_v2_final_reallyfinal.xlsx),变更记录靠微信截图,ECN(工程变更通知)落地时发现A表删了电阻R12,B表却新增了同型号但不同供应商的R12a,C表压根没更新——等PCB贴片完才发现阻值不匹配。这不是玄学,是典型BOM失控。这个开源BOM Management Software,核心价值就一句话:把Excel里游离的BOM数据,一次性导入、结构化存储、带版本快照与操作留痕地托管进轻量级PostgreSQL/SQLite数据库,同时保留Excel作为前端编辑入口。它不替代ERP,也不做PLM那么重,而是卡在“设计输出→制造输入”这个最脆弱的交接缝里,给中小研发团队一个零许可成本、可本地部署、改配置就能上线的BOM中枢。适合硬件初创团队、教育实验室、产线试制组——只要你们还在用Excel传BOM,又开始为版本打架、字段不一致、找不到历史修改人而头疼,它就不是玩具,是能立刻止血的工具。


2. 为什么选它?从Excel直连数据库的架构设计与技术选型逻辑

2.1 它不是又一个Web表格编辑器:三层数据流拆解

这个项目最反直觉的设计点在于:它不把Excel当“视图”,而当“协议”。很多同类工具试图用Web界面模拟Excel操作,结果陷入渲染性能泥潭;而本项目采用“Excel ↔ 数据库 ↔ Web API”三段式流转:

  • 前端层:用户仍用熟悉的Excel(.xlsx)编辑,通过官方提供的模板文件(含预设表头、数据验证规则、隐藏元数据列)填写;
  • 转换层:运行bom_import.py脚本,将Excel解析为标准化JSON Schema(含物料ID、父级关系、用量、版本号、生效日期等12个必填字段),再经SQLAlchemy ORM映射写入数据库;
  • 服务层:Flask后端暴露RESTful接口(如GET /api/bom/revision/2024Q3_A?format=csv),供MES系统拉取结构化BOM,或供内部看板查询变更对比。

提示:这种设计牺牲了“所见即所得”的实时性,但换来的是数据一致性——所有Excel文件最终都收敛到同一份数据库快照,避免了“打开Excel即产生新分支”的Git式混乱。

2.2 数据库选型:SQLite够用,PostgreSQL才真正释放能力

项目默认支持SQLite(单文件,开箱即用),但生产环境我强烈建议切到PostgreSQL。原因很实际:

能力维度SQLite表现PostgreSQL优势
并发写入多用户同时导入会锁整个DB文件行级锁,5人同时上传BOM无阻塞
历史版本追溯需手动建表存快照,易遗漏内置pg_trgm扩展+时间旅行查询(AS OF SYSTEM TIME)
权限控制仅文件级读写权限细粒度到行级(如“采购员只能看status='approved'的物料”)
备份恢复VACUUM INTO备份慢且不可中断pg_dump --jobs=4并行导出,TB级BOM秒级备份

注意:切换数据库只需改.env文件两行:

DB_TYPE=postgresql DATABASE_URL=postgresql://bom_user:pwd@localhost:5432/bom_db

启动前运行python manage.py db upgrade自动执行迁移脚本(含索引优化、约束添加)。

2.3 Excel模板的“隐形契约”:12个字段如何定义BOM语义

项目强制要求Excel按固定列顺序填写,这看似死板,实则是防错关键。以某电源模块BOM为例,其模板头行为:

BOM_IDPARENT_IDITEM_NAMEMANUF_PART_NOSUPPLIERQTYUNITREF_DESIGNATORSTATUSEFFECTIVE_DATEREVISIONNOTES
  • BOM_ID:全局唯一物料编码(如PWR-DCDC-001),禁止空格/中文/特殊字符,这是数据库主键;
  • PARENT_ID:父级BOM_ID(顶层为空),构建树形结构,SELECT * FROM bom_items WHERE parent_id IS NULL即得所有顶层模块;
  • EFFECTIVE_DATE:必须为YYYY-MM-DD格式,系统据此判断当前生效版本(WHERE effective_date <= NOW() ORDER BY effective_date DESC LIMIT 1);
  • REVISION:字符串类型(非数字),支持1.2.0、2024Q3-A等语义化版本,数据库自动校验同一BOM_ID下不得存在日期重叠的修订版。

血泪经验:曾有团队把REF_DESIGNATOR(参考位号)填成C1,C2,C3逗号分隔,导致后续SMT编程无法识别单个电容位置。正确做法是每行只写一个位号,用量(QTY)字段体现数量。


3. 从零部署:5分钟跑通本地实例的完整命令链

3.1 环境准备:Python 3.9+与数据库驱动安装

项目依赖明确,无需conda虚拟环境(但推荐):

# 创建隔离环境(可选但强烈建议) python -m venv bom_env source bom_env/bin/activate # Linux/macOS # bom_env\Scripts\activate.bat # Windows # 安装核心依赖(含数据库驱动) pip install -r requirements.txt # 若选PostgreSQL,额外装psycopg2-binary(Windows用户注意:用binary版避编译坑) pip install psycopg2-binary

逻辑说明:requirements.txt中flask-sqlalchemy==3.0.5锁定版本,因4.x版移除了session.execute(text())的便捷写法,而项目大量使用原生SQL做树形查询(如WITH RECURSIVE bom_tree AS (...)),降级兼容性风险高。

3.2 初始化数据库与管理员账户

首次运行需创建表结构并注入初始数据:

# 执行数据库迁移(生成bom_items, bom_revisions等6张表) python manage.py db upgrade # 创建超级管理员(用户名admin,密码Admin@123) python manage.py create_admin --username admin --password "Admin@123" # 加载示例BOM数据(含3层嵌套结构,用于验证树形查询) python manage.py load_sample_data

参数说明:create_admin命令会哈希密码并写入users表,load_sample_data调用sample_bom.xlsx中的127行数据,自动构建父子关系链(如MAIN_PCB→POWER_MODULE→DCDC_CONVERTER)。

3.3 启动服务与验证API可用性

启动Flask开发服务器(生产环境请用Gunicorn):

# 设置环境变量(开发模式) export FLASK_ENV=development export FLASK_APP=app:create_app # 启动服务(监听5000端口) flask run --host=0.0.0.0 --port=5000

验证是否成功:

# 检查健康状态 curl http://localhost:5000/api/health # 返回 {"status":"healthy","db_connected":true,"version":"v2.4.1"} # 获取最新版BOM列表(应返回JSON数组) curl "http://localhost:5000/api/bom?limit=5" # 返回 [{"bom_id":"MAIN_PCB","revision":"2024Q3","effective_date":"2024-07-01",...}]

注意:若curl返回Connection refused,检查flask run输出是否有* Running on http://127.0.0.1:5000字样;若提示ModuleNotFoundError,确认app/目录下有__init__.py且manage.py路径正确。


4. Excel导入实战:手把手完成一次带版本快照的BOM入库

4.1 下载模板并填写真实数据

访问http://localhost:5000/download/template获取bom_template_v2.xlsx。重点填写以下区域:

  • Sheet1(BOM_DATA):按列填写,特别注意:
    • STATUS列填draft(草稿)、approved(已批准)、obsolete(作废);
    • EFFECTIVE_DATE填未来日期(如2024-10-01)可实现“计划生效”;
    • NOTES列支持Markdown语法(**加粗**、*斜体*),前端渲染为富文本。
  • Sheet2(BOM_METADATA):填写本次导入的元信息:
    • PROJECT_NAME:RobotArm_V2
    • IMPORTED_BY:zhangsan
    • CHANGE_REASON:电机驱动IC升级为TPS65988

提示:模板内置数据验证(如STATUS下拉菜单),关闭Excel的“启用内容”可能禁用验证,务必点击“启用编辑”。

4.2 执行导入并观察数据库变化

运行导入脚本(指定Excel路径与版本号):

python scripts/import_bom.py \ --file ./data/robot_arm_v2.xlsx \ --revision "2024Q4-B" \ --comment "电机驱动IC升级,兼容旧PCB" \ --user "zhangsan"

脚本执行后,数据库发生三件事:

  1. 在bom_revisions表插入新记录,含revision="2024Q4-B"、imported_at=now()、user_id=3;
  2. 将Excel中所有行写入bom_items,revision_id外键关联上一步ID;
  3. 自动触发bom_items表的ON INSERT触发器,校验PARENT_ID是否存在、QTY>0等业务规则。

逻辑说明:import_bom.py核心是parse_excel_to_json()函数,它用openpyxl读取Excel(避开pandas.read_excel对合并单元格的误解析),逐行生成字典,再批量插入。失败时回滚整个事务,确保“全成功或全失败”。

4.3 查询与导出:用SQL和API验证数据一致性

登录PostgreSQL执行验证查询:

-- 查看RobotArm_V2的最新版BOM(含层级关系) WITH RECURSIVE bom_tree AS ( SELECT id, bom_id, parent_id, item_name, qty, 1 as level FROM bom_items WHERE bom_id = 'ROBOT_ARM_MAIN' AND revision_id = ( SELECT id FROM bom_revisions WHERE revision = '2024Q4-B' LIMIT 1 ) UNION ALL SELECT c.id, c.bom_id, c.parent_id, c.item_name, c.qty, p.level + 1 FROM bom_items c INNER JOIN bom_tree p ON c.parent_id = p.bom_id ) SELECT * FROM bom_tree ORDER BY level;

同时调用API导出CSV供下游使用:

# 导出指定版本的扁平化BOM(含所有子项) curl -H "Accept: text/csv" \ "http://localhost:5000/api/bom/revision/2024Q4-B?flatten=true" \ > robot_arm_v2_flat.csv

参数说明:flatten=true参数展开所有子BOM(递归查询),?format=excel则返回.xlsx文件(含样式)。导出CSV时,QTY字段自动计算为累计用量(如顶层用1个,子模块用2个,则子项QTY=1×2=2)。


5. 避坑指南:5个让新手当场翻车的高频问题与根治方案

5.1 现象:导入Excel后,PARENT_ID显示为#REF!,数据库里该字段为NULL

原因:Excel中PARENT_ID列填写了不存在的BOM_ID(如填了MOTOR_CTRL,但数据库无此ID),且模板未开启严格校验。
解决:

  1. 运行python scripts/validate_bom.py --file robot_arm_v2.xlsx,脚本会扫描所有PARENT_ID并报告缺失项;
  2. 在bom_template_v2.xlsx的BOM_DATA页,为PARENT_ID列设置数据验证:允许“序列”,来源==INDIRECT("BOM_IDS"),其中BOM_IDS是BOM_METADATA页定义的命名区域。

5.2 现象:flask run报错sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) unable to open database file

原因:.env中DATABASE_PATH=./instance/app.db路径的instance/目录不存在,SQLite无法自动创建父目录。
解决:

mkdir -p instance # Linux/macOS # mkdir instance # Windows # 再次运行 flask run

5.3 现象:Web界面登录后,点击“BOM列表”空白,浏览器F12看到500 Internal Server Error

原因:bom_items表缺少created_at时间戳字段(旧版迁移脚本遗漏),而app/views.py中get_latest_boms()函数强制ORDER BY created_at DESC。
解决:

-- 连接数据库后执行 ALTER TABLE bom_items ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; UPDATE bom_items SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL;

5.4 现象:导入同一BOM_ID的两个版本(如2024Q3和2024Q4),但API只返回2024Q3

原因:EFFECTIVE_DATE填错格式(如2024/07/01而非2024-07-01),数据库存为NULL,WHERE effective_date <= NOW()永远不成立。
解决:

  1. 用SELECT bom_id, revision, effective_date FROM bom_revisions;确认日期格式;
  2. 修复Excel后重新导入,或直接SQL更新:
    UPDATE bom_revisions SET effective_date = '2024-10-01' WHERE revision = '2024Q4';

5.5 现象:curl调用API返回{"error":"Unauthorized"},但用户名密码正确

原因:Flask-WTF的CSRF保护在API模式下误触发(@login_required装饰器默认检查CSRF token)。
解决:
修改app/auth/views.py,在login()函数上方添加:

from flask_wtf.csrf import csrf_exempt @csrf_exempt def login():

注意:此操作仅对API端点开放,Web表单仍受CSRF保护,安全边界清晰。


6. 进阶技巧:用数据库触发器自动生成BOM变更报告与邮件通知

6.1 构建变更审计表:记录每一次BOM增删改

项目默认只存BOM快照,但产线需要知道“谁在何时改了哪个物料”。我们用PostgreSQL触发器补全这一环:

-- 创建审计表 CREATE TABLE bom_audit_log ( id SERIAL PRIMARY KEY, operation VARCHAR(10) NOT NULL, -- 'INSERT','UPDATE','DELETE' bom_id VARCHAR(50) NOT NULL, old_data JSONB, new_data JSONB, changed_by VARCHAR(50), changed_at TIMESTAMP DEFAULT NOW() ); -- 创建触发器函数 CREATE OR REPLACE FUNCTION log_bom_change() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'INSERT' THEN INSERT INTO bom_audit_log(operation, bom_id, new_data, changed_by) VALUES ('INSERT', NEW.bom_id, to_jsonb(NEW), current_setting('app.user_id', true)); ELSIF TG_OP = 'UPDATE' THEN INSERT INTO bom_audit_log(operation, bom_id, old_data, new_data, changed_by) VALUES ('UPDATE', NEW.bom_id, to_jsonb(OLD), to_jsonb(NEW), current_setting('app.user_id', true)); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -- 绑定到bom_items表 CREATE TRIGGER bom_audit_trigger AFTER INSERT OR UPDATE ON bom_items FOR EACH ROW EXECUTE FUNCTION log_bom_change();

关键点:current_setting('app.user_id', true)从Flask应用层透传用户ID(需在app/models.py的db.session.execute()前执行set_config('app.user_id', current_user.username)),确保审计日志可追溯到具体人。

6.2 用Python脚本生成周报:自动抓取变更并邮件发送

创建scripts/generate_weekly_report.py:

import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime, timedelta from sqlalchemy import create_engine # 连接数据库 engine = create_engine("postgresql://bom_user:pwd@localhost:5432/bom_db") # 查询上周变更 last_week = datetime.now() - timedelta(days=7) query = """ SELECT operation, bom_id, changed_by, changed_at FROM bom_audit_log WHERE changed_at >= %s ORDER BY changed_at DESC """ changes = engine.execute(query, (last_week,)).fetchall() # 生成HTML邮件 html = f"<h2>BOM变更周报({last_week.strftime('%Y-%m-%d')} 至 {datetime.now().strftime('%Y-%m-%d')})</h2>" html += "<table><tr><th>操作</th><th>BOM_ID</th><th>人员</th><th>时间</th></tr>" for op, bid, user, dt in changes: html += f"<tr><td>{op}</td><td>{bid}</td><td>{user}</td><td>{dt}</td></tr>" html += "</table>" # 发送邮件(配置SMTP) msg = MIMEMultipart() msg['Subject'] = "【BOM周报】上周共发生{}次变更".format(len(changes)) msg.attach(MIMEText(html, 'html')) server = smtplib.SMTP('smtp.company.com', 587) server.sendmail("bom@company.com", ["eng-team@company.com"], msg.as_string())

执行方式:加入Linux crontab0 9 * * 1 python /opt/bom/scripts/generate_weekly_report.py,每周一早9点自动发送。

6.3 一个硬核习惯:每次导入前,强制执行“差异预检”

我从某硬件团队踩坑后养成的习惯:绝不直接导入Excel,先跑差异分析。用scripts/diff_bom.py对比新旧版本:

# 对比2024Q3与2024Q4-B两个版本 python scripts/diff_bom.py \ --base-revision 2024Q3 \ --target-revision 2024Q4-B \ --output-format markdown

输出示例:

## 📉 删除项(2) | BOM_ID | ITEM_NAME | QTY | 原因 | |--------|-----------|-----|------| | R12 | 10kΩ电阻 | 4 | 替换为集成方案 | ## ➕ 新增项(3) | BOM_ID | ITEM_NAME | QTY | 供应商 | |--------|-----------|-----|--------| | U5 | TPS65988 | 1 | TI |

从那以后我每次导入BOM前,都强制走一遍diff_bom.py,把Markdown报告发到团队群,所有人确认无异议再点导入按钮。这多花的2分钟,省去了后面3小时的PCB返工排查。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询