随着移动互联网的普及,代驾小程序因其轻量便捷的特点受到用户青睐。本文将深入探讨代驾小程序的开发实现,通过具体代码示例展示核心功能的实现方法。

一、开发环境搭建

技术栈选择

  • 前端:微信小程序 + Taro多端框架

  • 数据库:MySQL + Redis

  • 地图服务:腾讯位置服务

  • 推送服务:微信模板消息

项目结构设计

daiJia-project/
├── daiJia-miniprogram/     # 小程序前端
├── daiJia-backend/         # 后端服务
├── daiJia-admin/           # 管理后台
└── daiJia-driver/          # 司机端

二、用户端核心功能实现

1. 用户定位与地址选择

// 小程序端定位代码示例
class LocationService {
  // 获取用户当前位置
  async getCurrentLocation() {
    return new Promise((resolve, reject) => {
      wx.getLocation({
        type: 'gcj02',
        success: (res) => {
          const location = {
            latitude: res.latitude,
            longitude: res.longitude,
            accuracy: res.accuracy
          };
          this.reverseGeocode(location).then(resolve);
        },
        fail: reject
      });
    });
  }
  
  // 逆地址解析
  async reverseGeocode(location) {
    const qqMap = require('./libs/qqmap-wx-jssdk.min.js');
    const mapSDK = new qqMap({
      key: 'YOUR_MAP_KEY'
    });
    
    return new Promise((resolve) => {
      mapSDK.reverseGeocoder({
        location: location,
        success: (res) => {
          resolve({
            ...location,
            address: res.result.address,
            formatted_address: res.result.formatted_addresses.recommend
          });
        }
      });
    });
  }
  
  // 地址搜索自动补全
  async searchAddress(keyword) {
    const url = `https://apis.map.qq.com/ws/place/v1/suggestion`;
    const params = {
      keyword,
      region: '全国',
      key: 'YOUR_MAP_KEY'
    };
    
    const res = await wx.request({
      url,
      data: params
    });
    
    return res.data.data;
  }
}

// 在页面中使用
Page({
  data: {
    currentAddress: '',
    addressList: []
  },
  
  onLoad() {
    this.locationService = new LocationService();
    this.initLocation();
  },
  
  async initLocation() {
    try {
      const location = await this.locationService.getCurrentLocation();
      this.setData({
        currentAddress: location.formatted_address
      });
    } catch (error) {
      console.error('获取位置失败:', error);
    }
  },
  
  async onSearchInput(e) {
    const keyword = e.detail.value;
    if (keyword.length > 1) {
      const list = await this.locationService.searchAddress(keyword);
      this.setData({ addressList: list });
    }
  }
});

2. 订单创建与状态管理

