1、 系统架构设计

出行类应用通常分为三个模块:

  • 乘客端:发布行程请求、选择车型、支付等。
  • 司机端:接收订单、导航、接单/拒单等。
  • 服务端:处理订单匹配、实时位置同步、计费等。

鸿蒙的分布式能力(如分布式数据管理、跨设备通信)可以优化乘客与司机端的交互。

2、乘客端下单功能实现

1. 前置准备

在module.json5中声明权限和依赖:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "获取乘客位置"
      },
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  },
  "dependencies": {
    "@ohos/geolocation": "1.0.0",    // 定位
    "@ohos/net/http": "1.0.0",       // 网络请求
    "@ohos/notification": "1.0.0"    // 通知
  }
}

2. 乘客下单功能完整代码

// src/ets/pages/OrderPage.ets
import geolocation from '@ohos.geolocation';
import http from '@ohos.net.http';
import notification from '@ohos.notification';
import router from '@ohos.router';

@Entry
@Component
struct OrderPage {
  @State startAddress: string = '';  // 起点地址
  @State endAddress: string = '';    // 终点地址
  @State carType: string = 'economy'; // 车型
  @State orderStatus: string = '未下单';

  // 获取当前位置
  async getCurrentLocation() {
    try {
      const location = await geolocation.getCurrentLocation();
      this.startAddress = `${location.latitude.toFixed(6)},${location.longitude.toFixed(6)}`;
      console.info('当前位置:', this.startAddress);
    } catch (err) {
      console.error('定位失败:', err);
    }
  }

  // 提交订单到服务端
  async submitOrder() {
    if (!this.startAddress || !this.endAddress) {
      notification.show({ contentText: '请选择起点和终点' });
      return;
    }

    this.orderStatus = '提交中...';
    const orderData = {
      start: this.startAddress,
      end: this.endAddress,
      carType: this.carType,
      userId: 'user_123' // 实际替换为登录用户ID
    };

    try {
      const httpRequest = http.createHttp();
      const response = await httpRequest.request(
        'https://your-api-server.com/orders/create',
        {
          method: 'POST',
          header: { 'Content-Type': 'application/json' },
          extraData: JSON.stringify(orderData)
        }
      );

      if (response.responseCode === 200) {
        this.orderStatus = '已接单';
        notification.show({ contentText: '订单已发布,等待司机接单' });
        this.listenOrderStatus(JSON.parse(response.result).orderId); // 开始监听订单状态
      } else {
        this.orderStatus = '提交失败';
      }
    } catch (err) {
      console.error('订单提交失败:', err);
      this.orderStatus = '网络错误';
    }
  }

  // 监听订单状态变化(WebSocket)
  listenOrderStatus(orderId: string) {
    const ws = new WebSocket('wss://your-api-server.com/orders/ws/' + orderId);
    
    ws.onopen = () => {
      console.info('WebSocket连接已建立');
    };

    ws.onmessage = (event: MessageEvent) => {
      const data = JSON.parse(event.data);
      if (data.status === 'DRIVER_ACCEPTED') {
        this.orderStatus = '司机已接单';
        notification.show({
          contentTitle: '司机已接单',
          contentText: `车牌号: ${data.driverInfo.licensePlate}`
        });
        router.pushUrl({ url: 'pages/DriverTrackingPage' }); // 跳转到司机追踪页
      }
    };

    ws.onerror = (error) => {
      console.error('WebSocket错误:', error);
    };
  }

