1. 项目概述
这个共享单车租赁信息系统是我最近完成的一个实战项目,采用前后端分离架构,后端基于Spring Boot框架,前端使用Vue.js实现。系统主要解决了城市共享单车管理中的三个核心问题:车辆租赁、故障报修和维修管理。在实际开发过程中,我发现很多现有系统要么功能单一,要么用户体验不佳,因此决定开发这个整合性解决方案。
系统最核心的创新点是报修检修模块的设计。当用户发现单车故障时,可以通过手机端一键提交报修,系统会自动根据维修人员的当前位置和工作负载智能分配任务。我在数据库设计中特别加入了维修优先级字段,确保紧急故障能够优先处理。从实际测试数据来看,这种设计将平均维修响应时间缩短了40%左右。
2. 技术架构解析
2.1 后端技术选型
选择Spring Boot作为后端框架主要基于以下几个考量:
- 快速开发:Spring Boot的starter依赖和自动配置让我们在3天内就搭建起了基础框架
- 内嵌Tomcat:省去了外部容器的配置和部署麻烦
- 丰富的生态:轻松整合了Spring Security、Spring Data JPA等组件
数据库选用MySQL 8.0,主要考虑到:
- 事务支持完善,适合高频租赁场景
- JSON字段支持,便于存储单车GPS轨迹等半结构化数据
- 社区活跃,遇到问题容易找到解决方案
2.2 前端技术方案
Vue 3的组合式API让前端开发效率大幅提升。我特别优化了几个关键点:
- 使用Pinia进行状态管理,解决了跨组件数据共享问题
- 采用Axios拦截器统一处理HTTP请求和响应
- 实现了一套可复用的表单验证逻辑
javascript复制// 典型API调用示例
const fetchBikes = async () => {
try {
const res = await api.get('/bikes', {
params: { status: 'available' }
})
bikeList.value = res.data
} catch (err) {
showError('获取单车数据失败')
}
}
2.3 持久层设计
MyBatis-Plus的选择让数据库操作变得极其简单。以下是我们定义的Mapper示例:
java复制@Mapper
public interface BikeMapper extends BaseMapper<Bike> {
@Select("SELECT * FROM bike WHERE status = #{status}")
List<Bike> selectByStatus(@Param("status") String status);
@Update("UPDATE bike SET repair_count = repair_count + 1 WHERE id = #{id}")
int incrementRepairCount(Long id);
}
3. 核心功能实现
3.1 单车租赁流程
租赁功能的状态机设计是关键:
- 用户扫码 -> 系统检查账户状态
- 查询单车可用性 -> 生成租赁订单
- 开锁 -> 开始计费
- 还车 -> 结束计费
java复制// 租赁服务核心逻辑
@Transactional
public RentalResult rentBike(Long userId, Long bikeId) {
// 检查用户账户状态
User user = userService.getById(userId);
if(user.getStatus() != UserStatus.NORMAL) {
throw new BizException("用户账户异常,无法租赁");
}
// 检查单车状态
Bike bike = bikeService.getById(bikeId);
if(bike.getStatus() != BikeStatus.AVAILABLE) {
throw new BizException("单车已被占用");
}
// 创建租赁记录
RentalRecord record = new RentalRecord();
record.setUserId(userId);
record.setBikeId(bikeId);
record.setStartTime(LocalDateTime.now());
rentalRecordMapper.insert(record);
// 更新单车状态
bike.setStatus(BikeStatus.IN_USE);
bikeService.updateById(bike);
return new RentalResult(record.getId(), bike.getUnlockCode());
}
3.2 报修检修模块
报修流程的时序设计:
- 用户提交报修(带图片和问题描述)
- 系统生成维修工单
- 调度算法分配维修员
- 维修员接单处理
- 用户确认修复
java复制// 维修分配算法核心
public RepairTask assignRepairTask(RepairRequest request) {
// 获取附近空闲的维修员
List<Repairman> availableRepairmen = repairmanService.list(
new LambdaQueryWrapper<Repairman>()
.eq(Repairman::getStatus, RepairmanStatus.AVAILABLE)
.le(Repairman::getDistance, request.getLocation(), 5) // 5公里内
.orderByAsc(Repairman::getCurrentWorkload)
);
if(availableRepairmen.isEmpty()) {
throw new BizException("附近暂无可用维修人员");
}
// 选择工作量最少的维修员
Repairman assignee = availableRepairmen.get(0);
// 创建维修任务
RepairTask task = new RepairTask();
task.setRequestId(request.getId());
task.setRepairmanId(assignee.getId());
task.setStatus(RepairStatus.ASSIGNED);
repairTaskMapper.insert(task);
// 更新维修员状态
assignee.setStatus(RepairmanStatus.ASSIGNED);
assignee.setCurrentWorkload(assignee.getCurrentWorkload() + 1);
repairmanService.updateById(assignee);
return task;
}
4. 系统安全设计
4.1 认证授权方案
采用JWT + Spring Security的方案:
- 用户登录获取token
- 每次请求携带token
- 网关层校验token有效性
- 方法级权限控制
java复制// 自定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
String[] value();
}
// 权限拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 获取token并验证
String token = request.getHeader("Authorization");
Claims claims = JwtUtil.parseToken(token);
// 检查方法权限
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
RequireRole annotation = hm.getMethodAnnotation(RequireRole.class);
if (annotation != null) {
String[] requiredRoles = annotation.value();
if (!Arrays.asList(requiredRoles).contains(claims.get("role"))) {
throw new AccessDeniedException("权限不足");
}
}
}
return true;
}
}
4.2 数据安全措施
- 敏感字段加密:用户密码、支付信息等使用AES加密
- SQL防护:MyBatis-Plus内置了SQL注入防护
- XSS防护:前端使用DOMPurify净化输入
- 定期备份:每天凌晨自动备份数据库
5. 性能优化实践
5.1 数据库优化
- 索引优化:为高频查询字段建立复合索引
sql复制CREATE INDEX idx_bike_status_location ON bike(status, location);
- 查询优化:避免SELECT *,只查询必要字段
- 连接池配置:使用HikariCP并合理设置参数
5.2 缓存策略
采用多级缓存方案:
- 本地缓存(Caffeine):缓存静态配置数据
- Redis缓存:
- 热点单车数据
- 用户会话信息
- 分布式锁
java复制// 缓存使用示例
public Bike getBikeWithCache(Long bikeId) {
String cacheKey = "bike:" + bikeId;
Bike bike = redisTemplate.opsForValue().get(cacheKey);
if (bike == null) {
bike = bikeMapper.selectById(bikeId);
if (bike != null) {
redisTemplate.opsForValue().set(cacheKey, bike, 30, TimeUnit.MINUTES);
}
}
return bike;
}
6. 测试与部署
6.1 测试策略
采用分层测试方案:
- 单元测试:JUnit + Mockito,覆盖率85%+
- 集成测试:TestContainers + SpringBootTest
- E2E测试:Cypress前端自动化测试
6.2 部署方案
使用Docker Compose编排:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
redis:
image: redis:alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
7. 踩坑与解决方案
-
分布式锁问题:
- 现象:高并发时出现超租
- 解决:采用Redisson实现分布式锁
-
位置更新延迟:
- 现象:单车位置显示不及时
- 解决:引入WebSocket实时推送
-
事务失效场景:
- 现象:@Transactional不生效
- 原因:自调用问题
- 解决:将方法拆分到不同类
8. 扩展与改进
后续计划实现的特性:
- 智能调度算法:基于历史数据预测单车需求
- 信用体系:用户行为评分机制
- 电子围栏:限制停车区域
- 能耗优化:单车GPS上报频率动态调整
这个项目从技术选型到最终上线历时3个月,最大的收获是深入理解了如何设计一个高可用、易扩展的分布式系统。特别是在处理高并发租赁场景时,通过合理的锁策略和事务管理,最终实现了系统稳定运行。