// 后端订单服务核心代码
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private DriverService driverService;
    
    @Autowired
    private MessageService messageService;
    
    /**
     * 创建代驾订单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderDTO createOrder(OrderCreateRequest request) {
        // 1. 参数验证
        validateOrderRequest(request);
        
        // 2. 价格计算
        BigDecimal estimatedPrice = calculatePrice(request);
        
        // 3. 创建订单实体
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(request.getUserId());
        order.setStartAddress(request.getStartAddress());
        order.setEndAddress(request.getEndAddress());
        order.setStartLocation(request.getStartLocation());
        order.setEndLocation(request.getEndLocation());
        order.setEstimatedPrice(estimatedPrice);
        order.setStatus(OrderStatus.WAITING_DRIVER);
        order.setCreateTime(new Date());
        
        // 4. 保存订单
        orderMapper.insert(order);
        
        // 5. 寻找附近司机
        List<Driver> nearbyDrivers = findNearbyDrivers(
            request.getStartLocation(), 
            5.0  // 5公里范围内
        );
        
        // 6. 推送订单给司机
        pushOrderToDrivers(order, nearbyDrivers);
        
        // 7. 返回订单信息
        return convertToDTO(order);
    }
    
    /**
     * 智能匹配司机算法
     */
    private List<Driver> findNearbyDrivers(Location location, double radius) {
        // 使用Redis GEO查找附近司机
        String geoKey = "driver:locations";
        GeoResults<RedisGeoCommands.GeoLocation<Object>> results = 
            redisTemplate.opsForGeo().radius(
                geoKey,
                new Circle(
                    new Point(location.getLongitude(), location.getLatitude()),
                    new Distance(radius, Metrics.KILOMETERS)
                )
            );
        
        List<Driver> drivers = new ArrayList<>();
        for (GeoResult<RedisGeoCommands.GeoLocation<Object>> result : results) {
            String driverId = (String) result.getContent().getName();
            Driver driver = driverService.getDriverById(driverId);
            if (driver != null && driver.getStatus() == DriverStatus.AVAILABLE) {
                drivers.add(driver);
            }
        }
        
        // 根据评分、距离等排序
        return drivers.stream()
            .sorted(Comparator
                .comparing(Driver::getRating).reversed()
                .thenComparing(d -> calculateDistance(d.getLocation(), location)))
            .limit(10)
            .collect(Collectors.toList());
    }
    
    /**
     * 计算代驾价格
     */
    private BigDecimal calculatePrice(OrderCreateRequest request) {
        // 基础起步价
        BigDecimal basePrice = new BigDecimal("39.00");
        
        // 计算距离
        double distance = calculateDistance(
            request.getStartLocation(),
            request.getEndLocation()
        );
        
        // 距离费用(3公里后每公里5元)
        BigDecimal distancePrice = BigDecimal.ZERO;
        if (distance > 3) {
            distancePrice = new BigDecimal((distance - 3) * 5);
        }
        
        // 时段加价(夜间23:00-6:00)
        BigDecimal timeSurcharge = getTimeSurcharge();
        
        // 动态加价(高峰时段)
        BigDecimal surgeMultiplier = getSurgeMultiplier();
        
        // 总价计算
        BigDecimal total = basePrice
            .add(distancePrice)
            .add(timeSurcharge)
            .multiply(surgeMultiplier)
            .setScale(2, RoundingMode.HALF_UP);
        
        return total;
    }
    
    /**
     * 司机接单处理
     */
    @Override
    @Transactional
    public OrderDTO acceptOrder(String orderId, String driverId) {
        // 使用分布式锁防止重复接单
        String lockKey = "order:accept:" + orderId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                Order order = orderMapper.selectById(orderId);
                if (order == null) {
                    throw new BusinessException("订单不存在");
                }
                
                if (order.getStatus() != OrderStatus.WAITING_DRIVER) {
                    throw new BusinessException("订单已被接单");
                }
                
                // 更新订单状态
                order.setDriverId(driverId);
                order.setStatus(OrderStatus.DRIVER_ACCEPTED);
                order.setAcceptTime(new Date());
                orderMapper.updateById(order);
                
                // 更新司机状态
                driverService.updateDriverStatus(driverId, DriverStatus.ON_ORDER);
                
                // 发送推送通知
                messageService.sendOrderAcceptedMessage(order);
                
                return convertToDTO(order);
            } else {
                throw new BusinessException("系统繁忙,请稍后重试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("接单失败");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 订单状态机
     */
    @Override
    public boolean updateOrderStatus(String orderId, OrderStatus newStatus) {
        Order order = orderMapper.selectById(orderId);
        OrderStatus currentStatus = order.getStatus();
        
        // 验证状态转换是否合法
        if (!isValidTransition(currentStatus, newStatus)) {
            log.error("无效的状态转换: {} -> {}", currentStatus, newStatus);
            return false;
        }
        
        // 更新状态
        order.setStatus(newStatus);
        order.setUpdateTime(new Date());
        
        // 根据状态执行相应操作
        switch (newStatus) {
            case DRIVER_ARRIVED:
                order.setArriveTime(new Date());
                messageService.sendDriverArrivedMessage(order);
                break;
            case TRIP_STARTED:
                order.setStartTime(new Date());
                messageService.sendTripStartedMessage(order);
                break;
            case TRIP_COMPLETED:
                order.setEndTime(new Date());
                calculateFinalPrice(order);
                messageService.sendTripCompletedMessage(order);
                break;
            case PAYMENT_COMPLETED:
                order.setPaymentTime(new Date());
                settleDriverIncome(order);
                break;
        }
        
        return orderMapper.updateById(order) > 0;
    }
    
    /**
     * 订单状态转换验证
     */
    private boolean isValidTransition(OrderStatus from, OrderStatus to) {
        Map<OrderStatus, Set<OrderStatus>> validTransitions = new HashMap<>();
        
        validTransitions.put(OrderStatus.WAITING_DRIVER, 
            Set.of(OrderStatus.DRIVER_ACCEPTED, OrderStatus.CANCELLED));
        
        validTransitions.put(OrderStatus.DRIVER_ACCEPTED,
            Set.of(OrderStatus.DRIVER_ARRIVED, OrderStatus.CANCELLED));
        
        validTransitions.put(OrderStatus.DRIVER_ARRIVED,
            Set.of(OrderStatus.TRIP_STARTED, OrderStatus.CANCELLED));
        
        validTransitions.put(OrderStatus.TRIP_STARTED,
            Set.of(OrderStatus.TRIP_COMPLETED));
        
        validTransitions.put(OrderStatus.TRIP_COMPLETED,
            Set.of(OrderStatus.PAYMENT_COMPLETED));
        
        Set<OrderStatus> allowed = validTransitions.get(from);
        return allowed != null && allowed.contains(to);
    }
}

// 订单状态枚举
public enum OrderStatus {
    WAITING_DRIVER("等待接单"),
    DRIVER_ACCEPTED("司机已接单"),
    DRIVER_ARRIVED("司机已到达"),
    TRIP_STARTED("行程开始"),
    TRIP_COMPLETED("行程结束"),
    PAYMENT_COMPLETED("支付完成"),
    CANCELLED("已取消"),
    COMPLAINT("投诉中"),
    REFUNDED("已退款");
    
    private final String description;
    
    OrderStatus(String description) {
        this.description = description;
    }
}

三、实时通信实现

WebSocket实时位置同步

// 小程序端WebSocket实现
class RealTimeService {
  constructor() {
    this.socket = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }
  
  connect(userId, orderId) {
    return new Promise((resolve, reject) => {
      const wsUrl = `wss://your-domain.com/ws?userId=${userId}&orderId=${orderId}`;
      
      this.socket = wx.connectSocket({
        url: wsUrl,
        success: () => {
          this.bindEvents();
          resolve();
        },
        fail: reject
      });
    });
  }
  
  bindEvents() {
    wx.onSocketOpen(() => {
      console.log('WebSocket连接已打开');
      this.reconnectAttempts = 0;
    });
    
    wx.onSocketMessage((res) => {
      const message = JSON.parse(res.data);
      this.handleMessage(message);
    });
    
    wx.onSocketError((error) => {
      console.error('WebSocket错误:', error);
      this.reconnect();
    });
    
    wx.onSocketClose(() => {
      console.log('WebSocket连接已关闭');
      this.reconnect();
    });
  }
  
  sendLocation(location) {
    const message = {
      type: 'location_update',
      data: {
        latitude: location.latitude,
        longitude: location.longitude,
        timestamp: Date.now()
      }
    };
    
    if (this.socket && this.socket.readyState === 1) {
      wx.sendSocketMessage({
        data: JSON.stringify(message)
      });
    }
  }
  
  reconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      setTimeout(() => {
        console.log(`尝试重新连接,第${this.reconnectAttempts}次`);
        this.connect();
      }, 3000 * this.reconnectAttempts);
    }
  }
}

