Skip to content

工厂数字化项目实战案例(编号 121-140) ​

案例一:从 0 到 1 搭建 MES 系统 ​

🏭 项目背景 ​

markdown
客户:某中型汽车零部件制造企业
痛点:
• 纸质工单流转效率低,信息传递延迟 2-4 小时
• 人工统计良率易出错,月均误差率 3%
• 设备开机状态靠巡检,停机发现不及时
• 追溯全靠 Excel,召回时查找困难

目标:
✅ 实现无纸化生产
✅ 实时数据采集与监控
✅ 全流程质量追溯
✅ ERP-MES-WMS一体化集成

🎯 技术方案设计 ​

php
// 核心架构
┌─────────────────────────────────────────────┐
│              Web 前端(Vue3 + ElementPlus)   │
├─────────────────────────────────────────────┤
│         API Gateway (Laravel + OAuth2)      │
├─────────────────────────────────────────────┤
│        Business Layer (Services & Repos)    │
├──────────────┬──────────────┬───────────────┤
│   Order Svc  │  Device Svc  │ Quality Svc   │
├──────────────┼──────────────┼───────────────┤
│   MySQL(主库)│  Redis(缓存) │ MongoDB(日志) │
└──────────────┴──────────────┴───────────────┘
         ↓ 消息队列
┌─────────────────────────────────────────────┐
│           ERP / WMS / OA Integration        │
└─────────────────────────────────────────────┘

🔧 核心模块实现 ​

1. 工单管理系统 ​

php
class WorkOrderManagement {
    /**
     * 工单状态机定义
     */
    const STATUSES = [
        'draft' => ['release'],              // draft 可转换到 release
        'released' => ['start', 'cancel'],   // released可转换到 start/cancel
        'in_progress' => ['pause', 'complete'],
        'paused' => ['resume', 'cancel'],
        'completed' => [],                   // completed终态
        'cancelled' => [],                   // cancelled终态
    ];

    public function releaseToERP(Order $order): SyncResult {
        // ✅ 校验订单数据完整性
        if (!$this->validateOrder($order)) {
            return SyncResult::failed('订单数据不完整');
        }

        // ✅ 映射到 ERP 格式
        $erpData = [
            'work_order_no' => $order->code,
            'product_code' => $order->product->erp_product_code,
            'planned_qty' => $order->planned_quantity,
            'start_date' => $order->planned_start_date,
            'end_date' => $order->planned_end_date,
            'operations' => $order->bomOperations->map(fn($op) => [
                'operation_code' => $op->code,
                'operation_name' => $op->name,
                'std_time' => $op->standard_time,
                'device_id' => $op->requiredDevice?->erp_device_id,
            ])->toArray(),
        ];

        // ✅ 异步发送到 ERP
        SyncWorkOrderToERP::dispatch($order->id);

        // ✅ 记录操作日志
        $this->audit->log('RELEASED', $order->getOriginal('status'), $order->status);

        return SyncResult::success();
    }
}

2. 设备数据采集器 ​

php
class DeviceDataCollector {
    private ModbusClient $modbus;

    public function __construct() {
        $this->modbus = new ModbusTcpClient();
    }

    /**
     * 采集 CNC 机床参数
     */
    public function collectCNCParameters(string $deviceId): array {
        $ip = $this->getDeviceIp($deviceId);
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_connect($socket, $ip, 502);  // Modbus 默认端口

        $parameters = [];

        // 读取主轴温度(寄存器地址 40001)
        $temp = $this->readRegister($socket, 40001, 1);
        $parameters[] = ['param' => 'spindle_temp', 'value' => $temp];

        // 读取主轴转速(寄存器地址 40002)
        $speed = $this->readRegister($socket, 40002, 1);
        $parameters[] = ['param' => 'spindle_speed', 'value' => $speed];

        // 读取进给速度(寄存器地址 40003)
        $feed = $this->readRegister($socket, 40003, 1);
        $parameters[] = ['param' => 'feed_rate', 'value' => $feed];

        // 保存到数据库
        ProductionParameter::insert($this->formatParameters($deviceId, $parameters));

        // WebSocket 推送实时监控面板
        Broadcast::channel("device.{$deviceId}.monitor", [
            'timestamp' => now()->toIso8601ZonedDateTimeString(),
            'temperature' => $temp,
            'speed' => $speed,
            'feed_rate' => $feed,
        ]);

        socket_close($socket);

        return $parameters;
    }

