문제
지난 시간 알려드린 JWT(Json Web Token)(보안) 추가 하여 문제 풀이를 진행하겠습니다.
USER, OWNER, ADMIN으로 나누어 3가지로 진행하겠습니다.
테이블명세서
음식점 리뷰 시스템 테이블 명세서
1. user
칼럼명
|
데이터 타입
|
NULL
|
기본값
|
설명
|
id
|
BIGINT
|
NO
|
AUTO_INCREMENT
|
기본키
|
email
|
VARCHAR(100)
|
NO
|
-
|
사용자 이메일, UNIQUE
|
password
|
VARCHAR(200)
|
NO
|
-
|
암호화된 비밀번호
|
nickname
|
VARCHAR(50)
|
NO
|
-
|
사용자 닉네임
|
role
|
VARCHAR(10)
|
NO
|
'USER'
|
권한(USER, OWNER, ADMIN)
|
created_at
|
TIMESTAMP
|
NO
|
CURRENT_TIMESTAMP
|
생성일시
|
2. restaurant
칼럼명
|
데이터 타입
|
NULL
|
기본값
|
설명
|
id
|
BIGINT
|
NO
|
AUTO_INCREMENT
|
기본키
|
name
|
VARCHAR(100)
|
NO
|
-
|
음식점 이름
|
address
|
VARCHAR(200)
|
NO
|
-
|
음식점 주소
|
phone
|
VARCHAR(20)
|
NO
|
-
|
음식점 전화번호
|
category
|
VARCHAR(50)
|
NO
|
-
|
음식점 카테고리
|
description
|
TEXT
|
YES
|
NULL
|
음식점 설명
|
created_at
|
TIMESTAMP
|
NO
|
CURRENT_TIMESTAMP
|
생성일시
|
칼럼명
|
데이터 타입
|
NULL
|
기본값
|
설명
|
id
|
BIGINT
|
NO
|
AUTO_INCREMENT
|
기본키
|
restaurant_id
|
BIGINT
|
NO
|
-
|
RESTAURANT.id (FK)
|
name
|
VARCHAR(100)
|
NO
|
-
|
메뉴 이름
|
price
|
INT
|
NO
|
-
|
메뉴 가격
|
description
|
TEXT
|
YES
|
NULL
|
메뉴 설명
|
category
|
VARCHAR(50)
|
NO
|
-
|
메뉴 카테고리
|
created_at
|
TIMESTAMP
|
NO
|
CURRENT_TIMESTAMP
|
생성일시
|
4. review
리뷰 정보를 저장하는 테이블
API 명세서
1. 음식점 API
1.1. 음식점 목록 조회
다양한 조건으로 음식점 목록을 검색하고 조회할 수 있는 API입니다.
- URL: /api/v1/restaurants
- Method: GET
- 설명:
- 페이징 처리된 음식점 목록을 반환합니다.
- 카테고리 필터링과 키워드 검색을 지원합니다.
- 음식점의 평균 평점과 리뷰 수를 포함합니다.
- 최신 등록순으로 정렬됩니다.
- Query Parameters:
page: 페이지 번호 (기본값: 1)
size: 페이지 크기 (기본값: 10)
category: 카테고리 필터 (선택) - 한식, 중식, 일식, 양식 등
keyword: 검색어 - 이름, 주소 검색 (선택, 최소 2글자 이상)
- Response:
{
"content": [
{
"id": 1,
"name": "맛있는 식당",
"category": "한식",
"address": "서울시 강남구",
"phone": "02-1234-5678",
"description": "전통 한식집",
"avgRating": 4.5,
"reviewCount": 100,
"createdAt": "2024-12-27T10:00:00"
}
],
"pageable": {
"page": 1,
"size": 10,
"totalElements": 100,
"totalPages": 10
}
}
1.2. 음식점 상세 조회
특정 음식점의 상세 정보와 메뉴 목록을 조회하는 API입니다.
- URL: /api/v1/restaurants/{id}
- Method: GET
- 설명:
- 음식점의 기본 정보를 제공합니다.
- 해당 음식점의 전체 메뉴 목록을 함께 반환합니다.
- 평균 평점과 총 리뷰 수를 포함합니다.
- 각 메뉴별 리뷰 수도 함께 제공됩니다.
- 존재하지 않는 음식점 ID인 경우 404 에러를 반환합니다.
- Response:
{
"restaurant": {
"id": 1,
"name": "맛있는 식당",
"category": "한식",
"address": "서울시 강남구",
"phone": "02-1234-5678",
"description": "전통 한식집",
"avgRating": 4.5,
"reviewCount": 100,
"createdAt": "2024-12-27T10:00:00",
},
"menus": [
{
"id": 1,
"name": "김치찌개",
"price": 8000,
"description": "돼지고기 김치찌개",
"category": "찌개",
"reviewCount": 50
}
]
}
2. 메뉴 API
2.1. 음식점별 메뉴 목록 조회
특정 음식점의 메뉴 목록을 조회하는 API입니다.
- URL: /api/v1/restaurants/{restaurantId}/menus
- Method: GET
- 설명:
- 특정 음식점의 전체 메뉴를 페이징 처리하여 제공합니다.
- 카테고리별 필터링을 지원합니다.
- 각 메뉴의 리뷰 수를 포함합니다.
- 메뉴는 카테고리별, 가격순으로 정렬 가능합니다.
- 존재하지 않는 음식점 ID인 경우 404 에러를 반환합니다.
- Query Parameters:
page: 페이지 번호 (기본값: 1)
size: 페이지 크기 (기본값: 10)
category: 메뉴 카테고리 필터 (선택)
sort: 정렬 기준 (price_asc, price_desc, name_asc)
- Response:
{
"content": [
{
"id": 1,
"name": "김치찌개",
"price": 8000,
"description": "돼지고기 김치찌개",
"category": "찌개",
"reviewCount": 50,
"createdAt": "2024-12-27T10:00:00"
}
],
"pageable": {
"page": 1,
"size": 10,
"totalElements": 50,
"totalPages": 5
}
}
3. 리뷰 API
3.1. 리뷰 작성
새로운 리뷰를 작성하는 API입니다.
- URL: /api/v1/reviews
- Method: POST
- 설명:
- 인증된 사용자만 리뷰를 작성할 수 있습니다.
- 동일한 메뉴에 대해 한 사용자는 하나의 리뷰만 작성 가능합니다.
- 리뷰 작성 시 해당 음식점의 평균 평점이 자동으로 갱신됩니다.
- 음식점과 메뉴의 리뷰 카운트가 자동으로 증가합니다.
- Request Header:
Authorization: Bearer {accessToken}
- Request Body:
{
"restaurantId": 1,
"menuId": 1,
"rating": 5,
"content": "정말 맛있었습니다!"
}
- Validation:
- rating: 1-5 사이의 정수만 가능
- content: 최소 10자 이상 작성
- restaurantId: 유효한 음식점 ID
- menuId: 해당 음식점의 유효한 메뉴 ID
- Response:
- 성공 201 문제있을경우 400
3.2. 리뷰 수정
기존 리뷰를 수정하는 API입니다.
- URL: /api/v1/reviews/{id}
- Method: PUT
- 설명:
- 리뷰 작성자만 수정이 가능합니다.
- 리뷰 수정 시 음식점의 평균 평점이 자동으로 갱신됩니다.
- 리뷰 내용과 평점만 수정 가능합니다.
- 존재하지 않는 리뷰 ID인 경우 404 에러를 반환합니다.
- Request Header:
Authorization: Bearer {accessToken}
- Request Body:
{
"rating": 4,
"content": "맛있었지만 조금 짰어요"
}
- Response:
- 성공 200 문제있을경우 400
4. 사용자 API
4.1. 회원가입
새로운 사용자를 등록하는 API입니다.
- URL: /api/v1/users/signup
- Method: POST
- 설명:
- 새로운 사용자를 시스템에 등록합니다.
- 이메일 중복 확인을 수행합니다.
- 비밀번호는 암호화하여 저장됩니다.
- 기본 사용자 권한(USER)으로 생성됩니다.
- Request Body:
{
"email": "user@example.com",
"password": "password123",
"nickname": "사용자닉네임"
}
- Validation:
- email: 유효한 이메일 형식 (@포함)
- password: 최소 8자 이상, 영문/숫자/특수문자 조합
- nickname: 2-20자 이내, 한글/영문/숫자 허용
- Response:
- 201
4.2. 로그인
사용자 인증을 위한 로그인 API입니다.
- URL: /api/v1/users/login
- Method: POST
- 설명:
- 이메일과 비밀번호로 사용자를 인증합니다.
- 인증 성공 시 JWT 토큰을 발급합니다.
- Access Token의 유효기간은 1시간입니다.
- Request Body:
{
"email": "user@example.com",
"password": "password123"
}
- Response:
{
"token": "eyJhbGciOiJIUzI1NiJ9..."
}
공통 사항
1. 에러 처리
주요 에러 상황:
- 400 Bad Request: 잘못된 요청 파라미터
- 401 Unauthorized: 인증 실패 또는 토큰 만료
- 403 Forbidden: 권한 없음
- 404 Not Found: 리소스를 찾을 수 없음
- 409 Conflict: 데이터 충돌 (예: 이메일 중복)
- 500 Internal Server Error: 서버 오류
2. 페이징 처리
페이징이 적용된 모든 API는 다음 정보를 포함합니다:
- page: 현재 페이지 번호 (1부터 시작)
- size: 페이지당 항목 수
- totalElements: 전체 데이터 수
- totalPages: 전체 페이지 수
3. 인증
- Bearer 토큰 방식의 JWT 인증을 사용합니다.
- 인증이 필요한 API의 경우 반드시 Authorization 헤더에 토큰을 포함해야 합니다.
4. API 버전 관리
- URI의 /api/v1 prefix로 API 버전을 관리합니다.
- 향후 하위 호환성이 깨지는 변경사항이 있을 경우 버전이 변경될 수 있습니다.
파일 처리하기 (나중에 함)
리뷰 작성 시, 사진과 별점과 내용을 보내는 방법
3.1. 리뷰 작성
새로운 리뷰를 작성하는 API입니다.
- URL: /api/v1/reviews/restaurant/{restaurant_id}/menu/{menu_id}
- Method: POST
- 설명:
- 인증된 사용자만 리뷰를 작성할 수 있습니다.
- 동일한 메뉴에 대해 한 사용자는 하나의 리뷰만 작성 가능합니다.
- 리뷰 작성 시 해당 음식점의 평균 평점이 자동으로 갱신됩니다.
- 음식점과 메뉴의 리뷰 카운트가 자동으로 증가합니다.
- Request Header:
Authorization: Bearer {accessToken}
- Request Body: Form-Data
- rating (text) : 5
- content (text) : 이 음식 정말 맛있게 잘 먹었습니다~ 굿!
- image (file) : 사진파일
- Validation:
- rating: 1-5 사이의 정수만 가능
- content: 최소 10자 이상 작성
- restaurantId: 유효한 음식점 ID
- menuId: 해당 음식점의 유효한 메뉴 ID
- Response:
- 성공 201 문제있을경우 400
풀이
DBeaver와 intelliJ 를 데이터 베이스의 내용에 맞게 설계 해줍니다.
DBeaver
- 각 서버는 아래의 초기 세팅 참조로 하여 진행
40. [ JAVA ] API 문제 풀이 : 초기 세팅
문제 풀이를 하기전에 초기 세팅 방법에 대하여 알아보겠습니다.문제풀이에 필요한 프로그램 및 사이트는 DBeaver,Spring Boot,Gibhubdp, intelliJ ,putty입니다. DBeaver DBeaver 실행후 새로운 Databases생성 -> Tab
danmi1109.tistory.com
45. [ JAVA ] JWT(Json Web Token)(보안) 기초 세팅
JWT(Json Web Token)에 대하여 알아보았았습니다. intelliJ 위와 같이 Postman 에서 로그인을 진행하면 token이 발행될 수 있도록 intelliJ에서 초기 작업을 진행하도록 하겠습니다. 초기 세팅을
danmi1109.tistory.com
- 순서 설명
- 회원가입을 제일 먼저 진행하여 줍니다.
- 회원가입으로 인하여 정보 입력및 로그인후 토큰을 전달하여 userId를 확인 할수 있게 됩니다.
4.1. 회원가입
새로운 사용자를 등록하는 API입니다.
- URI의 /api/v1 prefix로 API 버전을 관리합니다.
- http://localhost:8080/api/v1 으로 진행
Postman
intelliJ
- Controller 패키지 생성- UserController 클래스 생성
- URL: /api/v1/users/signup
- Method: POST
- 설명:
- 새로운 사용자를 시스템에 등록합니다.
- 이메일 중복 확인을 수행합니다.
- 비밀번호는 암호화하여 저장됩니다.
- 기본 사용자 권한(USER)으로 생성됩니다.
- Response:
- 201
새로운 사용자를 등록하는 API입니다.
- URL: /api/v1/users/signup
- Method: POST
@PostMapping("/api/v1/users/signup")
signUp(@RequestBody UserRequest userRequest) {
userService.signUp(userRequest);
- UserRequest/ userSevice 를 요하기때문에
- dto 패키지 생성
- UserRequest 클래스 생성
- Validation:
- email: 유효한 이메일 형식 (@포함)
- password: 최소 8자 이상, 영문/숫자/특수문자 조합
- nickname: 2-20자 이내, 한글/영문/숫자 허용
- Request Body:
{
"email": "user@example.com",
"password": "password123",
"nickname": "사용자닉네임"
}
package com.example.jwt3.dto;
public class UserRequest {
public String email;
public String password;
public String nickname;
public UserRequest() {
}
public UserRequest(String email, String password, String nickname) {
this.email = email;
this.password = password;
this.nickname = nickname;
}
}
- sevice 패키지 생성
- UserSevice 클래스 생성
- Validation:
- email: 유효한 이메일 형식 (@포함)
- password: 최소 8자 이상, 영문/숫자/특수문자 조합
- nickname: 2-20자 이내, 한글/영문/숫자 허용
@Service
public class UserService {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
UserDAO userDAO;
@Autowired
JwtConfig jwtConfig;
public int signUp(UserRequest userRequest){
if( EmailValidator.isValidEmail( userRequest.email ) == false ){
return 1;
}
if( PasswordValidator.isValidPassword( userRequest.password ) == false ){
return 2;
}
if(NicknameValidator.isValidInput(userRequest.nickname) == false){
return 3;
}
// 비밀번호 암호화
userRequest.password = passwordEncoder.encode(userRequest.password);
// DB에 저장한다.
try {
userDAO.signUp(userRequest);
System.out.println("UserService 2");
} catch (Exception e) {
// 이메일이 중복된경우.
System.out.println("UserService 3");
return 4;
}
return 0;
}
userDAO.signUp(userRequest);
- dao 패키지 생성
- UserDAO 클래스 생성
DBeaver
- 확인후
sql 문 확인
- sql 문 작성하여 실행 유무 확인
- 실행 확인후 intelliJ 에 복사하여 진행합니다. ( 문자열의 경우 '? ' 로 대체 하여 진행합니다. )
예) DBeaver sql 문
INSERT INTO user(email,password,nickname,role)
values("admin4@gmail.com","pass1234567","맛있는집헌터","USER");
String sql = "insert INTO user \n" +
"(email, password, nickname, role)\n" +
" values (? , ? , ? , 'USER');";
위와 같이 변환 하여 진행하여 줍니다.
@Repository
public class UserDAO {
@Autowired
JdbcTemplate jdbcTemplate;
public int signUp(UserRequest userRequest){
// DB에 유저 정보를 저장하는 메소드
String sql = "insert INTO user \n" +
"(email, password, nickname, role)\n" +
" values (? , ? , ? , 'USER');";
return jdbcTemplate.update(sql, userRequest.email,
userRequest.password, userRequest.nickname);
}
- 설명:
- 새로운 사용자를 시스템에 등록합니다.
- 이메일 중복 확인을 수행합니다.
- 비밀번호는 암호화하여 저장됩니다.
- 기본 사용자 권한(USER)으로 생성됩니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
@Repository
public class UserDAO {
@Autowired
JdbcTemplate jdbcTemplate;
public int signUp(UserRequest userRequest){
// DB에 유저 정보를 저장하는 메소드
String sql = "insert INTO user \n" +
"(email, password, nickname, role)\n" +
" values (? , ? , ? , 'USER');";
return jdbcTemplate.update(sql, userRequest.email,
userRequest.password, userRequest.nickname);
}
@RestController
public class UserController {
@Autowired
UserService userService;
//- 새로운 사용자를 시스템에 등록합니다.
//- 이메일 중복 확인을 수행합니다.
//- 비밀번호는 암호화하여 저장됩니다.
//- 기본 사용자 권한(USER)으로 생성됩니다.
@PostMapping("/api/v1/users/signup")
public ResponseEntity<Object> signUp(@RequestBody UserRequest userRequest) {
System.out.println(userRequest.email);
int result = userService.signUp(userRequest);
if(result != 0){
return ResponseEntity.status(400).build();
}
return ResponseEntity.status(201).build();
}
- 회원가입 진행이 잘되었는지 확인 진행
- Response:
- 201
Postman
- 회원가입 완료 진행 (201)
4.2. 로그인
사용자 인증을 위한 로그인 API입니다.
- URL: /api/v1/users/login
- Method: POST
- 설명:
- 이메일과 비밀번호로 사용자를 인증합니다.
- 인증 성공 시 JWT 토큰을 발급합니다.
- Access Token의 유효기간은 1시간입니다.
- Request Body:
{
"email": "user@example.com",
"password": "password123"
}
- Response:
{
"token": "eyJhbGciOiJIUzI1NiJ9..."
}
Postman
intelliJ
- URL: /api/v1/users/login
- Method: POST
- 설명:
- 이메일과 비밀번호로 사용자를 인증합니다.
- 인증 성공 시 JWT 토큰을 발급합니다.
- Access Token의 유효기간은 1시간입니다.
공통사항- 인증
- Bearer 토큰 방식의 JWT 인증을 사용합니다.
- 인증이 필요한 API의 경우 반드시 Authorization 헤더에 토큰을 포함해야 합니다.
JwtConfig
@Configuration
public class JwtConfig {
Key key;
long tokenValidMillisecond = 1*60*60*1000;
public JwtConfig(@Value("${jwt.secret}") String secretKey) {
this.key = Keys.hmacShaKeyFor(secretKey.getBytes());
}
UserController
@PostMapping("/api/v1/users/login")
public ResponseEntity<UserLoginResponse> userLogin(@RequestBody UserRequest userRequest) {
// 로그인 로직
Object result = userService.userLogin(userRequest);
if(result instanceof Integer){
return ResponseEntity.status(400).build();
}
return ResponseEntity.status(200).body(new UserLoginResponse((String)result));
}
}
UserService
public Object userLogin(UserRequest userRequest) {
// 이메일 형식이 올바른지 체크
if( EmailValidator.isValidEmail( userRequest.email ) == false ){
return 1;
}
try {
User user = userDAO.userLogin(userRequest);
// 비밀번호가 일치하는지 체크
if( passwordEncoder.matches(userRequest.password, user.password) == false ){
return 3;
}
// jwt 토큰 생성
String token = jwtConfig.createToken(user.id);
return token;
} catch (Exception e) {
return 2;
}
}
DBeaver
intelliJ
public User userLogin(UserRequest userRequest) {
// DB 에서 유저 정보를 조회하는 메소드
String sql = "SELECT *\n" +
" from user\n" +
" where email = ? ;";
return jdbcTemplate.queryForObject(sql, new UserRowMapper(), userRequest.email);
}
- entity 패키지 생성
- User 클래스 생성
package com.example.jwt3.entity;
public class User {
public long id;
public String email;
public String password;
public String nickname;
public String role;
public String createdAt;
public User() {
}
public User(long id, String email, String password,
String nickname, String role, String createdAt) {
this.id = id;
this.email = email;
this.password = password;
this.nickname = nickname;
this.role = role;
this.createdAt = createdAt;
}
}
- dto 패키지
- UserLoginResponse 클래스 생성
package com.example.jwt3.dto;
public class UserLoginResponse {
public String token;
public UserLoginResponse() {
}
public UserLoginResponse(String token) {
this.token = token;
}
}
Postman
1.1. 음식점 목록 조회
다양한 조건으로 음식점 목록을 검색하고 조회할 수 있는 API입니다.
- URL: /api/v1/restaurants
- Method: GET
- 설명:
- 페이징 처리된 음식점 목록을 반환합니다.
- 카테고리 필터링과 키워드 검색을 지원합니다.
- 음식점의 평균 평점과 리뷰 수를 포함합니다.
- 최신 등록순으로 정렬됩니다.
Postman
intelliJ
@RestController
public class RestaurantController {
@Autowired
RestaurantService restaurantService;
//- 페이징 처리된 음식점 목록을 반환합니다.
// - 카테고리 필터링과 키워드 검색을 지원합니다.
//- 음식점의 평균 평점과 리뷰 수를 포함합니다.
//- 최신 등록순으로 정렬됩니다.
@GetMapping("/api/v1/restaurants")
public ResponseEntity<RestaurantListResponse> getRestaurants(@RequestParam int page,
@RequestParam int size,
@RequestParam(required = false) String category,
@RequestParam(required = false) String keyword) {
// 카테코리는 있고 키워드가 없는 경우
if (category == null && keyword == null) {
RestaurantListResponse restaurantListResponse =
restaurantService.getRestaurants(page, size, category, null);
return ResponseEntity.status(200).body(restaurantListResponse);
} else if (category == null && keyword != null) {
// 카테고리가 없고 키워드가 있는경우
RestaurantListResponse restaurantListResponse =
restaurantService.getRestaurants(page, size, null, keyword);
return ResponseEntity.status(200).body(restaurantListResponse);
} else {
// 카테고리와 키워드가 모두 있는 경우
RestaurantListResponse restaurantListResponse =
restaurantService.getRestaurants(page, size, category, keyword);
return ResponseEntity.status(200).body(restaurantListResponse);
}
}
- Query Parameters:
page: 페이지 번호 (기본값: 1)
size: 페이지 크기 (기본값: 10)
category: 카테고리 필터 (선택) - 한식, 중식, 일식, 양식 등
keyword: 검색어 - 이름, 주소 검색 (선택, 최소 2글자 이상)
@Service
public class RestaurantService {
@Autowired
private RestaurantDAO restaurantDAO;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
JwtConfig jwtConfig;
public RestaurantListResponse getRestaurants(int page,
int size,
String category,
String keyword){
// 카테고리가 있고 키워드는 없는 경우
if(category != null && keyword == null){
int offset = (page - 1) * size;
List<RestaurantResponse> restaurantList =
restaurantDAO.getRestaurants(offset, size, category, null);
int totalElements = restaurantDAO.getTotalElements(category, null);
int totalPages = (int) Math.ceil( (double) totalElements / size );
PageableResponse pageableResponse =
new PageableResponse(page, size, totalElements,totalPages);
RestaurantListResponse restaurantListResponse =
new RestaurantListResponse(restaurantList, pageableResponse);
return restaurantListResponse;
} else if(category == null && keyword != null){
// 카테고리가 없고 키워드가 있는 경우
int offset = (page - 1) * size;
List<RestaurantResponse> restaurantList =
restaurantDAO.getRestaurants(offset, size, null, keyword);
int totalElements = restaurantDAO.getTotalElements(null, keyword);
int totalPages = (int) Math.ceil( (double) totalElements / size );
PageableResponse pageableResponse =
new PageableResponse(page, size, totalElements,totalPages);
RestaurantListResponse restaurantListResponse =
new RestaurantListResponse(restaurantList, pageableResponse);
return restaurantListResponse;
} else {
// 카테고리와 키워드가 모두 있는 경우
int offset = (page - 1) * size;
List<RestaurantResponse> restaurantList =
restaurantDAO.getRestaurants(offset, size, category, keyword);
int totalElements = restaurantDAO.getTotalElements(category, keyword);
int totalPages = (int) Math.ceil( (double) totalElements / size );
PageableResponse pageableResponse =
new PageableResponse(page, size, totalElements,totalPages);
RestaurantListResponse restaurantListResponse =
new RestaurantListResponse(restaurantList, pageableResponse);
return restaurantListResponse;
}
}
- Response:
{
"content": [
{
"id": 1,
"name": "맛있는 식당",
"category": "한식",
"address": "서울시 강남구",
"phone": "02-1234-5678",
"description": "전통 한식집",
"avgRating": 4.5,
"reviewCount": 100,
"createdAt": "2024-12-27T10:00:00"
}
],
"pageable": {
"page": 1,
"size": 10,
"totalElements": 100,
"totalPages": 10
}
}
DBeaver
intelliJ
public List<RestaurantResponse> getRestaurants(int offset,
int size,
String category,
String keyword) {
// 카테고리가 있고, 키워드가 없는 경우.
if(category != null && keyword == null){
String sql = "SELECT r.id, r.name, r.category, r.address, r.phone, r.description, \n" +
"\t\t\tIFNULL( avg(rv.rating) , 0) avg_rating ,\n" +
"\t\t\tcount(rv.id) review_count,\n" +
"\t\t\tr.created_at\n" +
"from restaurant r\n" +
"left join review rv\n" +
"on r.id = rv.restaurant_id\n" +
"where r.category = ? \n" +
"group by r.id \n" +
"order by r.created_at desc\n" +
"limit ? , ? ;";
return jdbcTemplate.query(sql, new RestaurantRowMapper(), category, offset, size);
} else if(category == null && keyword != null){
// 카테고리가 없고, 키워드가 있는 경우.
String sql = "SELECT r.id, r.name, r.category, r.address, r.phone, r.description, \n" +
"\t\t\tIFNULL( avg(rv.rating) , 0) avg_rating ,\n" +
"\t\t\tcount(rv.id) review_count,\n" +
"\t\t\tr.created_at\n" +
"from restaurant r\n" +
"left join review rv\n" +
"on r.id = rv.restaurant_id\n" +
"where r.name like ? or r.address like ? \n" +
"group by r.id \n" +
"order by r.created_at desc\n" +
"limit ? , ? ;";
return jdbcTemplate.query(sql, new RestaurantRowMapper(),
"%"+keyword+"%", "%"+keyword+"%" , offset, size);
} else {
// 카테고리와 키워드 둘다 있는 경우
String sql = "SELECT r.id, r.name, r.category, r.address, r.phone, r.description, \n" +
"\t\t\tIFNULL( avg(rv.rating) , 0) avg_rating ,\n" +
"\t\t\tcount(rv.id) review_count,\n" +
"\t\t\tr.created_at\n" +
"from restaurant r\n" +
"left join review rv\n" +
"on r.id = rv.restaurant_id\n" +
"where (r.name like ? or r.address like ? ) and category = ? \n" +
"group by r.id \n" +
"order by r.created_at desc\n" +
"limit ? , ? ;";
return jdbcTemplate.query(sql, new RestaurantRowMapper(),
"%"+keyword+"%", "%"+keyword+"%", category, offset, size);
}
package com.example.jwt3.dto;
public class RestaurantResponse {
public Long id;
public String name;
public String category;
public String address;
public String phone;
public String description;
public Double avgRating;
public Integer reviewCount;
public String createdAt;
public RestaurantResponse() {
}
public RestaurantResponse(Long id, String name, String category,
String address, String phone, String description,
Double avgRating, Integer reviewCount, String createdAt) {
this.id = id;
this.name = name;
this.category = category;
this.address = address;
this.phone = phone;
this.description = description;
this.avgRating = avgRating;
this.reviewCount = reviewCount;
this.createdAt = createdAt;
}
}
package com.example.jwt3.dto;
public class PageableResponse {
public Integer page;
public Integer size;
public Integer totalElements;
public Integer totalPages;
public PageableResponse() {
}
public PageableResponse(Integer page, Integer size,
Integer totalElements, Integer totalPages) {
this.page = page;
this.size = size;
this.totalElements = totalElements;
this.totalPages = totalPages;
}
}
Postman
1.2. 음식점 상세 조회
특정 음식점의 상세 정보와 메뉴 목록을 조회하는 API입니다.
- URL: /api/v1/restaurants/{id}
- Method: GET
- 설명:
- 음식점의 기본 정보를 제공합니다.
- 해당 음식점의 전체 메뉴 목록을 함께 반환합니다.
- 평균 평점과 총 리뷰 수를 포함합니다.
- 각 메뉴별 리뷰 수도 함께 제공됩니다.
- 존재하지 않는 음식점 ID인 경우 404 에러를 반환합니다.
intelliJ
@GetMapping("/api/v1/restaurants/{id}")
public ResponseEntity<RestaurantDetailResponse> getRestaurantDetail(
@PathVariable long id) {
RestaurantDetailResponse restaurantDetailResponse =
restaurantService.getRestaurantDetail(id);
return ResponseEntity.status(200).body(restaurantDetailResponse);
}
package com.example.jwt3.dto;
import java.util.List;
public class RestaurantDetailResponse {
public Long id;
public String name;
public String category;
public String address;
public String phone;
public String description;
public Double avgRating;
public Integer reviewCount;
public String createdAt;
public List<MenuListResponse> menus;
public RestaurantDetailResponse() {
}
public RestaurantDetailResponse(Long id, String name,
String category, String address, String phone,
String description, Double avgRating, Integer reviewCount,
String createdAt, List<MenuListResponse> menus) {
this.id = id;
this.name = name;
this.category = category;
this.address = address;
this.phone = phone;
this.description = description;
this.avgRating = avgRating;
this.reviewCount = reviewCount;
this.createdAt = createdAt;
this.menus = menus;
}
}
public RestaurantDetailResponse getRestaurantDetail(long id){
RestaurantResponse restaurantResponse =
restaurantDAO.getRestaurantDetail(id);
List<MenuListResponse> menuList = restaurantDAO.getMenuList(id);
RestaurantDetailResponse restaurantDetailResponse =
new RestaurantDetailResponse();
restaurantDetailResponse.id = restaurantResponse.id;
restaurantDetailResponse.menus = menuList;
restaurantDetailResponse.address = restaurantResponse.address;
restaurantDetailResponse.createdAt = restaurantResponse.createdAt;
restaurantDetailResponse.avgRating = restaurantResponse.avgRating;
restaurantDetailResponse.category = restaurantResponse.category;
restaurantDetailResponse.description = restaurantResponse.description;
restaurantDetailResponse.name = restaurantResponse.name;
restaurantDetailResponse.phone = restaurantResponse.phone;
restaurantDetailResponse.reviewCount = restaurantResponse.reviewCount;
return restaurantDetailResponse;
}
- Response:
{
"restaurant": {
"id": 1,
"name": "맛있는 식당",
"category": "한식",
"address": "서울시 강남구",
"phone": "02-1234-5678",
"description": "전통 한식집",
"avgRating": 4.5,
"reviewCount": 100,
"createdAt": "2024-12-27T10:00:00",
},
"menus": [
{
"id": 1,
"name": "김치찌개",
"price": 8000,
"description": "돼지고기 김치찌개",
"category": "찌개",
"reviewCount": 50
}
]
}
package com.example.jwt3.dto;
public class MenuListResponse {
public Long id;
public String name;
public Integer price;
public String description;
public String category;
public Integer reviewCount;
public MenuListResponse() {
}
public MenuListResponse(Long id, String name, Integer price,
String description, String category, Integer reviewCount) {
this.id = id;
this.name = name;
this.price = price;
this.description = description;
this.category = category;
this.reviewCount = reviewCount;
}
}
DBeaver
intelliJ
public RestaurantResponse getRestaurantDetail(long id){
String sql = "SELECT r.id, r.name, r.category, r.address,\n"
"r.phone, r.description, \n" +
"IFNULL( avg(rv.rating) , 0) avg_rating ,\n" +
"count(rv.id) review_count,\n" +
"r.created_at\n" +
"from restaurant r\n" +
"left join review rv\n" +
"on r.id = rv.restaurant_id\n" +
"where r.id = ? \n" +
"group by r.id;";
return jdbcTemplate.queryForObject(sql, new RestaurantRowMapper(), id );
}
public static class MenuListRowMapper implements RowMapper<MenuListResponse>{
@Override
public MenuListResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
MenuListResponse menuListResponse = new MenuListResponse();
menuListResponse.id = rs.getLong("id");
menuListResponse.name = rs.getString("name");
menuListResponse.price = rs.getInt("price");
menuListResponse.description = rs.getString("description");
menuListResponse.category = rs.getString("category");
menuListResponse.reviewCount = rs.getInt("reviewCount");
return menuListResponse;
}
}
Postman
'API > 실습' 카테고리의 다른 글
53. [ JAVA ] Spring Boot를 이용한 API 통신 : 뉴스 검색 예시 (1) | 2025.01.11 |
---|---|
48. [ JAVA ] API 문서 문제 : JWT(Json Web Token)(보안) / Admin(1) (0) | 2025.01.06 |
47. [ JAVA ] API 문서 문제 : JWT(Json Web Token)(보안) / User (2) (0) | 2025.01.05 |
42. [ JAVA ] API 문서 문제 : review_db (1) (3) | 2024.12.25 |
41. [ JAVA ] API 문서 문제 : 유저 관리(sb-user-server) (0) | 2024.12.21 |