🎯 AI 브레인스토밍 플랫폼 개발 일지 - Day 4
📅 작업 일자
2025년 11월 27일 (Day 4)
🎯 오늘의 목표
- Controller 계층 구현 (REST API)
- Postman으로 API 테스트
- CRUD 완전 동작 확인
✅ 완료 사항
1️⃣ REST API 설계
API 엔드포인트를 먼저 설계했다.
User API (4개)
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /api/users |
회원가입 |
| GET | /api/users/{id} |
회원 조회 |
| GET | /api/users/email?email= |
이메일로 조회 |
| DELETE | /api/users/{id} |
회원 탈퇴 |
Idea API (5개)
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /api/ideas |
아이디어 저장 |
| GET | /api/ideas/{id} |
아이디어 조회 |
| GET | /api/ideas?userId= |
사용자별 조회 |
| GET | /api/ideas/count?userId= |
개수 조회 |
| DELETE | /api/ideas/{id} |
아이디어 삭제 |
Inquiry API (4개)
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /api/inquiries |
문의 작성 |
| GET | /api/inquiries/{id} |
문의 조회 |
| GET | /api/inquiries?userId= |
사용자별 조회 |
| DELETE | /api/inquiries/{id} |
문의 삭제 |
총 13개 API 설계 완료
2️⃣ Controller 구현
UserController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
// 회원가입
@PostMapping
public ResponseEntity<UserResponseDto> createUser(@RequestBody UserRequestDto requestDto) {
User user = requestDto.toEntity();
User savedUser = userService.save(user);
return ResponseEntity.ok(UserResponseDto.from(savedUser));
}
// 회원 조회
@GetMapping("/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(UserResponseDto.from(user));
}
// 이메일로 조회
@GetMapping("/email")
public ResponseEntity<UserResponseDto> getUserByEmail(@RequestParam String email) {
Optional<User> userOptional = userService.findByEmail(email);
User user = userOptional.orElseThrow(
() -> new RuntimeException("해당 이메일의 사용자가 없습니다.")
);
return ResponseEntity.ok(UserResponseDto.from(user));
}
// 회원 탈퇴
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
특징:
@RestController- REST API용 컨트롤러@RequiredArgsConstructor- Service 자동 주입@RequestBody- JSON을 DTO로 자동 변환@PathVariable- URL 경로 변수 (/users/1에서 1)@RequestParam- 쿼리 파라미터 (?email=test@gmail.com)ResponseEntity- HTTP 상태 코드 + 응답 바디
IdeaController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/ideas")
public class IdeaController {
private final IdeaService ideaService;
// 아이디어 생성
@PostMapping
public ResponseEntity<IdeaResponseDto> createIdea(@RequestBody IdeaRequestDto requestDto) {
Idea idea = requestDto.toEntity();
Idea saveIdea = ideaService.save(idea);
return ResponseEntity.ok(IdeaResponseDto.from(saveIdea));
}
// 아이디어 조회
@GetMapping("/{id}")
public ResponseEntity<IdeaResponseDto> getIdea(@PathVariable Long id) {
Idea idea = ideaService.findById(id);
return ResponseEntity.ok(IdeaResponseDto.from(idea));
}
// 사용자별 전체 조회
@GetMapping
public ResponseEntity<List<IdeaResponseDto>> getIdeasByUser(@RequestParam Long userId) {
List<Idea> ideas = ideaService.findByUserId(userId);
List<IdeaResponseDto> responseDtos = ideas.stream()
.map(IdeaResponseDto::from)
.toList();
return ResponseEntity.ok(responseDtos);
}
// 아이디어 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteIdea(@PathVariable Long id) {
ideaService.delete(id);
return ResponseEntity.noContent().build();
}
// 아이디어 개수 조회
@GetMapping("/count")
public ResponseEntity<Long> countIdeas(@RequestParam Long userId) {
long count = ideaService.countByUserId(userId);
return ResponseEntity.ok(count);
}
}
특징:
List<IdeaResponseDto>- 여러 개 반환stream().map().toList()- List→ List 변환 ResponseEntity<Long>- 숫자 반환
InquiryController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inquiries")
public class InquiryController {
private final InquiryService inquiryService;
// 문의 작성
@PostMapping
public ResponseEntity<InquiryResponseDto> createInquiry(@RequestBody InquiryRequestDto requestDto) {
Inquiry inquiry = requestDto.toEntity();
Inquiry savedInquiry = inquiryService.save(inquiry);
return ResponseEntity.ok(InquiryResponseDto.from(savedInquiry));
}
// 문의 조회
@GetMapping("/{id}")
public ResponseEntity<InquiryResponseDto> getInquiry(@PathVariable Long id) {
Inquiry inquiry = inquiryService.findById(id);
return ResponseEntity.ok(InquiryResponseDto.from(inquiry));
}
// 사용자별 문의 조회
@GetMapping
public ResponseEntity<List<InquiryResponseDto>> getInquiries(@RequestParam Long userId) {
List<Inquiry> inquiries = inquiryService.findByUserId(userId);
List<InquiryResponseDto> responseDtos = inquiries.stream()
.map(InquiryResponseDto::from)
.toList();
return ResponseEntity.ok(responseDtos);
}
// 문의 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteInquiry(@PathVariable Long id) {
inquiryService.delete(id);
return ResponseEntity.noContent().build();
}
}
3️⃣ Postman 테스트
User API 테스트
1. POST - 회원가입
POST http://localhost:8080/api/users
Content-Type: application/json
{
"email": "test@gmail.com",
"username": "홍길동",
"provider": "GOOGLE",
"providerId": "google-123456"
}
응답 (200 OK):
{
"userId": 1,
"email": "test@gmail.com",
"username": "홍길동",
"provider": "GOOGLE",
"createdAt": "2025-11-27T15:30:00"
}
2. GET - 회원 조회
GET http://localhost:8080/api/users/1
응답 (200 OK):
{
"userId": 1,
"email": "test@gmail.com",
"username": "홍길동",
"provider": "GOOGLE",
"createdAt": "2025-11-27T15:30:00"
}
3. GET - 이메일로 조회
GET http://localhost:8080/api/users/email?email=test@gmail.com
응답 (200 OK):
동일한 결과
4. DELETE - 회원 탈퇴
DELETE http://localhost:8080/api/users/1
응답 (204 No Content):
(응답 바디 없음)
Idea API 테스트
1. POST - 아이디어 생성
POST http://localhost:8080/api/ideas
Content-Type: application/json
{
"userId": 1,
"title": "학습 앱 아이디어",
"content": "AI 기반 맞춤형 학습 플랫폼",
"purpose": "학생들의 학습 효율 향상"
}
응답 (200 OK):
{
"ideaId": 1,
"userId": 1,
"title": "학습 앱 아이디어",
"content": "AI 기반 맞춤형 학습 플랫폼",
"purpose": "학생들의 학습 효율 향상"
}
2. GET - 사용자별 조회
GET http://localhost:8080/api/ideas?userId=1
응답 (200 OK):
[
{
"ideaId": 1,
"userId": 1,
"title": "학습 앱 아이디어",
...
}
]
3. GET - 개수 조회
GET http://localhost:8080/api/ideas/count?userId=1
응답 (200 OK):
1
Inquiry API 테스트
1. POST - 문의 작성
POST http://localhost:8080/api/inquiries
Content-Type: application/json
{
"userId": 1,
"title": "로그인 오류",
"content": "구글 로그인이 안 돼요"
}
응답 (200 OK):
{
"inquiryId": 1,
"userId": 1,
"title": "로그인 오류",
"content": "구글 로그인이 안 돼요",
"status": "PENDING",
"reply": null
}
전체 API 테스트 성공! ✅
💡 배운 점 & 트러블슈팅
1. @PathVariable vs @RequestParam
@PathVariable: URL 경로의 일부
@GetMapping("/users/{id}")
public void getUser(@PathVariable Long id) {
// GET /api/users/1 → id = 1
@RequestParam: 쿼리 파라미터 (? 뒤)
@GetMapping("/ideas")
public void getIdeas(@RequestParam Long userId) {
// GET /api/ideas?userId=1 → userId = 1
언제 쓸까?
- PathVariable: "몇 번?" (특정 리소스)
- RequestParam: "누구의?" (필터링/검색)
2. DTO 변환의 중요성
왜 Entity를 직접 반환하면 안 되나?
- 보안 문제: providerId, role 같은 민감 정보 노출
- Mass Assignment 공격: 클라이언트가 role을 ADMIN으로 변경 시도
- API 스펙 독립성: Entity 변경 시 API 스펙도 변경됨
올바른 흐름:
클라이언트
↓ (JSON)
RequestDto
↓ toEntity()
Entity
↓ Service 처리
Entity
↓ from(entity)
ResponseDto
↓ (JSON)
클라이언트3. List 처리 패턴
Entity List → DTO List 변환:
List<Idea> ideas = ideaService.findByUserId(userId);
List<IdeaResponseDto> responseDtos = ideas.stream()
.map(IdeaResponseDto::from)
.toList();
Stream API:
stream(): List를 Stream으로 변환map(): 각 요소를 변환 (Idea → IdeaResponseDto)toList(): 다시 List로 변환
4. HTTP 상태 코드
| 상태 코드 | 의미 | 사용 |
|---|---|---|
| 200 OK | 성공 | 조회, 생성 성공 |
| 204 No Content | 성공 (응답 없음) | 삭제 성공 |
| 404 Not Found | 없음 | 리소스 없음 |
| 500 Internal Server Error | 서버 오류 | 예외 발생 |
📊 프로젝트 현황
완료된 계층
✅ Entity (User, Idea, Inquiry)
✅ Repository (Spring Data JPA)
✅ Service (비즈니스 로직)
✅ DTO (Request/Response)
✅ Controller (REST API)
✅ Postman 테스트디렉토리 구조
src/main/java/com/brainstorming/brainstorming_platform/
├── domain/
│ ├── user/
│ │ ├── entity/User.java
│ │ ├── repository/UserRepository.java
│ │ ├── service/UserService.java
│ │ ├── controller/UserController.java ✅
│ │ └── dto/
│ │ ├── UserRequestDto.java
│ │ └── UserResponseDto.java
│ ├── idea/
│ │ ├── entity/Idea.java
│ │ ├── repository/IdeaRepository.java
│ │ ├── service/IdeaService.java
│ │ ├── controller/IdeaController.java ✅
│ │ └── dto/
│ │ ├── IdeaRequestDto.java
│ │ └── IdeaResponseDto.java
│ └── inquiry/
│ ├── entity/Inquiry.java
│ ├── repository/InquiryRepository.java
│ ├── service/InquiryService.java
│ ├── controller/InquiryController.java ✅
│ └── dto/
│ ├── InquiryRequestDto.java
│ └── InquiryResponseDto.java
└── global/
└── BaseEntity.java🎯 다음 단계 (Day 5)
우선순위 1: Python 연동 🔥 (핵심!)
1. Python FastAPI 준비
- 기존 브레인스토밍 모듈 정리
- FastAPI 엔드포인트 생성
2. Spring-Python 연동
- RestTemplate or WebClient
- BrainstormingService 작성
- BrainstormingController 작성
3. 통합 테스트
- 질문 → Python → 아이디어 생성 → DB 저장우선순위 2: OAuth2 로그인
1. Google/Kakao 로그인
2. JWT 토큰 발급 (선택)
3. Security 설정 강화우선순위 3: 프론트엔드
1. React or Vue.js
2. 로그인/회원가입 UI
3. 브레인스토밍 채팅 UI
4. 아이디어 관리 UI📚 참고 자료
- Spring Boot REST API 공식 문서
- Postman 공식 문서
- RESTful API 설계 가이드
🔗 GitHub
- Repository: brainstorming-platform
- Commit: Day 4 - Controller 구현 및 Postman 테스트 완료
💭 회고
잘한 점
- Controller 3개를 패턴에 따라 일관되게 구현
- Postman으로 13개 API 전체 테스트 완료
- @PathVariable과 @RequestParam의 차이 이해
- DTO 변환의 필요성 완전 이해
개선할 점
- 예외 처리가 RuntimeException으로만 되어 있음
- Validation 추가 필요 (@Valid, @NotNull)
- 테스트 코드 추가 (MockMvc)
느낀 점
"클론코딩으로 막 치는 것보다 뭔가 좀 더 내 머릿속에 들어오는 것 같으면서 손으로 모래잡듯이 바로 흩어지기도 하네"
이 느낌은 정상적인 학습 과정입니다!
- 개념을 이해하고 (✅)
- 직접 코드를 작성하고 (✅)
- 실제로 동작하는 걸 확인했으니 (✅)
- 이제 반복하면서 점점 손에 익을 것입니다!
Day 4 완료! 수고하셨습니다! 🎉
오늘의 성과:
- REST API 13개 완성 ✅
- CRUD 완전 동작 확인 ✅
- Controller 패턴 이해 ✅
- Postman 테스트 마스터 ✅
내일은 핵심 기능인 Python 연동을 시작합니다! 🚀
'그래서 일단 프로젝트 > 개인프로젝트-IdeaMaker(클로드 작성본 md 모음)' 카테고리의 다른 글
| 기록 7 . OAuth2.0 / JWT 구현...너무 어려운데 ?? (0) | 2025.12.02 |
|---|---|
| 기록 6 . 파이썬 모듈 JAVA 백엔드에 연결하기 .. (1) | 2025.12.01 |
| 기록 4 . DTO 작성 및 Test 코드 작성 단계 (0) | 2025.11.26 |
| 기록 3 . Java Service 계층과 Repository 계층 작성 (0) | 2025.11.25 |
| 기록 2 . 엔티티 만들기 ERD까지. (0) | 2025.11.21 |