    private function readRegister($socket, int $address, int $count): float {
        // Modbus TCP 请求构建(省略细节)
        $request = $this->buildReadRequest($address, $count);
        socket_write($socket, $request, strlen($request));

        $response = socket_read($socket, 1024);
        return $this->parseResponse($response);
    }
}

3. 生产报工系统 ​

php
class ProductionReporting {
    public function submitReport(ReportRequest $request): ReportRecord {
        DB::beginTransaction();

        try {
            // ✅ 步骤 1: 验证操作工身份
            $operator = auth()->user();
            if (!$this->isOperatorAssigned($operator->employee_id, $request->workOrderNo)) {
                throw new AuthorizationException('操作员未分配到此工单');
            }

            // ✅ 步骤 2: 验证工位匹配(防止代打卡)
            $workCenter = $this->getWorkCenter($request->workOrderNo, $request->operationCode);
            if ($workCenter->deviceId !== $request->deviceId) {
                throw new BusinessException('工位不匹配');
            }

            // ✅ 步骤 3: 防作弊 - GPS 定位校验
            if ($this->requireGeofencing) {
                $operatorLocation = $request->input('gps_location');
                if (!$this->isWithinRange($operatorLocation, $workCenter->location)) {
                    throw new BusinessException('超出允许作业范围');
                }
            }

            // ✅ 步骤 4: 创建报工记录
            $report = ReportRecord::create([
                'work_order_no' => $request->workOrderNo,
                'operation_code' => $request->operationCode,
                'operator_id' => $operator->employee_id,
                'device_id' => $request->deviceId,
                'produced_qty' => $request->producedQty,
                'defect_qty' => $request->defectQty ?? 0,
                'scrap_qty' => $request->scrapQty ?? 0,
                'scrap_reason' => $request->scrapReason,

                // 工艺参数(关键工序必填)
                'process_params' => $request->processParams ? json_encode($request->processParams) : null,

                'reported_at' => now(),
                'created_by' => $operator->id,
                'updated_by' => $operator->id,
            ]);

            // ✅ 步骤 5: 更新工单进度
            $this->updateWorkOrderProgress($report);

            // ✅ 步骤 6: 触发扣料逻辑
            if ($request->producedQty > 0) {
                $materialDeduction = new MaterialDeductionService();
                $materialDeduction->deduct($report->work_order_no, $report->produced_qty);
            }

            DB::commit();

            // ✅ 步骤 7: 发送通知
            Notification::send(
                $workCenter->manager,
                new ProductionReported($report)
            );

            return $report;

        } catch (Exception $e) {
            DB::rollBack();
            Log::error("报工失败:{$e->getMessage()}");
            throw $e;
        }
    }

    private function updateWorkOrderProgress(ReportRecord $report): void {
        $workOrder = WorkOrder::lockForUpdate()
            ->where('work_order_no', $report->work_order_no)
            ->first();

        $totalProduced = $workOrder->reports
            ->where('operation_code', $report->operation_code)
            ->sum('produced_qty');

        $progress = min(100, round(($totalProduced / $workOrder->planned_quantity) * 100, 2));

        $updateData = [
            'progress_percentage' => $progress,
            'actual_output' => $totalProduced,
            'last_reported_at' => now(),
        ];

        // 如果所有工序都完成,自动关闭工单
        if ($this->allOperationsComplete($workOrder)) {
            $updateData['status'] = 'completed';
            $updateData['completion_time'] = now();
        }

        $workOrder->update($updateData);

        // 触发工单完成事件
        if ($updateData['status'] === 'completed') {
            event(new WorkOrderCompleted($workOrder));
        }
    }
}

📊 成果展示 ​

markdown
实施效果:
• 工单传递时间:4 小时 → 5 分钟(提升 288 倍)
• 良率统计准确率:97% → 99.9%
• 停机发现时效:2 小时 → 30 秒
• 追溯查询时间:2 小时 → 3 分钟
• 人工录入错误:3% → 0.1%

用户反馈:
"系统上线后,我们的生产效率提升了 25%,质量客诉下降了 40%。"
—— 生产总监

