Skip to content

Commit 2f774f8

Browse files
authored
refactor: 셔틀 버스 API 리팩토링 (#2098)
* chore: 개행 추가 * refactor: 이너 클래스 명 변경 * docs: 스웨거 명세 예시 추가 * refactor: 이름, 세부 이름 파싱 로직 분리 * refactor: 문자열 분리 로직 모델에서 호출하도록 수정 * refactor: 파서 패키지 이동 * refactor: 엑셀 데이터 추출 클래스 명 Parser -> Extractor로 변경 및 패키지 이동 * refactor: 셀 내용 추출 클래스 이름 변경 및 메소드 명 변경 * refactor: 파일 업로드 미리보기 메소드 이름 변경 * refactor: 엑셀 셀 문자열 추출을 PoiExtractor로 통일 * refactor: Extractor 클래스 stateful하도록 수정 * refactor: PoiCellExtractor.extractStringValue() 호출하도록 수정 * refactor: 셀 내용 추출 시 서식에 대한 문제 방지 * refactor: 응답 DTO 구조 변경 * refactor: 메소드 명 변경 * chore: 코드 포맷팅 * refactor: 서비스 메소드 명 수정 * fix: 업로드 API에서 요청 Content-Type을 파일로 지정 * chore: EOF 추가 * fix: cell 값 추출 시 null-safe 처리하도록 수정 * chore: 불필요한 개행 제거
1 parent 5db762c commit 2f774f8

16 files changed

+249
-211
lines changed

src/main/java/in/koreatech/koin/admin/bus/shuttle/controller/AdminShuttleBusTimetableApi.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import static in.koreatech.koin.admin.history.enums.DomainType.SHUTTLE_BUS;
44
import static in.koreatech.koin.domain.user.model.UserType.ADMIN;
55
import static in.koreatech.koin.global.code.ApiResponseCode.*;
6-
7-
import java.util.List;
6+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
87

98
import org.springframework.http.ResponseEntity;
109
import org.springframework.web.bind.annotation.PostMapping;
@@ -36,8 +35,8 @@ public interface AdminShuttleBusTimetableApi {
3635
})
3736
@Operation(summary = "엑셀 파일을 업로드하여 파싱된 데이터를 미리보기 한다.")
3837
@AdminActivityLogging(domain = SHUTTLE_BUS)
39-
@PostMapping("/excel")
40-
ResponseEntity<List<AdminShuttleBusTimetableResponse>> previewShuttleBusTimetable(
38+
@PostMapping(value = "/excel", consumes = MULTIPART_FORM_DATA_VALUE)
39+
ResponseEntity<AdminShuttleBusTimetableResponse> uploadTimetableExcelForPreview(
4140
@Auth(permit = {ADMIN}) Integer adminId,
4241
@RequestParam(name = "shuttle-bus-timetable") MultipartFile file
4342
);

src/main/java/in/koreatech/koin/admin/bus/shuttle/controller/AdminShuttleBusTimetableController.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import static in.koreatech.koin.admin.history.enums.DomainType.SHUTTLE_BUS;
44
import static in.koreatech.koin.domain.user.model.UserType.ADMIN;
5-
6-
import java.util.List;
5+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
76

87
import org.springframework.http.ResponseEntity;
98
import org.springframework.web.bind.annotation.PostMapping;
@@ -33,13 +32,12 @@ public class AdminShuttleBusTimetableController implements AdminShuttleBusTimeta
3332
private final AdminShuttleBusService adminShuttleBusService;
3433

3534
@AdminActivityLogging(domain = SHUTTLE_BUS)
36-
@PostMapping("/excel")
37-
public ResponseEntity<List<AdminShuttleBusTimetableResponse>> previewShuttleBusTimetable(
35+
@PostMapping(value = "/excel", consumes = MULTIPART_FORM_DATA_VALUE)
36+
public ResponseEntity<AdminShuttleBusTimetableResponse> uploadTimetableExcelForPreview(
3837
@Auth(permit = {ADMIN}) Integer adminId,
3938
@RequestParam(name = "shuttle-bus-timetable") MultipartFile file
4039
) {
41-
List<AdminShuttleBusTimetableResponse> response = adminShuttleBusExcelService
42-
.previewShuttleBusTimetable(file);
40+
AdminShuttleBusTimetableResponse response = adminShuttleBusExcelService.getShuttleBusTimetablePreview(file);
4341

4442
return ResponseEntity.ok(response);
4543
}

src/main/java/in/koreatech/koin/admin/bus/shuttle/dto/request/AdminShuttleBusUpdateRequest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ public List<ShuttleBusRoute.RouteInfo> toRouteInfoEntity() {
5757
return routeInfo.stream()
5858
.map(innerRouteInfoRequest ->
5959
InnerRouteInfoRequest.toEntity(
60-
innerRouteInfoRequest.name, innerRouteInfoRequest.detail, innerRouteInfoRequest.arrivalTime
60+
innerRouteInfoRequest.name,
61+
innerRouteInfoRequest.detail,
62+
innerRouteInfoRequest.arrivalTime
6163
)
6264
).toList();
6365
}

src/main/java/in/koreatech/koin/admin/bus/shuttle/dto/response/AdminShuttleBusTimetableResponse.java

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,73 +13,84 @@
1313
import lombok.Builder;
1414

1515
@JsonNaming(SnakeCaseStrategy.class)
16-
@Builder
1716
public record AdminShuttleBusTimetableResponse(
18-
@Schema(description = "운행 지역", example = "CHEONAN_ASAN", requiredMode = REQUIRED)
19-
String region,
20-
21-
@Schema(description = "노선 타입", example = "SHUTTLE", requiredMode = REQUIRED)
22-
String routeType,
23-
24-
@Schema(description = "노선 이름", example = "천안 셔틀", requiredMode = REQUIRED)
25-
String routeName,
26-
27-
@Schema(description = "노선 부제목", example = "토요일, 일요일", requiredMode = NOT_REQUIRED)
28-
String subName,
29-
30-
@Schema(description = "정류소 정보 리스트")
31-
List<NodeInfo> nodeInfo,
32-
33-
@Schema(description = "회차별 도착 시간 및 운행 요일 정보 리스트")
34-
List<RouteInfo> routeInfo
17+
@Schema(description = "셔틀 버스 시간표 정보 리스트")
18+
List<InnerAdminShuttleBusTimetableResponse> shuttleBusTimetables
3519
) {
36-
37-
public static AdminShuttleBusTimetableResponse from(ShuttleBusTimetable table) {
38-
List<NodeInfo> nodeInfos = table.getNodeInfos().stream()
39-
.map(n -> new AdminShuttleBusTimetableResponse.NodeInfo(
40-
n.getName(),
41-
n.getDetail()
42-
))
43-
.toList();
44-
45-
List<RouteInfo> routeInfos = table.getRouteInfos().stream()
46-
.map(r -> new AdminShuttleBusTimetableResponse.RouteInfo(
47-
r.getName(),
48-
r.getDetail(),
49-
r.getArrivalTime()
50-
))
51-
.toList();
52-
53-
return AdminShuttleBusTimetableResponse.builder()
54-
.nodeInfo(nodeInfos)
55-
.region(table.getRegion())
56-
.routeInfo(routeInfos)
57-
.routeName(table.getRouteName())
58-
.routeType(table.getRouteType())
59-
.subName(table.getSubName())
60-
.build();
61-
}
62-
6320
@JsonNaming(SnakeCaseStrategy.class)
64-
public record NodeInfo(
65-
@Schema(description = "정류소 이름", example = "한기대", requiredMode = REQUIRED)
66-
String name,
21+
@Builder
22+
public record InnerAdminShuttleBusTimetableResponse(
23+
@Schema(description = "운행 지역", example = "CHEONAN_ASAN", requiredMode = REQUIRED)
24+
String region,
6725

68-
@Schema(description = "정류소 이름 추가 설명 (없으면 null)", example = "학화호두과자 앞", requiredMode = NOT_REQUIRED)
69-
String detail
70-
) {
71-
}
26+
@Schema(description = "노선 타입", example = "SHUTTLE", requiredMode = REQUIRED)
27+
String routeType,
7228

73-
@JsonNaming(SnakeCaseStrategy.class)
74-
public record RouteInfo(
75-
@Schema(description = "회차 이름", example = "1회", requiredMode = REQUIRED)
76-
String name,
29+
@Schema(description = "노선 이름", example = "천안 셔틀", requiredMode = REQUIRED)
30+
String routeName,
31+
32+
@Schema(description = "노선 부제목", example = "토요일, 일요일", requiredMode = NOT_REQUIRED)
33+
String subName,
7734

78-
@Schema(description = "회차 세부 이름", example = "(청주역→본교)", requiredMode = NOT_REQUIRED)
79-
String detail,
35+
@Schema(description = "정류소 정보 리스트")
36+
List<InnerNodeInfoResponse> nodeInfo,
8037

81-
@Schema(description = "각 정류소 별 도착 시간 (미정차인 경우 null)", requiredMode = REQUIRED)
82-
List<String> arrivalTime
38+
@Schema(description = "회차별 도착 시간 및 운행 요일 정보 리스트")
39+
List<InnerRouteInfoResponse> routeInfo
8340
) {
41+
public static InnerAdminShuttleBusTimetableResponse from(ShuttleBusTimetable table) {
42+
List<InnerNodeInfoResponse> nodeInfo = table.getNodeInfos()
43+
.stream()
44+
.map(n -> new InnerNodeInfoResponse(
45+
n.getName(),
46+
n.getDetail()
47+
))
48+
.toList();
49+
50+
List<InnerRouteInfoResponse> routeInfo = table.getRouteInfos()
51+
.stream()
52+
.map(r -> new InnerRouteInfoResponse(
53+
r.getName(),
54+
r.getDetail(),
55+
r.getArrivalTime()
56+
))
57+
.toList();
58+
59+
return InnerAdminShuttleBusTimetableResponse.builder()
60+
.nodeInfo(nodeInfo)
61+
.region(table.getRegion())
62+
.routeInfo(routeInfo)
63+
.routeName(table.getRouteName())
64+
.routeType(table.getRouteType())
65+
.subName(table.getSubName())
66+
.build();
67+
}
68+
69+
@JsonNaming(SnakeCaseStrategy.class)
70+
public record InnerNodeInfoResponse(
71+
@Schema(description = "정류소 이름", example = "한기대", requiredMode = REQUIRED)
72+
String name,
73+
74+
@Schema(description = "정류소 이름 추가 설명 (없으면 null)", example = "학화호두과자 앞", requiredMode = NOT_REQUIRED)
75+
String detail
76+
) {
77+
}
78+
79+
@JsonNaming(SnakeCaseStrategy.class)
80+
public record InnerRouteInfoResponse(
81+
@Schema(description = "회차 이름", example = "1회", requiredMode = REQUIRED)
82+
String name,
83+
84+
@Schema(description = "회차 세부 이름", example = "(청주역→본교)", requiredMode = NOT_REQUIRED)
85+
String detail,
86+
87+
@Schema(
88+
description = "각 정류소 별 도착 시간 (미정차인 경우 null)",
89+
example = "[\"08:00\", \"09:00\"]",
90+
requiredMode = REQUIRED
91+
)
92+
List<String> arrivalTime
93+
) {
94+
}
8495
}
8596
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package in.koreatech.koin.admin.bus.shuttle.extractor;
2+
3+
import org.apache.poi.ss.usermodel.Cell;
4+
import org.apache.poi.ss.usermodel.DataFormatter;
5+
6+
public class PoiCellExtractor {
7+
8+
private static final DataFormatter FORMATTER = new DataFormatter();
9+
10+
public static String extractStringValue(Cell cell) {
11+
String value = FORMATTER.formatCellValue(cell);
12+
13+
return value != null ? value.trim() : "";
14+
}
15+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package in.koreatech.koin.admin.bus.shuttle.extractor;
2+
3+
import org.apache.poi.ss.usermodel.Cell;
4+
import org.apache.poi.ss.usermodel.Row;
5+
import org.apache.poi.ss.usermodel.Sheet;
6+
7+
import in.koreatech.koin.admin.bus.shuttle.model.RouteName;
8+
import in.koreatech.koin.admin.bus.shuttle.model.RouteType;
9+
import in.koreatech.koin.admin.bus.shuttle.model.SubName;
10+
import in.koreatech.koin.domain.bus.enums.ShuttleBusRegion;
11+
import lombok.RequiredArgsConstructor;
12+
13+
@RequiredArgsConstructor
14+
public class ShuttleBusMetaDataExtractor {
15+
16+
private final Sheet sheet;
17+
18+
private static final int REGION_ROW = 0;
19+
private static final int REGION_COL = 1;
20+
21+
private static final int ROUTE_TYPE_ROW = 1;
22+
private static final int ROUTE_TYPE_COL = 1;
23+
24+
public ShuttleBusRegion extractRegion() {
25+
Row row = sheet.getRow(REGION_ROW);
26+
Cell cell = row.getCell(REGION_COL);
27+
28+
return ShuttleBusRegion.of(PoiCellExtractor.extractStringValue(cell));
29+
}
30+
31+
public RouteType extractRouteType() {
32+
Row row = sheet.getRow(ROUTE_TYPE_ROW);
33+
Cell cell = row.getCell(ROUTE_TYPE_COL);
34+
35+
return RouteType.of(PoiCellExtractor.extractStringValue(cell));
36+
}
37+
38+
public RouteName extractRouteName() {
39+
String sheetName = sheet.getSheetName();
40+
41+
return RouteName.of(sheetName);
42+
}
43+
44+
public SubName extractSubName() {
45+
String sheetName = sheet.getSheetName();
46+
47+
return SubName.of(sheetName);
48+
}
49+
}

src/main/java/in/koreatech/koin/admin/bus/shuttle/util/ShuttleBusNodeInfoParser.java renamed to src/main/java/in/koreatech/koin/admin/bus/shuttle/extractor/ShuttleBusNodeInfoExtractor.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package in.koreatech.koin.admin.bus.shuttle.util;
1+
package in.koreatech.koin.admin.bus.shuttle.extractor;
22

33
import static in.koreatech.koin.admin.bus.shuttle.model.ShuttleBusTimetable.NodeInfo;
44

@@ -10,12 +10,17 @@
1010
import org.apache.poi.ss.usermodel.Sheet;
1111
import org.springframework.util.StringUtils;
1212

13-
public class ShuttleBusNodeInfoParser {
13+
import lombok.RequiredArgsConstructor;
14+
15+
@RequiredArgsConstructor
16+
public class ShuttleBusNodeInfoExtractor {
17+
18+
private final Sheet sheet;
1419

1520
private static final int START_BUS_STOP_ROW = 5;
1621
private static final int START_BUS_STOP_COL = 0;
1722

18-
public static List<NodeInfo> getNodeInfos(Sheet sheet) {
23+
public List<NodeInfo> extractNodeInfos() {
1924
List<NodeInfo> nodeInfos = new ArrayList<>();
2025

2126
for (int i = START_BUS_STOP_ROW; i <= sheet.getLastRowNum(); i++) {
@@ -26,18 +31,15 @@ public static List<NodeInfo> getNodeInfos(Sheet sheet) {
2631
}
2732

2833
Cell cell = row.getCell(START_BUS_STOP_COL);
29-
String nameWithDetail = ExcelStringUtil.getCellValueToString(cell);
34+
String nameWithDetail = (cell == null) ? "" : PoiCellExtractor.extractStringValue(cell);
3035

31-
if (cell == null || !StringUtils.hasText(nameWithDetail)) {
36+
if (!StringUtils.hasText(nameWithDetail)) {
3237
break;
3338
}
3439

3540
nameWithDetail = nameWithDetail.trim();
3641

37-
String name = ExcelStringUtil.extractNameWithoutBrackets(nameWithDetail);
38-
String detail = ExcelStringUtil.extractDetailFromBrackets(nameWithDetail);
39-
40-
nodeInfos.add(NodeInfo.of(name, detail));
42+
nodeInfos.add(NodeInfo.of(nameWithDetail));
4143
}
4244

4345
return nodeInfos;

src/main/java/in/koreatech/koin/admin/bus/shuttle/util/ShuttleBusRouteInfoParser.java renamed to src/main/java/in/koreatech/koin/admin/bus/shuttle/extractor/ShuttleBusRouteInfoExtractor.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package in.koreatech.koin.admin.bus.shuttle.util;
1+
package in.koreatech.koin.admin.bus.shuttle.extractor;
22

33
import static in.koreatech.koin.admin.bus.shuttle.model.ShuttleBusTimetable.RouteInfo;
44
import static in.koreatech.koin.admin.bus.shuttle.model.ShuttleBusTimetable.RouteInfo.InnerNameDetail;
@@ -15,23 +15,28 @@
1515

1616
import in.koreatech.koin.admin.bus.shuttle.enums.RunningDays;
1717
import in.koreatech.koin.admin.bus.shuttle.model.ArrivalTime;
18+
import in.koreatech.koin.admin.bus.shuttle.util.ExcelRangeUtil;
19+
import lombok.RequiredArgsConstructor;
1820

19-
public class ShuttleBusRouteInfoParser {
21+
@RequiredArgsConstructor
22+
public class ShuttleBusRouteInfoExtractor {
23+
24+
private final Sheet sheet;
2025

2126
private static final int START_HEADER_ROW = 3;
2227
private static final int START_DETAIL_ROW = 4;
2328
private static final int START_TIME_DATA_ROW = 5;
2429

2530
private static final int START_COL = 1;
2631

27-
public static List<RouteInfo> getRouteInfos(Sheet sheet) {
28-
List<InnerNameDetail> innerNameDetails = extractRouteNameDetails(sheet);
32+
public List<RouteInfo> extractRouteInfos() {
33+
List<InnerNameDetail> innerNameDetails = extractRouteNameDetails();
2934

3035
List<RunningDays> runningDays = innerNameDetails.stream()
3136
.map(RunningDays::from)
3237
.toList();
3338

34-
List<ArrivalTime> arrivalTimes = extractArrivalTimes(sheet);
39+
List<ArrivalTime> arrivalTimes = extractArrivalTimes();
3540

3641
return IntStream.range(0, innerNameDetails.size())
3742
.mapToObj(i -> from(
@@ -42,7 +47,7 @@ public static List<RouteInfo> getRouteInfos(Sheet sheet) {
4247
.toList();
4348
}
4449

45-
private static List<InnerNameDetail> extractRouteNameDetails(Sheet sheet) {
50+
private List<InnerNameDetail> extractRouteNameDetails() {
4651
List<InnerNameDetail> innerNameDetails = new ArrayList<>();
4752

4853
Row headerRow = sheet.getRow(START_HEADER_ROW);
@@ -59,12 +64,12 @@ private static List<InnerNameDetail> extractRouteNameDetails(Sheet sheet) {
5964
break;
6065
}
6166

62-
String name = nameCell.getStringCellValue().trim();
67+
String name = PoiCellExtractor.extractStringValue(nameCell);
6368

6469
Cell detailCell = detailRow.getCell(col);
6570

6671
String detail = (detailCell != null && StringUtils.hasText(detailCell.toString()))
67-
? detailCell.getStringCellValue().trim()
72+
? PoiCellExtractor.extractStringValue(detailCell)
6873
: null;
6974

7075
innerNameDetails.add(InnerNameDetail.of(name, detail));
@@ -73,7 +78,7 @@ private static List<InnerNameDetail> extractRouteNameDetails(Sheet sheet) {
7378
return innerNameDetails;
7479
}
7580

76-
private static List<ArrivalTime> extractArrivalTimes(Sheet sheet) {
81+
private List<ArrivalTime> extractArrivalTimes() {
7782
List<ArrivalTime> arrivalTimes = new ArrayList<>();
7883

7984
for (int colNum = START_COL;
@@ -93,7 +98,7 @@ private static List<ArrivalTime> extractArrivalTimes(Sheet sheet) {
9398
}
9499

95100
Cell cell = row.getCell(colNum);
96-
String strTime = ExcelStringUtil.getCellValueToString(cell);
101+
String strTime = PoiCellExtractor.extractStringValue(cell);
97102

98103
if (cell == null || !StringUtils.hasText(strTime)) {
99104
times.add(null);

0 commit comments

Comments
 (0)