  build() {
    Column() {
      // 地址输入区域
      TextInput({ placeholder: '起点' })
        .onChange((value: string) => { this.startAddress = value })
        .width('90%')
        .margin(10);

      TextInput({ placeholder: '终点' })
        .onChange((value: string) => { this.endAddress = value })
        .width('90%')
        .margin(10);

      // 车型选择
      Picker({ range: ['economy', 'comfort', 'luxury'] })
        .onChange((value: string) => { this.carType = value })
        .width('90%')
        .margin(10);

      // 定位按钮
      Button('获取当前位置')
        .onClick(() => this.getCurrentLocation())
        .width('90%')
        .margin(10);

      // 下单按钮
      Button('呼叫车辆')
        .onClick(() => this.submitOrder())
        .width('90%')
        .margin(10)
        .backgroundColor('#FF007DFF')
        .fontColor(Color.White);

      // 订单状态显示
      Text(`订单状态: ${this.orderStatus}`)
        .fontSize(16)
        .margin(10);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

3. 关键代码解析

定位功能:

  • 使用@ohos/geolocation获取经纬度坐标。
  • 需在module.json5中声明LOCATION权限。

订单提交:

  • 通过@ohos/net/http发送 POST 请求到服务端。
  • 请求体包含起点、终点、车型等数据。

状态监听:

  • 建立 WebSocket 连接实时接收订单状态(如司机接单)。
  • 使用@ohos/notification显示接单通知。

跨页面跳转:

  • 接单后通过router.pushUrl跳转到司机追踪页面。

4. 服务端接口要求

订单创建接口(POST):

  https://your-api-server.com/orders/create
  请求体: { start, end, carType, userId }
  响应: { orderId, status }

WebSocket 推送地址:

  wss://your-api-server.com/orders/ws/{orderId}
  推送消息: { status: 'DRIVER_ACCEPTED', driverInfo: { ... } }

3、司机端听单功能实现

1. 前置准备

在module.json5中声明权限和依赖:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "获取司机实时位置"
      },
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
        "reason": "后台持续监听订单"
      }
    ]
  },
  "dependencies": {
    "@ohos/geolocation": "1.0.0",
    "@ohos/net/http": "1.0.0",
    "@ohos/notification": "1.0.0",
    "@ohos/websocket": "1.0.0"
  }
}

2. 司机端听单功能完整代码

// src/ets/pages/DriverListenPage.ets
import geolocation from '@ohos/geolocation';
import http from '@ohos/net/http';
import notification from '@ohos/notification';
import websocket from '@ohos/websocket';
import router from '@ohos/router';

@Entry
@Component
struct DriverListenPage {
  @State currentLocation: string = ''; // 当前经纬度
  @State isListening: boolean = false; // 是否在听单中
  @State newOrder: any = null;        // 最新订单数据
  private ws: websocket.WebSocket | null = null;

  // 实时上报位置到服务端
  startReportingLocation() {
    setInterval(async () => {
      try {
        const location = await geolocation.getCurrentLocation();
        this.currentLocation = `${location.latitude},${location.longitude}`;
        
        // 如果已连接听单服务,上报位置
        if (this.isListening) {
          this.reportLocationToServer(this.currentLocation);
        }
      } catch (err) {
        console.error('定位失败:', err);
      }
    }, 5000); // 每5秒上报一次
  }

  // 上报位置到服务端(HTTP)
  async reportLocationToServer(location: string) {
    const httpRequest = http.createHttp();
    try {
      await httpRequest.request(
        'https://your-api-server.com/drivers/location',
        {
          method: 'POST',
          header: { 'Content-Type': 'application/json' },
          extraData: JSON.stringify({
            driverId: 'driver_123', // 实际替换为司机ID
            location: location
          })
        }
      );
    } catch (err) {
      console.error('位置上报失败:', err);
    }
  }

  // 开始听单(WebSocket连接)
  startListeningOrders() {
    this.ws = new websocket.WebSocket('wss://your-api-server.com/drivers/ws/listen');
    
    this.ws.on('open', () => {
      this.isListening = true;
      console.info('听单服务已连接');
      // 首次连接时发送司机信息
      this.ws?.send(JSON.stringify({
        type: 'register',
        driverId: 'driver_123',
        carType: 'economy'
      }));
    });

    // 监听新订单推送
    this.ws.on('message', (data: string) => {
      const message = JSON.parse(data);
      if (message.type === 'new_order') {
        this.newOrder = message.order;
        this.showNewOrderNotification();
      }
    });

    this.ws.on('close', () => {
      this.isListening = false;
      console.warn('听单服务已断开');
    });
  }

  // 显示新订单通知(鸿蒙通知+按钮)
  showNewOrderNotification() {
    notification.show({
      id: 1, // 通知ID(用于后续更新)
      contentTitle: '新订单待接单',
      contentText: `${this.newOrder.start}${this.newOrder.end}`,
      buttons: [
        {
          text: '接单',
          action: () => this.acceptOrder()
        },
        {
          text: '拒单',
          action: () => this.rejectOrder()
        }
      ]
    });
  }