"以前月底做报表要花 3 天,现在一键导出,立即可用!"
—— 计划专员

案例二:医疗器械 E-DHR 合规系统 ​

⚕️ 项目背景 ​

markdown
行业要求:FDA 21 CFR Part 11(电子记录和电子签名规范)
客户类型:III 类医疗器械生产企业
特殊需求:
✅ 所有操作必须可追溯、不可篡改
✅ 关键操作需双人复核
✅ 电子签名必须唯一绑定责任人
✅ 审计追踪日志保存至少产品有效期 +2 年

🛡️ 合规设计方案 ​

1. 审计追踪引擎 ​

php
class AuditTrailEngine {
    /**
     * 审计日志表结构
     */
    protected static function createAuditTable(): void {
        Schema::create('audit_logs', function (Blueprint $table) {
            $table->id();
            $table->uuid('correlation_id')->index();  // 关联同一操作的多个日志

            $table->string('user_id');               // 操作用户
            $table->string('action');                // 动作:CREATE/UPDATE/DELETE/APPROVE
            $table->string('entity_type');           // 实体类型:Order/Inspection
            $table->unsignedBigInteger('entity_id'); // 实体 ID
            $table->json('old_values')->nullable();  // 修改前的值
            $table->json('new_values')->nullable();  // 修改后的值
            $table->text('reason')->nullable();      // 修改原因(强制填写)

            // 防篡改字段
            $table->string('ip_address');
            $table->string('user_agent');
            $table->string('session_id');
            $table->timestamp('occurred_at');

            // 数字签名
            $table->string('signature_hash');  // SHA-256 哈希

            $table->timestamps();

            $table->index(['entity_type', 'entity_id']);
        });
    }

    /**
     * 记录审计日志
     */
    public function log(AuditAction $action): void {
        DB::transaction(function () use ($action) {
            // 生成关联 ID(同一操作的多个日志共用一个 ID)
            $correlationId = Str::uuid();

            // 计算签名哈希
            $hashContent = $action->userId . $action->action .
                          $action->entityType . $action->entityId .
                          now()->toISOString();
            $signatureHash = hash('sha256', $hashContent);

            AuditLog::create([
                'correlation_id' => $correlationId,
                'user_id' => $action->userId,
                'action' => $action->action,
                'entity_type' => $action->entityType,
                'entity_id' => $action->entityId,
                'old_values' => $action->oldValues ? json_encode($action->oldValues) : null,
                'new_values' => $action->newValues ? json_encode($action->newValues) : null,
                'reason' => $action->reason,
                'ip_address' => request()->ip(),
                'user_agent' => request()->userAgent(),
                'session_id' => session()->getId(),
                'occurred_at' => now(),
                'signature_hash' => $signatureHash,
            ]);

            // 如果是审批动作,同时记录审批快照
            if ($action->action === 'APPROVE') {
                ApprovalSnapshot::create([
                    'correlation_id' => $correlationId,
                    'approver_id' => $action->userId,
                    'decision' => $action->approved ? 'APPROVED' : 'REJECTED',
                    'signature_hash' => $signatureHash,
                ]);
            }
        });
    }
}

2. Model Observer 自动注入 ​

php
class InspectionRecordObserver {
    public function __construct(private AuditTrailEngine $audit) {}

    /**
     * 创建前记录
     */
    public function creating(InspectionRecord $record): void {
        $record->setAttribute('created_by_audit', true);
    }

    /**
     * 创建时记录审计日志
     */
    public function created(InspectionRecord $record): void {
        if (property_exists($record, 'created_by_audit')) {
            return;  // 内部调用跳过
        }

        $this->audit->log(new AuditAction(
            action: 'CREATE',
            userId: auth()->id(),
            entityType: 'inspection_record',
            entityId: $record->id,
            oldValues: null,
            newValues: $record->toArray(),
            reason: null,  // CREATE 不需要原因
        ));
    }

    /**
     * 更新时捕获变化
     */
    public function updating(InspectionRecord $record): void {
        $record->setAttribute('original_values', $record->getOriginal());
    }

