系统接口设计与集成实战(编号 51-80)
整体架构说明
51. ERP 订单同步完整流程
php
class ErpOrderSyncService {
public function __construct(
private OrderRepository $orderRepo,
private ErpClient $erpClient,
private SyncLogRepository $logRepo,
) {}
/**
* 将订单同步到 ERP 系统
*/
public function sync(Order $order): SyncResult {
// ✅ 步骤 1: 数据校验
if (!$this->validateOrder($order)) {
return SyncResult::failed('订单数据不完整');
}
// ✅ 步骤 2: 准备 ERP 所需数据格式
$erpData = $this->mapToErpFormat($order);
// ✅ 步骤 3: 异步队列发送
SyncOrderToERP::dispatch($order->id);
// ✅ 步骤 4: 记录同步日志
$this->logRepo->create([
'order_id' => $order->id,
'action' => 'sync_to_erp',
'status' => 'pending',
'payload' => json_encode($erpData),
]);
return SyncResult::success();
}
private function mapToErpFormat(Order $order): array {
return [
'sales_order_no' => $order->order_code,
'customer_code' => $order->customer->erp_customer_code,
'order_date' => $order->created_at->format('Y-m-d'),
'lines' => $order->items->map(fn($item) => [
'material_code' => $item->product->erp_sku,
'quantity' => $item->quantity,
'unit_price' => $item->unit_price,
'tax_rate' => 0.13, // 13%增值税
])->toArray(),
'delivery_date' => $order->delivery_date?->format('Y-m-d'),
];
}
private function validateOrder(Order $order): bool {
return $order->status === 'confirmed'
&& $order->customer->erp_customer_code !== null
&& $order->items->where('quantity', 0)->isEmpty();
}
}关键设计说明:
点击查看时序图
52. 物料主数据同步
php
class MaterialMasterSync {
public function syncFromMES(string $materialCode): Material {
// 从 MES 拉取物料主数据
$mesMaterial = $this->mesClient->getMaterial($materialCode);
return DB::transaction(function () use ($mesMaterial) {
// ✅ 更新或创建
$material = Material::firstOrCreate(
['code' => $mesMaterial->code],
[
'name' => $mesMaterial->name,
'specification' => $mesMaterial->spec,
'unit' => $mesMaterial->unit,
'weight' => $mesMaterial->weight,
'volume' => $mesMaterial->volume,
'erp_material_code' => $mesMaterial->erp_code,
]
);
// ✅ 触发事件通知其他系统
event(new MaterialUpdated($material));
return $material;
});
}
}
// Laravel Event Listener
class UpdateInventoryAfterMaterialUpdate {
public function handle(MaterialUpdated $event): void {
$material = $event->material;
// 更新库存系统的物料信息
InventoryService::updateMaterial([
'material_code' => $material->erp_material_code,
'specification' => $material->specification,
]);
}
}53. BOM(Bill of Materials)同步
php
class BomSyncService {
/**
* 工艺路线和 BOM 表同步
*/
public function syncBom(int $productId): void {
$product = Product::with('bomOperations')->find($productId);
$bomData = [
'product_code' => $product->code,
'version' => $product->bom_version,
'components' => $product->bom->map(fn($comp) => [
'component_code' => $comp->material->code,
'quantity' => $comp->quantity,
'unit' => $comp->material->unit,
'yield_rate' => $comp->yield_rate, // 良品率
'operation_seq' => $comp->operationSequence,
])->toArray(),
];
// 发送到 MES 系统
$this->mesClient->syncBom($bomData);
// 发送到 ERP 成本核算
$this->erpClient->updateBomCost([
'product_code' => $product->code,
'standard_cost' => $this->calculateStandardCost($product->bom),
]);
}
private function calculateStandardCost(Collection $bom): float {
$total = 0;
foreach ($bom as $comp) {
$total += $comp->material->cost * $comp->quantity;
}
return $total * (1 + $this->laborRate); // 加上人工费
}
}54. 工单状态回传
php
class WorkOrderStatusCallback {
public function notifyWorkOrderStatus(WorkOrder $workOrder): void {
$statusMapping = [
'created' => 'RELEASED',
'in_progress' => 'EXECUTING',
'completed' => 'CLOSED',
'cancelled' => 'CANCELLED',
];
$erpStatus = $statusMapping[$workOrder->status] ?? null;
if ($erpStatus) {
$this->erpClient->updateWorkOrderStatus([
'work_order_no' => $workOrder->erp_work_order_no,
'status' => $erpStatus,
'completion_rate' => $workOrder->progress_percentage,
'actual_start_time' => $workOrder->actual_start_time,
'actual_end_time' => $workOrder->actual_end_time,
]);
}
}
}
// 使用观察者模式
class WorkOrder extends Model {
protected static function boot() {
parent::boot();
static::updated(function (WorkOrder $wo) {
if ($wo->isDirty('status')) {
app(WorkOrderStatusCallback::class)->notifyWorkOrderStatus($wo);
}
});
}
}55-60. ERP 快速问答
55. ERP 接口异常如何处理?
重试机制 + 死信队列 + 人工干预界面 + 详细错误日志
56. 如何保证数据一致性?
分布式事务(TCC)、最终一致性补偿、对账机制
57. ERP 订单数量巨大怎么优化?
分批处理(chunk)、消息队列、批量 API、压缩传输
58. 如何处理字段映射冲突?
建立映射配置表、版本化转换规则、数据清洗层
59. 实时 vs 异步同步?
关键业务实时(如库存变更),非关键批量(如报表)
60. ERP 对接常见坑?
编码不一致、时区差异、主键冲突、数据字典映射
MES 系统集成(61-70)
61. 生产报工数据采集
php
class ProductionReportingService {
public function submitReport(ReportRequest $request): ReportRecord {
// 验证工序和机台
if (!$this->validateOperation($request->operationCode, $request->deviceId)) {
throw new BusinessException('工序机台不匹配');
}
// 防作弊:操作员不能代打卡
$worker = auth()->user();
if (!$this->workerIsOnStation($worker->id, $request->deviceId)) {
throw new BusinessException('操作员不在指定工位');
}
// 创建报工记录
$report = ReportRecord::create([
'work_order_no' => $request->workOrderNo,
'operation_code' => $request->operationCode,
'operator_id' => $worker->employee_id,
'device_id' => $request->deviceId,
'produced_qty' => $request->producedQty,
'defect_qty' => $request->defectQty,
'scrap_rate' => $this->calculateScrapRate($request),
'process_params' => $request->processParams, // 温度、压力等
'reported_at' => now(),
]);
// 实时更新工单进度
$workOrder = WorkOrder::where('work_order_no', $request->workOrderNo)->first();
$this->updateWorkOrderProgress($workOrder);
return $report;
}
private function updateWorkOrderProgress(WorkOrder $wo): void {
$totalReported = $wo->reports->sum('produced_qty');
$plannedQty = $wo->planned_quantity;
$wo->update([
'progress_percentage' => min(100, round($totalReported / $plannedQty * 100)),
'actual_output' => $totalReported,
]);
}
}62. 设备参数采集
php
class DeviceParameterCollector {
public function collectFromPLC(string $deviceId, array $parameterCodes): array {
// Modbus TCP通信
$socket = $this->getConnection($deviceId);
$results = [];
foreach ($parameterCodes as $code) {
$address = $this->getParameterAddress($code);
$value = socket_read($socket, $address, 2); // 读取寄存器
$results[] = [
'param_code' => $code,
'param_name' => $this->getParamName($code),
'value' => $value,
'timestamp' => now(),
];
}
// 保存到数据库
ProductionParameter::insert(collection($results)->map(fn($r) => [
'device_id' => $deviceId,
'parameter_code' => $r['param_code'],
'parameter_value' => $r['value'],
'collected_at' => $r['timestamp'],
])->toArray());
return $results;
}
}
// WebSocket推送实时数据到前端
class ParameterWebSocket {
public function pushParameterChange(string $deviceId, float $temperature): void {
Broadcast::channel("device.{$deviceId}.parameters", [
'device_id' => $deviceId,
'temperature' => $temperature,
'timestamp' => now()->toIso8601ZonedDateTimeString(),
]);
}
}63. 质量检验记录
php
class QualityInspectionService {
public function createInspection(QualityInspectionRequest $request): InspectionRecord {
$inspection = InspectionRecord::create([
'order_id' => $request->orderId,
'batch_no' => $request->batchNo,
'inspection_type' => $request->type, // INCOMING/FINAL
'inspector_id' => auth()->id(),
'sample_size' => $request->sampleSize,
'passed_count' => $request->passedCount,
'failed_count' => $request->failedCount,
'defect_types' => $request->defects, // JSON数组
'dimensional_data' => $request->measurements, // 尺寸数据
'result' => $request->passed ? 'PASS' : 'FAIL',
'remark' => $request->remark,
]);
// 不合格品自动触发 NCR 流程
if (!$request->passed) {
NotConformanceReport::create([
'related_inspection_id' => $inspection->id,
'defect_summary' => $this->summarizeDefects($request->defects),
'severity' => $this->classifySeverity($request->failedCount),
]);
}
return $inspection;
}
}64. 追溯码管理
php
class TraceabilityCodeManager {
public function assignTraceCode(string $workOrderNo): string {
// SN 码生成规则:批次年月 + 流水号 + 校验位
$prefix = now()->format('Ymd') . substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 3);
$serial = $this->getNextSerial($workOrderNo, $prefix);
$traceCode = $prefix . str_pad($serial, 6, '0', STR_PAD_LEFT) . $this->checkDigit($prefix.$serial);
TraceCode::create([
'trace_code' => $traceCode,
'work_order_no' => $workOrderNo,
'product_id' => $this->getProductFromWorkOrder($workOrderNo),
'assigned_at' => now(),
'status' => 'ASSIGNED',
]);
return $traceCode;
}
public function traceBack(string $traceCode): TracebackResult {
$code = TraceCode::where('trace_code', $traceCode)->firstOrFail();
// 向上追溯:原材料批次
$materials = $this->getUsedMaterials($code->work_order_no);
// 向下追溯:使用情况
$usage = $this->getUsageRecords($code->product_id);
return new TracebackResult($code, $materials, $usage);
}
}65-70. MES 快速问答
65. 如何防止报工作弊?
GPS 定位 + NFC 打卡 + 人脸识别 + 随机验证码
66. 设备停机时间如何统计?
PLC 信号监控 + 状态轮询 + 异常告警 + 停机原因代码
67. OEE(设备综合效率)计算?
OEE = 可用率 × 表现性 × 质量率
68. 如何实现无纸化车间?
PDA 扫码 + 电子看板 + 移动终端 + 云端同步
69. 良率分析怎么做?
SPC统计过程控制 + Pareto 分析 + 鱼骨图 + 趋势监控
70. 防错(Poka-Yoke)系统设计?
扫码核对 + 工装夹具检测 + 视觉识别 + 逻辑校验
OA 系统集成(71-75)
71. 审批流引擎
php
class ApprovalWorkflowEngine {
public function submitApproval(ApprovalRequest $request): ApprovalNode {
// 获取审批流定义
$workflow = WorkflowDefinition::where('type', $request->type)->first();
// 创建审批实例
$approval = ApprovalNode::create([
'workflow_id' => $workflow->id,
'business_type' => $request->type,
'business_id' => $request->businessId,
'applicant_id' => $request->applicantId,
'current_node' => $workflow->startNodeId,
'status' => 'PENDING',
'data' => $request->payload,
]);
// 自动审批节点
$this->routeToApprovers($approval);
return $approval;
}
private function routeToApprovers(ApprovalNode $approval): void {
$nextNode = $approval->workflow->getNode($approval->current_node);
// 串行审批
foreach ($nextNode->approvers as $approver) {
Notification::send(User::findOrFail($approver->user_id),
new ApprovalPending($approval)
);
}
}
}72. 组织架构同步
php
class OrgStructureSync {
public function syncFromOA(): void {
// 从 OA 系统拉取部门和员工数据
$oaDepartments = $this->oaClient->getDepartments();
foreach ($oaDepartments as $oaDept) {
$dept = Department::updateOrCreate(
['oa_dept_code' => $oaDept->code],
[
'name' => $oaDept->name,
'parent_id' => $this->getParentDeptId($oaDept->parentId),
'manager_id' => $this->getManagerId($oaDept->managerCode),
'level' => $oaDept->level,
'path' => $oaDept->path, // 部门路径
]
);
}
// 同步员工
$oaEmployees = $this->oaClient->getEmployees();
foreach ($oaEmployees as $oaEmp) {
Employee::updateOrCreate(
['oa_employee_code' => $oaEmp->code],
[
'name' => $oaEmp->name,
'department_id' => $oaEmp->departmentId,
'position' => $oaEmp->position,
'email' => $oaEmp->email,
'phone' => $oaEmp->phone,
'status' => $oaEmp->status,
]
);
}
}
}73. 权限账号同步
php
class UserAccountSync {
public function syncUserToOA(User $user): void {
// 创建或更新 OA 账号
$oaResponse = $this->oaClient->upsertEmployee([
'employee_code' => $user->employee_id,
'name' => $user->name,
'email' => $user->email,
'password' => $this->hashPassword($user->password),
'departments' => [$user->department->oa_dept_code],
'roles' => $user->getRoleNames(),
]);
// 记录同步状态
UserOASync::create([
'user_id' => $user->id,
'oa_user_id' => $oaResponse->userId,
'sync_status' => 'SUCCESS',
'synced_at' => now(),
]);
}
}74. 消息互通
php
class MessageIntegration {
public function sendToOANotification(string $title, string $content, array $recipients): void {
$this->oaClient->sendNotification([
'message_type' => 'WORKFLOW',
'title' => $title,
'content' => $content,
'recipients' => $recipients,
'callback_url' => route('oa.message.callback'),
]);
}
public function webhookHandler(Request $request): Response {
// 处理 OA 消息回调
$webhook = $request->json()->all();
if ($webhook['type'] === 'APPROVAL_COMPLETE') {
$this->handleApprovalComplete($webhook);
}
return response()->json(['status' => 'OK']);
}
}75-75. OA 快速问答
75. 如何处理多系统用户账号一致性问题?
统一身份认证(SSO)、LDAP/AD集成、账号中间表、定时对账
UniApp 开发(76-80)
76. 微信小程序与 APP 统一开发
76. 微信小程序开发要点
vue
<!-- pages/work-report/index.vue -->
<template>
<view class="container">
<!-- 表单验证 -->
<form @submit="onSubmit">
<input v-model="formData.workOrderNo" placeholder="工单号" />
<!-- 扫码功能 -->
<button @tap="scanQRCode">扫码报工</button>
<!-- 提交按钮 -->
<button type="primary" formType="submit">提交报工</button>
</form>
<!-- 实时位置 -->
<button @tap="getLocation">当前位置</button>
</view>
</template>
<script>
export default {
data() {
return {
formData: {
workOrderNo: "",
producedQty: 0,
defectQty: 0,
devicePosition: null,
},
};
},
methods: {
async scanQRCode() {
const res = await uni.scanCode({
type: "qr",
resultType: "string",
});
this.formData.workOrderNo = res.result;
},
getLocation() {
uni.getLocation({
type: "gcj02",
success: (res) => {
this.formData.devicePosition = {
latitude: res.latitude,
longitude: res.longitude,
};
},
});
},
onSubmit(e) {
const { detail } = e.detail;
// 防重复提交
if (this.submitting) return;
this.submitting = true;
wx.request({
url: "/api/production/report",
method: "POST",
data: this.formData,
success: (res) => {
if (res.data.code === 0) {
uni.showToast({ title: "报工成功" });
setTimeout(() => wx.navigateBack(), 1500);
}
},
fail: (err) => {
uni.showToast({ title: "提交失败", icon: "none" });
},
complete: () => {
this.submitting = false;
},
});
},
},
};
</script>77. APP跨平台开发策略
javascript
// manifest.json 配置
{
"name": "工厂助手",
"appid": "__UNI__FactoryApp",
"description": "工厂生产管理系统",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvue": true, // 支持原生 UI
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true
},
"orientation": ["portrait"],
"modules": {
"Barcode": {}, // 条码扫描
"Location": {}, // GPS定位
"Push": {} // 推送通知
}
}
}
// 混合开发:NVue 页面
// pages/scanner.nvue
<template>
<view class="scanner">
<barcode v-model="scanResult" @scan="onScan"></barcode>
<text>{{ scanResult }}</text>
</view>
</template>
<script>
export default {
data() {
return {
scanResult: ''
}
},
methods: {
onScan(e) {
console.log('扫描结果:', e.content);
this.scanResult = e.content;
// 自动提交
this.submitScanResult(e.content);
}
}
}
</script>78. 离线数据存储
javascript
// 使用 SQLite 本地缓存
import db from "@/common/sqlite.js";
class OfflineStorage {
constructor() {
this.initDB();
}
async initDB() {
await db.open({ name: "factory-db" });
// 创建表
await db.exec(`
CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_order_no TEXT,
produced_qty INTEGER,
status TEXT,
created_at DATETIME
)
`);
}
async saveReportLocally(report) {
// 离线保存报工数据
await db.run(
"INSERT INTO reports (work_order_no, produced_qty, status, created_at) VALUES (?, ?, ?, ?)",
[report.workOrderNo, report.producedQty, "PENDING_SYNC", new Date()],
);
uni.showToast({ title: "已离线保存" });
}
async getOfflineReports() {
return db.select("SELECT * FROM reports WHERE status = ?", [
"PENDING_SYNC",
]);
}
async syncOfflineData() {
const reports = await this.getOfflineReports();
for (const report of reports) {
try {
await api.submitReport(report);
// 同步成功后更新状态
await db.run("UPDATE reports SET status = ? WHERE id = ?", [
"SYNCED",
report.id,
]);
} catch (error) {
console.error("同步失败:", error);
}
}
}
}79. 离线数据存储方案
javascript
// 懒加载列表
<scroll-view scroll-y @scrolltolower="loadMore" :style="{ height: windowHeight }">
<view v-for="item in list" :key="item.id">{{ item.name }}</view>
</scroll-view>
<script>
export default {
data() {
return {
list: [],
page: 1,
loading: false
}
},
onLoad() {
this.loadData();
},
onReachBottom() {
this.loadMore();
},
methods: {
async loadData() {
if (this.loading) return;
this.loading = true;
const res = await api.getReports({
page: this.page,
limit: 20
});
this.list.push(...res.data);
this.page++;
this.loading = false;
},
loadMore() {
this.loadData();
}
}
}
</script>80. 原生模块调用
javascript
// 调用原生蓝牙打印小票打印机
async function printWorkOrder Slip(workOrderNo) {
const scannerModule = plus.os.modules('com.factory.barcode.Scanner');
const barcode = await scannerModule.scan();
// 打印标签
const bluetoothModule = plus.os.modules('com.factory.bluetooth.Printer');
await bluetoothModule.connect(PRINTER_ADDRESS);
const content = \`
工单号:${workOrderNo}
产品:${productName}
数量:${quantity}
\`;
await bluetoothModule.print(content);
await bluetoothModule.disconnect();
}附录:典型集成架构图
提示
本文档覆盖了系统集成面试的核心理论与实战案例,重点展示了 Laravel 在企业级应用开发中的集成能力。
