diff --git a/Dockerfile b/Dockerfile index d31c777..f2deef4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,23 @@ -# Build stage -FROM eclipse-temurin:17-jdk-alpine AS build -WORKDIR /workspace/app +FROM eclipse-temurin:17-jdk-alpine +WORKDIR /app + +# 安裝 Maven +RUN apk add --no-cache maven +# 複製 Maven wrapper 和 pom.xml COPY mvnw . COPY .mvn .mvn COPY pom.xml . -COPY src src -# Grant execution rights on the mvnw wrapper +# 下載依賴(這層會被快取) RUN chmod +x mvnw -RUN ./mvnw install -DskipTests -RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) +RUN ./mvnw dependency:go-offline -B -# Run stage -FROM eclipse-temurin:17-jdk-alpine -VOLUME /tmp -ARG DEPENDENCY=/workspace/app/target/dependency -COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib -COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF -COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.ride_hailing.RideHailingApplication"] +# 複製源碼 +COPY src src + +# 暴露端口 +EXPOSE 8080 5005 + +# 使用 spring-boot:run 啟動,支援 devtools hot-reload +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"] diff --git a/README.md b/README.md index 991e976..aa77669 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,114 @@ -# 白癡 SA 作業 +# 系統分析與設計作業--簡易乘車配對系統 (Ride-Hailing System) -## 專案更新 (2025-11-28) -- **架構重整**:已將專案結構調整為標準 Maven 目錄結構 (`src/main/java/...`)。 -- **容器化**:新增 `Dockerfile` 與 `docker-compose.yml`,支援一鍵啟動。 -- **依賴修正**:修復 `pom.xml` 依賴與 Plugin 設定,解決編譯錯誤。 -- **功能修復**:修正 Model 缺漏與語法錯誤,Web 服務已可正常存取。 +- 這是一個基於 Java Spring Boot 開發的簡易乘車配對平台,模擬了乘客發送乘車請求、司機報價以及乘客選擇司機進行配對的完整流程。 -## 啟動方式 (推薦) -本專案已支援 Docker Compose,無需手動安裝 Java 或 Maven 環境。 +## 👥 組員資訊 +- 資管三 112403531 汪筠涵 +- 資管三 112403026 魏仁祥 +- 資管三 112403523 傅聖祐 +- 資管三 112403525 黃媺涵 +- 資管三 112403543 王暐元 -1. 確保已安裝 **Docker** 與 **Docker Compose**。 -2. 在專案根目錄執行: + +## 系統簡介 + +系統架構前後端分離,旨在展示一個簡易的叫車服務架構 (前端為 GenAI 生成之單一 HTML 檔案)。 +- **乘客**:可以輸入上車地點、目的地與時間,發送請求,並查看司機的即時報價。 +- **司機**:可以查看線上的乘車請求並提交報價。 +- **配對**:乘客從報價列表中選擇心儀的司機,完成配對並交換聯絡資訊。 + +--- + +## 安裝與啟動教學 + +### 環境需求 +- **Docker** +- **Java JDK 17+** 與 **Maven** + +### 方法一:Docker + +1. 開啟終端機 (Terminal/PowerShell)。 +2. 進入專案根目錄。 +3. 執行以下指令: ```bash - docker-compose up --build + docker compose up --build ``` -3. 等待啟動完成後,開啟瀏覽器存取: - - 首頁:http://localhost:8080 - -## 傳統啟動方式 (不推薦) -若仍需使用本機 Maven 啟動: -1. `java -version` (需 JDK 17+) -2. `mvn -v` -3. `mvn spring-boot:run` - -## 專案結構 -- `src/main/java`: 後端原始碼 (Controller, Model, Service) -- `src/main/resources`: 設定檔與靜態資源 (HTML) -- `docker-compose.yml`: 容器編排設定 +4. 等待看到 `Started RideHailingApplication` 字樣。 +5. 開啟瀏覽器訪問:[http://localhost:8080](http://localhost:8080) + +--- + +### 方法二:使用 Maven 本機執行 + +1. 確保已安裝 JDK 17 或以上版本。 +2. 在專案根目錄執行: + - **Windows**: + ```powershell + .\mvnw spring-boot:run + ``` + - **Mac/Linux**: + ```bash + ./mvnw spring-boot:run + ``` +3. 啟動後訪問:[http://localhost:8080](http://localhost:8080) + +## 專案結構說明 + +採用標準的 MVC 分層架構,主要程式碼位於 `src/main/java/com/example/ride_hailing/` 下。 + +### 1. Controller 層 (`controller/`) +負責接收前端的 HTTP 請求,並將請求轉發給 Service 層處理。 +- **`RideController.java`**: + - 定義了所有的 API 端點 (Endpoints),如 `/api/rides/request` (建立請求), `/api/rides/bids` (取得報價)。 + - 作為前端與後端邏輯的入口點。 + +### 2. Service 層 (`service/`) +負責業務邏輯的處理與狀態管理。 +- **`RideService.java`**: + - 系統的核心服務類別。 + - 管理司機列表 (`drivers`)、當前的乘客 (`currentPassenger`) 與乘車請求 (`currentRideRequest`)。 + - 協調 Model 層的物件進行運算(例如呼叫 `Passenger` 物件來獲取報價列表)。 + +### 3. Model 層 (`model/`) +定義系統的資料結構與核心商業邏輯。 +- **`Passenger.java`**: + - 代表乘客實體。 + - **關鍵邏輯**:包含 `updateBideList` 方法,負責決定乘客能看到哪些報價(這是系統設計的一個特點,報價列表是由乘客物件視角提供的)。 +- **`Driver.java`**: 代表司機實體,包含車輛資訊與狀態。 +- **`RideRequest.java`**: 代表一筆乘車請求,包含起訖點、狀態 (`INITIATE`, `MATCHED`) 與收到的報價列表。 +- **`Bid.java`**: 代表司機對某一請求的報價。 +- **`MatchService.java`**: 處理具體的配對邏輯,如建立請求、新增報價、執行配對等。 + +### 4. 前端資源 (`src/main/resources/static/`) +- **`index.html`**: 使用者介面,包含乘客端與司機端的模擬畫面。 +- **`script.js`**: 前端邏輯,負責透過 `fetch` API 與後端 Controller 溝通,並動態更新頁面 DOM 元素。 + +--- + +## 系統交互流程 + +以下說明一個完整的叫車流程是如何在各個檔案間交互運作的: + +1. **發送請求**: + - **前端** (`script.js`) 呼叫 `POST /api/rides/request`。 + - **Controller** (`RideController`) 接收請求並呼叫 `RideService.createRideRequest`。 + - **Service** 透過 `MatchService` 建立一個新的 `RideRequest` 物件。 + +2. **司機報價**: + - **前端** (司機介面) 呼叫 `POST /api/rides/bids/submit`。 + - **Controller** 接收並轉發給 `RideService`。 + - **Service** 將報價 (`Bid`) 加入到該 `RideRequest` 的報價列表中。 + +3. **顯示報價列表 (關鍵交互)**: + - **前端** 定時輪詢 `GET /api/rides/bids`。 + - **Controller** 呼叫 `RideService.getAllBids()`。 + - **Service** 呼叫 `currentPassenger.updateBideList(currentRideRequest)`。 + - **Model** (`Passenger`) 執行邏輯並回傳報價列表給 Service -> Controller -> 前端。 + - *這確保了資料是經過乘客物件的邏輯過濾或處理的。* + +4. **選擇配對**: + - **前端** (乘客介面) 呼叫 `POST /api/rides/bids/select`。 + - **Service** 更新請求狀態為 `MATCHED`。 + - **前端** 顯示配對成功資訊。 + +--- diff --git a/controller/RideController.java b/controller/RideController.java deleted file mode 100644 index 1266f5f..0000000 --- a/controller/RideController.java +++ /dev/null @@ -1,25 +0,0 @@ -// @PostMapping("/request") -// public List requestRide(@RequestBody RideRequest rideRequest) { -// Passenger passenger = rideRequest.getPassenger(); -// rideRequest.newRide(passenger, rideRequest.getPickUpLocation(), rideRequest.getDestination()); -// List availableDrivers = matchService.matchDriver(rideRequest); -// if (availableDrivers == null || availableDrivers.isEmpty()) { -// System.out.println("No drivers available."); -// return new ArrayList<>(); -// } -// matchService.sendAcceptedDriver(availableDrivers); -// return availableDrivers; -// } - -// @PostMapping("/chooseDriver") -// public RideRequest chooseDriver(@RequestParam String driverId, @RequestBody RideRequest rideRequest) { -// Driver chosenDriver = drivers.stream() -// .filter(driver -> driver.getId().equals(driverId)) -// .findFirst() -// .orElseThrow(() -> new IllegalArgumentException("Driver not found")); - -// rideRequest.setDriver(chosenDriver); -// chosenDriver.setAvailable(false); -// rideRequest.updateStatus("Matched"); -// return rideRequest; -// } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 397f04a..002d555 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,5 +4,17 @@ services: build: . ports: - "8080:8080" + - "5005:5005" # Debug port environment: - - SPRING_PROFILES_ACTIVE=docker + - SPRING_PROFILES_ACTIVE=dev + - SPRING_DEVTOOLS_RESTART_ENABLED=true + - SPRING_DEVTOOLS_LIVERELOAD_ENABLED=true + volumes: + # 掛載源碼目錄實現 hot-reload + - ./src:/app/src:delegated + # 掛載 Maven 本地倉庫以加速構建 + - ~/.m2:/root/.m2:cached + # 避免掛載 target 目錄 + - /app/target + stdin_open: true + tty: true diff --git a/docs/intro-mvn.md b/docs/intro-mvn.md new file mode 100644 index 0000000..74dfafe --- /dev/null +++ b/docs/intro-mvn.md @@ -0,0 +1,55 @@ +# Maven 與 .mvn 資料夾介紹 + +## 什麼是 Maven? + +**Apache Maven** 是一個主要用於 Java 專案的**專案管理與自動化建置工具**。它基於專案物件模型(Project Object Model,簡稱 POM)的概念,透過一個核心的設定檔 `pom.xml` 來管理專案的建置、報告和文件。 + +### Maven 的核心功能: + +1. **依賴管理 (Dependency Management)**: + 這是 Maven 最強大的功能之一。您只需要在 `pom.xml` 中宣告您需要的函式庫(例如 Spring Boot, JUnit),Maven 就會自動從中央儲存庫(Maven Central Repository)下載這些 jar 檔及其依賴的其他 jar 檔,免去了手動管理 classpath 的痛苦。 + +2. **標準化的目錄結構**: + Maven 提倡 "Convention over Configuration"(慣例優於配置)。它定義了一套標準的專案結構(如 `src/main/java` 放原始碼,`src/test/java` 放測試碼),讓任何熟悉 Maven 的開發者都能快速看懂您的專案結構。 + +3. **建置生命週期 (Build Lifecycle)**: + Maven 定義了標準的建置階段,例如: + - `clean`: 清除上次建置產生的檔案。 + - `compile`: 編譯原始碼。 + - `test`: 執行單元測試。 + - `package`: 將編譯後的程式碼打包成可發布的格式(如 JAR 或 WAR)。 + - `install`: 將打包好的檔案安裝到本地儲存庫,供其他本地專案使用。 + +--- + +## 什麼是 .mvn 資料夾? + +`.mvn` 資料夾是 **Maven Wrapper** 機制的配置目錄。它的主要目的是確保專案可以在任何環境下(開發者的電腦、CI/CD 伺服器等)使用**特定版本**的 Maven 進行建置,而不需要預先手動安裝 Maven。 + +## 資料夾結構與內容 + +在 `.mvn` 資料夾中,通常包含以下內容: + +- **wrapper/** + - `maven-wrapper.properties`: 這是核心設定檔。它指定了該專案所需的 Maven 版本(例如 3.8.6)以及下載 Maven 的 URL。 + - `maven-wrapper.jar` (有時會存在,或由 script 下載): 負責實際下載並執行 Maven 的 Java 程式庫。 + +## 它是如何運作的? + +當您在專案根目錄看到 `mvnw` (Linux/macOS) 和 `mvnw.cmd` (Windows) 這兩個腳本時,它們就是搭配 `.mvn` 資料夾運作的。 + +1. 當您執行 `./mvnw clean install` 時,腳本會讀取 `.mvn/wrapper/maven-wrapper.properties`。 +2. 它會檢查您的電腦是否已經下載了指定版本的 Maven。 +3. 如果沒有,它會自動下載該版本的 Maven 到使用者的家目錄 (`~/.m2/wrapper/`)。 +4. 最後,它會使用這個下載的 Maven 來執行您的建置命令。 + +## 為什麼要保留它? + +保留 `.mvn` 資料夾與 `mvnw` 腳本有以下好處: + +1. **環境一致性**:確保所有開發者和 CI 伺服器都使用完全相同的 Maven 版本,避免因為版本差異導致的建置錯誤 ("It works on my machine" 問題)。 +2. **快速上手**:新加入的開發者不需要安裝 Maven,只要有 Java 環境,拉下程式碼後直接執行 `./mvnw` 即可開始工作。 + +## 總結 + +`.mvn` 資料夾是專案建置工具鏈的重要組成部分,**不建議刪除**。它讓您的專案更加獨立且易於移植。 diff --git a/model/MatchService.java b/model/MatchService.java deleted file mode 100644 index ae406f8..0000000 --- a/model/MatchService.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.ride_hailing.model; - -import java.util.List; - -public class MatchService { - - private List drivers; - - public MatchService(List drivers) { - this.drivers = drivers; - } - - public Driver matchDriver(RideRequest rideRequest) { - for (Driver driver : drivers) { - if (driver.isAvailable()) { - driver.acceptRide(rideRequest); - System.out.println("Driver matched: " + driver.getName()); - return driver; - } - } - System.out.println("No available drivers at the moment."); - return null; - } - - public void notifyPassenger(RideRequest rideRequest) { - System.out.println("Notification sent to passenger: Ride status - " + rideRequest.getStatus()); - } - - public void notifyDriver(RideRequest rideRequest) { - System.out.println("Notification sent to driver: Ride status - " + rideRequest.getStatus()); - } - -} diff --git a/pom.xml b/pom.xml index 6dd653e..0917141 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,18 @@ + + + dev + + true + + + false + + + + diff --git a/src/main/java/com/example/ride_hailing/controller/RideController.java b/src/main/java/com/example/ride_hailing/controller/RideController.java index b2947f7..d1bc62b 100644 --- a/src/main/java/com/example/ride_hailing/controller/RideController.java +++ b/src/main/java/com/example/ride_hailing/controller/RideController.java @@ -1,6 +1,8 @@ package com.example.ride_hailing.controller; -import com.example.ride_hailing.model.*; +import com.example.ride_hailing.model.Bid; +import com.example.ride_hailing.model.Driver; +import com.example.ride_hailing.model.RideRequest; import com.example.ride_hailing.service.RideService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -12,8 +14,12 @@ @RequestMapping("/api/rides") public class RideController { + private final RideService rideService; + @Autowired - private RideService rideService; + public RideController(RideService rideService) { + this.rideService = rideService; + } @GetMapping("/drivers") public List getAllDrivers() { @@ -22,14 +28,17 @@ public List getAllDrivers() { @PostMapping("/request") public RideRequest requestRide(@RequestBody Map payload) { - String pickUpLocation = payload.get("pickUpLocation"); - String destination = payload.get("destination"); - return rideService.createRideRequest(pickUpLocation, destination); + return rideService.createRideRequest( + payload.get("pickUpLocation"), + payload.get("destination"), + payload.get("expectedPickUpTime") + ); } @PostMapping("/cancel") - public void cancelRide() { + public Map cancelRide() { rideService.cancelRideRequest(); + return Map.of("message", "Ride request cancelled"); } @GetMapping("/current") @@ -37,28 +46,37 @@ public RideRequest getCurrentRideRequest() { return rideService.getCurrentRideRequest(); } - @GetMapping("/available-drivers") - public List getAvailableDrivers() { - return rideService.getAvailableDrivers(); + @GetMapping("/bids") + public List getAllBids() { + return rideService.getAllBids(); + } + + @PostMapping("/bids/submit") + public Bid submitBid(@RequestBody Map payload) { + String driverId = (String) payload.get("driverId"); + Driver driver = rideService.getDriverById(driverId); + int price = ((Number) payload.get("price")).intValue(); + return rideService.submitBid(driver, price); } - @PostMapping("/choose-driver") - public void chooseDriver(@RequestBody Map payload) { - String driverName = payload.get("driverName"); - rideService.passengerChooseDriver(driverName); + @PostMapping("/bids/select") + public Map selectBid(@RequestBody Map payload) { + String bidId = payload.get("bidId"); + rideService.passengerSelectBid(bidId); + return Map.of("message", "Bid selected, ride matched!"); } - @PostMapping("/driver-confirm") - public boolean driverConfirm(@RequestBody Map payload) { - String driverName = (String) payload.get("driverName"); - boolean confirm = (Boolean) payload.get("confirm"); - return rideService.driverConfirmRide(driverName, confirm); + @GetMapping("/contact-info") + public Map getContactInfo() { + String info = rideService.getMatchedContactInfo(); + return Map.of("info", info); } @PostMapping("/driver-status") - public void setDriverStatus(@RequestBody Map payload) { - String driverName = (String) payload.get("driverName"); + public Map setDriverStatus(@RequestBody Map payload) { + String driverId = (String) payload.get("driverId"); boolean available = (Boolean) payload.get("available"); - rideService.setDriverAvailability(driverName, available); + rideService.setDriverAvailability(driverId, available); + return Map.of("message", "Driver status updated"); } } \ No newline at end of file diff --git a/src/main/java/com/example/ride_hailing/model/Bid.java b/src/main/java/com/example/ride_hailing/model/Bid.java new file mode 100644 index 0000000..d9347dc --- /dev/null +++ b/src/main/java/com/example/ride_hailing/model/Bid.java @@ -0,0 +1,47 @@ +package com.example.ride_hailing.model; + +import java.util.UUID; + +public class Bid { + + private String id; + private Driver driver; + private int price; + private boolean accepted; + + public Bid(Driver driver, int price) { + this.id = UUID.randomUUID().toString(); + this.driver = driver; + this.price = price; + this.accepted = false; + } + + public String getId() { + return id; + } + + public Driver getDriver() { + return driver; + } + + public int getPrice() { + return price; + } + + public boolean isAccepted() { + return accepted; + } + + public void setAccepted(boolean accepted) { + this.accepted = accepted; + } + + public boolean isPending() { + return !accepted; + } + + @Override + public String toString() { + return "Name:" + this.driver.getName() + ", price: " + this.price; + } +} diff --git a/src/main/java/com/example/ride_hailing/model/Driver.java b/src/main/java/com/example/ride_hailing/model/Driver.java index 33d01cb..a761f22 100644 --- a/src/main/java/com/example/ride_hailing/model/Driver.java +++ b/src/main/java/com/example/ride_hailing/model/Driver.java @@ -1,19 +1,28 @@ package com.example.ride_hailing.model; +import java.util.ArrayList; +import java.util.List; + public class Driver { private String id; private String name; private String phoneNumber; + private String vehicleInfo; private boolean isAvailable; - public Driver(String id, String name, String phoneNumber, boolean isAvailable) { + public Driver(String id, String name, String phoneNumber, String vehicleInfo, boolean isAvailable) { this.id = id; this.name = name; this.phoneNumber = phoneNumber; + this.vehicleInfo = vehicleInfo; this.isAvailable = isAvailable; } + public String getId() { + return id; + } + public String getName() { return name; } @@ -22,6 +31,10 @@ public String getPhoneNumber() { return phoneNumber; } + public String getVehicleInfo() { + return vehicleInfo; + } + public boolean isAvailable() { return isAvailable; } @@ -31,18 +44,19 @@ public void setAvailable(boolean newStatus) { System.out.println("Driver " + name + " availability set to: " + (newStatus ? "Available" : "Unavailable")); } - // the driver confimr the ride, the status change into "matched" - public boolean confirm(RideRequest rideRequest, boolean confirmRide) { - if (!confirmRide) { - System.out.println("Driver declined to confirm the ride."); - rideRequest.updateStatus("Initiate"); - rideRequest.setDriver(null); - return false; - } - this.isAvailable = false; - rideRequest.setDriver(this); - rideRequest.updateStatus("Matched"); - System.out.println("Ride confirmed by driver: " + name); - return true; + // must + public void notifyNewRide(RideRequest rideRequest) { + System.out.println("Driver " + name + " notified of new ride request: " + rideRequest.getId()); + System.out.println(" From: " + rideRequest.getPickUpLocation() + " To: " + rideRequest.getDestination()); + System.out.println(" Expected pickup time: " + rideRequest.getExpectedPickUpTime()); + } + + // must + public void notifyMatchInfo(RideRequest rideRequest) { + Passenger passenger = rideRequest.getPassenger(); + System.out.println("Driver " + name + " notified of match for ride: " + rideRequest.getId()); + System.out.println("Passenger: " + passenger.getName()); + System.out.println("Passenger phone number: " + passenger.getPhoneNumber()); + System.out.println(" Pick up: " + rideRequest.getPickUpLocation()); } } diff --git a/src/main/java/com/example/ride_hailing/model/MatchService.java b/src/main/java/com/example/ride_hailing/model/MatchService.java index 0fa6ca0..632afbf 100644 --- a/src/main/java/com/example/ride_hailing/model/MatchService.java +++ b/src/main/java/com/example/ride_hailing/model/MatchService.java @@ -5,46 +5,100 @@ public class MatchService { - // private RideRequest ridequest; + private List rideRequests; private List drivers; public MatchService(List drivers) { this.drivers = drivers; + this.rideRequests = new ArrayList<>(); } - // send the request to all the drivers, and get the available driver list - public List askAvailableDriver(RideRequest rideRequest) { - List availableDrivers = new ArrayList<>(); + public List getRideRequests() { + return rideRequests; + } + + public List getDrivers() { + return drivers; + } + + public Driver getDriverById(String driverId) { for (Driver driver : drivers) { - if (driver.isAvailable()) { - availableDrivers.add(driver); + if (driver.getId().equals(driverId)) { + return driver; } } - if (availableDrivers.isEmpty()) { - System.out.println("No available drivers at the moment."); - } else { - System.out.println("Available drivers collected."); - } - return availableDrivers; + return null; } - // send the collected accepted drivers to the passenger - public void sendAcceptedDriver(List drivers, RideRequest rideRequest) { - rideRequest.updateStatus("Pending"); - System.out.println("Available drivers sent to passenger."); + // must + public void createRideRequest(RideRequest rideRequest) { + this.rideRequests.add(rideRequest); + System.out.println("RideRequest " + rideRequest.getId() + " added to MatchService."); + + System.out.println("Broadcasting RideRequest " + rideRequest.getId() + " to all drivers..."); for (Driver driver : drivers) { - System.out.println("Driver: " + driver.getName()); + if (driver.isAvailable()) { + driver.notifyNewRide(rideRequest); + } + } + } + + // must + public void cancelRideRequest(RideRequest rideRequest) { + if (rideRequests.contains(rideRequest)) { + rideRequest.cancel(); + System.out.println("RideRequest " + rideRequest.getId() + " cancelled in MatchService."); } } - // Collect available drivers and let the passenger choose one - public void matchDriver(Passenger passenger, RideRequest rideRequest) { - List availableDrivers = askAvailableDriver(rideRequest); - if (!availableDrivers.isEmpty()) { - passenger.chooseDriver(availableDrivers, rideRequest); - } else { - System.out.println("No drivers available to match with the passenger."); + public String exchangeContactInfo(RideRequest rideRequest) { + // get Driver + Driver driver = rideRequest.getSelectedBid().getDriver(); + Passenger passenger = rideRequest.getPassenger(); + // 叫passenger 方法 + String info = passenger.showMatchSuccess(rideRequest); + // 叫driver 方法 + driver.notifyMatchInfo(rideRequest); + return info; + } + + // must + public RideRequest createRideRequest(Passenger passenger, String pickUpLocation, String destination, String expectedPickUpTime) { + RideRequest rideRequest = new RideRequest(passenger, pickUpLocation, destination, expectedPickUpTime); + System.out.println("Ride requested by passenger: " + passenger.getName()); + createRideRequest(rideRequest); + return rideRequest; + } + + public void selectBid(String bidId, RideRequest rideRequest) { + for (Bid bid : rideRequest.getBids()) { + if (bid.getId().equals(bidId)) { + rideRequest.selectBid(bid); + + Driver driver = getDriverById(bid.getDriver().getId()); + if (driver != null) { + driver.setAvailable(false); + } + return; + } } } + // must + public Bid submitBid(Driver driver, int price, RideRequest rideRequest) { + if (rideRequest.getStatus() != RequestStatus.INITIATE) { + System.out.println("Cannot submit bid. RideRequest is not in INITIATE status."); + return null; + } + + if (driver == null) { + System.out.println("Driver not found"); + return null; + } + + Bid bid = new Bid(driver, price); + rideRequest.addBid(bid); + System.out.println("Driver " + driver.getName() + " submitted bid of $" + price + " for ride " + rideRequest.getId()); + return bid; + } } diff --git a/src/main/java/com/example/ride_hailing/model/Passenger.java b/src/main/java/com/example/ride_hailing/model/Passenger.java index b689202..c120a0f 100644 --- a/src/main/java/com/example/ride_hailing/model/Passenger.java +++ b/src/main/java/com/example/ride_hailing/model/Passenger.java @@ -1,7 +1,7 @@ package com.example.ride_hailing.model; +import java.util.ArrayList; import java.util.List; -import java.util.UUID; public class Passenger { @@ -15,6 +15,10 @@ public Passenger(String id, String name, String phoneNumber) { this.phoneNumber = phoneNumber; } + public String getId() { + return id; + } + public String getName() { return name; } @@ -23,37 +27,38 @@ public String getPhoneNumber() { return phoneNumber; } - public RideRequest requestRide(String pickUpLocation, String destination) { - RideRequest rideRequest = new RideRequest(); - rideRequest.newRide(this, pickUpLocation, destination); - System.out.println("Ride requested by passenger: " + name); - return rideRequest; - } - - // the passenger cancel the ride "actively" - public void cancelRide(RideRequest rideRequest) { - // if (!rideRequest.getPassenger().equals(this)) { - // System.out.println("This passenger is not associated with the ride."); - // return; - // } - rideRequest.updateStatus("Cancelled"); - System.out.println("Ride canceled by passenger: " + name); + // must + public List updateBideList(RideRequest currentRideRequest) { + if (currentRideRequest != null && currentRideRequest.getStatus() == RequestStatus.INITIATE) { + System.out.println("Current bids:" + currentRideRequest.getBids()); + return currentRideRequest.getBids(); + } + return new ArrayList<>(); } - public void chooseDriver(List availableDrivers, RideRequest rideRequest) { - if (availableDrivers == null || availableDrivers.isEmpty()) { - System.out.println("No drivers available to choose from."); - return; + // must + public String showMatchSuccess(RideRequest rideRequest) { + if (rideRequest.getStatus() != RequestStatus.MATCHED) { + return "Cannot exchange contact info. Ride is not matched."; } - // For simplicity, choose the first available driver - Driver chosenDriver = availableDrivers.get(0); - if (chosenDriver.isAvailable()) { - rideRequest.setDriver(chosenDriver); - System.out.println("Driver chosen: " + chosenDriver.getName()); - } else { - System.out.println("Driver is no longer available."); + Bid selectedBid = rideRequest.getSelectedBid(); + if (selectedBid == null) { + return "No bid selected."; + } + Driver driver = selectedBid.getDriver(); + if (driver == null) { + return "Driver not found."; + } + Passenger passenger = rideRequest.getPassenger(); + if (passenger == null) { + return "Passenger not found."; } - } + String info = "Match completed! Contact information exchanged:\n"; + info += "Passenger: " + passenger.getName() + " - " + passenger.getPhoneNumber() + "\n"; + info += "Driver: " + driver.getName() + " - " + driver.getPhoneNumber(); + System.out.println(info); + return info; + } } diff --git a/src/main/java/com/example/ride_hailing/model/RequestStatus.java b/src/main/java/com/example/ride_hailing/model/RequestStatus.java new file mode 100644 index 0000000..e988fb0 --- /dev/null +++ b/src/main/java/com/example/ride_hailing/model/RequestStatus.java @@ -0,0 +1,7 @@ +package com.example.ride_hailing.model; + +public enum RequestStatus { + INITIATE, + MATCHED, + CANCELLED +} diff --git a/src/main/java/com/example/ride_hailing/model/RideRequest.java b/src/main/java/com/example/ride_hailing/model/RideRequest.java index 3d17641..9992d68 100644 --- a/src/main/java/com/example/ride_hailing/model/RideRequest.java +++ b/src/main/java/com/example/ride_hailing/model/RideRequest.java @@ -1,47 +1,42 @@ package com.example.ride_hailing.model; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; public class RideRequest { private String id; private Passenger passenger; - private Driver driver; private String pickUpLocation; private String destination; - private String status; + private String expectedPickUpTime; + private RequestStatus status; + private List bids; + private Bid selectedBid; public RideRequest() { // Default constructor for creating an empty RideRequest instance + this.bids = new ArrayList<>(); } - public RideRequest( - String id, - Passenger passenger, - Driver driver, - String pickUpLocation, - String destination, - String status - ) { - this.id = id; - this.passenger = passenger; - this.driver = driver; - this.pickUpLocation = pickUpLocation; - this.destination = destination; - this.status = status; - } - - public RideRequest(Passenger passenger, String pickUpLocation, String destination) { + public RideRequest(Passenger passenger, String pickUpLocation, String destination, String expectedPickUpTime) { this.id = UUID.randomUUID().toString(); this.passenger = passenger; - this.driver = null; this.pickUpLocation = pickUpLocation; this.destination = destination; - this.status = "Initiate"; + this.expectedPickUpTime = expectedPickUpTime; + this.status = RequestStatus.INITIATE; + this.bids = new ArrayList<>(); + this.selectedBid = null; System.out.println("New ride created: " + id); } - public String getStatus() { + public String getId() { + return id; + } + + public RequestStatus getStatus() { return status; } @@ -49,10 +44,6 @@ public Passenger getPassenger() { return passenger; } - public Driver getDriver() { - return driver; - } - public String getPickUpLocation() { return pickUpLocation; } @@ -61,26 +52,50 @@ public String getDestination() { return destination; } - public void newRide(Passenger passenger, String pickUpLocation, String destination) { - this.id = UUID.randomUUID().toString(); - this.passenger = passenger; - this.pickUpLocation = pickUpLocation; - this.destination = destination; - this.status = "Requested"; - System.out.println("New ride created: " + id); + public String getExpectedPickUpTime() { + return expectedPickUpTime; + } + + public List getBids() { + return bids; + } + + public Bid getSelectedBid() { + return selectedBid; + } + + // must + public void addBid(Bid bid) { + if (this.status != RequestStatus.INITIATE) { + System.out.println("Cannot add bid. RideRequest status is not INITIATE."); + return; + } + this.bids.add(bid); + System.out.println("Bid added to RideRequest " + id + " from driver " + bid.getDriver().getId()); } - public void updateStatus(String newStatus) { - if (newStatus == null || newStatus.isEmpty()) { - System.out.println("Invalid status."); + // must + public void selectBid(Bid bid) { + if (this.status != RequestStatus.INITIATE) { + System.out.println("Cannot select bid. RideRequest is not in INITIATE status."); return; } - this.status = newStatus; - System.out.println("Ride status updated to: " + status); + if (!bids.contains(bid)) { + System.out.println("Bid not found in this request."); + return; + } + this.selectedBid = bid; + this.status = RequestStatus.MATCHED; + bid.setAccepted(true); + System.out.println("Bid " + bid.getId() + " selected. Ride matched."); } - public void setDriver(Driver driver) { - this.driver = driver; - System.out.println("Driver assigned: " + (driver != null ? driver.getName() : "None")); + public void cancel() { + if (this.status == RequestStatus.INITIATE) { + this.status = RequestStatus.CANCELLED; + System.out.println("RideRequest " + id + " has been cancelled."); + } else { + System.out.println("Cannot cancel RideRequest. Current status: " + status); + } } } diff --git a/src/main/java/com/example/ride_hailing/service/RideService.java b/src/main/java/com/example/ride_hailing/service/RideService.java index 1e984cc..9cbc382 100644 --- a/src/main/java/com/example/ride_hailing/service/RideService.java +++ b/src/main/java/com/example/ride_hailing/service/RideService.java @@ -5,49 +5,47 @@ import java.util.ArrayList; import java.util.List; -import java.util.Optional; @Service public class RideService { - private MatchService matchService; - private List drivers; + private final MatchService matchService; + private final List drivers; + private final Passenger currentPassenger; private RideRequest currentRideRequest; - private Passenger currentPassenger; public RideService() { - this.drivers = new ArrayList<>(); - // Initialize with 4 drivers as requested - drivers.add(new Driver("1", "Driver 1", "1234567890", true)); - drivers.add(new Driver("2", "Driver 2", "0987654321", true)); - drivers.add(new Driver("3", "Driver 3", "1112223333", true)); - drivers.add(new Driver("4", "Driver 4", "4445556666", true)); - + this.drivers = initializeDrivers(); this.matchService = new MatchService(drivers); this.currentPassenger = new Passenger("p1", "Passenger 1", "5555555555"); } + private List initializeDrivers() { + List driverList = new ArrayList<>(); + driverList.add(new Driver("1", "Wilson Wong", "1234567890", "Toyota Camry - ABC123", true)); + driverList.add(new Driver("2", "Loyuyu Fu", "0987654321", "Honda Civic - XYZ789", true)); + driverList.add(new Driver("3", "Jack Hung", "1112223333", "Tesla Model 3 - TES456", true)); + driverList.add(new Driver("4", "Oswin Q", "4445556666", "BMW 3 Series - BMW999", true)); + return driverList; + } + public List getAllDrivers() { return drivers; } public Driver getDriverById(String id) { - return drivers.stream() - .filter(d -> d.getName().equals(id) || d.getName().contains(id)) // Simplified matching for demo - .findFirst() - .orElse(null); + return matchService.getDriverById(id); } - public RideRequest createRideRequest(String pickUpLocation, String destination) { - currentRideRequest = new RideRequest(); - currentRideRequest.newRide(currentPassenger, pickUpLocation, destination); + public RideRequest createRideRequest(String pickUpLocation, String destination, String expectedPickUpTime) { + currentRideRequest = matchService.createRideRequest(currentPassenger, pickUpLocation, destination, expectedPickUpTime); return currentRideRequest; } public void cancelRideRequest() { if (currentRideRequest != null) { - currentPassenger.cancelRide(currentRideRequest); - currentRideRequest = null; // Reset current request + matchService.cancelRideRequest(currentRideRequest); + currentRideRequest = null; // 清空當前請求 } } @@ -55,36 +53,31 @@ public RideRequest getCurrentRideRequest() { return currentRideRequest; } - public List getAvailableDrivers() { - if (currentRideRequest == null) return new ArrayList<>(); - return matchService.askAvailableDriver(currentRideRequest); + public List getAllBids() { + return currentPassenger.updateBideList(currentRideRequest); } - public void passengerChooseDriver(String driverName) { - if (currentRideRequest == null) return; - - Driver chosenDriver = getDriverById(driverName); - if (chosenDriver != null) { - // We create a list because the updated Passenger.chooseDriver expects a list - List driverList = new ArrayList<>(); - driverList.add(chosenDriver); - currentPassenger.chooseDriver(driverList, currentRideRequest); + public Bid submitBid(Driver driver, int price) { + if (currentRideRequest == null) { + System.out.println("No active ride request."); + return null; } + + return matchService.submitBid(driver, price, currentRideRequest); } - public boolean driverConfirmRide(String driverName, boolean confirm) { - if (currentRideRequest == null) return false; - - Driver driver = getDriverById(driverName); - if (driver != null && currentRideRequest.getDriver() != null && - currentRideRequest.getDriver().getName().equals(driver.getName())) { - return driver.confirm(currentRideRequest, confirm); - } - return false; + public void passengerSelectBid(String bidId) { + if (currentRideRequest == null) return; + matchService.selectBid(bidId, currentRideRequest); } - - public void setDriverAvailability(String driverName, boolean available) { - Driver driver = getDriverById(driverName); + + public String getMatchedContactInfo() { + if (currentRideRequest == null) return "No active ride request."; + return matchService.exchangeContactInfo(currentRideRequest); + } + + public void setDriverAvailability(String driverId, boolean available) { + Driver driver = getDriverById(driverId); if (driver != null) { driver.setAvailable(available); } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..6e53cc5 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,13 @@ +# Development profile configuration +spring.devtools.restart.enabled=true +spring.devtools.livereload.enabled=true +spring.devtools.restart.poll-interval=1000 +spring.devtools.restart.quiet-period=400 + +# devtools Docker settings +spring.devtools.remote.secret=mysecret +spring.devtools.restart.additional-paths=src/main/java + +# Logging levels +logging.level.root=INFO +logging.level.com.example.ride_hailing=DEBUG diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 9230efc..54d3ea3 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -1,283 +1,99 @@ - + - Ride Hailing Demo - + 簡易乘車配對系統 + + + -
-

Passenger

-
-
-
- - -
-
Status: Idle
- -

Available Drivers

-
-
- -
- -
-

Driver 1

-
Availability: Available
- - - -
-
- - -
-

Driver 2

-
Availability: Available
- - - -
+
+
+

簡易乘車配對系統

+

即時乘客與司機配對平台

+
- -
-

Driver 3

-
Availability: Available
- - -