From 22f5e9ca614ef2787a53cef02b1c0e4e19bd7658 Mon Sep 17 00:00:00 2001 From: Ji-minhyeok Date: Wed, 16 Jul 2025 17:05:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B2=95=EC=A0=95=EB=8F=99=EB=AA=85=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C,=20=EA=B2=80=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../controller/LocationCsvController.java | 43 +++++++++++++ .../domain/lesson/admin/entity/Location.java | 34 ++++++++++ .../admin/repository/LocationRepository.java | 11 ++++ .../admin/service/LocationCsvService.java | 63 +++++++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 src/main/java/com/threestar/trainus/domain/lesson/admin/controller/LocationCsvController.java create mode 100644 src/main/java/com/threestar/trainus/domain/lesson/admin/entity/Location.java create mode 100644 src/main/java/com/threestar/trainus/domain/lesson/admin/repository/LocationRepository.java create mode 100644 src/main/java/com/threestar/trainus/domain/lesson/admin/service/LocationCsvService.java diff --git a/build.gradle b/build.gradle index 256fad2..74b00fa 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9' implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4' + implementation 'com.opencsv:opencsv:5.11.2' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/threestar/trainus/domain/lesson/admin/controller/LocationCsvController.java b/src/main/java/com/threestar/trainus/domain/lesson/admin/controller/LocationCsvController.java new file mode 100644 index 0000000..47a7496 --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/lesson/admin/controller/LocationCsvController.java @@ -0,0 +1,43 @@ +package com.threestar.trainus.domain.lesson.admin.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.threestar.trainus.domain.lesson.admin.service.LocationCsvService; + +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/location") +public class LocationCsvController { + + private final LocationCsvService locationCsvService; + + // 업로드 api + @PostMapping("/upload-location") + @Operation(summary = "법정동명 csv파일 업로드 api", description = "현재 쓰이는 열만 필터링하여 업로드") + public ResponseEntity uploadCsv(@RequestParam("file") MultipartFile file) { + locationCsvService.processCsv(file); + return ResponseEntity.ok("법정동 업로드 완료"); + } + + // 검증용 api + @GetMapping("/exists") + @Operation(summary = "법정동명 검증 api", description = "") + public ResponseEntity checkExists( + @RequestParam String city, + @RequestParam String district, + @RequestParam String dong, + @RequestParam(required = false) String ri + ) { + boolean exists = locationCsvService.checkLocation(city, district, dong, ri); + return ResponseEntity.ok(exists); + } +} \ No newline at end of file diff --git a/src/main/java/com/threestar/trainus/domain/lesson/admin/entity/Location.java b/src/main/java/com/threestar/trainus/domain/lesson/admin/entity/Location.java new file mode 100644 index 0000000..453e145 --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/lesson/admin/entity/Location.java @@ -0,0 +1,34 @@ +package com.threestar.trainus.domain.lesson.admin.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "location") +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Location { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String city; // 시도명 + private String district; // 시군구명 + private String dong; // 읍면동명 + private String ri; // 리명 + + public Location(String city, String district, String dong, String ri) { + this.city = city; + this.district = district; + this.dong = dong; + this.ri = ri; + } +} \ No newline at end of file diff --git a/src/main/java/com/threestar/trainus/domain/lesson/admin/repository/LocationRepository.java b/src/main/java/com/threestar/trainus/domain/lesson/admin/repository/LocationRepository.java new file mode 100644 index 0000000..f8387bf --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/lesson/admin/repository/LocationRepository.java @@ -0,0 +1,11 @@ +package com.threestar.trainus.domain.lesson.admin.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.threestar.trainus.domain.lesson.admin.entity.Location; + +public interface LocationRepository extends JpaRepository { + boolean existsByCityAndDistrictAndDongAndRi(String city, String district, String dong, String ri); + + boolean existsByCityAndDistrictAndDong(String city, String district, String dong); +} \ No newline at end of file diff --git a/src/main/java/com/threestar/trainus/domain/lesson/admin/service/LocationCsvService.java b/src/main/java/com/threestar/trainus/domain/lesson/admin/service/LocationCsvService.java new file mode 100644 index 0000000..19b16e8 --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/lesson/admin/service/LocationCsvService.java @@ -0,0 +1,63 @@ +package com.threestar.trainus.domain.lesson.admin.service; + +import static com.threestar.trainus.global.exception.domain.ErrorCode.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import com.opencsv.exceptions.CsvException; +import com.threestar.trainus.domain.lesson.admin.entity.Location; +import com.threestar.trainus.domain.lesson.admin.repository.LocationRepository; +import com.threestar.trainus.global.exception.handler.BusinessException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class LocationCsvService { + + private final LocationRepository locationRepository; + + public void processCsv(MultipartFile file) { + List locations = new ArrayList<>(); + + try (Reader reader = new BufferedReader(new InputStreamReader(file.getInputStream())); + CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build()) { + + String[] row; + while ((row = csvReader.readNext()) != null) { + String city = row[1].trim(); + String district = row[2].trim(); + String dong = row[3].trim(); + String ri = row[4].trim(); + + // 동이 존재하는 경우에 저장 + if (!city.isBlank() && !district.isBlank() && !dong.isBlank()) { + locations.add(new Location(city, district, dong, ri)); + } + } + + locationRepository.saveAll(locations); + + } catch (IOException | CsvException e) { + throw new BusinessException(INTERNAL_SERVER_ERROR); + } + } + + public boolean checkLocation(String city, String district, String dong, String ri) { + if (ri == null || ri.isBlank()) { + return locationRepository.existsByCityAndDistrictAndDong(city, district, dong); + } else { + return locationRepository.existsByCityAndDistrictAndDongAndRi(city, district, dong, ri); + } + } +}