一、打包业务的特殊性
在代购集运中,仓库打包员需要将多个商品合并到一个包裹(或者分多个包裹),并填写最终快递单号、重量、尺寸。这个过程可能需要多次扫描、绑定。如果用传统HTTP请求,每次操作都要刷新页面,效率低。我们采用WebSocket + 临时会话的方式,实现打包员实时操作、实时反馈。
二、打包会话设计
@Data
public class PackingSession {
private Long packageId; // 当前包裹ID
private Long warehouseId;
private List<Long> boundItemIds; // 已绑定的商品ID
private String outTrackingNo; // 最终快递单号
private Double finalWeight;
private Double finalLength;
private Double finalWidth;
private Double finalHeight;
private LocalDateTime createTime;
}
后端维护一个内存中的会话Map(生产环境可用Redis存储)。
@Component
public class PackingSessionManager {
private final Map<String, PackingSession> sessions = new ConcurrentHashMap<>();
public void createSession(String sessionId, Long packageId) {
PackingSession session = new PackingSession();
session.setPackageId(packageId);
session.setCreateTime(LocalDateTime.now());
sessions.put(sessionId, session);
}
public PackingSession getSession(String sessionId) {
PackingSession session = sessions.get(sessionId);
if (session == null) {
throw new BusinessException("会话已过期,请重新进入打包");
}
return session;
}
public void bindItem(String sessionId, Long itemId) {
PackingSession session = getSession(sessionId);
session.getBoundItemIds().add(itemId);
}
public void updateTracking(String sessionId, String trackingNo) {
getSession(sessionId).setOutTrackingNo(trackingNo);
}
public void updateWeight(String sessionId, Double weight) {
getSession(sessionId).setFinalWeight(weight);
}
public void complete(String sessionId) {
PackingSession session = sessions.remove(sessionId);
// 保存到数据库
savePackingResult(session);
}
}
三、WebSocket消息处理
@Controller
@MessageMapping("/packing")
public class PackingWebSocketController {
@Autowired
private PackingSessionManager sessionManager;
@MessageMapping("/bind")
@SendTo("/topic/packing/status")
public PackingStatus bindItem(@Payload BindItemMessage msg) {
sessionManager.bindItem(msg.getSessionId(), msg.getItemId());
return new PackingStatus("bound", msg.getItemId(), sessionManager.getBoundCount(msg.getSessionId()));
}
@MessageMapping("/tracking")
@SendTo("/topic/packing/status")
public PackingStatus updateTracking(@Payload TrackingMessage msg) {
sessionManager.updateTracking(msg.getSessionId(), msg.getTrackingNo());
return new PackingStatus("tracking_updated", msg.getTrackingNo(), null);
}
@MessageMapping("/complete")
public void complete(@Payload CompleteMessage msg) {
sessionManager.complete(msg.getSessionId());
// 广播打包完成
messagingTemplate.convertAndSend("/topic/packing/complete", msg.getPackageId());
}
}
四、前端实现(Vue3 + Stomp)
import {
Stomp } from '@stomp/stompjs';
const stompClient = Stomp.over(() => new SockJS('/ws'));
stompClient.connect({
}, () => {
stompClient.subscribe('/topic/packing/status', (message) => {
const data = JSON.parse(message.body);
if (data.type === 'bound') {
showToast(`已绑定商品: ${
data.itemId}`);
refreshBoundList();
} else if (data.type === 'tracking_updated') {
showToast(`运单号: ${
data.trackingNo}`);
}
});
});
function bindItem(itemId) {
stompClient.send('/app/packing/bind', {
}, JSON.stringify({
sessionId: sessionId,
itemId: itemId
}));
}
五、打包完成后的运费补差
打包完成后,后端自动计算实际运费与预付运费的差额,触发补差流程。这部分逻辑在第4篇文章中已详述。
六、与Taocarts系统的集成
Taocarts系统的代购集运打包模块正是基于上述WebSocket方案实现的。仓库打包员使用PDA或PC浏览器,实时绑定商品、填写运单号,全程无需刷新页面,效率提升50%以上。如果你正在开发跨境独立站的代购系统,这套方案值得参考。