  // 接单逻辑
  async acceptOrder() {
    if (!this.newOrder) return;

    const httpRequest = http.createHttp();
    try {
      const response = await httpRequest.request(
        'https://your-api-server.com/orders/accept',
        {
          method: 'POST',
          header: { 'Content-Type': 'application/json' },
          extraData: JSON.stringify({
            orderId: this.newOrder.id,
            driverId: 'driver_123'
          })
        }
      );

      if (response.responseCode === 200) {
        notification.show({ contentText: '接单成功,请前往接客' });
        router.pushUrl({ url: 'pages/NavigationPage' }); // 跳转到导航页
      }
    } catch (err) {
      notification.show({ contentText: '接单失败,请重试' });
    }
  }

  // 拒单逻辑
  async rejectOrder() {
    this.newOrder = null; // 清空当前订单
    notification.cancel(1); // 关闭通知
  }

  // 停止听单
  stopListening() {
    this.ws?.close();
    this.isListening = false;
  }

  onPageShow() {
    this.startReportingLocation(); // 启动位置上报
    this.startListeningOrders();    // 开始听单
  }

  onPageHide() {
    this.stopListening(); // 页面隐藏时停止听单
  }

  build() {
    Column() {
      // 状态显示区域
      Text(this.isListening ? '听单中...' : '未听单')
        .fontSize(20)
        .margin(10);

      Text(`当前位置: ${this.currentLocation || '未知'}`)
        .fontSize(16)
        .margin(10);

      // 手动操作按钮
      Button(this.isListening ? '停止听单' : '开始听单')
        .onClick(() => this.isListening ? this.stopListening() : this.startListeningOrders())
        .width('90%')
        .margin(10);

      // 订单信息卡片(有新订单时显示)
      if (this.newOrder) {
        Card() {
          Column() {
            Text(`起点: ${this.newOrder.start}`).margin(5);
            Text(`终点: ${this.newOrder.end}`).margin(5);
            Text(`车型: ${this.newOrder.carType}`).margin(5);
          }
          .padding(10)
        }
        .margin(10)
        .width('90%')
      }
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
  }
}

3. 关键代码解析

实时位置上报:

  • 通过geolocation每5秒获取一次位置,通过HTTP上报到服务端。
  • 服务端根据位置匹配附近订单(代码中未展示服务端逻辑)。

WebSocket听单:

  • 建立长连接接收实时订单推送。
  • 收到新订单后触发鸿蒙通知(带接单/拒单按钮)。

接单/拒单处理:

  • 接单:调用服务端接口确认接单,跳转到导航页。
  • 拒单:关闭通知并清空当前订单数据。

后台保活:

  • 声明KEEP_BACKGROUND_RUNNING权限保证后台持续监听。

4. 服务端接口要求

接口类型	地址	说明
HTTP POST	https://your-api-server.com/drivers/location	司机位置上报(经纬度字符串)
WebSocket	wss://your-api-server.com/drivers/ws/listen	    监听新订单(需发送driverId注册)
HTTP POST	https://your-api-server.com/orders/accept	    提交接单请求

5. 优化建议

断线重连:

   this.ws.on('close', () => {
     setTimeout(() => this.startListeningOrders(), 3000); // 3秒后重连
   });

省电模式:
根据电量动态调整位置上报频率(低电量时降低频率)。

分布式协同:

   // 在车机大屏上显示订单详情(需配合@ohos.distributedDeviceManager)
   import deviceManager from '@ohos.distributedDeviceManager';
   deviceManager.publish('order_detail', this.newOrder);

订单过滤:
司机端本地缓存拒单记录,避免重复推送同一订单。

4、服务端平台功能实现

1. 服务端架构设计

在这里插入图片描述

2. 数据库设计(MySQL)

在这里插入图片描述

3. 核心代码实现(Spring Boot)

(1) 订单创建接口
// OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;
    
    @PostMapping("/create")
    public ResponseEntity<Order> createOrder(@RequestBody OrderCreateDTO dto) {
        Order order = orderService.createOrder(
            dto.getPassengerId(),
            dto.getStartLat(),
            dto.getStartLng(),
            dto.getEndAddress(),
            dto.getCarType()
        );
        return ResponseEntity.ok(order);
    }
}
(2) 司机位置上报与听单服务
// DriverService.java
@Service
public class DriverService {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 司机位置更新(存入Redis Geo)
    public void updateDriverLocation(String driverId, double lat, double lng) {
        redisTemplate.opsForGeo().add(
            "driver_locations", 
            new Point(lng, lat), 
            driverId
        );
        
        // 更新司机状态到MySQL
        driverRepository.updateLocation(driverId, lat, lng, new Date());
    }