    public function updated(InspectionRecord $record): void {
        if (property_exists($record, 'original_values')) {
            $original = $record->original_values;
            $changes = Arr::except($record->getChanges(), ['updated_at']);

            // 只记录有变化的字段
            if (!empty($changes)) {
                $this->audit->log(new AuditAction(
                    action: 'UPDATE',
                    userId: auth()->id(),
                    entityType: 'inspection_record',
                    entityId: $record->id,
                    oldValues: $original,
                    newValues: array_merge($original, $changes),
                    reason: $record->getAttribute('change_reason') ?? '未填写原因',
                ));
            }
        }
    }

    /**
     * 软删除时记录
     */
    public function deleted(InspectionRecord $record): void {
        $this->audit->log(new AuditAction(
            action: 'DELETE',
            userId: auth()->id(),
            entityType: 'inspection_record',
            entityId: $record->id,
            oldValues: $record->toArray(),
            newValues: null,
            reason: '已删除',  // DELETE 通常不允许
        ));
    }
}

// 注册 Observer
InspectionRecord::observe(InspectionRecordObserver::class);

3. 电子签名实现 ​

php
class ElectronicSignatureService {
    /**
     * 两步验证签名流程
     */
    public function signWithTwoFactor(User $user, string $documentId): SignedDocument {
        // Step 1: 生成一次性验证码
        $otp = rand(100000, 999999);
        Session::put('signature_otp', [
            'otp' => $otp,
            'document_id' => $documentId,
            'expires_at' => now()->addMinutes(10),
        ]);

        // 发送短信/邮件验证码
        SMS::send($user->phone, "您的电子签名验证码是:{$otp},10 分钟内有效");

        return SignedDocument::pendingVerification($user->id, $documentId);
    }

    public function verifyAndComplete(User $user, string $otp): SignedDocument {
        $sessionData = Session::get('signature_otp');

        // 验证 OTP
        if ($otp !== $sessionData['otp']) {
            throw new VerificationException('验证码错误');
        }

        // 检查是否过期
        if (now()->isAfter($sessionData['expires_at'])) {
            throw new VerificationException('验证码已过期');
        }

        // 验签成功,生成数字签名
        $document = Document::findOrFail($sessionData['document_id']);

        $signature = [
            'signer_id' => $user->id,
            'signer_name' => $user->name,
            'sign_time' => now()->toIso8601ZonedDateTimeString(),
            'certificate_serial' => $this->generateCertificateSerial(),
            'hash_algorithm' => 'SHA-256',
            'signature_value' => $this->createDigitalSignature($document),
        ];

        // 存入签名表
        DocumentSignature::create($signature);

        // 更新文档状态
        $document->update(['signed_at' => now(), 'signature_count' => $document->signature_count + 1]);

        // 记录审计日志
        event(new DocumentSigned($document, $signature));

        return $document;
    }

    private function createDigitalSignature(Document $document): string {
        $content = json_encode([
            'document_id' => $document->id,
            'content_hash' => hash('sha256', $document->content),
            'sign_time' => now()->toIso8601ZonedDateTimeString(),
        ]);

        return hash('sha256', $content . config('services.signature_secret_key'));
    }
}

📋 验证测试报告 ​

markdown
IQ/OQ/PQ 验证结果:

安装确认(IQ)✓
• 数据库备份策略已配置
• 审计日志存储周期设置正确(5 年)
• 权限控制符合最小特权原则

运行确认(OQ)✓
• 电子签名成功率 100%
• 审计日志无法删除(测试多次尝试均失败)
• 双人复核机制正常工作

性能确认(PQ)✓
• 支撑 500 并发用户在线
• 审计日志查询响应<2 秒
• 系统可用性达到 99.95%

药监局飞行检查:顺利通过 ✓

案例三:UniApp 移动报工系统优化 ​

📱 挑战与解决方案 ​

1. 离线数据存储 ​

javascript
// SQLite 本地缓存方案
import db from "@/common/sqlite.js";

