
原文链接:从 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
);
}
// 降级处理(车位服务不可用时返回默认值)
@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("车位状态更新失败,已记录异常");
}
}计费服务中调用示例:
@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 依赖:
<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();
}设备数据发送逻辑:
@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. 消息消费者(车位状态更新服务)
消费逻辑实现:
@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):
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)
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. 事务实现(车位服务侧)
@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. 调用端(计费服务)
@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)
针对车主端高频查询接口(如车位列表)设置限流,避免瞬时流量压垮服务。
网关配置类:
@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")
);
}
}五、核心技术选型清单
| 技术组件 | 版本 | 作用 | 选型理由 |
|---|---|---|---|
| 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 增强熔断能力)。如需完整项目源码,可留言或与我联系获取

