Skip to content

Commit 787ef30

Browse files
authored
feat: 법정동명 업로드, 검증 테스트 구현 (#96)
1 parent 3b3af32 commit 787ef30

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
3434
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
3535
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
36+
implementation 'com.opencsv:opencsv:5.11.2'
3637
compileOnly 'org.projectlombok:lombok'
3738
runtimeOnly 'com.mysql:mysql-connector-j'
3839
annotationProcessor 'org.projectlombok:lombok'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.threestar.trainus.domain.lesson.admin.controller;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RequestParam;
8+
import org.springframework.web.bind.annotation.RestController;
9+
import org.springframework.web.multipart.MultipartFile;
10+
11+
import com.threestar.trainus.domain.lesson.admin.service.LocationCsvService;
12+
13+
import io.swagger.v3.oas.annotations.Operation;
14+
import lombok.RequiredArgsConstructor;
15+
16+
@RestController
17+
@RequiredArgsConstructor
18+
@RequestMapping("/api/v1/location")
19+
public class LocationCsvController {
20+
21+
private final LocationCsvService locationCsvService;
22+
23+
// 업로드 api
24+
@PostMapping("/upload-location")
25+
@Operation(summary = "법정동명 csv파일 업로드 api", description = "현재 쓰이는 열만 필터링하여 업로드")
26+
public ResponseEntity<String> uploadCsv(@RequestParam("file") MultipartFile file) {
27+
locationCsvService.processCsv(file);
28+
return ResponseEntity.ok("법정동 업로드 완료");
29+
}
30+
31+
// 검증용 api
32+
@GetMapping("/exists")
33+
@Operation(summary = "법정동명 검증 api", description = "")
34+
public ResponseEntity<Boolean> checkExists(
35+
@RequestParam String city,
36+
@RequestParam String district,
37+
@RequestParam String dong,
38+
@RequestParam(required = false) String ri
39+
) {
40+
boolean exists = locationCsvService.checkLocation(city, district, dong, ri);
41+
return ResponseEntity.ok(exists);
42+
}
43+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.threestar.trainus.domain.lesson.admin.entity;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.GenerationType;
6+
import jakarta.persistence.Id;
7+
import jakarta.persistence.Table;
8+
import lombok.AllArgsConstructor;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
12+
@Entity
13+
@Table(name = "location")
14+
@Getter
15+
@NoArgsConstructor
16+
@AllArgsConstructor
17+
public class Location {
18+
19+
@Id
20+
@GeneratedValue(strategy = GenerationType.IDENTITY)
21+
private Long id;
22+
23+
private String city; // 시도명
24+
private String district; // 시군구명
25+
private String dong; // 읍면동명
26+
private String ri; // 리명
27+
28+
public Location(String city, String district, String dong, String ri) {
29+
this.city = city;
30+
this.district = district;
31+
this.dong = dong;
32+
this.ri = ri;
33+
}
34+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.threestar.trainus.domain.lesson.admin.repository;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
5+
import com.threestar.trainus.domain.lesson.admin.entity.Location;
6+
7+
public interface LocationRepository extends JpaRepository<Location, Long> {
8+
boolean existsByCityAndDistrictAndDongAndRi(String city, String district, String dong, String ri);
9+
10+
boolean existsByCityAndDistrictAndDong(String city, String district, String dong);
11+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.threestar.trainus.domain.lesson.admin.service;
2+
3+
import static com.threestar.trainus.global.exception.domain.ErrorCode.*;
4+
5+
import java.io.BufferedReader;
6+
import java.io.IOException;
7+
import java.io.InputStreamReader;
8+
import java.io.Reader;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.web.multipart.MultipartFile;
14+
15+
import com.opencsv.CSVReader;
16+
import com.opencsv.CSVReaderBuilder;
17+
import com.opencsv.exceptions.CsvException;
18+
import com.threestar.trainus.domain.lesson.admin.entity.Location;
19+
import com.threestar.trainus.domain.lesson.admin.repository.LocationRepository;
20+
import com.threestar.trainus.global.exception.handler.BusinessException;
21+
22+
import lombok.RequiredArgsConstructor;
23+
24+
@Service
25+
@RequiredArgsConstructor
26+
public class LocationCsvService {
27+
28+
private final LocationRepository locationRepository;
29+
30+
public void processCsv(MultipartFile file) {
31+
List<Location> locations = new ArrayList<>();
32+
33+
try (Reader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
34+
CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build()) {
35+
36+
String[] row;
37+
while ((row = csvReader.readNext()) != null) {
38+
String city = row[1].trim();
39+
String district = row[2].trim();
40+
String dong = row[3].trim();
41+
String ri = row[4].trim();
42+
43+
// 동이 존재하는 경우에 저장
44+
if (!city.isBlank() && !district.isBlank() && !dong.isBlank()) {
45+
locations.add(new Location(city, district, dong, ri));
46+
}
47+
}
48+
49+
locationRepository.saveAll(locations);
50+
51+
} catch (IOException | CsvException e) {
52+
throw new BusinessException(INTERNAL_SERVER_ERROR);
53+
}
54+
}
55+
56+
public boolean checkLocation(String city, String district, String dong, String ri) {
57+
if (ri == null || ri.isBlank()) {
58+
return locationRepository.existsByCityAndDistrictAndDong(city, district, dong);
59+
} else {
60+
return locationRepository.existsByCityAndDistrictAndDongAndRi(city, district, dong, ri);
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)