充电宝业务实现
# 充电宝业务实现
# 业务概述
充电宝租赁业务是一种基于物联网技术的共享经济模式,用户可以通过移动应用扫码租借充电宝,使用完毕后归还到任意网点。
# 系统架构
# 整体架构
用户端APP <-> API网关 <-> 业务服务 <-> 数据库
|
v
设备管理服务 <-> 充电宝设备
# 核心组件
- 用户服务:用户注册、登录、实名认证
- 订单服务:租借订单管理、计费结算
- 设备服务:充电宝设备管理、状态监控
- 支付服务:支付、退款、押金管理
- 位置服务:网点管理、设备定位
# 核心业务流程
# 1. 用户注册与认证
@Service
public class UserService {
/**
* 用户注册
*/
public UserRegisterResponse register(UserRegisterRequest request) {
// 1. 验证手机号格式
validatePhoneNumber(request.getPhoneNumber());
// 2. 发送验证码
smsService.sendVerificationCode(request.getPhoneNumber());
// 3. 创建用户账户
User user = new User();
user.setPhoneNumber(request.getPhoneNumber());
user.setStatus(UserStatus.UNVERIFIED);
userRepository.save(user);
return UserRegisterResponse.success();
}
/**
* 实名认证
*/
public void realNameAuth(Long userId, String realName, String idCard) {
// 1. 调用第三方实名认证接口
AuthResult result = authService.authenticate(realName, idCard);
if (result.isSuccess()) {
// 2. 更新用户状态
User user = userRepository.findById(userId);
user.setRealName(realName);
user.setIdCard(idCard);
user.setStatus(UserStatus.VERIFIED);
userRepository.save(user);
}
}
}
# 2. 设备扫码租借
@Service
public class RentalService {
/**
* 扫码租借充电宝
*/
@Transactional
public RentalResponse rentPowerBank(Long userId, String deviceCode) {
// 1. 验证用户状态
User user = userService.getUser(userId);
if (!user.isVerified()) {
throw new BusinessException("用户未实名认证");
}
// 2. 检查设备状态
PowerBankDevice device = deviceService.getByCode(deviceCode);
if (!device.isAvailable()) {
throw new BusinessException("设备不可用");
}
// 3. 检查用户是否有未归还订单
if (orderService.hasUnreturnedOrder(userId)) {
throw new BusinessException("存在未归还的充电宝");
}
// 4. 创建租借订单
RentalOrder order = new RentalOrder();
order.setUserId(userId);
order.setDeviceId(device.getId());
order.setStartTime(LocalDateTime.now());
order.setStatus(OrderStatus.RENTING);
orderRepository.save(order);
// 5. 更新设备状态
device.setStatus(DeviceStatus.RENTED);
device.setCurrentUserId(userId);
deviceRepository.save(device);
// 6. 发送开锁指令
deviceControlService.unlock(device.getId());
return RentalResponse.success(order.getId());
}
}
# 3. 充电宝归还
@Service
public class ReturnService {
/**
* 归还充电宝
*/
@Transactional
public ReturnResponse returnPowerBank(Long userId, String stationCode, String deviceCode) {
// 1. 查找用户当前租借订单
RentalOrder order = orderService.getCurrentOrder(userId);
if (order == null) {
throw new BusinessException("无有效租借订单");
}
// 2. 验证归还设备
PowerBankDevice device = deviceService.getByCode(deviceCode);
if (!device.getId().equals(order.getDeviceId())) {
throw new BusinessException("设备不匹配");
}
// 3. 验证归还网点
Station station = stationService.getByCode(stationCode);
if (!station.isActive()) {
throw new BusinessException("归还网点不可用");
}
// 4. 计算费用
BigDecimal totalFee = calculateFee(order.getStartTime(), LocalDateTime.now());
// 5. 更新订单状态
order.setEndTime(LocalDateTime.now());
order.setReturnStationId(station.getId());
order.setTotalFee(totalFee);
order.setStatus(OrderStatus.COMPLETED);
orderRepository.save(order);
// 6. 更新设备状态
device.setStatus(DeviceStatus.AVAILABLE);
device.setCurrentUserId(null);
device.setStationId(station.getId());
deviceRepository.save(device);
// 7. 扣费
paymentService.charge(userId, totalFee, order.getId());
return ReturnResponse.success(totalFee);
}
/**
* 计算租借费用
*/
private BigDecimal calculateFee(LocalDateTime startTime, LocalDateTime endTime) {
Duration duration = Duration.between(startTime, endTime);
long hours = duration.toHours();
// 按小时计费,不足1小时按1小时计算
if (duration.toMinutes() % 60 > 0) {
hours++;
}
// 每小时2元
return BigDecimal.valueOf(hours * 2);
}
}
# 设备管理
# 设备状态监控
@Component
public class DeviceMonitor {
/**
* 设备心跳监控
*/
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void monitorDeviceHeartbeat() {
List<PowerBankDevice> devices = deviceRepository.findAll();
for (PowerBankDevice device : devices) {
if (isDeviceOffline(device)) {
// 设备离线处理
handleDeviceOffline(device);
}
}
}
/**
* 处理设备离线
*/
private void handleDeviceOffline(PowerBankDevice device) {
// 1. 更新设备状态
device.setStatus(DeviceStatus.OFFLINE);
deviceRepository.save(device);
// 2. 如果设备正在被租借,通知用户
if (device.getCurrentUserId() != null) {
notificationService.notifyDeviceOffline(device.getCurrentUserId(), device.getId());
}
// 3. 发送告警
alertService.sendDeviceOfflineAlert(device);
}
}
# 设备远程控制
@Service
public class DeviceControlService {
/**
* 远程开锁
*/
public void unlock(Long deviceId) {
PowerBankDevice device = deviceRepository.findById(deviceId);
// 构建控制指令
DeviceCommand command = DeviceCommand.builder()
.deviceId(deviceId)
.command("UNLOCK")
.timestamp(System.currentTimeMillis())
.build();
// 发送到设备
mqttService.publish(device.getTopic(), command);
// 记录操作日志
deviceLogService.log(deviceId, "UNLOCK", "远程开锁");
}
/**
* 查询设备状态
*/
public DeviceStatus queryStatus(Long deviceId) {
PowerBankDevice device = deviceRepository.findById(deviceId);
DeviceCommand command = DeviceCommand.builder()
.deviceId(deviceId)
.command("QUERY_STATUS")
.timestamp(System.currentTimeMillis())
.build();
return mqttService.sendAndReceive(device.getTopic(), command, DeviceStatus.class);
}
}
# 支付系统
# 押金管理
@Service
public class DepositService {
/**
* 收取押金
*/
public void chargeDeposit(Long userId) {
User user = userService.getUser(userId);
// 检查是否已缴纳押金
if (user.getDepositStatus() == DepositStatus.PAID) {
return;
}
// 创建押金订单
DepositOrder order = new DepositOrder();
order.setUserId(userId);
order.setAmount(new BigDecimal("99.00")); // 押金99元
order.setStatus(DepositStatus.PENDING);
depositOrderRepository.save(order);
// 调用支付接口
PaymentResult result = paymentService.pay(order);
if (result.isSuccess()) {
// 更新用户押金状态
user.setDepositStatus(DepositStatus.PAID);
userRepository.save(user);
// 更新订单状态
order.setStatus(DepositStatus.PAID);
depositOrderRepository.save(order);
}
}
/**
* 退还押金
*/
public void refundDeposit(Long userId) {
User user = userService.getUser(userId);
// 检查是否有未归还订单
if (orderService.hasUnreturnedOrder(userId)) {
throw new BusinessException("存在未归还订单,无法退还押金");
}
// 查找押金订单
DepositOrder order = depositOrderRepository.findByUserId(userId);
// 执行退款
RefundResult result = paymentService.refund(order);
if (result.isSuccess()) {
// 更新用户状态
user.setDepositStatus(DepositStatus.REFUNDED);
userRepository.save(user);
}
}
}
# 数据模型
# 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`phone_number` varchar(11) NOT NULL COMMENT '手机号',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-未验证,1-已验证',
`deposit_status` tinyint NOT NULL DEFAULT '0' COMMENT '押金状态:0-未缴纳,1-已缴纳,2-已退还',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone_number`)
) ENGINE=InnoDB COMMENT='用户表';
# 设备表
CREATE TABLE `power_bank_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`device_code` varchar(32) NOT NULL COMMENT '设备编码',
`station_id` bigint DEFAULT NULL COMMENT '所属网点ID',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-可用,2-已租借,3-故障,4-离线',
`battery_level` int DEFAULT NULL COMMENT '电量百分比',
`current_user_id` bigint DEFAULT NULL COMMENT '当前使用用户ID',
`last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_device_code` (`device_code`),
KEY `idx_station_id` (`station_id`),
KEY `idx_current_user_id` (`current_user_id`)
) ENGINE=InnoDB COMMENT='充电宝设备表';
# 订单表
CREATE TABLE `rental_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`device_id` bigint NOT NULL COMMENT '设备ID',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`rent_station_id` bigint NOT NULL COMMENT '租借网点ID',
`return_station_id` bigint DEFAULT NULL COMMENT '归还网点ID',
`total_fee` decimal(10,2) DEFAULT NULL COMMENT '总费用',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-租借中,2-已完成,3-异常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='租借订单表';
# 技术要点
# 1. 分布式锁
使用Redis分布式锁防止同一设备被多人同时租借:
@Component
public class DistributedLock {
public boolean tryLock(String key, String value, long expireTime) {
String result = redisTemplate.execute((RedisCallback<String>) connection -> {
return connection.set(key.getBytes(), value.getBytes(),
Expiration.seconds(expireTime), RedisStringCommands.SetOption.SET_IF_ABSENT);
});
return "OK".equals(result);
}
}
# 2. 消息队列
使用RabbitMQ处理异步业务:
@RabbitListener(queues = "device.status.queue")
public void handleDeviceStatusChange(DeviceStatusMessage message) {
// 处理设备状态变更
deviceService.updateStatus(message.getDeviceId(), message.getStatus());
}
# 3. 缓存策略
使用Redis缓存热点数据:
@Cacheable(value = "device", key = "#deviceCode")
public PowerBankDevice getByCode(String deviceCode) {
return deviceRepository.findByDeviceCode(deviceCode);
}
# 运维监控
# 1. 业务监控指标
- 设备在线率
- 订单成功率
- 平均租借时长
- 收入统计
# 2. 告警规则
- 设备离线超过5分钟告警
- 订单异常率超过1%告警
- 支付失败率超过0.5%告警
# 3. 日志规范
// 业务日志
log.info("用户{}租借设备{}成功,订单号:{}", userId, deviceId, orderId);
// 错误日志
log.error("设备{}开锁失败,错误信息:{}", deviceId, e.getMessage(), e);
# 总结
充电宝业务系统涉及用户管理、设备控制、订单处理、支付结算等多个模块,需要考虑高并发、分布式、实时性等技术挑战。通过合理的架构设计和技术选型,可以构建一个稳定可靠的充电宝租赁平台。