四、安全与性能优化

1. 接口安全设计

// JWT认证拦截器
@Component
public class JwtInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        String token = request.getHeader("Authorization");
        
        if (StringUtils.isEmpty(token)) {
            throw new AuthenticationException("未提供认证令牌");
        }
        
        try {
            Claims claims = JwtUtil.parseToken(token);
            String userId = claims.getSubject();
            
            // 验证用户状态
            User user = userService.getUserById(userId);
            if (user == null || user.getStatus() != UserStatus.NORMAL) {
                throw new AuthenticationException("用户状态异常");
            }
            
            // 将用户信息存入请求上下文
            RequestContext.setCurrentUser(user);
            
            return true;
        } catch (Exception e) {
            throw new AuthenticationException("认证失败");
        }
    }
}

2. 数据库性能优化

-- 订单表分表策略
CREATE TABLE orders_202401 (
    id BIGINT PRIMARY KEY,
    order_no VARCHAR(32) UNIQUE,
    user_id BIGINT,
    driver_id BIGINT,
    status TINYINT,
    -- 其他字段...
    INDEX idx_user_status (user_id, status),
    INDEX idx_driver_status (driver_id, status),
    INDEX idx_create_time (create_time)
) ENGINE=InnoDB PARTITION BY RANGE (YEAR(create_time)) (
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026)
);

五、测试与部署

单元测试示例

@SpringBootTest
class OrderServiceTest {
    
    @Autowired
    private OrderService orderService;
    
    @Test
    void testCreateOrder() {
        OrderCreateRequest request = new OrderCreateRequest();
        request.setUserId("123456");
        request.setStartAddress("北京市海淀区");
        request.setEndAddress("北京市朝阳区");
        
        OrderDTO order = orderService.createOrder(request);
        
        assertNotNull(order);
        assertEquals("123456", order.getUserId());
        assertEquals(OrderStatus.WAITING_DRIVER, order.getStatus());
    }
}

Docker部署配置

# docker-compose.yml
version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  
  backend:
    build: ./daiJia-backend
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis
    environment:
      SPRING_PROFILES_ACTIVE: prod

结语

代驾系统的开发需要综合考虑功能完整性、系统稳定性和用户体验。本文通过具体的代码示例展示了核心功能的实现方法,但在实际开发中还需要根据具体业务需求进行调整和优化。建议采用持续集成、自动化测试和灰度发布等 DevOps 实践,确保系统的质量和稳定性。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