    // 查找附近可用司机
    public List<String> findNearbyDrivers(double lat, double lng, double radiusKm, String carType) {
        Circle area = new Circle(new Point(lng, lat), 
            new Distance(radiusKm, Metrics.KILOMETERS));
        
        RedisGeoCommands.GeoRadiusCommandArgs args = GeoRadiusCommandArgs
            .newGeoRadiusArgs()
            .includeCoordinates()
            .sortAscending(); // 按距离排序
        
        return redisTemplate.opsForGeo()
            .radius("driver_locations", area, args)
            .getContent()
            .stream()
            .map(geoResult -> geoResult.getContent().getName())
            .collect(Collectors.toList());
    }
}
(3) 订单-司机匹配逻辑
// OrderMatchingService.java
@Service
public class OrderMatchingService {
    @Autowired
    private WebSocketHandler webSocketHandler;
    
    @Scheduled(fixedRate = 5000) // 每5秒执行一次订单匹配
    public void matchOrders() {
        List<Order> pendingOrders = orderRepository.findByStatus("pending");
        
        for (Order order : pendingOrders) {
            List<String> driverIds = driverService.findNearbyDrivers(
                order.getStartLat(),
                order.getStartLng(),
                5.0, // 5公里半径
                order.getCarType()
            );
            
            if (!driverIds.isEmpty()) {
                // 推送给第一个司机(可根据评分等优化)
                webSocketHandler.sendToDriver(
                    driverIds.get(0), 
                    new OrderNotification(order)
                );
            }
        }
    }
}
(4) WebSocket实时通信
// OrderWebSocketHandler.java
@Component
public class OrderWebSocketHandler extends TextWebSocketHandler {
    private static final Map<String, WebSocketSession> driverSessions = new ConcurrentHashMap<>();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        String driverId = session.getHandshakeHeaders().getFirst("driver-id");
        driverSessions.put(driverId, session);
    }
    
    // 向指定司机推送订单
    public void sendToDriver(String driverId, OrderNotification notification) {
        WebSocketSession session = driverSessions.get(driverId);
        if (session != null && session.isOpen()) {
            session.sendMessage(new TextMessage(notification.toJSON()));
        }
    }
    
    // 处理司机接单/拒单消息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        JSONObject json = new JSONObject(message.getPayload());
        if ("accept".equals(json.getString("action"))) {
            orderService.acceptOrder(
                json.getString("orderId"),
                json.getString("driverId")
            );
        }
    }
}

4. 关键API列表

在这里插入图片描述

5. 华为云部署建议

在这里插入图片描述
在这里插入图片描述

6. 扩展功能实现

(1) 订单状态机(防止重复接单)
// Order.java 中定义状态流转
public enum OrderStatus {
    PENDING {
        @Override
        public boolean canTransitionTo(OrderStatus newStatus) {
            return newStatus == MATCHED || newStatus == CANCELLED;
        }
    },
    MATCHED {
        @Override
        public boolean canTransitionTo(OrderStatus newStatus) {
            return newStatus == COMPLETED;
        }
    }
}
(2) 分布式锁(防止订单重复分配)
// 使用Redis实现分布式锁
public boolean tryLockOrder(String orderId) {
    return redisTemplate.opsForValue()
        .setIfAbsent("lock:" + orderId, "1", 10, TimeUnit.SECONDS);
}
(3) 鸿蒙服务端推送(集成华为Push Kit)
// 向鸿蒙设备推送通知
public void pushToHarmony(String deviceToken, String message) {
    HttpClient.newBuilder()
        .build()
        .send(
            HttpRequest.newBuilder()
                .uri(URI.create("https://push-api.cloud.huawei.com/v1/notification"))
                .header("Authorization", "Bearer {access_token}")
                .POST(HttpRequest.BodyPublishers.ofString(
                    new JSONObject()
                        .put("device_tokens", new String[]{deviceToken})
                        .put("message", message)
                        .toString()
                ))
                .build(),
            HttpResponse.BodyHandlers.ofString()
        );
}

5、疑点总结

在这里插入图片描述

1、Redis的GEO功能进行纯内存计算怎么找到附近的司机

在这里插入图片描述
实际就是司机都上报了位置信息并存入到redis中,然后构造中心位置距离参数和范围参数,去redis中过滤出来位置最近的10个司机。
在这里插入图片描述
在这里插入图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