시스템 개요 및 기술 스택
전통적인 정보 관리 방식은 데이터의 축적에 따라 처리 효율성이 저하되고 보안 취약점이 노출되는 한계가 있습니다. 이를 해결하기 위해 본 크라우드펀딩 플랫폼은 최신 웹 기술을 활용하여 데이터의 실시간 처리, 안전한 저장, 그리고 과학적인 관리를 목표로 설계되었습니다. 시스템의 핵심 기능으로는 크라우드펀딩 프로젝트 관리, 상품 및 카테고리 관리, 사용자 및 관리자 인증, 파일 업로드 등이 포함됩니다.
데이터의 무결성과 안전한 저장을 위해 관계형 데이터베이스인 MySQL을 사용하며, 백엔드는 Spring Boot 프레임워크를 기반으로 RESTful API를 제공합니다. 프론트엔드는 Vue.js를 활용하여 반응형 사용자 인터페이스를 구현하였으며, 비동기 데이터 통신을 위해 Axios 및 Ajax 기술을 적용했습니다.
관리자 기능 설계
크라우드펀딩 프로젝트 관리
관리자는 대시보드를 통해 등록된 크라우드펀딩 프로젝트의 전체 데이터를 관리합니다. 주요 기능으로는 신규 프로젝트 등록, 기존 프로젝트 정보 수정, 상태에 따른 데이터 삭제 및 상세 정보 조회가 있으며, 펀딩 목표액 및 현재 모금 현황을 실시간으로 모니터링할 수 있습니다.
상품 정보 및 카테고리 관리
펀딩 리워드 또는 관련 상품을 효율적으로 관리하기 위한 모듈입니다. 관리자는 상품 정보의 추가, 수정, 삭제 작업을 수행할 수 있습니다. 또한, 상품 카테고리(유형) 관리 페이지를 통해 새로운 카테고리를 동적으로 추가하거나, 더 이상 사용되지 않는 카테고리를 비활성화 및 삭제하여 상품 분류 체계를 유연하게 유지할 수 있습니다.
핵심 모듈 구현 및 코드 리팩토링
기존 레거시 코드의 하드코딩된 경로 및 상속 기반의 응답 클래스를 개선하여, 유지보수성과 보안성을 높인 최신 Spring Boot 아키텍처로 리팩토링한 핵심 모듈의 코드를 소개합니다.
1. 인증 및 로그인 모듈
사용자의 자격 증명을 검증하고 JWT(JSON Web Token)를 발급하여 세션 상태를 관리하는 인증 컨트롤러입니다. 비밀번호는 평문으로 비교하지 않고 인코딩된 값을 검증하도록 보안 로직을 적용했습니다.
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
private final JwtTokenProvider jwtTokenProvider;
private final PasswordEncoder passwordEncoder;
@PostMapping("/login")
public ApiResponse<LoginResponse> login(@RequestBody @Valid LoginRequest request) {
UserEntity user = userService.findByUsername(request.getUsername());
if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new AuthenticationException("아이디 또는 비밀번호가 일치하지 않습니다.");
}
String token = jwtTokenProvider.generateToken(user.getId(), user.getRole());
return ApiResponse.success("로그인 성공", new LoginResponse(token));
}
@PostMapping("/register")
public ApiResponse<Void> register(@RequestBody @Valid RegisterRequest request) {
if (userService.existsByUsername(request.getUsername())) {
throw new IllegalArgumentException("이미 존재하는 사용자입니다.");
}
userService.createUser(request);
return ApiResponse.success("회원가입 성공", null);
}
}
2. 파일 업로드 모듈
파일 업로드 시 절대 경로가 하드코딩되는 기존 문제를 해결하기 위해, application.yml 설정 파일을 통해 동적으로 업로드 디렉토리를 주입받도록 개선했습니다. 또한 파일명 충돌을 방지하기 위해 UUID를 활용하여 파일명을 재정의합니다.
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.*;
import java.util.UUID;
@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
public class FileUploadController {
@Value("${app.file.upload-dir}")
private String uploadDir;
@PostMapping("/upload")
public ApiResponse<FileUploadResponse> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("업로드할 파일이 비어 있습니다.");
}
try {
Path rootPath = Paths.get(uploadDir);
if (!Files.exists(rootPath)) {
Files.createDirectories(rootPath);
}
String originalFilename = file.getOriginalFilename();
String extension = originalFilename != null && originalFilename.contains(".")
? originalFilename.substring(originalFilename.lastIndexOf(".") + 1)
: "bin";
String newFileName = UUID.randomUUID().toString() + "." + extension;
Path targetPath = rootPath.resolve(newFileName);
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
return ApiResponse.success("파일 업로드 성공", new FileUploadResponse(newFileName));
} catch (IOException e) {
throw new RuntimeException("파일 업로드 중 서버 오류가 발생했습니다.", e);
}
}
}
3. 통합 응답 데이터 구조
기존의 HashMap을 상속받던 방식은 타입 안전성이 떨어지고 직렬화 시 불필요한 데이터가 포함될 수 있습니다. 이를 제네릭을 활용한 표준 POJO 클래스로 재설계하여 API 응답의 일관성과 가독성을 높였습니다.
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "요청이 성공적으로 처리되었습니다.", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(500, message, null);
}
}
시스템 테스트 및 품질 보증 전략
시스템의 안정성을 확보하기 위해 다각도의 테스트 전략을 수립합니다. 개발 단계에서는 JUnit과 Mockito를 활용한 단위 테스트를 통해 각 서비스 계층의 비즈니스 로직이 의도대로 동작하는지 검증합니다. 또한, MockMvc를 사용하여 REST API 컨트롤러의 엔드포인트별 요청 및 응답 상태를 확인하는 통합 테스트를 수행합니다.
배포 전 단계에서는 실제 운영 환경과 유사한 설정에서 성능 테스트와 보안 취약점 검사를 진행합니다. 특히 파일 업로드 모듈의 경우, 악성 파일 업로드 시도 및 경로 탐색(Path Traversal) 공격에 대한 방어 로직이 제대로 적용되었는지 페네테이션 테스트를 통해 검증합니다. 모든 테스트 케이스는 CI/CD 파이프라인에 통합되어, 코드 변경 시 회귀 오류가 발생하지 않도록 자동화되어 있습니다.