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-insrc/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 undertarget/ -
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— sharedResult, 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.javahandles JWT auth for rider/admin/platform APIs. -
src/main/java/com/diligrp/rider/config/OpenApiInterceptor.javahandles signed auth for/api/open/**usingX-App-Key,X-Timestamp,X-Nonce, andX-Sign. -
src/main/java/com/diligrp/rider/config/WebMvcConfig.javawires 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 sometimescityIdinto the request. - Substation admins get
cityIdfrom thesubstationrecord, not from the request. - Open-platform requests derive
cityIdfrom the boundOpenApp, 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_plandelivery_fee_plan_dimensiondelivery_fee_plan_distance_stepdelivery_fee_plan_piece_ruledelivery_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
OpenAppfromappKey - forces
cityIdfrom the app binding - optionally hydrates store info from merchant data
- computes the delivery fee
- creates the
ordersrecord - 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_locationrows - open
orders - rider/day statistics
Scheduled jobs then advance the order lifecycle:
-
src/main/java/com/diligrp/rider/task/DispatchScheduleTask.javaruns every 3 seconds to auto-dispatch timed-out grab orders -
src/main/java/com/diligrp/rider/task/OrderScheduleTask.javaruns 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.