关于我们 / 企业动态
解决方案 3 分钟阅读

从 0 到 1 搭智能路侧停车系统:SpringCloud Nacos/Feign/Seata 全链路实现(源码可复用)

SpringCloud路侧停车系统核心技术实现聚焦服务通信、设备数据处理、分布式事务三大场景。 1. 服务注册与通信 采用Nacos服务发现与Feign声明式调用 车位状态变更时通过Feign触发计费服务(含降级处理) 2. 设备数据异步处

从 0 到 1 搭智能路侧停车系统:SpringCloud Nacos/Feign/Seata 全链路实现(源码可复用)

原文链接:从 0 到 1 搭智能路侧停车系统:SpringCloud Nacos/Feign/Seata 全链路实现(源码可复用)

SpringCloud 路侧停车系统核心技术实现

以下补充 SpringCloud 智慧路侧停车系统的核心技术实现细节与源码片段,聚焦服务通信、设备数据处理、分布式事务三大关键场景,代码可直接复用或作为参考模板。

一、服务注册与跨服务通信(基于 Nacos+Feign)

路侧停车系统中,车位状态服务与计费服务需实时通信(如车位占用状态变更触发计费开始),通过 Nacos 实现服务发现,Feign 实现声明式调用。

1. 服务注册配置(Nacos)

pom.xml 核心依赖

XML
<dependency>

<groupId>com.alibaba.cloud\</groupId>

<artifactId>spring-cloud-starter-alibaba-nacos-discovery\</artifactId>

<version>2.2.7.RELEASE\</version>

</dependency>

application.yml(车位服务配置)

CODE
spring:

 application:

	name: parking-space-service  # 服务名,用于Feign调用

		cloud:

		   nacos:

		    discovery:

			      server-addr: 192.168.X.X:8848  # Nacos注册中心地址

			      namespace: parking-prod  # 生产环境命名空间

2. Feign 跨服务调用(计费服务调用车位服务)

Feign 客户端定义(计费服务中)

JAVA
// 声明调用"车位服务"的接口

@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

   );

}

// 降级处理(车位服务不可用时返回默认值)

@Component

public class ParkingSpaceFallback implements ParkingSpaceClient {

   @Override

   public Result\<Integer> getSpaceStatus(String spaceId) {

       return Result.fail("车位服务暂不可用,请稍后重试");

   }

   @Override

   public Result\<Boolean> updateSpaceStatus(String spaceId, Integer status, String carNo) {

       return Result.fail("车位状态更新失败,已记录异常");

   }

}

计费服务中调用示例

JAVA
@Service

public class BillingService {

  @Autowired

  private 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 依赖

XML
<dependency>

   <groupId>org.springframework.cloud</groupId>

   <artifactId>spring-cloud-starter-stream-rabbit</artifactId>

   <version>3.1.4</version>

</dependency>

消息通道定义

JAVA
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();

}

设备数据发送逻辑

JAVA
@Service

public class DeviceDataService {

 @Autowired

 private 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. 消息消费者(车位状态更新服务)

消费逻辑实现

JAVA
@Service

public class DeviceDataConsumer {

  @Autowired

  private 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)

CODE
spring:

 cloud:

   stream:

     bindings:

       device-data-output:

         destination: device.data.exchange  # 交换机名称

         content-type: application/json

         binder: rabbit

       device-data-input:

         destination: device.data.exchange

         content-type: application/json

         group: space-service-group  # 消费组,避免重复消费

         binder: rabbit

     rabbit:

       bindings:

         device-data-input:

           consumer:

             durable-subscription: true  # 持久化订阅,避免重启丢失消息

             max-concurrency: 10  # 最大并发消费者数量

三、分布式事务处理(Seata TCC 模式)

车辆入场时需同时完成「车位状态更新」和「计费订单创建」,采用 Seata TCC 模式保证一致性。

1. 事务接口定义(TCC Try/Confirm/Cancel)

JAVA
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. 事务实现(车位服务侧)

JAVA
@Service

public class ParkingTccServiceImpl implements ParkingTccService {

   @Autowired

   private ParkingSpaceMapper spaceMapper;

   @Autowired

   private RedissonClient redissonClient;  // 分布式锁

   @Override

   @Transactional

   public 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

   @Transactional

   public 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

   @Transactional

   public 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. 调用端(计费服务)

JAVA
@Service

public class BillingTccClient {

   @Autowired

   private ParkingTccService parkingTccService;

   @Autowired

   private 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)

针对车主端高频查询接口(如车位列表)设置限流,避免瞬时流量压垮服务。

网关配置类

JAVA
@Configuration

public class GatewayConfig {

   @Bean

   public 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次/分钟)

   @Bean

   public RedisRateLimiter redisRateLimiter() {

       return new RedisRateLimiter(100, 200); // 令牌桶容量200,每秒填充100/60≈1.67个令牌

   }

   // 限流key解析器(按IP地址限流)

   @Bean

   public KeyResolver userKeyResolver() {

       return exchange -> Mono.just(

           Optional.ofNullable(exchange.getRequest().getRemoteAddress())

               .map(InetSocketAddress::getHostString)

               .orElse("default-ip")

       );

   }

}

五、核心技术选型清单

技术组件版本作用选型理由
SpringCloudHoxton.SR12微服务框架基础生态成熟,组件丰富
SpringCloud Alibaba2.2.7.RELEASE服务注册 / 配置中心国内场景适配好,Nacos 易用性强
SpringCloud Gateway2.2.9.RELEASEAPI 网关非阻塞,支持动态路由与限流
SpringCloud Stream3.1.4消息驱动开发屏蔽 MQ 差异,便于切换 RabbitMQ/Kafka
Seata1.4.2分布式事务TCC 模式适配停车场景的跨服务一致性
Redis6.2.6缓存 / 分布式锁高性能,支持多种数据结构
MyBatis-Plus3.5.1ORM 框架简化 CRUD 操作,支持分页 / 条件查询

以上代码片段覆盖了路侧停车系统的服务通信、设备数据处理、分布式事务、流量控制核心场景,实际开发中可根据业务复杂度扩展(如增加 ElasticSearch 实现车位热点分析、集成 Sentinel 增强熔断能力)。如需完整项目源码,可留言或与我联系获取

想看更多与您场景匹配的落地案例?

立即咨询