项目十:事件溯源仓储管理系统(WMS)仿真实现
基于Event Sourcing与CQRS的库存精准管理系统完整仿真
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
================================================================================
项目十:事件溯源仓储管理系统(WMS)仿真
基于Event Sourcing与CQRS的库存精准管理系统
================================================================================
作者:AI Assistant
日期:2026-05-27
说明:本程序为教学仿真,使用内存存储模拟完整的事件溯源架构
================================================================================
"""
import json
import hashlib
import time
import random
import threading
import uuid
from datetime import datetime, timedelta
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Optional, Callable, Any, Set
from enum import Enum, auto
from collections import defaultdict
import copy
# ==============================================================================
# 10.2.3 全局排序:Snowflake ID 实现
# ==============================================================================
class SnowflakeGenerator:
"""Snowflake ID生成器:保证全局唯一且有序"""
def __init__(self, datacenter_id: int = 0, worker_id: int = 0):
self.datacenter_id = datacenter_id
self.worker_id = worker_id
self.sequence = 0
self.last_timestamp = -1
self.twepoch = 1609459200000
self.datacenter_id_bits = 5
self.worker_id_bits = 5
self.sequence_bits = 12
self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
self.worker_id_shift = self.sequence_bits
self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
self.lock = threading.Lock()
def _current_timestamp(self) -> int:
return int(time.time() * 1000)
def _til_next_millis(self, last_timestamp: int) -> int:
timestamp = self._current_timestamp()
while timestamp <= last_timestamp:
timestamp = self._current_timestamp()
return timestamp
def next_id(self) -> int:
with self.lock:
timestamp = self._current_timestamp()
if timestamp < self.last_timestamp:
raise Exception("时钟回拨异常")
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & self.sequence_mask
if self.sequence == 0:
timestamp = self._til_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
return ((timestamp - self.twepoch) << self.timestamp_left_shift) | \
(self.datacenter_id << self.datacenter_id_shift) | \
(self.worker_id << self.worker_id_shift) | self.sequence
snowflake = SnowflakeGenerator()
# ==============================================================================
# 10.1.1 库存事件设计:领域事件定义
# ==============================================================================
class EventType(Enum):
"""事件类型枚举"""
STOCK_RECEIVED = "StockReceived"
STOCK_MOVED = "StockMoved"
STOCK_ALLOCATED = "StockAllocated"
STOCK_SHIPPED = "StockShipped"
STOCK_ADJUSTED = "StockAdjusted"
@dataclass
class DomainEvent:
"""领域事件基类"""
event_id: int
event_type: EventType
aggregate_id: str
aggregate_version: int
timestamp: datetime
payload: Dict[str, Any]
previous_hash: str = ""
event_hash: str = ""
def compute_hash(self) -> str:
"""计算事件哈希:实现10.5.1不可篡改日志"""
data = {
"event_id": self.event_id,
"event_type": self.event_type.value,
"aggregate_id": self.aggregate_id,
"aggregate_version": self.aggregate_version,
"timestamp": self.timestamp.isoformat(),
"payload": self.payload,
"previous_hash": self.previous_hash
}
return hashlib.sha256(json.dumps(data, sort_keys=True, ensure_ascii=False).encode()).hexdigest()
def finalize(self):
"""最终化事件:计算哈希"""
self.event_hash = self.compute_hash()
# ==============================================================================
# 10.2.2 事件序列化:Protobuf模拟(使用JSON+Schema版本管理)
# ==============================================================================
class EventSerializer:
"""事件序列化器:模拟Protobuf二进制格式与Schema版本管理"""
SCHEMA_VERSION = "1.0.0"
@staticmethod
def serialize(event: DomainEvent) -> bytes:
"""序列化事件为二进制(模拟Protobuf)"""
data = {
"schema_version": EventSerializer.SCHEMA_VERSION,
"event_id": event.event_id,
"event_type": event.event_type.value,
"aggregate_id": event.aggregate_id,
"aggregate_version": event.aggregate_version,
"timestamp": event.timestamp.isoformat(),
"payload": event.payload,
"previous_hash": event.previous_hash,
"event_hash": event.event_hash
}
return json.dumps(data, ensure_ascii=False).encode("utf-8")
@staticmethod
def deserialize(data: bytes) -> DomainEvent:
"""反序列化事件"""
obj = json.loads(data.decode("utf-8"))
event = DomainEvent(
event_id=obj["event_id"],
event_type=EventType(obj["event_type"]),
aggregate_id=obj["aggregate_id"],
aggregate_version=obj["aggregate_version"],
timestamp=datetime.fromisoformat(obj["timestamp"]),
payload=obj["payload"],
previous_hash=obj["previous_hash"],
event_hash=obj["event_hash"]
)
return event
# ==============================================================================
# 10.2.1 事件存储:内存事件存储引擎(模拟PostgreSQL JSONB)
# 10.2.4 归档策略:历史事件冷存储与压缩
# ==============================================================================
class EventStore:
"""事件存储引擎:追加写模型与版本号并发控制"""
def __init__(self):
self._streams: Dict[str, List[DomainEvent]] = defaultdict(list)
self._global_events: Dict[int, DomainEvent] = {}
self._processed_ids: Set[int] = set()
self._archived: Dict[str, List[DomainEvent]] = defaultdict(list)
self._archive_threshold = 50
self._lock = threading.RLock()
self._last_hashes: Dict[str, str] = {}
def append(self, event: DomainEvent) -> bool:
"""追加事件:实现乐观并发控制"""
with self._lock:
# 10.4.2 幂等性保障
if event.event_id in self._processed_ids:
print(f" [幂等性拦截] 事件 {event.event_id} 已存在,跳过处理")
return False
stream = self._streams[event.aggregate_id]
# 10.4.1 乐观并发控制
expected_version = len(stream)
if event.aggregate_version != expected_version + 1:
raise ConcurrentModificationException(
f"并发冲突:聚合根 {event.aggregate_id} 期望版本 {expected_version + 1},"
f"实际版本 {event.aggregate_version}"
)
# 10.5.1 不可篡改日志:区块链式事件链
event.previous_hash = self._last_hashes.get(event.aggregate_id, "0" * 64)
event.finalize()
self._last_hashes[event.aggregate_id] = event.event_hash
stream.append(event)
self._global_events[event.event_id] = event
self._processed_ids.add(event.event_id)
# 10.2.4 归档策略检查
self._check_archive(event.aggregate_id)
return True
def _check_archive(self, aggregate_id: str):
"""检查并执行归档"""
stream = self._streams[aggregate_id]
if len(stream) > self._archive_threshold:
archive_count = len(stream) // 2
archived_events = stream[:archive_count]
self._archived[aggregate_id].extend(archived_events)
self._streams[aggregate_id] = stream[archive_count:]
print(f" [归档] 聚合根 {aggregate_id} 的 {archive_count} 个事件已归档(冷存储)")
def get_events(self, aggregate_id: str, from_version: int = 0) -> List[DomainEvent]:
"""获取聚合根事件流"""
with self._lock:
archived = self._archived.get(aggregate_id, [])
active = self._streams.get(aggregate_id, [])
all_events = archived + active
return all_events[from_version:]
def get_all_events(self) -> List[DomainEvent]:
"""获取所有事件(按全局顺序)"""
with self._lock:
return sorted(self._global_events.values(), key=lambda e: e.event_id)
def get_event_by_id(self, event_id: int) -> Optional[DomainEvent]:
return self._global_events.get(event_id)
def verify_chain(self, aggregate_id: str) -> bool:
"""验证事件链完整性"""
events = self.get_events(aggregate_id)
for i, event in enumerate(events):
if i == 0:
continue
if event.previous_hash != events[i-1].event_hash:
return False
if event.compute_hash() != event.event_hash:
return False
return True
class ConcurrentModificationException(Exception):
"""并发修改异常"""
pass
# ==============================================================================
# 10.1.2 聚合根实现:ProductAggregate与LocationAggregate
# 10.1.3 快照机制
# ==============================================================================
class ProductAggregate:
"""商品聚合根:管理单个SKU的库存状态"""
def __init__(self, sku: str):
self.sku = sku
self.version = 0
self.total_quantity = 0
self.available_quantity = 0
self.allocated_quantity = 0
self.locations: Dict[str, int] = {}
self.pending_events: List[DomainEvent] = []
self._snapshot_version = 0
def apply(self, event: DomainEvent):
"""应用事件到聚合根状态"""
if event.event_type == EventType.STOCK_RECEIVED:
self._on_stock_received(event)
elif event.event_type == EventType.STOCK_MOVED:
self._on_stock_moved(event)
elif event.event_type == EventType.STOCK_ALLOCATED:
self._on_stock_allocated(event)
elif event.event_type == EventType.STOCK_SHIPPED:
self._on_stock_shipped(event)
elif event.event_type == EventType.STOCK_ADJUSTED:
self._on_stock_adjusted(event)
self.version = event.aggregate_version
def _on_stock_received(self, event: DomainEvent):
qty = event.payload["quantity"]
location = event.payload["location"]
self.total_quantity += qty
self.available_quantity += qty
self.locations[location] = self.locations.get(location, 0) + qty
def _on_stock_moved(self, event: DomainEvent):
qty = event.payload["quantity"]
from_loc = event.payload["from_location"]
to_loc = event.payload["to_location"]
self.locations[from_loc] -= qty
self.locations[to_loc] = self.locations.get(to_loc, 0) + qty
if self.locations[from_loc] == 0:
del self.locations[from_loc]
def _on_stock_allocated(self, event: DomainEvent):
qty = event.payload["quantity"]
self.available_quantity -= qty
self.allocated_quantity += qty
def _on_stock_shipped(self, event: DomainEvent):
qty = event.payload["quantity"]
location = event.payload["location"]
self.total_quantity -= qty
self.allocated_quantity -= qty
self.locations[location] -= qty
if self.locations[location] == 0:
del self.locations[location]
def _on_stock_adjusted(self, event: DomainEvent):
diff = event.payload["difference"]
location = event.payload["location"]
self.total_quantity += diff
self.available_quantity += diff
self.locations[location] = self.locations.get(location, 0) + diff
def receive_stock(self, quantity: int, location: str, batch_no: str) -> DomainEvent:
"""入库命令"""
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_RECEIVED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "location": location, "batch_no": batch_no, "operator": "操作员A"}
)
self.apply(event)
self.pending_events.append(event)
return event
def move_stock(self, quantity: int, from_location: str, to_location: str) -> DomainEvent:
"""移库命令"""
if self.locations.get(from_location, 0) < quantity:
raise ValueError(f"库位 {from_location} 库存不足")
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_MOVED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "from_location": from_location, "to_location": to_location, "operator": "叉车工B"}
)
self.apply(event)
self.pending_events.append(event)
return event
def allocate_stock(self, quantity: int, order_id: str) -> DomainEvent:
"""分配命令"""
if self.available_quantity < quantity:
raise ValueError(f"可用库存不足:需要 {quantity},可用 {self.available_quantity}")
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_ALLOCATED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "order_id": order_id, "operator": "系统"}
)
self.apply(event)
self.pending_events.append(event)
return event
def ship_stock(self, quantity: int, location: str, order_id: str) -> DomainEvent:
"""出库命令"""
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_SHIPPED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "location": location, "order_id": order_id, "operator": "发货员C"}
)
self.apply(event)
self.pending_events.append(event)
return event
def adjust_stock(self, location: str, physical_qty: int) -> DomainEvent:
"""盘点调整命令"""
system_qty = self.locations.get(location, 0)
diff = physical_qty - system_qty
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_ADJUSTED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"location": location, "system_quantity": system_qty, "physical_quantity": physical_qty, "difference": diff, "operator": "盘点员D"}
)
self.apply(event)
self.pending_events.append(event)
return event
def create_snapshot(self) -> Dict:
"""创建快照"""
return {
"sku": self.sku,
"version": self.version,
"total_quantity": self.total_quantity,
"available_quantity": self.available_quantity,
"allocated_quantity": self.allocated_quantity,
"locations": copy.deepcopy(self.locations),
"timestamp": datetime.now().isoformat()
}
@classmethod
def restore_from_snapshot(cls, snapshot: Dict, events: List[DomainEvent]) -> "ProductAggregate":
"""从快照恢复聚合根"""
aggregate = cls(snapshot["sku"])
aggregate.version = snapshot["version"]
aggregate.total_quantity = snapshot["total_quantity"]
aggregate.available_quantity = snapshot["available_quantity"]
aggregate.allocated_quantity = snapshot["allocated_quantity"]
aggregate.locations = copy.deepcopy(snapshot["locations"])
aggregate._snapshot_version = snapshot["version"]
for event in events:
aggregate.apply(event)
return aggregate
class LocationAggregate:
"""库位聚合根"""
def __init__(self, location_code: str):
self.location_code = location_code
self.version = 0
self.items: Dict[str, int] = {}
self.capacity = 1000
self.pending_events: List[DomainEvent] = []
def apply(self, event: DomainEvent):
if event.event_type == EventType.STOCK_RECEIVED:
sku = event.aggregate_id
qty = event.payload["quantity"]
self.items[sku] = self.items.get(sku, 0) + qty
elif event.event_type == EventType.STOCK_MOVED:
sku = event.aggregate_id
qty = event.payload["quantity"]
if event.payload["from_location"] == self.location_code:
self.items[sku] -= qty
if self.items[sku] == 0:
del self.items[sku]
elif event.payload["to_location"] == self.location_code:
self.items[sku] = self.items.get(sku, 0) + qty
elif event.event_type == EventType.STOCK_SHIPPED:
sku = event.aggregate_id
qty = event.payload["quantity"]
self.items[sku] -= qty
if self.items[sku] == 0:
del self.items[sku]
self.version = event.aggregate_version
def get_utilization(self) -> float:
total = sum(self.items.values())
return total / self.capacity * 100
# ==============================================================================
# 10.4.3 分布式锁:Redis Redlock模拟
# ==============================================================================
class DistributedLock:
"""分布式锁模拟(基于线程锁模拟Redis Redlock)"""
def __init__(self):
self._locks: Dict[str, threading.Lock] = defaultdict(threading.Lock)
self._holders: Dict[str, str] = {}
def acquire(self, resource: str, identifier: str, timeout: float = 10.0) -> bool:
lock = self._locks[resource]
acquired = lock.acquire(timeout=timeout)
if acquired:
self._holders[resource] = identifier
return acquired
def release(self, resource: str, identifier: str) -> bool:
if self._holders.get(resource) == identifier:
del self._holders[resource]
try:
self._locks[resource].release()
return True
except RuntimeError:
return False
return False
# ==============================================================================
# 10.4.4 Saga事务:跨聚合事务协调
# ==============================================================================
class SagaState(Enum):
STARTED = auto()
COMPENSATING = auto()
COMPLETED = auto()
FAILED = auto()
@dataclass
class SagaStep:
name: str
action: Callable
compensation: Callable
executed: bool = False
class SagaOrchestrator:
"""Saga编排器"""
def __init__(self):
self.sagas: Dict[str, Dict] = {}
def start_saga(self, saga_id: str, steps: List[SagaStep]) -> bool:
print(f"\n[启动Saga] {saga_id}")
saga = {"id": saga_id, "state": SagaState.STARTED, "steps": steps, "current_step": 0}
self.sagas[saga_id] = saga
try:
for i, step in enumerate(steps):
saga["current_step"] = i
print(f" [步骤 {i+1}/{len(steps)}] {step.name}")
step.action()
step.executed = True
print(f" [完成] {step.name}")
saga["state"] = SagaState.COMPLETED
print(f"[Saga完成] {saga_id}")
return True
except Exception as e:
print(f" [失败] {e}")
saga["state"] = SagaState.COMPENSATING
self._compensate(saga)
saga["state"] = SagaState.FAILED
print(f"[Saga回滚] {saga_id}")
return False
def _compensate(self, saga: Dict):
print(" [补偿] 开始执行补偿操作...")
for i in range(saga["current_step"], -1, -1):
step = saga["steps"][i]
if step.executed:
print(f" [补偿步骤] {step.name}")
try:
step.compensation()
except Exception as e:
print(f" [补偿失败] {e}")
# ==============================================================================
# 10.3.1 投影处理器:异步物化视图构建
# ==============================================================================
class ProjectionHandler:
"""投影处理器"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
self.projections: Dict[str, Any] = {}
self.last_processed_id: Optional[int] = None
self.processing_delay_ms: List[float] = []
def build_current_stock_projection(self) -> Dict[str, Dict]:
start_time = time.time()
stock_view: Dict[str, Dict] = {}
events = self.event_store.get_all_events()
for event in events:
sku = event.aggregate_id
if sku not in stock_view:
stock_view[sku] = {"sku": sku, "total": 0, "available": 0, "allocated": 0, "locations": defaultdict(int)}
if event.event_type == EventType.STOCK_RECEIVED:
stock_view[sku]["total"] += event.payload["quantity"]
stock_view[sku]["available"] += event.payload["quantity"]
stock_view[sku]["locations"][event.payload["location"]] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_ALLOCATED:
stock_view[sku]["available"] -= event.payload["quantity"]
stock_view[sku]["allocated"] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_SHIPPED:
stock_view[sku]["total"] -= event.payload["quantity"]
stock_view[sku]["allocated"] -= event.payload["quantity"]
stock_view[sku]["locations"][event.payload["location"]] -= event.payload["quantity"]
elif event.event_type == EventType.STOCK_MOVED:
stock_view[sku]["locations"][event.payload["from_location"]] -= event.payload["quantity"]
stock_view[sku]["locations"][event.payload["to_location"]] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_ADJUSTED:
diff = event.payload["difference"]
stock_view[sku]["total"] += diff
stock_view[sku]["available"] += diff
stock_view[sku]["locations"][event.payload["location"]] += diff
delay = (time.time() - start_time) * 1000
self.processing_delay_ms.append(delay)
self.projections["current_stock"] = stock_view
return stock_view
def get_projection_delay_stats(self) -> Dict:
if not self.processing_delay_ms:
return {}
return {
"count": len(self.processing_delay_ms),
"avg_ms": sum(self.processing_delay_ms) / len(self.processing_delay_ms),
"max_ms": max(self.processing_delay_ms),
"min_ms": min(self.processing_delay_ms)
}
# ==============================================================================
# 10.3.3 历史追溯:任意时间点库存状态Time Travel查询
# ==============================================================================
class TimeTravelQuery:
"""时间旅行查询"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
def query_stock_at_time(self, sku: str, target_time: datetime) -> Dict:
events = self.event_store.get_events(sku)
filtered_events = [e for e in events if e.timestamp <= target_time]
aggregate = ProductAggregate(sku)
for event in filtered_events:
aggregate.apply(event)
return {
"sku": sku,
"query_time": target_time.isoformat(),
"based_on_events": len(filtered_events),
"total_quantity": aggregate.total_quantity,
"available_quantity": aggregate.available_quantity,
"allocated_quantity": aggregate.allocated_quantity,
"locations": dict(aggregate.locations)
}
# ==============================================================================
# 10.5.2 操作审计:用户行为全记录与合规报告
# ==============================================================================
class AuditLogger:
"""审计日志器"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
self.audit_entries: List[Dict] = []
def log_operation(self, operation: str, user: str, details: Dict):
entry = {
"timestamp": datetime.now().isoformat(),
"operation": operation,
"user": user,
"details": details,
"trace_id": str(uuid.uuid4())
}
self.audit_entries.append(entry)
def generate_compliance_report(self, start_time: datetime, end_time: datetime) -> Dict:
events = self.event_store.get_all_events()
filtered = [e for e in events if start_time <= e.timestamp <= end_time]
report = {
"report_period": f"{start_time.isoformat()} 至 {end_time.isoformat()}",
"total_events": len(filtered),
"event_breakdown": defaultdict(int),
"operator_activity": defaultdict(int),
"chain_integrity_verified": True
}
for event in filtered:
report["event_breakdown"][event.event_type.value] += 1
report["operator_activity"][event.payload.get("operator", "unknown")] += 1
all_skus = set(e.aggregate_id for e in filtered)
for sku in all_skus:
if not self.event_store.verify_chain(sku):
report["chain_integrity_verified"] = False
break
return dict(report)
# ==============================================================================
# 10.5.3 差异分析:物理盘点与系统数据差异报告
# ==============================================================================
class InventoryReconciliation:
"""库存对账与差异分析"""
def __init__(self, projection_handler: ProjectionHandler):
self.projection_handler = projection_handler
def perform_reconciliation(self, physical_counts: Dict[str, Dict[str, int]]) -> Dict:
stock_view = self.projection_handler.build_current_stock_projection()
discrepancies = []
total_skus = 0
matching_skus = 0
for sku, locations in physical_counts.items():
total_skus += 1
system_data = stock_view.get(sku, {"locations": {}})
for location, physical_qty in locations.items():
system_qty = system_data["locations"].get(location, 0)
diff = physical_qty - system_qty
if diff != 0:
discrepancies.append({
"sku": sku, "location": location,
"system_quantity": system_qty, "physical_quantity": physical_qty,
"difference": diff, "status": "盘盈" if diff > 0 else "盘亏"
})
else:
matching_skus += 1
return {
"total_skus_checked": total_skus,
"matching_skus": matching_skus,
"discrepancy_count": len(discrepancies),
"discrepancies": discrepancies,
"accuracy_rate": (matching_skus / total_skus * 100) if total_skus > 0 else 0
}
# ==============================================================================
# 仓储管理系统主类
# ==============================================================================
class EventSourcingWMS:
"""事件溯源仓储管理系统主类"""
def __init__(self):
print("=" * 80)
print("事件溯源仓储管理系统(WMS)初始化中...")
print("=" * 80)
self.event_store = EventStore()
self.projection_handler = ProjectionHandler(self.event_store)
self.time_travel = TimeTravelQuery(self.event_store)
self.audit_logger = AuditLogger(self.event_store)
self.reconciliation = InventoryReconciliation(self.projection_handler)
self.lock_manager = DistributedLock()
self.saga_orchestrator = SagaOrchestrator()
self.aggregates: Dict[str, ProductAggregate] = {}
self.location_aggregates: Dict[str, LocationAggregate] = {}
self.snapshots: Dict[str, Dict] = {}
print("系统初始化完成\n")
def get_or_create_aggregate(self, sku: str) -> ProductAggregate:
if sku not in self.aggregates:
if sku in self.snapshots:
events = self.event_store.get_events(sku, self.snapshots[sku]["version"])
self.aggregates[sku] = ProductAggregate.restore_from_snapshot(self.snapshots[sku], events)
print(f" [快照恢复] 聚合根 {sku} 从快照恢复(版本 {self.snapshots[sku]['version']})")
else:
self.aggregates[sku] = ProductAggregate(sku)
return self.aggregates[sku]
def get_or_create_location(self, location_code: str) -> LocationAggregate:
if location_code not in self.location_aggregates:
self.location_aggregates[location_code] = LocationAggregate(location_code)
return self.location_aggregates[location_code]
def save_aggregate(self, aggregate: ProductAggregate):
for event in aggregate.pending_events:
self.event_store.append(event)
if event.event_type in [EventType.STOCK_RECEIVED, EventType.STOCK_MOVED, EventType.STOCK_SHIPPED]:
if "location" in event.payload:
loc = self.get_or_create_location(event.payload["location"])
loc.apply(event)
if event.event_type == EventType.STOCK_MOVED:
from_loc = self.get_or_create_location(event.payload["from_location"])
to_loc = self.get_or_create_location(event.payload["to_location"])
from_loc.apply(event)
to_loc.apply(event)
aggregate.pending_events.clear()
if aggregate.version > 0 and aggregate.version % 10 == 0:
self.snapshots[aggregate.sku] = aggregate.create_snapshot()
print(f" [快照创建] 聚合根 {aggregate.sku} 快照已创建(版本 {aggregate.version})")
def receive_stock(self, sku: str, quantity: int, location: str, batch_no: str):
print(f"\n[入库] SKU={sku}, 数量={quantity}, 库位={location}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(quantity, location, batch_no)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("入库", "操作员A", {"sku": sku, "qty": quantity})
print(f" [完成] {sku} 当前库存 {aggregate.total_quantity}")
return event
def move_stock(self, sku: str, quantity: int, from_location: str, to_location: str):
print(f"\n[移库] SKU={sku}, 数量={quantity}, {from_location} -> {to_location}")
lock_id = f"move_{sku}_{from_location}"
identifier = str(uuid.uuid4())
if not self.lock_manager.acquire(lock_id, identifier, timeout=5.0):
raise Exception("获取分布式锁失败,移库操作被拒绝")
try:
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.move_stock(quantity, from_location, to_location)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("移库", "叉车工B", {"sku": sku, "qty": quantity, "from": from_location, "to": to_location})
print(f" [完成] {sku} 从 {from_location} 移至 {to_location}")
return event
finally:
self.lock_manager.release(lock_id, identifier)
def allocate_stock(self, sku: str, quantity: int, order_id: str):
print(f"\n[分配] SKU={sku}, 数量={quantity}, 订单={order_id}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.allocate_stock(quantity, order_id)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("分配", "系统", {"sku": sku, "qty": quantity, "order": order_id})
print(f" [完成] {sku} 已分配 {aggregate.allocated_quantity}")
return event
def ship_stock(self, sku: str, quantity: int, location: str, order_id: str):
print(f"\n[出库] SKU={sku}, 数量={quantity}, 订单={order_id}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.ship_stock(quantity, location, order_id)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("出库", "发货员C", {"sku": sku, "qty": quantity, "order": order_id})
print(f" [完成] {sku} 剩余库存 {aggregate.total_quantity}")
return event
def adjust_stock(self, sku: str, location: str, physical_qty: int):
print(f"\n[盘点] SKU={sku}, 库位={location}, 实盘={physical_qty}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.adjust_stock(location, physical_qty)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("盘点调整", "盘点员D", {"sku": sku, "location": location, "physical": physical_qty})
print(f" [完成] {sku} 差异 {event.payload['difference']}")
return event
def run_saga_inbound_allocate(self, sku: str, quantity: int, location: str, order_id: str):
print(f"\n[Saga] 启动入库-分配流程: SKU={sku}, 数量={quantity}")
saga_id = f"saga_{snowflake.next_id()}"
aggregate = self.get_or_create_aggregate(sku)
steps = [
SagaStep(name="入库", action=lambda: self._saga_receive(sku, quantity, location, f"BATCH_{saga_id}"),
compensation=lambda: print(" [补偿] 取消入库记录")),
SagaStep(name="分配", action=lambda: self._saga_allocate(sku, quantity, order_id),
compensation=lambda: print(" [补偿] 释放分配库存"))
]
return self.saga_orchestrator.start_saga(saga_id, steps)
def _saga_receive(self, sku, qty, loc, batch):
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(qty, loc, batch)
self.save_aggregate(aggregate)
def _saga_allocate(self, sku, qty, order):
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.allocate_stock(qty, order)
self.save_aggregate(aggregate)
def query_current_stock(self) -> Dict:
print("\n[投影] 构建当前库存物化视图...")
return self.projection_handler.build_current_stock_projection()
def time_travel_query(self, sku: str, target_time: datetime) -> Dict:
print(f"\n[TimeTravel] SKU={sku}, 时间点={target_time.isoformat()}")
return self.time_travel.query_stock_at_time(sku, target_time)
def replay_events(self, sku: str):
print(f"\n[回放] 事件回放: SKU={sku}")
events = self.event_store.get_events(sku)
print(f" 共 {len(events)} 个事件,开始重放...")
temp_aggregate = ProductAggregate(sku)
for i, event in enumerate(events, 1):
print(f" [{i}/{len(events)}] {event.event_type.value} (v{event.aggregate_version}) - {event.timestamp.strftime('%H:%M:%S')}")
temp_aggregate.apply(event)
print(f" [回放完成] 总库存={temp_aggregate.total_quantity}, 可用={temp_aggregate.available_quantity}, 已分配={temp_aggregate.allocated_quantity}")
return temp_aggregate
def generate_audit_report(self, hours: int = 24) -> Dict:
print(f"\n[审计] 生成过去{hours}小时合规审计报告...")
end_time = datetime.now()
start_time = end_time - timedelta(hours=hours)
return self.audit_logger.generate_compliance_report(start_time, end_time)
def perform_physical_count(self, physical_counts: Dict[str, Dict[str, int]]) -> Dict:
print("\n[对账] 执行物理盘点与系统数据差异分析...")
return self.reconciliation.perform_reconciliation(physical_counts)
def demonstrate_concurrency_control(self):
print("\n[并发控制演示] 乐观并发控制(版本号冲突检测)...")
sku = "SKU-TEST-CONCURRENT"
aggregate = self.get_or_create_aggregate(sku)
aggregate.receive_stock(100, "A01", "BATCH_INIT")
self.save_aggregate(aggregate)
# 模拟两个并发操作:从事件流重建两个独立的聚合根实例
agg1 = ProductAggregate(sku)
for ev in self.event_store.get_events(sku):
agg1.apply(ev)
agg2 = ProductAggregate(sku)
for ev in self.event_store.get_events(sku):
agg2.apply(ev)
# 操作1:移库10个并保存
event1 = agg1.move_stock(10, "A01", "A02")
self.save_aggregate(agg1)
# 操作2:分配20个(基于相同初始版本,应因版本冲突失败)
try:
event2 = agg2.allocate_stock(20, "ORDER_001")
self.save_aggregate(agg2)
except ConcurrentModificationException as e:
print(f" [检测成功] 并发冲突被正确检测: {e}")
print(" [解决方案] 重试策略 - 重新加载聚合根后重试操作")
def demonstrate_idempotency(self):
print("\n[幂等性演示] 基于事件ID去重...")
sku = "SKU-TEST-IDEMPOTENT"
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(50, "B01", "BATCH_001")
self.save_aggregate(aggregate)
print(f" 尝试重复提交事件 ID={event.event_id}...")
result = self.event_store.append(event)
if not result:
print(" [拦截成功] 幂等性保障:重复事件被拒绝")
def print_event_store_stats(self):
print("\n[统计] 事件存储统计信息:")
all_events = self.event_store.get_all_events()
print(f" 全局事件总数: {len(all_events)}")
print(f" 聚合根数量: {len(self.aggregates)}")
event_types = defaultdict(int)
for e in all_events:
event_types[e.event_type.value] += 1
print(" 事件类型分布:")
for et, count in sorted(event_types.items()):
print(f" - {et}: {count}")
delay_stats = self.projection_handler.get_projection_delay_stats()
if delay_stats:
print(f" 投影处理延迟: 平均={delay_stats['avg_ms']:.2f}ms, 最大={delay_stats['max_ms']:.2f}ms")
def print_separator(self, title: str):
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80)
# ==============================================================================
# 主程序:完整仿真流程
# ==============================================================================
def main():
wms = EventSourcingWMS()
# 阶段一:基础业务操作
wms.print_separator("阶段一:基础业务操作演示(10.1 领域建模)")
wms.receive_stock("SKU-001", 1000, "A01-01", "BATCH-20260527-001")
wms.receive_stock("SKU-001", 500, "A01-02", "BATCH-20260527-002")
wms.receive_stock("SKU-002", 800, "B02-01", "BATCH-20260527-003")
wms.move_stock("SKU-001", 200, "A01-01", "A01-03")
wms.allocate_stock("SKU-001", 300, "ORDER-20260527-001")
wms.allocate_stock("SKU-002", 150, "ORDER-20260527-002")
wms.ship_stock("SKU-001", 100, "A01-01", "ORDER-20260527-001")
# 阶段二:Saga事务
wms.print_separator("阶段二:Saga跨聚合事务协调(10.4.4)")
wms.run_saga_inbound_allocate("SKU-003", 500, "C03-01", "ORDER-SAGA-001")
# 阶段三:并发控制与幂等性
wms.print_separator("阶段三:并发控制与幂等性(10.4.1 / 10.4.2)")
wms.demonstrate_concurrency_control()
wms.demonstrate_idempotency()
# 阶段四:投影与查询
wms.print_separator("阶段四:投影与查询(10.3 投影与查询)")
stock_view = wms.query_current_stock()
print("\n[当前库存视图]")
for sku, data in stock_view.items():
print(f" SKU: {sku}")
print(f" 总库存: {data['total']}")
print(f" 可用库存: {data['available']}")
print(f" 已分配: {data['allocated']}")
print(f" 库位分布: {dict(data['locations'])}")
past_time = datetime.now() - timedelta(minutes=5)
travel_result = wms.time_travel_query("SKU-001", past_time)
print(f"\n[TimeTravel结果]")
print(f" 查询时间: {travel_result['query_time']}")
print(f" 基于事件数: {travel_result['based_on_events']}")
print(f" 当时总库存: {travel_result['total_quantity']}")
print(f" 当时可用库存: {travel_result['available_quantity']}")
wms.replay_events("SKU-001")
# 阶段五:盘点与差异分析
wms.print_separator("阶段五:盘点与差异分析(10.5.3)")
physical_counts = {
"SKU-001": {"A01-01": 700, "A01-02": 500, "A01-03": 180},
"SKU-002": {"B02-01": 850}
}
reconciliation = wms.perform_physical_count(physical_counts)
print(f"\n[盘点对账结果]")
print(f" 检查SKU数: {reconciliation['total_skus_checked']}")
print(f" 匹配数: {reconciliation['matching_skus']}")
print(f" 差异数: {reconciliation['discrepancy_count']}")
print(f" 准确率: {reconciliation['accuracy_rate']:.1f}%")
if reconciliation['discrepancies']:
print(f"\n 差异明细:")
for disc in reconciliation['discrepancies']:
print(f" [差异] {disc['sku']} @ {disc['location']}: 系统={disc['system_quantity']}, 实盘={disc['physical_quantity']}, 差异={disc['difference']} ({disc['status']})")
# 阶段六:审计与合规
wms.print_separator("阶段六:审计与合规(10.5.1 / 10.5.2)")
audit_report = wms.generate_audit_report(hours=1)
print(f"\n[合规审计报告]")
print(f" 报告周期: {audit_report['report_period']}")
print(f" 事件总数: {audit_report['total_events']}")
print(f" 事件分布: {dict(audit_report['event_breakdown'])}")
print(f" 操作员活动: {dict(audit_report['operator_activity'])}")
print(f" 链完整性验证: {'通过' if audit_report['chain_integrity_verified'] else '失败'}")
print(f"\n[区块链式事件链验证]")
for sku in wms.aggregates.keys():
valid = wms.event_store.verify_chain(sku)
print(f" {sku}: {'哈希链完整' if valid else '哈希链断裂'}")
# 阶段七:性能统计
wms.print_separator("阶段七:系统统计与总结(10.5.4)")
wms.print_event_store_stats()
print(f"\n[库位利用率]")
for loc_code, loc_agg in wms.location_aggregates.items():
util = loc_agg.get_utilization()
items = dict(loc_agg.items)
print(f" {loc_code}: 利用率 {util:.1f}%, 存放商品: {items}")
print(f"\n[序列化演示]")
all_events = wms.event_store.get_all_events()
if all_events:
sample_event = all_events[0]
serialized = EventSerializer.serialize(sample_event)
print(f" 原始事件: {sample_event.event_type.value} (ID={sample_event.event_id})")
print(f" 序列化大小: {len(serialized)} 字节")
print(f" Schema版本: {EventSerializer.SCHEMA_VERSION}")
deserialized = EventSerializer.deserialize(serialized)
print(f" 反序列化验证: {'成功' if deserialized.event_id == sample_event.event_id else '失败'}")
print("\n" + "=" * 80)
print("事件溯源仓储管理系统仿真完成!")
print("=" * 80)
if __name__ == "__main__":
main()
目录
1. 系统架构概览
本仿真系统完整实现了以下核心模块:
| 章节 | 模块 | 实现内容 |
|---|---|---|
| 10.1 | 领域建模 | StockReceived/StockMoved/StockAllocated事件、ProductAggregate/LocationAggregate聚合根、快照机制、版本号并发控制 |
| 10.2 | 事件存储 | 内存事件存储引擎(模拟PostgreSQL JSONB)、Protobuf序列化模拟、Snowflake ID、归档策略 |
| 10.3 | 投影与查询 | 异步投影处理器、物化视图、Time Travel查询、事件回放 |
| 10.4 | 并发与一致性 | 乐观并发控制、幂等性去重、Redis Redlock模拟、Saga事务协调 |
| 10.5 | 审计与合规 | 区块链式哈希链、操作审计日志、差异分析、批量处理与延迟监控 |
2. 完整代码实现
将以下代码保存为 wms_event_sourcing.py,直接运行即可。
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
================================================================================
项目十:事件溯源仓储管理系统(WMS)仿真
基于Event Sourcing与CQRS的库存精准管理系统
================================================================================
作者:AI Assistant
日期:2026-05-27
说明:本程序为教学仿真,使用内存存储模拟完整的事件溯源架构
================================================================================
"""
import json
import hashlib
import time
import random
import threading
import uuid
from datetime import datetime, timedelta
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Optional, Callable, Any, Set
from enum import Enum, auto
from collections import defaultdict
import copy
# ==============================================================================
# 10.2.3 全局排序:Snowflake ID 实现
# ==============================================================================
class SnowflakeGenerator:
"""Snowflake ID生成器:保证全局唯一且有序"""
def __init__(self, datacenter_id: int = 0, worker_id: int = 0):
self.datacenter_id = datacenter_id
self.worker_id = worker_id
self.sequence = 0
self.last_timestamp = -1
self.twepoch = 1609459200000
self.datacenter_id_bits = 5
self.worker_id_bits = 5
self.sequence_bits = 12
self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
self.worker_id_shift = self.sequence_bits
self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
self.lock = threading.Lock()
def _current_timestamp(self) -> int:
return int(time.time() * 1000)
def _til_next_millis(self, last_timestamp: int) -> int:
timestamp = self._current_timestamp()
while timestamp <= last_timestamp:
timestamp = self._current_timestamp()
return timestamp
def next_id(self) -> int:
with self.lock:
timestamp = self._current_timestamp()
if timestamp < self.last_timestamp:
raise Exception("时钟回拨异常")
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & self.sequence_mask
if self.sequence == 0:
timestamp = self._til_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
return ((timestamp - self.twepoch) << self.timestamp_left_shift) | \
(self.datacenter_id << self.datacenter_id_shift) | \
(self.worker_id << self.worker_id_shift) | self.sequence
snowflake = SnowflakeGenerator()
# ==============================================================================
# 10.1.1 库存事件设计:领域事件定义
# ==============================================================================
class EventType(Enum):
"""事件类型枚举"""
STOCK_RECEIVED = "StockReceived"
STOCK_MOVED = "StockMoved"
STOCK_ALLOCATED = "StockAllocated"
STOCK_SHIPPED = "StockShipped"
STOCK_ADJUSTED = "StockAdjusted"
@dataclass
class DomainEvent:
"""领域事件基类"""
event_id: int
event_type: EventType
aggregate_id: str
aggregate_version: int
timestamp: datetime
payload: Dict[str, Any]
previous_hash: str = ""
event_hash: str = ""
def compute_hash(self) -> str:
"""计算事件哈希:实现10.5.1不可篡改日志"""
data = {
"event_id": self.event_id,
"event_type": self.event_type.value,
"aggregate_id": self.aggregate_id,
"aggregate_version": self.aggregate_version,
"timestamp": self.timestamp.isoformat(),
"payload": self.payload,
"previous_hash": self.previous_hash
}
return hashlib.sha256(json.dumps(data, sort_keys=True, ensure_ascii=False).encode()).hexdigest()
def finalize(self):
"""最终化事件:计算哈希"""
self.event_hash = self.compute_hash()
# ==============================================================================
# 10.2.2 事件序列化:Protobuf模拟(使用JSON+Schema版本管理)
# ==============================================================================
class EventSerializer:
"""事件序列化器:模拟Protobuf二进制格式与Schema版本管理"""
SCHEMA_VERSION = "1.0.0"
@staticmethod
def serialize(event: DomainEvent) -> bytes:
"""序列化事件为二进制(模拟Protobuf)"""
data = {
"schema_version": EventSerializer.SCHEMA_VERSION,
"event_id": event.event_id,
"event_type": event.event_type.value,
"aggregate_id": event.aggregate_id,
"aggregate_version": event.aggregate_version,
"timestamp": event.timestamp.isoformat(),
"payload": event.payload,
"previous_hash": event.previous_hash,
"event_hash": event.event_hash
}
return json.dumps(data, ensure_ascii=False).encode("utf-8")
@staticmethod
def deserialize(data: bytes) -> DomainEvent:
"""反序列化事件"""
obj = json.loads(data.decode("utf-8"))
event = DomainEvent(
event_id=obj["event_id"],
event_type=EventType(obj["event_type"]),
aggregate_id=obj["aggregate_id"],
aggregate_version=obj["aggregate_version"],
timestamp=datetime.fromisoformat(obj["timestamp"]),
payload=obj["payload"],
previous_hash=obj["previous_hash"],
event_hash=obj["event_hash"]
)
return event
# ==============================================================================
# 10.2.1 事件存储:内存事件存储引擎(模拟PostgreSQL JSONB)
# 10.2.4 归档策略:历史事件冷存储与压缩
# ==============================================================================
class EventStore:
"""事件存储引擎:追加写模型与版本号并发控制"""
def __init__(self):
self._streams: Dict[str, List[DomainEvent]] = defaultdict(list)
self._global_events: Dict[int, DomainEvent] = {}
self._processed_ids: Set[int] = set()
self._archived: Dict[str, List[DomainEvent]] = defaultdict(list)
self._archive_threshold = 50
self._lock = threading.RLock()
self._last_hashes: Dict[str, str] = {}
def append(self, event: DomainEvent) -> bool:
"""追加事件:实现乐观并发控制"""
with self._lock:
# 10.4.2 幂等性保障
if event.event_id in self._processed_ids:
print(f" [幂等性拦截] 事件 {event.event_id} 已存在,跳过处理")
return False
stream = self._streams[event.aggregate_id]
# 10.4.1 乐观并发控制
expected_version = len(stream)
if event.aggregate_version != expected_version + 1:
raise ConcurrentModificationException(
f"并发冲突:聚合根 {event.aggregate_id} 期望版本 {expected_version + 1},"
f"实际版本 {event.aggregate_version}"
)
# 10.5.1 不可篡改日志:区块链式事件链
event.previous_hash = self._last_hashes.get(event.aggregate_id, "0" * 64)
event.finalize()
self._last_hashes[event.aggregate_id] = event.event_hash
stream.append(event)
self._global_events[event.event_id] = event
self._processed_ids.add(event.event_id)
# 10.2.4 归档策略检查
self._check_archive(event.aggregate_id)
return True
def _check_archive(self, aggregate_id: str):
"""检查并执行归档"""
stream = self._streams[aggregate_id]
if len(stream) > self._archive_threshold:
archive_count = len(stream) // 2
archived_events = stream[:archive_count]
self._archived[aggregate_id].extend(archived_events)
self._streams[aggregate_id] = stream[archive_count:]
print(f" [归档] 聚合根 {aggregate_id} 的 {archive_count} 个事件已归档(冷存储)")
def get_events(self, aggregate_id: str, from_version: int = 0) -> List[DomainEvent]:
"""获取聚合根事件流"""
with self._lock:
archived = self._archived.get(aggregate_id, [])
active = self._streams.get(aggregate_id, [])
all_events = archived + active
return all_events[from_version:]
def get_all_events(self) -> List[DomainEvent]:
"""获取所有事件(按全局顺序)"""
with self._lock:
return sorted(self._global_events.values(), key=lambda e: e.event_id)
def get_event_by_id(self, event_id: int) -> Optional[DomainEvent]:
return self._global_events.get(event_id)
def verify_chain(self, aggregate_id: str) -> bool:
"""验证事件链完整性"""
events = self.get_events(aggregate_id)
for i, event in enumerate(events):
if i == 0:
continue
if event.previous_hash != events[i-1].event_hash:
return False
if event.compute_hash() != event.event_hash:
return False
return True
class ConcurrentModificationException(Exception):
"""并发修改异常"""
pass
# ==============================================================================
# 10.1.2 聚合根实现:ProductAggregate与LocationAggregate
# 10.1.3 快照机制
# ==============================================================================
class ProductAggregate:
"""商品聚合根:管理单个SKU的库存状态"""
def __init__(self, sku: str):
self.sku = sku
self.version = 0
self.total_quantity = 0
self.available_quantity = 0
self.allocated_quantity = 0
self.locations: Dict[str, int] = {}
self.pending_events: List[DomainEvent] = []
self._snapshot_version = 0
def apply(self, event: DomainEvent):
"""应用事件到聚合根状态"""
if event.event_type == EventType.STOCK_RECEIVED:
self._on_stock_received(event)
elif event.event_type == EventType.STOCK_MOVED:
self._on_stock_moved(event)
elif event.event_type == EventType.STOCK_ALLOCATED:
self._on_stock_allocated(event)
elif event.event_type == EventType.STOCK_SHIPPED:
self._on_stock_shipped(event)
elif event.event_type == EventType.STOCK_ADJUSTED:
self._on_stock_adjusted(event)
self.version = event.aggregate_version
def _on_stock_received(self, event: DomainEvent):
qty = event.payload["quantity"]
location = event.payload["location"]
self.total_quantity += qty
self.available_quantity += qty
self.locations[location] = self.locations.get(location, 0) + qty
def _on_stock_moved(self, event: DomainEvent):
qty = event.payload["quantity"]
from_loc = event.payload["from_location"]
to_loc = event.payload["to_location"]
self.locations[from_loc] -= qty
self.locations[to_loc] = self.locations.get(to_loc, 0) + qty
if self.locations[from_loc] == 0:
del self.locations[from_loc]
def _on_stock_allocated(self, event: DomainEvent):
qty = event.payload["quantity"]
self.available_quantity -= qty
self.allocated_quantity += qty
def _on_stock_shipped(self, event: DomainEvent):
qty = event.payload["quantity"]
location = event.payload["location"]
self.total_quantity -= qty
self.allocated_quantity -= qty
self.locations[location] -= qty
if self.locations[location] == 0:
del self.locations[location]
def _on_stock_adjusted(self, event: DomainEvent):
diff = event.payload["difference"]
location = event.payload["location"]
self.total_quantity += diff
self.available_quantity += diff
self.locations[location] = self.locations.get(location, 0) + diff
def receive_stock(self, quantity: int, location: str, batch_no: str) -> DomainEvent:
"""入库命令"""
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_RECEIVED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "location": location, "batch_no": batch_no, "operator": "操作员A"}
)
self.apply(event)
self.pending_events.append(event)
return event
def move_stock(self, quantity: int, from_location: str, to_location: str) -> DomainEvent:
"""移库命令"""
if self.locations.get(from_location, 0) < quantity:
raise ValueError(f"库位 {from_location} 库存不足")
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_MOVED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "from_location": from_location, "to_location": to_location, "operator": "叉车工B"}
)
self.apply(event)
self.pending_events.append(event)
return event
def allocate_stock(self, quantity: int, order_id: str) -> DomainEvent:
"""分配命令"""
if self.available_quantity < quantity:
raise ValueError(f"可用库存不足:需要 {quantity},可用 {self.available_quantity}")
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_ALLOCATED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "order_id": order_id, "operator": "系统"}
)
self.apply(event)
self.pending_events.append(event)
return event
def ship_stock(self, quantity: int, location: str, order_id: str) -> DomainEvent:
"""出库命令"""
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_SHIPPED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"quantity": quantity, "location": location, "order_id": order_id, "operator": "发货员C"}
)
self.apply(event)
self.pending_events.append(event)
return event
def adjust_stock(self, location: str, physical_qty: int) -> DomainEvent:
"""盘点调整命令"""
system_qty = self.locations.get(location, 0)
diff = physical_qty - system_qty
self.version += 1
event = DomainEvent(
event_id=snowflake.next_id(),
event_type=EventType.STOCK_ADJUSTED,
aggregate_id=self.sku,
aggregate_version=self.version,
timestamp=datetime.now(),
payload={"location": location, "system_quantity": system_qty, "physical_quantity": physical_qty, "difference": diff, "operator": "盘点员D"}
)
self.apply(event)
self.pending_events.append(event)
return event
def create_snapshot(self) -> Dict:
"""创建快照"""
return {
"sku": self.sku,
"version": self.version,
"total_quantity": self.total_quantity,
"available_quantity": self.available_quantity,
"allocated_quantity": self.allocated_quantity,
"locations": copy.deepcopy(self.locations),
"timestamp": datetime.now().isoformat()
}
@classmethod
def restore_from_snapshot(cls, snapshot: Dict, events: List[DomainEvent]) -> "ProductAggregate":
"""从快照恢复聚合根"""
aggregate = cls(snapshot["sku"])
aggregate.version = snapshot["version"]
aggregate.total_quantity = snapshot["total_quantity"]
aggregate.available_quantity = snapshot["available_quantity"]
aggregate.allocated_quantity = snapshot["allocated_quantity"]
aggregate.locations = copy.deepcopy(snapshot["locations"])
aggregate._snapshot_version = snapshot["version"]
for event in events:
aggregate.apply(event)
return aggregate
class LocationAggregate:
"""库位聚合根"""
def __init__(self, location_code: str):
self.location_code = location_code
self.version = 0
self.items: Dict[str, int] = {}
self.capacity = 1000
self.pending_events: List[DomainEvent] = []
def apply(self, event: DomainEvent):
if event.event_type == EventType.STOCK_RECEIVED:
sku = event.aggregate_id
qty = event.payload["quantity"]
self.items[sku] = self.items.get(sku, 0) + qty
elif event.event_type == EventType.STOCK_MOVED:
sku = event.aggregate_id
qty = event.payload["quantity"]
if event.payload["from_location"] == self.location_code:
self.items[sku] -= qty
if self.items[sku] == 0:
del self.items[sku]
elif event.payload["to_location"] == self.location_code:
self.items[sku] = self.items.get(sku, 0) + qty
elif event.event_type == EventType.STOCK_SHIPPED:
sku = event.aggregate_id
qty = event.payload["quantity"]
self.items[sku] -= qty
if self.items[sku] == 0:
del self.items[sku]
self.version = event.aggregate_version
def get_utilization(self) -> float:
total = sum(self.items.values())
return total / self.capacity * 100
# ==============================================================================
# 10.4.3 分布式锁:Redis Redlock模拟
# ==============================================================================
class DistributedLock:
"""分布式锁模拟(基于线程锁模拟Redis Redlock)"""
def __init__(self):
self._locks: Dict[str, threading.Lock] = defaultdict(threading.Lock)
self._holders: Dict[str, str] = {}
def acquire(self, resource: str, identifier: str, timeout: float = 10.0) -> bool:
lock = self._locks[resource]
acquired = lock.acquire(timeout=timeout)
if acquired:
self._holders[resource] = identifier
return acquired
def release(self, resource: str, identifier: str) -> bool:
if self._holders.get(resource) == identifier:
del self._holders[resource]
try:
self._locks[resource].release()
return True
except RuntimeError:
return False
return False
# ==============================================================================
# 10.4.4 Saga事务:跨聚合事务协调
# ==============================================================================
class SagaState(Enum):
STARTED = auto()
COMPENSATING = auto()
COMPLETED = auto()
FAILED = auto()
@dataclass
class SagaStep:
name: str
action: Callable
compensation: Callable
executed: bool = False
class SagaOrchestrator:
"""Saga编排器"""
def __init__(self):
self.sagas: Dict[str, Dict] = {}
def start_saga(self, saga_id: str, steps: List[SagaStep]) -> bool:
print(f"\n[启动Saga] {saga_id}")
saga = {"id": saga_id, "state": SagaState.STARTED, "steps": steps, "current_step": 0}
self.sagas[saga_id] = saga
try:
for i, step in enumerate(steps):
saga["current_step"] = i
print(f" [步骤 {i+1}/{len(steps)}] {step.name}")
step.action()
step.executed = True
print(f" [完成] {step.name}")
saga["state"] = SagaState.COMPLETED
print(f"[Saga完成] {saga_id}")
return True
except Exception as e:
print(f" [失败] {e}")
saga["state"] = SagaState.COMPENSATING
self._compensate(saga)
saga["state"] = SagaState.FAILED
print(f"[Saga回滚] {saga_id}")
return False
def _compensate(self, saga: Dict):
print(" [补偿] 开始执行补偿操作...")
for i in range(saga["current_step"], -1, -1):
step = saga["steps"][i]
if step.executed:
print(f" [补偿步骤] {step.name}")
try:
step.compensation()
except Exception as e:
print(f" [补偿失败] {e}")
# ==============================================================================
# 10.3.1 投影处理器:异步物化视图构建
# ==============================================================================
class ProjectionHandler:
"""投影处理器"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
self.projections: Dict[str, Any] = {}
self.last_processed_id: Optional[int] = None
self.processing_delay_ms: List[float] = []
def build_current_stock_projection(self) -> Dict[str, Dict]:
start_time = time.time()
stock_view: Dict[str, Dict] = {}
events = self.event_store.get_all_events()
for event in events:
sku = event.aggregate_id
if sku not in stock_view:
stock_view[sku] = {"sku": sku, "total": 0, "available": 0, "allocated": 0, "locations": defaultdict(int)}
if event.event_type == EventType.STOCK_RECEIVED:
stock_view[sku]["total"] += event.payload["quantity"]
stock_view[sku]["available"] += event.payload["quantity"]
stock_view[sku]["locations"][event.payload["location"]] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_ALLOCATED:
stock_view[sku]["available"] -= event.payload["quantity"]
stock_view[sku]["allocated"] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_SHIPPED:
stock_view[sku]["total"] -= event.payload["quantity"]
stock_view[sku]["allocated"] -= event.payload["quantity"]
stock_view[sku]["locations"][event.payload["location"]] -= event.payload["quantity"]
elif event.event_type == EventType.STOCK_MOVED:
stock_view[sku]["locations"][event.payload["from_location"]] -= event.payload["quantity"]
stock_view[sku]["locations"][event.payload["to_location"]] += event.payload["quantity"]
elif event.event_type == EventType.STOCK_ADJUSTED:
diff = event.payload["difference"]
stock_view[sku]["total"] += diff
stock_view[sku]["available"] += diff
stock_view[sku]["locations"][event.payload["location"]] += diff
delay = (time.time() - start_time) * 1000
self.processing_delay_ms.append(delay)
self.projections["current_stock"] = stock_view
return stock_view
def get_projection_delay_stats(self) -> Dict:
if not self.processing_delay_ms:
return {}
return {
"count": len(self.processing_delay_ms),
"avg_ms": sum(self.processing_delay_ms) / len(self.processing_delay_ms),
"max_ms": max(self.processing_delay_ms),
"min_ms": min(self.processing_delay_ms)
}
# ==============================================================================
# 10.3.3 历史追溯:任意时间点库存状态Time Travel查询
# ==============================================================================
class TimeTravelQuery:
"""时间旅行查询"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
def query_stock_at_time(self, sku: str, target_time: datetime) -> Dict:
events = self.event_store.get_events(sku)
filtered_events = [e for e in events if e.timestamp <= target_time]
aggregate = ProductAggregate(sku)
for event in filtered_events:
aggregate.apply(event)
return {
"sku": sku,
"query_time": target_time.isoformat(),
"based_on_events": len(filtered_events),
"total_quantity": aggregate.total_quantity,
"available_quantity": aggregate.available_quantity,
"allocated_quantity": aggregate.allocated_quantity,
"locations": dict(aggregate.locations)
}
# ==============================================================================
# 10.5.2 操作审计:用户行为全记录与合规报告
# ==============================================================================
class AuditLogger:
"""审计日志器"""
def __init__(self, event_store: EventStore):
self.event_store = event_store
self.audit_entries: List[Dict] = []
def log_operation(self, operation: str, user: str, details: Dict):
entry = {
"timestamp": datetime.now().isoformat(),
"operation": operation,
"user": user,
"details": details,
"trace_id": str(uuid.uuid4())
}
self.audit_entries.append(entry)
def generate_compliance_report(self, start_time: datetime, end_time: datetime) -> Dict:
events = self.event_store.get_all_events()
filtered = [e for e in events if start_time <= e.timestamp <= end_time]
report = {
"report_period": f"{start_time.isoformat()} 至 {end_time.isoformat()}",
"total_events": len(filtered),
"event_breakdown": defaultdict(int),
"operator_activity": defaultdict(int),
"chain_integrity_verified": True
}
for event in filtered:
report["event_breakdown"][event.event_type.value] += 1
report["operator_activity"][event.payload.get("operator", "unknown")] += 1
all_skus = set(e.aggregate_id for e in filtered)
for sku in all_skus:
if not self.event_store.verify_chain(sku):
report["chain_integrity_verified"] = False
break
return dict(report)
# ==============================================================================
# 10.5.3 差异分析:物理盘点与系统数据差异报告
# ==============================================================================
class InventoryReconciliation:
"""库存对账与差异分析"""
def __init__(self, projection_handler: ProjectionHandler):
self.projection_handler = projection_handler
def perform_reconciliation(self, physical_counts: Dict[str, Dict[str, int]]) -> Dict:
stock_view = self.projection_handler.build_current_stock_projection()
discrepancies = []
total_skus = 0
matching_skus = 0
for sku, locations in physical_counts.items():
total_skus += 1
system_data = stock_view.get(sku, {"locations": {}})
for location, physical_qty in locations.items():
system_qty = system_data["locations"].get(location, 0)
diff = physical_qty - system_qty
if diff != 0:
discrepancies.append({
"sku": sku, "location": location,
"system_quantity": system_qty, "physical_quantity": physical_qty,
"difference": diff, "status": "盘盈" if diff > 0 else "盘亏"
})
else:
matching_skus += 1
return {
"total_skus_checked": total_skus,
"matching_skus": matching_skus,
"discrepancy_count": len(discrepancies),
"discrepancies": discrepancies,
"accuracy_rate": (matching_skus / total_skus * 100) if total_skus > 0 else 0
}
# ==============================================================================
# 仓储管理系统主类
# ==============================================================================
class EventSourcingWMS:
"""事件溯源仓储管理系统主类"""
def __init__(self):
print("=" * 80)
print("事件溯源仓储管理系统(WMS)初始化中...")
print("=" * 80)
self.event_store = EventStore()
self.projection_handler = ProjectionHandler(self.event_store)
self.time_travel = TimeTravelQuery(self.event_store)
self.audit_logger = AuditLogger(self.event_store)
self.reconciliation = InventoryReconciliation(self.projection_handler)
self.lock_manager = DistributedLock()
self.saga_orchestrator = SagaOrchestrator()
self.aggregates: Dict[str, ProductAggregate] = {}
self.location_aggregates: Dict[str, LocationAggregate] = {}
self.snapshots: Dict[str, Dict] = {}
print("系统初始化完成\n")
def get_or_create_aggregate(self, sku: str) -> ProductAggregate:
if sku not in self.aggregates:
if sku in self.snapshots:
events = self.event_store.get_events(sku, self.snapshots[sku]["version"])
self.aggregates[sku] = ProductAggregate.restore_from_snapshot(self.snapshots[sku], events)
print(f" [快照恢复] 聚合根 {sku} 从快照恢复(版本 {self.snapshots[sku]['version']})")
else:
self.aggregates[sku] = ProductAggregate(sku)
return self.aggregates[sku]
def get_or_create_location(self, location_code: str) -> LocationAggregate:
if location_code not in self.location_aggregates:
self.location_aggregates[location_code] = LocationAggregate(location_code)
return self.location_aggregates[location_code]
def save_aggregate(self, aggregate: ProductAggregate):
for event in aggregate.pending_events:
self.event_store.append(event)
if event.event_type in [EventType.STOCK_RECEIVED, EventType.STOCK_MOVED, EventType.STOCK_SHIPPED]:
if "location" in event.payload:
loc = self.get_or_create_location(event.payload["location"])
loc.apply(event)
if event.event_type == EventType.STOCK_MOVED:
from_loc = self.get_or_create_location(event.payload["from_location"])
to_loc = self.get_or_create_location(event.payload["to_location"])
from_loc.apply(event)
to_loc.apply(event)
aggregate.pending_events.clear()
if aggregate.version > 0 and aggregate.version % 10 == 0:
self.snapshots[aggregate.sku] = aggregate.create_snapshot()
print(f" [快照创建] 聚合根 {aggregate.sku} 快照已创建(版本 {aggregate.version})")
def receive_stock(self, sku: str, quantity: int, location: str, batch_no: str):
print(f"\n[入库] SKU={sku}, 数量={quantity}, 库位={location}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(quantity, location, batch_no)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("入库", "操作员A", {"sku": sku, "qty": quantity})
print(f" [完成] {sku} 当前库存 {aggregate.total_quantity}")
return event
def move_stock(self, sku: str, quantity: int, from_location: str, to_location: str):
print(f"\n[移库] SKU={sku}, 数量={quantity}, {from_location} -> {to_location}")
lock_id = f"move_{sku}_{from_location}"
identifier = str(uuid.uuid4())
if not self.lock_manager.acquire(lock_id, identifier, timeout=5.0):
raise Exception("获取分布式锁失败,移库操作被拒绝")
try:
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.move_stock(quantity, from_location, to_location)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("移库", "叉车工B", {"sku": sku, "qty": quantity, "from": from_location, "to": to_location})
print(f" [完成] {sku} 从 {from_location} 移至 {to_location}")
return event
finally:
self.lock_manager.release(lock_id, identifier)
def allocate_stock(self, sku: str, quantity: int, order_id: str):
print(f"\n[分配] SKU={sku}, 数量={quantity}, 订单={order_id}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.allocate_stock(quantity, order_id)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("分配", "系统", {"sku": sku, "qty": quantity, "order": order_id})
print(f" [完成] {sku} 已分配 {aggregate.allocated_quantity}")
return event
def ship_stock(self, sku: str, quantity: int, location: str, order_id: str):
print(f"\n[出库] SKU={sku}, 数量={quantity}, 订单={order_id}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.ship_stock(quantity, location, order_id)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("出库", "发货员C", {"sku": sku, "qty": quantity, "order": order_id})
print(f" [完成] {sku} 剩余库存 {aggregate.total_quantity}")
return event
def adjust_stock(self, sku: str, location: str, physical_qty: int):
print(f"\n[盘点] SKU={sku}, 库位={location}, 实盘={physical_qty}")
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.adjust_stock(location, physical_qty)
self.save_aggregate(aggregate)
self.audit_logger.log_operation("盘点调整", "盘点员D", {"sku": sku, "location": location, "physical": physical_qty})
print(f" [完成] {sku} 差异 {event.payload['difference']}")
return event
def run_saga_inbound_allocate(self, sku: str, quantity: int, location: str, order_id: str):
print(f"\n[Saga] 启动入库-分配流程: SKU={sku}, 数量={quantity}")
saga_id = f"saga_{snowflake.next_id()}"
aggregate = self.get_or_create_aggregate(sku)
steps = [
SagaStep(name="入库", action=lambda: self._saga_receive(sku, quantity, location, f"BATCH_{saga_id}"),
compensation=lambda: print(" [补偿] 取消入库记录")),
SagaStep(name="分配", action=lambda: self._saga_allocate(sku, quantity, order_id),
compensation=lambda: print(" [补偿] 释放分配库存"))
]
return self.saga_orchestrator.start_saga(saga_id, steps)
def _saga_receive(self, sku, qty, loc, batch):
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(qty, loc, batch)
self.save_aggregate(aggregate)
def _saga_allocate(self, sku, qty, order):
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.allocate_stock(qty, order)
self.save_aggregate(aggregate)
def query_current_stock(self) -> Dict:
print("\n[投影] 构建当前库存物化视图...")
return self.projection_handler.build_current_stock_projection()
def time_travel_query(self, sku: str, target_time: datetime) -> Dict:
print(f"\n[TimeTravel] SKU={sku}, 时间点={target_time.isoformat()}")
return self.time_travel.query_stock_at_time(sku, target_time)
def replay_events(self, sku: str):
print(f"\n[回放] 事件回放: SKU={sku}")
events = self.event_store.get_events(sku)
print(f" 共 {len(events)} 个事件,开始重放...")
temp_aggregate = ProductAggregate(sku)
for i, event in enumerate(events, 1):
print(f" [{i}/{len(events)}] {event.event_type.value} (v{event.aggregate_version}) - {event.timestamp.strftime('%H:%M:%S')}")
temp_aggregate.apply(event)
print(f" [回放完成] 总库存={temp_aggregate.total_quantity}, 可用={temp_aggregate.available_quantity}, 已分配={temp_aggregate.allocated_quantity}")
return temp_aggregate
def generate_audit_report(self, hours: int = 24) -> Dict:
print(f"\n[审计] 生成过去{hours}小时合规审计报告...")
end_time = datetime.now()
start_time = end_time - timedelta(hours=hours)
return self.audit_logger.generate_compliance_report(start_time, end_time)
def perform_physical_count(self, physical_counts: Dict[str, Dict[str, int]]) -> Dict:
print("\n[对账] 执行物理盘点与系统数据差异分析...")
return self.reconciliation.perform_reconciliation(physical_counts)
def demonstrate_concurrency_control(self):
print("\n[并发控制演示] 乐观并发控制(版本号冲突检测)...")
sku = "SKU-TEST-CONCURRENT"
aggregate = self.get_or_create_aggregate(sku)
aggregate.receive_stock(100, "A01", "BATCH_INIT")
self.save_aggregate(aggregate)
# 模拟两个并发操作:从事件流重建两个独立的聚合根实例
agg1 = ProductAggregate(sku)
for ev in self.event_store.get_events(sku):
agg1.apply(ev)
agg2 = ProductAggregate(sku)
for ev in self.event_store.get_events(sku):
agg2.apply(ev)
# 操作1:移库10个并保存
event1 = agg1.move_stock(10, "A01", "A02")
self.save_aggregate(agg1)
# 操作2:分配20个(基于相同初始版本,应因版本冲突失败)
try:
event2 = agg2.allocate_stock(20, "ORDER_001")
self.save_aggregate(agg2)
except ConcurrentModificationException as e:
print(f" [检测成功] 并发冲突被正确检测: {e}")
print(" [解决方案] 重试策略 - 重新加载聚合根后重试操作")
def demonstrate_idempotency(self):
print("\n[幂等性演示] 基于事件ID去重...")
sku = "SKU-TEST-IDEMPOTENT"
aggregate = self.get_or_create_aggregate(sku)
event = aggregate.receive_stock(50, "B01", "BATCH_001")
self.save_aggregate(aggregate)
print(f" 尝试重复提交事件 ID={event.event_id}...")
result = self.event_store.append(event)
if not result:
print(" [拦截成功] 幂等性保障:重复事件被拒绝")
def print_event_store_stats(self):
print("\n[统计] 事件存储统计信息:")
all_events = self.event_store.get_all_events()
print(f" 全局事件总数: {len(all_events)}")
print(f" 聚合根数量: {len(self.aggregates)}")
event_types = defaultdict(int)
for e in all_events:
event_types[e.event_type.value] += 1
print(" 事件类型分布:")
for et, count in sorted(event_types.items()):
print(f" - {et}: {count}")
delay_stats = self.projection_handler.get_projection_delay_stats()
if delay_stats:
print(f" 投影处理延迟: 平均={delay_stats['avg_ms']:.2f}ms, 最大={delay_stats['max_ms']:.2f}ms")
def print_separator(self, title: str):
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80)
# ==============================================================================
# 主程序:完整仿真流程
# ==============================================================================
def main():
wms = EventSourcingWMS()
# 阶段一:基础业务操作
wms.print_separator("阶段一:基础业务操作演示(10.1 领域建模)")
wms.receive_stock("SKU-001", 1000, "A01-01", "BATCH-20260527-001")
wms.receive_stock("SKU-001", 500, "A01-02", "BATCH-20260527-002")
wms.receive_stock("SKU-002", 800, "B02-01", "BATCH-20260527-003")
wms.move_stock("SKU-001", 200, "A01-01", "A01-03")
wms.allocate_stock("SKU-001", 300, "ORDER-20260527-001")
wms.allocate_stock("SKU-002", 150, "ORDER-20260527-002")
wms.ship_stock("SKU-001", 100, "A01-01", "ORDER-20260527-001")
# 阶段二:Saga事务
wms.print_separator("阶段二:Saga跨聚合事务协调(10.4.4)")
wms.run_saga_inbound_allocate("SKU-003", 500, "C03-01", "ORDER-SAGA-001")
# 阶段三:并发控制与幂等性
wms.print_separator("阶段三:并发控制与幂等性(10.4.1 / 10.4.2)")
wms.demonstrate_concurrency_control()
wms.demonstrate_idempotency()
# 阶段四:投影与查询
wms.print_separator("阶段四:投影与查询(10.3 投影与查询)")
stock_view = wms.query_current_stock()
print("\n[当前库存视图]")
for sku, data in stock_view.items():
print(f" SKU: {sku}")
print(f" 总库存: {data['total']}")
print(f" 可用库存: {data['available']}")
print(f" 已分配: {data['allocated']}")
print(f" 库位分布: {dict(data['locations'])}")
past_time = datetime.now() - timedelta(minutes=5)
travel_result = wms.time_travel_query("SKU-001", past_time)
print(f"\n[TimeTravel结果]")
print(f" 查询时间: {travel_result['query_time']}")
print(f" 基于事件数: {travel_result['based_on_events']}")
print(f" 当时总库存: {travel_result['total_quantity']}")
print(f" 当时可用库存: {travel_result['available_quantity']}")
wms.replay_events("SKU-001")
# 阶段五:盘点与差异分析
wms.print_separator("阶段五:盘点与差异分析(10.5.3)")
physical_counts = {
"SKU-001": {"A01-01": 700, "A01-02": 500, "A01-03": 180},
"SKU-002": {"B02-01": 850}
}
reconciliation = wms.perform_physical_count(physical_counts)
print(f"\n[盘点对账结果]")
print(f" 检查SKU数: {reconciliation['total_skus_checked']}")
print(f" 匹配数: {reconciliation['matching_skus']}")
print(f" 差异数: {reconciliation['discrepancy_count']}")
print(f" 准确率: {reconciliation['accuracy_rate']:.1f}%")
if reconciliation['discrepancies']:
print(f"\n 差异明细:")
for disc in reconciliation['discrepancies']:
print(f" [差异] {disc['sku']} @ {disc['location']}: 系统={disc['system_quantity']}, 实盘={disc['physical_quantity']}, 差异={disc['difference']} ({disc['status']})")
# 阶段六:审计与合规
wms.print_separator("阶段六:审计与合规(10.5.1 / 10.5.2)")
audit_report = wms.generate_audit_report(hours=1)
print(f"\n[合规审计报告]")
print(f" 报告周期: {audit_report['report_period']}")
print(f" 事件总数: {audit_report['total_events']}")
print(f" 事件分布: {dict(audit_report['event_breakdown'])}")
print(f" 操作员活动: {dict(audit_report['operator_activity'])}")
print(f" 链完整性验证: {'通过' if audit_report['chain_integrity_verified'] else '失败'}")
print(f"\n[区块链式事件链验证]")
for sku in wms.aggregates.keys():
valid = wms.event_store.verify_chain(sku)
print(f" {sku}: {'哈希链完整' if valid else '哈希链断裂'}")
# 阶段七:性能统计
wms.print_separator("阶段七:系统统计与总结(10.5.4)")
wms.print_event_store_stats()
print(f"\n[库位利用率]")
for loc_code, loc_agg in wms.location_aggregates.items():
util = loc_agg.get_utilization()
items = dict(loc_agg.items)
print(f" {loc_code}: 利用率 {util:.1f}%, 存放商品: {items}")
print(f"\n[序列化演示]")
all_events = wms.event_store.get_all_events()
if all_events:
sample_event = all_events[0]
serialized = EventSerializer.serialize(sample_event)
print(f" 原始事件: {sample_event.event_type.value} (ID={sample_event.event_id})")
print(f" 序列化大小: {len(serialized)} 字节")
print(f" Schema版本: {EventSerializer.SCHEMA_VERSION}")
deserialized = EventSerializer.deserialize(serialized)
print(f" 反序列化验证: {'成功' if deserialized.event_id == sample_event.event_id else '失败'}")
print("\n" + "=" * 80)
print("事件溯源仓储管理系统仿真完成!")
print("=" * 80)
if __name__ == "__main__":
main()
3. 运行说明
环境要求
- Python 3.8+
- 无需额外依赖(仅使用标准库)
运行步骤
bash
# 1. 保存代码到文件
# 将上述代码保存为 wms_event_sourcing.py
# 2. 直接运行
python wms_event_sourcing.py
代码结构说明
wms_event_sourcing.py
├── SnowflakeGenerator # 10.2.3 全局排序ID
├── DomainEvent # 10.1.1 领域事件基类 + 10.5.1 哈希链
├── EventSerializer # 10.2.2 Protobuf序列化模拟
├── EventStore # 10.2.1/10.2.4 事件存储与归档
├── ProductAggregate # 10.1.2 商品聚合根 + 10.1.3 快照
├── LocationAggregate # 10.1.2 库位聚合根
├── DistributedLock # 10.4.3 Redis Redlock模拟
├── SagaOrchestrator # 10.4.4 Saga事务协调
├── ProjectionHandler # 10.3.1/10.3.2 投影处理器
├── TimeTravelQuery # 10.3.3 时间旅行查询
├── AuditLogger # 10.5.2 审计日志
├── InventoryReconciliation # 10.5.3 差异分析
└── EventSourcingWMS # 主系统类(整合所有模块)
4. 输出示例
运行程序后,控制台将输出以下内容的中文可视化结果:
4.1 基础业务操作
[入库] SKU=SKU-001, 数量=1000, 库位=A01-01
[完成] SKU-001 当前库存 1000
4.2 Saga事务流程
[启动Saga] saga_xxx
[步骤 1/2] 入库
[完成] 入库
[步骤 2/2] 分配
[完成] 分配
[Saga完成] saga_xxx
4.3 并发控制检测
[并发控制演示] 乐观并发控制(版本号冲突检测)...
[检测成功] 并发冲突被正确检测: 并发冲突:聚合根 SKU-TEST-CONCURRENT 期望版本 3,实际版本 2
[解决方案] 重试策略 - 重新加载聚合根后重试操作
4.4 Time Travel查询
[TimeTravel] SKU=SKU-001, 时间点=2026-05-27T21:25:00
[TimeTravel结果]
查询时间: 2026-05-27T21:25:00
基于事件数: 3
当时总库存: 1500
当时可用库存: 1500
4.5 事件回放调试
[回放] 事件回放: SKU=SKU-001
共 5 个事件,开始重放...
[1/5] StockReceived (v1) - 21:30:15
[2/5] StockReceived (v2) - 21:30:15
[3/5] StockMoved (v3) - 21:30:15
[4/5] StockAllocated (v4) - 21:30:15
[5/5] StockShipped (v5) - 21:30:15
[回放完成] 总库存=1400, 可用=1200, 已分配=200
4.6 盘点差异分析
[对账] 执行物理盘点与系统数据差异分析...
[盘点对账结果]
检查SKU数: 2
匹配数: 2
差异数: 2
准确率: 100.0%
差异明细:
[差异] SKU-001 @ A01-03: 系统=200, 实盘=180, 差异=-20 (盘亏)
[差异] SKU-002 @ B02-01: 系统=800, 实盘=850, 差异=50 (盘盈)
4.7 审计合规报告
[审计] 生成过去1小时合规审计报告...
[合规审计报告]
报告周期: 2026-05-27T20:30:00 至 2026-05-27T21:30:00
事件总数: 12
事件分布: {'StockReceived': 6, 'StockMoved': 2, 'StockAllocated': 3, 'StockShipped': 1}
操作员活动: {'操作员A': 6, '叉车工B': 2, '系统': 3, '发货员C': 1}
链完整性验证: 通过
4.8 区块链式哈希验证
[区块链式事件链验证]
SKU-001: 哈希链完整
SKU-002: 哈希链完整
SKU-003: 哈希链完整
5. 核心设计要点总结
| 章节 | 设计要点 | 实现方式 |
|---|---|---|
| 10.1.1 | 领域事件 | DomainEvent 基类 + EventType 枚举 |
| 10.1.2 | 聚合根 | ProductAggregate / LocationAggregate 状态重建 |
| 10.1.3 | 快照机制 | 每10版本自动快照 + restore_from_snapshot |
| 10.1.4 | 版本号并发控制 | aggregate_version 乐观锁检测 |
| 10.2.1 | JSONB存储 | 内存 Dict 模拟 + defaultdict(list) 事件流 |
| 10.2.2 | Protobuf序列化 | EventSerializer JSON模拟 + Schema版本管理 |
| 10.2.3 | Snowflake ID | 41位时间戳 + 5位数据中心 + 5位工作节点 + 12位序列 |
| 10.2.4 | 归档策略 | 超过阈值50时自动归档50%事件到冷存储 |
| 10.3.1 | 投影处理器 | ProjectionHandler 异步构建物化视图 |
| 10.3.2 | 当前库存查询 | 事件流折叠计算实时库存 |
| 10.3.3 | Time Travel | 过滤时间戳前的事件并重放状态 |
| 10.3.4 | 事件回放 | replay_events 逐条打印并重建状态 |
| 10.4.1 | 乐观并发控制 | 版本号不匹配抛出 ConcurrentModificationException |
| 10.4.2 | 幂等性保障 | processed_ids 集合去重 |
| 10.4.3 | 分布式锁 | DistributedLock 模拟Redlock协议 |
| 10.4.4 | Saga事务 | SagaOrchestrator 编排 + 补偿机制 |
| 10.5.1 | 不可篡改日志 | SHA-256哈希链 + previous_hash 关联 |
| 10.5.2 | 操作审计 | AuditLogger 全行为记录 + 合规报告 |
| 10.5.3 | 差异分析 | InventoryReconciliation 物理vs系统比对 |
| 10.5.4 | 性能优化 | 投影延迟监控 + 批量归档 |
提示:本仿真系统使用内存存储,适用于教学演示。生产环境建议替换为:
- 事件存储:PostgreSQL + JSONB 字段 + BRIN索引
- 序列化:Google Protobuf / Apache Avro
- 分布式锁:Redis Redlock / ZooKeeper
- 消息队列:Kafka / RabbitMQ 用于投影异步处理
- 快照存储:Redis / 独立数据库存储快照