在城市化进程加速的今天,停车难问题日益突出。传统车位管理方式主要依靠人工登记和纸质记录,这种方式存在诸多弊端:信息更新不及时、查询效率低下、数据易丢失且难以统计分析。以一个中型小区为例,500个车位如果采用人工管理,仅每日的进出记录就需要2名专职人员花费4小时处理,且容易出错。
我们开发的这套车位租赁系统正是为了解决这些痛点。系统采用B/S架构,前端使用Vue.js实现响应式界面,后端基于Spring+SpringMVC+MyBatis(SSM)框架,数据库选用MySQL 8.0。系统主要实现以下核心功能:
实际开发中发现,传统人工管理模式下,车位利用率通常只有60%-70%,而系统化管理后可以提升到85%以上,显著提高了资源使用效率。
系统采用经典的三层架构:
code复制表现层(Vue.js)
↓
业务逻辑层(Spring+SpringMVC)
↓
数据访问层(MyBatis+MySQL)
这种分层设计的优势在于:
MySQL表设计遵循第三范式,主要表包括:
t_user(用户表)t_parking_space(车位表)t_order(订单表)t_message(留言表)关键字段示例:
sql复制CREATE TABLE `t_parking_space` (
`id` bigint NOT NULL AUTO_INCREMENT,
`space_no` varchar(20) NOT NULL COMMENT '车位编号',
`location` varchar(100) NOT NULL COMMENT '具体位置',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-空闲 1-已租 2-预约中',
`price` decimal(10,2) NOT NULL COMMENT '月租金',
`image_url` varchar(255) DEFAULT NULL COMMENT '车位照片',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_space_no` (`space_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
后端Controller示例:
java复制@RestController
@RequestMapping("/api/parking")
public class ParkingSpaceController {
@Autowired
private ParkingSpaceService spaceService;
@GetMapping("/list")
public Result list(@RequestParam Map<String,Object> params) {
PageUtils page = spaceService.queryPage(params);
return Result.ok().put("page", page);
}
@PostMapping("/save")
public Result save(@RequestBody ParkingSpaceEntity space) {
spaceService.save(space);
return Result.ok();
}
}
前端Vue组件关键代码:
vue复制<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="spaceNo" label="车位编号" />
<el-table-column prop="location" label="位置" />
<el-table-column prop="status" label="状态">
<template #default="{row}">
<el-tag :type="statusMap[row.status].type">
{{ statusMap[row.status].text }}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
statusMap: {
0: { text: '空闲', type: 'success' },
1: { text: '已租', type: 'danger' },
2: { text: '预约中', type: 'warning' }
}
}
}
}
</script>
采用WebSocket实现状态推送:
java复制@ServerEndpoint("/ws/parking")
public class ParkingSpaceWebSocket {
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
sessions.put(session.getId(), session);
}
public static void sendStatusUpdate(ParkingSpace space) {
String message = JSON.toJSONString(space);
sessions.values().forEach(session -> {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("WebSocket发送失败", e);
}
});
}
}
mermaid复制stateDiagram
[*] --> UNPAID
UNPAID --> PAID: 支付成功
UNPAID --> CANCELLED: 用户取消
PAID --> COMPLETED: 租赁到期
PAID --> REFUNDED: 申请退款
对接支付宝沙箱环境示例:
java复制public class AlipayService {
public String createOrder(Order order) {
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipaydev.com/gateway.do",
APP_ID,
APP_PRIVATE_KEY,
"json",
"UTF-8",
ALIPAY_PUBLIC_KEY,
"RSA2");
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", order.getOrderNo());
bizContent.put("total_amount", order.getAmount());
bizContent.put("subject", "车位租赁-" + order.getSpaceNo());
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toString());
return alipayClient.pageExecute(request).getBody();
}
}
采用RBAC模型,权限关系如下:
code复制用户 -- 角色 -- 权限
|
菜单
Shiro配置示例:
java复制@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/api/admin/**", "roles[admin]");
filterMap.put("/api/user/**", "authc");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
java复制@Service
@CacheConfig(cacheNames = "parking")
public class ParkingSpaceServiceImpl implements ParkingSpaceService {
@Cacheable(key = "'list:' + #params.hashCode()")
public PageUtils queryPage(Map<String, Object> params) {
// 数据库查询
}
@CacheEvict(allEntries = true)
public void update(ParkingSpace space) {
// 更新操作
}
}
sql复制CREATE INDEX idx_location ON t_parking_space(location);
CREATE INDEX idx_status ON t_parking_space(status);
dockerfile复制# 后端服务Dockerfile
FROM openjdk:11-jre
COPY target/parking-system.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 前端Dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
Nginx配置示例:
nginx复制server {
listen 80;
server_name parking.example.com;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
}
使用JMeter进行测试,配置:
结果:
| 指标 | 平均值 | 90%线 |
|---|---|---|
| 响应时间 | 128ms | 215ms |
| 吞吐量 | 785/sec | - |
| 错误率 | 0% | - |
现象:多个用户同时看到空闲车位并尝试租赁
解决方案:
java复制@Update("UPDATE t_parking_space SET status=#{status}, version=version+1
WHERE id=#{id} AND version=#{version}")
int updateWithVersion(ParkingSpace space);
java复制public boolean lock(String key, long expire) {
return redisTemplate.opsForValue()
.setIfAbsent(key, "1", expire, TimeUnit.SECONDS);
}
注意事项:
在实际开发过程中,有几个关键点值得特别注意:
状态一致性:车位状态管理是核心难点,我们最终采用"数据库锁+缓存+消息队列"的多重保障机制。测试阶段发现,单纯依赖数据库事务在高并发时仍会出现超卖问题,引入Redis后性能提升明显。
支付流程:与第三方支付对接时,回调接口的安全验证至关重要。我们采用双验证机制(签名验证+订单状态检查),有效防止了重复支付和虚假支付问题。
前端性能:车位地图展示初期性能较差,通过以下优化显著提升:
监控体系:上线后建立了完善的监控:
这个项目让我深刻体会到,一个完整的系统开发不仅需要掌握技术栈,更需要考虑实际业务场景中的各种边界情况。比如在车位预约功能中,最初设计没有考虑预约超时释放,导致大量车位被"僵尸预约"占用,后来增加了30分钟自动释放机制才解决问题。