시스템 구성 개요
대리운전 서비스는 사용자 앱과 관리 백엔드로 구성되며, 프론트엔드는 uniapp 기반의 크로스플랫폼 앱을, 서버는 JAVA 생태계로 구축한다.
앱 핵심 기능
- 계정 체계: SMS 인증, 위채 오픈ID 연동
- GIS 기능: 실시간 위치 수집, 목적지 검색
- 예약 흐름: 요청 → 배차 → 완료 정산
- 결제 연동: 위채/알리페이 결제
- 만족도 평가: 별점 및 코멘트
서버 기술 스택
| 구성 요소 | 적용 기술 |
|---|---|
| API 계층 | Spring Boot 3.x |
| 영속 계층 | MySQL + MyBatis |
| 캐시/세션 | Redis Cluster |
| 실시간 통신 | WebSocket / SSE |
| 외부 연동 | 지도 SDK, 결제 게이트웨이 |
핵심 모듈 구현
경로 탐색 서비스
@Service
public class NavigationService {
private final MapPort mapAdapter;
public NavigationService(MapPort mapAdapter) {
this.mapAdapter = mapAdapter;
}
public PathResult findOptimalPath(Coordinate from, Coordinate to) {
return mapAdapter.computeRoute(from, to, RouteStrategy.FASTEST);
}
}
주문 상태 실시간 전파
@Component
public class OrderEventBroadcaster {
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void notifyDriverAssigned(Long orderNo, DriverInfo driver) {
messagingTemplate.convertAndSend(
"/topic/order/" + orderNo,
new DispatchEvent(driver.getName(), driver.getPlateNumber())
);
}
}
uniapp 프론트엔드 코드
위치 권한 획득 및 좌표 변환
async function fetchCurrentPosition() {
try {
const pos = await uni.getLocation({
type: 'gcj02',
altitude: false,
geocode: true
});
return {
lat: pos.latitude,
lng: pos.longitude,
addr: pos.address ?? ''
};
} catch (err) {
console.error('위치 획득 실패:', err);
throw new PositionError('GPS 신호를 확인해주세요');
}
}
주문 생성 및 폴링 로직
class OrderRepository {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async submit(dto) {
const { data } = await uni.request({
url: `${this.baseUrl}/v1/rides`,
method: 'POST',
header: { 'Content-Type': 'application/json' },
data: JSON.stringify({
pickup: dto.origin,
dropoff: dto.destination,
vehicleType: dto.carClass
})
});
return data.rideId;
}
subscribeStatus(rideId, callback) {
const socket = uni.connectSocket({
url: `wss://ws.example.com/rides/${rideId}`
});
socket.onMessage((evt) => {
const payload = JSON.parse(evt.data);
callback(payload.status, payload.eta);
});
return () => socket.close();
}
}
Spring Boot 컨트롤러
@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/rides")
public class RideCommandController {
private final RideApplicationService rideService;
@PostMapping
public ResponseEntity<RideCreatedResponse> requestRide(
@RequestBody @Valid RideRequest command,
@AuthenticationPrincipal UserPrincipal user) {
var rideId = rideService.placeOrder(user.getId(), command);
return ResponseEntity.status(HttpStatus.CREATED)
.body(new RideCreatedResponse(rideId));
}
@PatchMapping("/{rideId}/cancel")
public ResponseEntity<Void> cancelRide(
@PathVariable UUID rideId,
@RequestParam CancellationReason cause) {
rideService.revoke(rideId, cause);
return ResponseEntity.noContent().build();
}
}
오픈소스 활용 팁
- UI 프레임워크:
uview-plus또는uni-ui활용 - 지도 SDK:
amap-uniapp플러그인으로 네이티브 지도 임베드 - 백엔드 참고:
jeecg-boot,ruoyi등의 권한/CRUD 모듈 활용
운영 고려사항
- 지도 API는 월별 호출량에 따른 상용 라이선스 필수
- 결제 모듈은 ICP 등록 및 기업 인증 완료 후 개통 가능
- WebSocket 연결은 재접속 로직과 하트비트 필수
- 기사 전용 앱은 별도 빌드 타겟으로 분리 권장