充电宝业务实现

# 充电宝业务实现

# 业务概述

充电宝租赁业务是一种基于物联网技术的共享经济模式,用户可以通过移动应用扫码租借充电宝,使用完毕后归还到任意网点。

# 系统架构

# 整体架构

用户端APP <-> API网关 <-> 业务服务 <-> 数据库
                    |
                    v
                设备管理服务 <-> 充电宝设备

# 核心组件

  1. 用户服务:用户注册、登录、实名认证
  2. 订单服务:租借订单管理、计费结算
  3. 设备服务:充电宝设备管理、状态监控
  4. 支付服务:支付、退款、押金管理
  5. 位置服务:网点管理、设备定位

# 核心业务流程

# 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);

# 总结

充电宝业务系统涉及用户管理、设备控制、订单处理、支付结算等多个模块,需要考虑高并发、分布式、实时性等技术挑战。通过合理的架构设计和技术选型,可以构建一个稳定可靠的充电宝租赁平台。