从 0 到 1 搭智能路侧停车系统:SpringCloud Nacos/Feign/Seata 全链路实现(源码可复用)
SpringCloud 路侧停车系统核心技术实现
以下补充 SpringCloud 智慧路侧停车系统的核心技术实现细节与源码片段,聚焦服务通信、设备数据处理、分布式事务三大关键场景,代码可直接复用或作为参考模板。
一、服务注册与跨服务通信(基于 Nacos+Feign)
路侧停车系统中,车位状态服务与计费服务需实时通信(如车位占用状态变更触发计费开始),通过 Nacos 实现服务发现,Feign 实现声明式调用。
1. 服务注册配置(Nacos)
pom.xml 核心依赖:
<dependency><groupId>com.alibaba.cloud\</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery\</artifactId><version>2.2.7.RELEASE\</version></dependency>
application.yml(车位服务配置):
spring:application:name: parking-space-service # 服务名,用于Feign调用cloud:nacos:discovery:server-addr: 192.168.X.X:8848 # Nacos注册中心地址namespace: parking-prod # 生产环境命名空间
2. Feign 跨服务调用(计费服务调用车位服务)
Feign 客户端定义(计费服务中):
// 声明调用"车位服务"的接口@FeignClient(name = "parking-space-service", fallback = ParkingSpaceFallback.class)public interface ParkingSpaceClient {/\*\*\* 查询车位当前状态\* @param spaceId 车位ID(如"road-001-005"表示001路段第5个车位)\* @return 车位状态(1-占用,0-空闲)\*/@GetMapping("/api/v1/space/status")Result\<Integer> getSpaceStatus(@RequestParam("spaceId") String spaceId);/\*\*\* 更新车位状态(入场时标记为占用)\* @param spaceId 车位ID\* @param status 状态(1-占用)\* @param carNo 车牌号\*/@PostMapping("/api/v1/space/update")Result\<Boolean> updateSpaceStatus(@RequestParam("spaceId") String spaceId,@RequestParam("status") Integer status,@RequestParam("carNo") String carNo);}// 降级处理(车位服务不可用时返回默认值)@Componentpublic class ParkingSpaceFallback implements ParkingSpaceClient {@Overridepublic Result\<Integer> getSpaceStatus(String spaceId) {return Result.fail("车位服务暂不可用,请稍后重试");}@Overridepublic Result\<Boolean> updateSpaceStatus(String spaceId, Integer status, String carNo) {return Result.fail("车位状态更新失败,已记录异常");}}
计费服务中调用示例:
@Servicepublic class BillingService {@Autowiredprivate ParkingSpaceClient parkingSpaceClient;/\*\*\* 车辆入场时触发计费初始化\*/public void initBilling(String spaceId, String carNo) {// 1. 调用车位服务确认状态并更新为"占用"Result\<Boolean> updateResult = parkingSpaceClient.updateSpaceStatus(spaceId, 1, carNo);if (!updateResult.isSuccess()) {throw new BusinessException("车位状态更新失败,入场失败");}// 2. 创建计费订单(省略订单创建逻辑)createBillingOrder(spaceId, carNo, LocalDateTime.now());}}
二、设备数据实时接入(SpringCloud Stream + RabbitMQ)
路侧摄像头 / 地磁设备每 3 秒上传一次车位状态数据,需通过消息队列异步处理,避免设备请求阻塞。
1. 消息生产者(设备接入服务)
pom.xml 依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-rabbit</artifactId><version>3.1.4</version></dependency>
消息通道定义:
public interface DeviceChannel {String DEVICE\_DATA\_INPUT = "device-data-input"; // 消费者通道String DEVICE\_DATA\_OUTPUT = "device-data-output"; // 生产者通道@Output(DEVICE\_DATA\_OUTPUT)MessageChannel output();@Input(DEVICE\_DATA\_INPUT)SubscribableChannel input();}
设备数据发送逻辑:
@Servicepublic class DeviceDataService {@Autowiredprivate DeviceChannel deviceChannel;/*** 接收设备上传的原始数据并发送到消息队列*/public void receiveDeviceData(DeviceDataDTO data) {// 设备数据格式:{spaceId: "road-001-005", status: 1, carNo: "京A12345", uploadTime: 1620000000000}Message\<DeviceDataDTO> message = MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT\_TYPE, MimeTypeUtils.APPLICATION\_JSON).build();// 发送到RabbitMQ队列boolean sendResult = deviceChannel.output().send(message);if (!sendResult) {log.error("设备数据发送失败:{}", data);// 失败时存入本地缓存,定时重试retryFailedDataCache.put(data.getSpaceId(), data);}}}
2. 消息消费者(车位状态更新服务)
消费逻辑实现:
@Servicepublic class DeviceDataConsumer {@Autowiredprivate ParkingSpaceService parkingSpaceService;/\*\** 消费设备数据,更新车位状态*/@StreamListener(DeviceChannel.DEVICE\_DATA\_INPUT)public void handleDeviceData(Message\<DeviceDataDTO> message) {DeviceDataDTO data = message.getPayload();log.info("收到设备数据:{}", data);// 1. 校验数据合法性(如车牌格式、车位ID格式)if (!validateDeviceData(data)) {log.warn("无效设备数据:{}", data);return;}// 2. 更新车位状态到数据库+Redis缓存parkingSpaceService.updateStatus(data.getSpaceId(), data.getStatus(), data.getCarNo(),LocalDateTime.ofEpochMilli(data.getUploadTime())); // 3. 若状态为"占用"且无计费订单,触发入场逻辑(通过Feign调用计费服务)if (data.getStatus() == 1 && !hasBillingOrder(data.getSpaceId())) {billingClient.initBilling(data.getSpaceId(), data.getCarNo());}}}
RabbitMQ 绑定配置(application.yml):
spring:cloud:stream:bindings:device-data-output:destination: device.data.exchange # 交换机名称content-type: application/jsonbinder: rabbitdevice-data-input:destination: device.data.exchangecontent-type: application/jsongroup: space-service-group # 消费组,避免重复消费binder: rabbitrabbit:bindings:device-data-input:consumer:durable-subscription: true # 持久化订阅,避免重启丢失消息max-concurrency: 10 # 最大并发消费者数量
三、分布式事务处理(Seata TCC 模式)
车辆入场时需同时完成「车位状态更新」和「计费订单创建」,采用 Seata TCC 模式保证一致性。
1. 事务接口定义(TCC Try/Confirm/Cancel)
public interface ParkingTccService {/*** Try阶段:预检查并锁定资源*/@TwoPhaseBusinessAction(name = "parkingTcc", commitMethod = "confirm", rollbackMethod = "cancel")Result<Boolean> tryInitiateParking(@BusinessActionContextParameter(paramName = "spaceId") String spaceId,@BusinessActionContextParameter(paramName = "carNo") String carNo);/*** Confirm阶段:确认提交(Try成功后执行)*/Result<Boolean> confirm(BusinessActionContext context);/*** Cancel阶段:回滚(Try失败后执行)*/Result<Boolean> cancel(BusinessActionContext context);}
2. 事务实现(车位服务侧)
@Servicepublic class ParkingTccServiceImpl implements ParkingTccService {@Autowiredprivate ParkingSpaceMapper spaceMapper;@Autowiredprivate RedissonClient redissonClient; // 分布式锁@Override@Transactionalpublic Result<Boolean> tryInitiateParking(String spaceId, String carNo) {// 1. 加分布式锁,防止并发更新RLock lock = redissonClient.getLock("space:lock:" + spaceId);lock.lock(30, TimeUnit.SECONDS);try {// 2. 检查车位是否空闲ParkingSpace space = spaceMapper.selectById(spaceId);if (space == null || space.getStatus() != 0) {return Result.fail("车位不可用");}// 3. 预更新为"锁定中"(中间状态,避免其他请求干扰)space.setStatus(2); // 0-空闲,1-占用,2-锁定中space.setCarNo(carNo);spaceMapper.updateById(space);return Result.success(true);} finally {lock.unlock();}}@Override@Transactionalpublic Result<Boolean> confirm(BusinessActionContext context) {String spaceId = context.getActionContext("spaceId").toString();// 确认更新为"占用"ParkingSpace space = new ParkingSpace();space.setId(spaceId);space.setStatus(1);spaceMapper.updateById(space);return Result.success(true);}@Override@Transactionalpublic Result<Boolean> cancel(BusinessActionContext context) {String spaceId = context.getActionContext("spaceId").toString();// 回滚为"空闲"ParkingSpace space = new ParkingSpace();space.setId(spaceId);space.setStatus(0);space.setCarNo(null);spaceMapper.updateById(space);return Result.success(true);}}
3. 调用端(计费服务)
@Servicepublic class BillingTccClient {@Autowiredprivate ParkingTccService parkingTccService;@Autowiredprivate BillingMapper billingMapper;/*** 发起TCC事务:同时更新车位状态和创建订单*/@GlobalTransactional // Seata全局事务注解public Result<Boolean> initiateParkingAndBilling(String spaceId, String carNo) {// 1. 调用车位服务的Try方法Result<Boolean> parkingResult = parkingTccService.tryInitiateParking(spaceId, carNo);if (!parkingResult.isSuccess()) {throw new BusinessException("车位锁定失败");}// 2. 创建计费订单(本地事务)BillingOrder order = new BillingOrder();order.setSpaceId(spaceId);order.setCarNo(carNo);order.setStartTime(LocalDateTime.now());order.setStatus(0); // 0-未支付int insert = billingMapper.insert(order);if (insert <= 0) {// 订单创建失败,触发全局回滚(车位服务会执行cancel)throw new BusinessException("订单创建失败");}return Result.success(true);}}
四、API 网关限流与路由(SpringCloud Gateway)
针对车主端高频查询接口(如车位列表)设置限流,避免瞬时流量压垮服务。
网关配置类:
@Configurationpublic class GatewayConfig {@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()// 1. 车主端API路由.route("user-api-route", r -> r.path("/api/v1/user/**").filters(f -> f.rewritePath("/api/v1/user/(?<segment>.*)", "/${segment}").requestRateLimiter(c -> c // 限流配置.setRateLimiter(redisRateLimiter()).setKeyResolver(userKeyResolver())).addResponseHeader("X-Response-Time", LocalDateTime.now().toString())).uri("lb://user-service") // 负载均衡到用户服务)// 2. 车位查询API路由.route("space-api-route", r -> r.path("/api/v1/space/**").filters(f -> f.rewritePath("/api/v1/space/(?<segment>.*)", "/${segment}").circuitBreaker(c -> c // 熔断配置.setName("spaceServiceCircuitBreaker").setFallbackUri("forward:/fallback/space"))).uri("lb://parking-space-service")).build();}// 基于Redis的限流计算器(100次/分钟)@Beanpublic RedisRateLimiter redisRateLimiter() {return new RedisRateLimiter(100, 200); // 令牌桶容量200,每秒填充100/60≈1.67个令牌}// 限流key解析器(按IP地址限流)@Beanpublic KeyResolver userKeyResolver() {return exchange -> Mono.just(Optional.ofNullable(exchange.getRequest().getRemoteAddress()).map(InetSocketAddress::getHostString).orElse("default-ip"));}}
五、核心技术选型清单
技术组件 | 版本 | 作用 | 选型理由 |
---|---|---|---|
SpringCloud | Hoxton.SR12 | 微服务框架基础 | 生态成熟,组件丰富 |
SpringCloud Alibaba | 2.2.7.RELEASE | 服务注册 / 配置中心 | 国内场景适配好,Nacos 易用性强 |
SpringCloud Gateway | 2.2.9.RELEASE | API 网关 | 非阻塞,支持动态路由与限流 |
SpringCloud Stream | 3.1.4 | 消息驱动开发 | 屏蔽 MQ 差异,便于切换 RabbitMQ/Kafka |
Seata | 1.4.2 | 分布式事务 | TCC 模式适配停车场景的跨服务一致性 |
Redis | 6.2.6 | 缓存 / 分布式锁 | 高性能,支持多种数据结构 |
MyBatis-Plus | 3.5.1 | ORM 框架 | 简化 CRUD 操作,支持分页 / 条件查询 |
以上代码片段覆盖了路侧停车系统的服务通信、设备数据处理、分布式事务、流量控制核心场景,实际开发中可根据业务复杂度扩展(如增加 ElasticSearch 实现车位热点分析、集成 Sentinel 增强熔断能力)。如需完整项目源码,可留言或与我联系获取