CLAUDE.md 7.28 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Common commands

  • mvn spring-boot:run — run the service with the checked-in src/main/resources/application.yml
  • mvn test — run all tests
  • mvn -Dtest=DeliveryFeeServiceImplTest test — run the pricing-engine test class
  • mvn -Dtest=DeliveryFeeServiceImplTest#shouldApplyMinFee test — run one test method
  • mvn package — build the jar under target/
  • mvn -DskipTests package — build without running tests
  • mysql -u root -p dili_rider < src/main/resources/schema.sql — initialize schema
  • mysql -u root -p dili_rider < src/main/resources/data-init.sql — load seed data

Notes:

  • There is no Maven wrapper in this repo; use the system mvn.
  • No dedicated lint/format command is configured in pom.xml.
  • Test coverage is currently sparse; the only checked-in unit tests are in src/test/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImplTest.java.

Architecture overview

This is a Java 17 / Spring Boot 3.2 monolith for rider delivery operations. The main entrypoint is src/main/java/com/diligrp/rider/RiderServiceApplication.java, which enables MyBatis mapper scanning and @EnableAsync.

Main layers

Code follows a conventional Spring layout under src/main/java/com/diligrp/rider/:

  • controller — REST endpoints grouped by caller type
  • service / service/impl — business logic
  • mapper — MyBatis-Plus mappers
  • entity — database entities
  • dto / vo — request and response shapes
  • config — interceptors, JWT utilities, MVC and WebSocket wiring
  • task — scheduled background jobs
  • websocket — live rider-location subscriptions and push
  • common — shared Result, enums, and exception handling

Most persistence uses MyBatis-Plus query/update wrappers. XML SQL exists, but only in a few files under src/main/resources/mapper/. Logical delete is globally configured through the isDel field in src/main/resources/application.yml.

API surfaces

Controllers are split by audience rather than by technical module:

  • /api/rider/** — rider app APIs
  • /api/admin/** — admin and substation admin APIs
  • /api/platform/** — super-admin platform APIs
  • /api/open/** — signed open-platform APIs for third-party integrations
  • /api/delivery/fee/** — internal fee-calculation APIs

All controllers return the shared Result<T> envelope from src/main/java/com/diligrp/rider/common/result/Result.java. Cross-cutting exception mapping lives in src/main/java/com/diligrp/rider/common/exception/GlobalExceptionHandler.java.

Authentication and tenant/city scoping

This service does not use Spring Security. Authentication is interceptor-driven:

  • src/main/java/com/diligrp/rider/config/AuthInterceptor.java handles JWT auth for rider/admin/platform APIs.
  • src/main/java/com/diligrp/rider/config/OpenApiInterceptor.java handles signed auth for /api/open/** using X-App-Key, X-Timestamp, X-Nonce, and X-Sign.
  • src/main/java/com/diligrp/rider/config/WebMvcConfig.java wires both interceptors.
  • JWT creation/parsing lives in src/main/java/com/diligrp/rider/config/JwtUtil.java.

Important boundary rule: city/tenant identity is derived from trusted server-side state, not from caller input.

  • Rider/admin JWTs inject riderId, adminId, role, and sometimes cityId into the request.
  • Substation admins get cityId from the substation record, not from the request.
  • Open-platform requests derive cityId from the bound OpenApp, not from payload fields.

If you touch auth or routing, preserve that pattern.

Also note: password checking in RiderAuthServiceImpl and AdminAuthServiceImpl uses MD5 hashing, so do not assume bcrypt/Spring Security conventions are already in place.

Core business flows

Delivery pricing is DB-driven

The pricing engine is centered on src/main/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImpl.java, but the actual pricing configuration comes from DB tables, not hardcoded constants.

src/main/java/com/diligrp/rider/service/impl/CityServiceImpl.java assembles the active pricing plan from:

  • delivery_fee_plan
  • delivery_fee_plan_dimension
  • delivery_fee_plan_distance_step
  • delivery_fee_plan_piece_rule
  • delivery_fee_plan_time_rule

That assembled config is then used to compute:

  • base fee
  • distance fee / distance steps
  • weight fee
  • piece-count fee
  • time-window surcharge
  • minimum fee
  • estimated delivery time

If you change pricing behavior, check both CityServiceImpl and DeliveryFeeServiceImpl, and extend DeliveryFeeServiceImplTest.

Open-platform order creation drives the main order lifecycle

src/main/java/com/diligrp/rider/service/impl/DeliveryOrderServiceImpl.java is the main open-platform order entry path. It:

  • resolves the OpenApp from appKey
  • forces cityId from the app binding
  • optionally hydrates store info from merchant data
  • computes the delivery fee
  • creates the orders record
  • emits webhook notifications for order events

That service is a good starting point when tracing order ingestion and external callbacks.

Dispatch is a scoring engine over DB state

src/main/java/com/diligrp/rider/service/impl/DispatchServiceImpl.java performs rider selection. It scores candidates using current city, online/rest state, rider location, order load, refusal history, daily counts, and configured dispatch conditions.

The dispatch engine depends on:

  • the active dispatch rule template for the city
  • current rider_location rows
  • open orders
  • rider/day statistics

Scheduled jobs then advance the order lifecycle:

  • src/main/java/com/diligrp/rider/task/DispatchScheduleTask.java runs every 3 seconds to auto-dispatch timed-out grab orders
  • src/main/java/com/diligrp/rider/task/OrderScheduleTask.java runs every 60 seconds to auto-cancel stale unaccepted orders

This app is DB-state-driven; there is no message queue coordinating dispatch.

Real-time location flow

Live rider location is implemented with raw Spring WebSocket, not STOMP/SockJS.

  • WebSocket endpoint: /ws/location
  • Config: src/main/java/com/diligrp/rider/config/LocationWebSocketConfig.java
  • Handshake auth: src/main/java/com/diligrp/rider/websocket/LocationWebSocketHandshakeInterceptor.java
  • Update/push path: src/main/java/com/diligrp/rider/service/impl/RiderLocationServiceImpl.java

Rider location updates are written to rider_location and then pushed to subscribed admin clients. Super admins must provide cityId when connecting; substation admins derive it from their account.

External integrations

External notifications are sent asynchronously by src/main/java/com/diligrp/rider/service/impl/WebhookServiceImpl.java using JDK HttpClient plus @Async. There is no Kafka/RabbitMQ-style event bus in this repo.

Redis is present, but its main visible use is SMS verification code storage in src/main/java/com/diligrp/rider/service/impl/RiderAuthServiceImpl.java; do not assume Redis-backed sessions or broad caching layers exist.

Database and runtime config

  • Main runtime config is src/main/resources/application.yml
  • Schema lives in src/main/resources/schema.sql
  • Seed data lives in src/main/resources/data-init.sql

There is only one checked-in Spring config file; do not assume profile-specific config files already exist.