class OfflineReportStorage {
  async initDB() {
    await db.open({ name: "factory-reports" });

    await db.exec(`
      CREATE TABLE IF NOT EXISTS offline_reports (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        work_order_no TEXT NOT NULL,
        operation_code TEXT,
        produced_qty INTEGER DEFAULT 0,
        defect_qty INTEGER DEFAULT 0,
        process_params TEXT,
        gps_location TEXT,
        status TEXT DEFAULT 'PENDING_SYNC',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // 创建索引加速查询
    await db.exec(`
      CREATE INDEX IF NOT EXISTS idx_status ON offline_reports(status);
      CREATE INDEX IF NOT EXISTS idx_created ON offline_reports(created_at);
    `);
  }

  async saveOffline(report) {
    const result = await db.run(
      "INSERT INTO offline_reports (work_order_no, operation_code, produced_qty, ...) VALUES (?, ?, ?, ...)",
      [
        report.workOrderNo,
        report.operationCode,
        report.producedQty,
        report.defectQty,
        JSON.stringify(report.processParams || {}),
        JSON.stringify(report.gpsLocation),
      ],
    );

    return result.lastInsertRowid;
  }

  async getPendingReports() {
    return db.select("SELECT * FROM offline_reports WHERE status = ?", [
      "PENDING_SYNC",
    ]);
  }

  async markAsSynced(ids) {
    const placeholders = ids.map(() => "?").join(",");
    await db.run(
      `UPDATE offline_reports SET status = 'SYNCED', updated_at = ? WHERE id IN (${placeholders})`,
      [new Date(), ...ids],
    );
  }
}

2. PWA progressive web app ​

html
<!-- manifest.json -->
{ "name": "工厂报工助手", "short_name": "报工助手", "start_url": "/app/",
"display": "standalone", "background_color": "#ffffff", "theme_color":
"#1890ff", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192",
"type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512",
"type": "image/png" } ], "offline_enabled": true }

3. 性能优化对比 ​

markdown
优化前 vs 优化后:

首次加载时间:
• 优化前:8.5 秒
• 优化后:2.1 秒 ✅ 提升 75%

页面切换流畅度:
• 优化前:偶尔卡顿(FPS 平均 35)
• 优化后:丝滑流畅(FPS 稳定 60) ✅

内存占用:
• 优化前:峰值 280MB
• 优化后:峰值 120MB ✅ 降低 57%

离线可用次数:
• 优化前:不支持
• 优化后:完全支持离线报工 ✅

网络异常恢复:
• 优化前:需手动刷新重试
• 优化后:自动重连 + 断点续传 ✅

关键技术总结 ​

🔍 系统设计原则 ​

  1. 稳定性优先:制造业系统停机等不起
  2. 数据准确性:差之毫厘,谬以千里
  3. 易用性:一线工人年龄跨度大,界面要简单直观
  4. 可扩展性:工艺调整频繁,系统要灵活配置
  5. 可追溯性:质量问题能迅速定位原因

📈 性能优化经验 ​

php
// 批量操作优化
// ❌ 逐条插入
foreach ($items as $item) {
    ProductionData::create($item);
}

// ✅ 批量插入
DB::table('production_data')->insert($items);

// ✅ 游标方式处理大数据
$users = User::cursor();
foreach ($users as $user) {
    processUser($user);
}

⚠️ 避坑指南 ​

  1. 不要过度设计:小公司用不上 SAP 级别的复杂度
  2. 现场调研很重要:不去车间永远不懂工人的真实需求
  3. 培训比功能更重要:系统再好,不会用也是零
  4. 网络环境要考虑:车间信号差,必须有离线方案
  5. 硬件兼容性:不同品牌 PLC 协议差异巨大

总结与反思 ​

✅ 成功经验 ​

  1. 渐进式上线:先跑通核心流程,再逐步完善
  2. 快速迭代:每周根据用户反馈调整功能
  3. 培训到位:编制操作手册 + 视频教程 + 现场指导
  4. 应急预案:断网/断电场景都有替代方案

❌ 踩过的坑 ​

  1. 初期没考虑扫码枪兼容性(花了一周调试)
  2. 忽略了车间噪音环境,语音提示听不见
  3. 没有预留足够的扩展字段(后期改造很痛苦)
  4. 对一线工人年龄结构预估不足(UI 不够简洁)

最后建议

如果你准备参与类似的工厂数字化项目:

  1. 先去车间待一周,观察工人怎么干活
  2. 找几个关键用户深度沟通,了解他们的痛点
  3. 技术选型时优先考虑稳定性和易用性,而不是先进性
  4. 做好长期陪战的准备(制造业信息化从来不是短平快的项目)
最近更新