这套基于Java的24小时无人洗车扫码系统,本质上是一个融合物联网技术与微服务架构的智能终端解决方案。我在实际部署过三套类似系统的经验中发现,这类项目的核心挑战在于如何实现设备层与业务层的高效协同。系统采用前后端分离设计,通过MQTT协议将分布在物理空间的洗车设备接入云端,形成完整的"用户-平台-设备"闭环。
从技术栈来看,前端使用UniApp实现跨端兼容是个明智选择——洗车场景下用户可能随时通过小程序或APP操作,而管理人员需要PC端后台进行监控。后端采用Spring Cloud微服务架构,每个业务模块(用户、订单、支付等)独立部署,通过Nacos实现服务发现。这种架构特别适合需要7×24小时运行的无人值守系统,单个服务故障不会导致全线瘫痪。
关键设计原则:设备状态实时同步是系统稳定性的生命线。我们通过在Redis中维护设备心跳数据(每10秒更新),结合RocketMQ的削峰能力处理突发指令,确保在200ms内响应扫码请求。
用户扫码动作看似简单,背后涉及6个微服务的协同:
/api/device/validatestatus=IDLE)PENDING_PAYMENT)/device/{id}/start指令java复制// 设备状态检查的优化实现
public DeviceStatus checkDevice(String deviceId) {
// 优先从Redis获取(减少DB压力)
String cacheKey = "device:status:" + deviceId;
String status = redisTemplate.opsForValue().get(cacheKey);
if (StringUtils.isEmpty(status)) {
// 缓存未命中时查询数据库并重建缓存
status = deviceMapper.selectStatus(deviceId);
redisTemplate.opsForValue().set(cacheKey, status, 30, TimeUnit.SECONDS);
}
return DeviceStatus.valueOf(status);
}
预约功能的核心在于设备资源的最优分配。我们采用改进的贪心算法,考虑以下因素:
java复制// 设备分配逻辑增强版
public Device assignDevice(OrderRequest request) {
return availableDevices.stream()
.filter(d -> d.getCapacity() >= request.getCarType().getRequiredCapacity())
.min(Comparator.comparingDouble(d ->
calculateDistance(d, request) * 0.6
+ d.getQueueSize() * 0.3
+ d.getFailureRate() * 0.1))
.orElseThrow(() -> new DeviceNotAvailableException());
}
设备通信采用MQTT 3.1.1协议,关键配置参数:
java复制// 完整的MQTT连接管理
public class MqttCarWashClient {
private IMqttClient client;
private String deviceId;
public void connect() throws MqttException {
MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(60);
options.setSocketFactory(SSLContext.getDefault().getSocketFactory());
client = new MqttClient(brokerUrl, "server_"+deviceId);
client.connect(options);
// 订阅设备状态主题
client.subscribe("/carwash/"+deviceId+"/status", 0, this::handleStatusMessage);
}
private void handleStatusMessage(String topic, MqttMessage message) {
CarWashStatus status = parseMessage(message);
deviceCache.updateStatus(deviceId, status);
if(status.isEmergencyStop()) {
alertService.notifyMaintenance(deviceId);
}
}
}
支付环节最易出现并发问题,我们采用Redis分布式锁+乐观锁方案:
order_token(UUID)java复制// 支付回调的线程安全处理
@Transactional
public PaymentResult handleCallback(PaymentNotify notify) {
// 分布式锁防止重复处理
String lockKey = "pay:lock:" + notify.getOrderId();
boolean locked = redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS);
if (!locked) throw new ConcurrentPaymentException();
try {
Order order = orderMapper.selectForUpdate(notify.getOrderId());
if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
return PaymentResult.duplicate();
}
// 乐观锁更新
int updated = orderMapper.updateStatus(
notify.getOrderId(),
OrderStatus.PENDING_PAYMENT,
OrderStatus.PAID,
order.getVersion());
if (updated == 0) throw new OptimisticLockException();
// 触发设备启动
mqttClient.publishStartCommand(order.getDeviceId());
return PaymentResult.success();
} finally {
redisLock.unlock(lockKey);
}
}
我们遇到最频繁的问题是网络抖动导致设备离线,解决方案:
java复制// 设备断线重连实现
public void reconnect() {
int retry = 0;
while (true) {
try {
if (client.isConnected()) return;
connect();
retry = 0; // 重置重试计数器
break;
} catch (Exception e) {
retry++;
long waitTime = Math.min(1000 * (1 << retry), 60000);
Thread.sleep(waitTime);
if (retry > 10) {
alertService.notifyCritical(deviceId);
break;
}
}
}
}
在促销活动期间,我们遭遇过订单状态不一致的情况,最终通过以下方案解决:
sql复制-- 对账任务的SQL示例
SELECT o.order_id, o.status, p.status as payment_status
FROM orders o
LEFT JOIN payments p ON o.order_id = p.order_id
WHERE o.status = 'PAID' AND (p.status IS NULL OR p.status != 'SUCCESS')
经过压力测试和线上调优,系统达到以下性能基准:
优化手段包括:
yaml复制# 部分关键JVM参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=35
-XX:ConcGCThreads=4
-Xms4g -Xmx4g
java复制// 设备认证拦截器实现
public class DeviceAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String deviceId = request.getHeader("X-Device-ID");
String signature = request.getHeader("X-Device-Sign");
DeviceCert cert = certCache.get(deviceId);
if (cert == null || !verifySignature(cert.getPublicKey(), signature)) {
throw new UnauthorizedDeviceException();
}
return true;
}
}
java复制// 支付金额防篡改校验
public void validateAmount(String orderId, Integer requestAmount) {
Order order = orderService.getById(orderId);
if (order == null) throw new OrderNotFoundException();
// 金额以分为单位比较
if (order.getTotalFee().compareTo(requestAmount) != 0) {
log.warn("金额不一致 order:{}, request:{}", order.getTotalFee(), requestAmount);
throw new AmountMismatchException();
}
}
yaml复制# Prometheus的告警规则示例
- alert: HighDeviceTimeoutRate
expr: rate(device_timeout_total[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "设备响应超时率过高"
description: "实例 {{ $labels.instance }} 超时率 {{ $value }}"
为确保系统稳定性,我们采用分阶段发布:
bash复制# 典型的金丝雀发布命令
kubectl set image deployment/carwash-service \
carwash-service=registry.example.com/carwash:v2.1.0 \
--record --dry-run=client