외부 경로에 사진 폴더 연결하기
WebMvcConfig 만들기
@Configuration //컴포넌트 스캔
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${file.path}") //application.yml에서 경로 가져오기 (./images/)
private String filePath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry); //Resource Handler 등록
// 1. 윈도우 절대경로 file:///c:/images/
// 2. 상대경로 file:./images/
// 3. 상대경로에 했을 때 실행파일 jar파일이 만들어지면, jar파일과 동일한 위치에 images 폴더가 있으면 찾는다.
registry
.addResourceHandler("/images/**") // /images/1.jpg와 같은 경로가 들어오면
.addResourceLocations("file:" + filePath) // filePath(./images/) 위치에서 찾기
.setCachePeriod(60 * 60) // 초 단위 => 한시간
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
/images/1.jpg와 같은 경로가 들어오면 .jar파일(빌드 파일)에 위치하는 /images/ 폴더에서 1.jpg 파일 찾기
images 폴더 추가
빌드 파일 명
ex) kakao-1.0.jar
settings.gradle // 이름 설정 가능
rootProject.name = 'kakao'
build.gradle
jar {
enabled = false
}
위 부분이 없으면 plain.jar 파일이 생성된다.(kakao-1.0-plain.jar)
즉 라이브러리가 연결되지 않은 작성한 java 파일만을 모아둔 jar파일이 생성된다. - 실행 불가
✅ kakao에서 이미 .jar 파일에 이미지 폴더를 배포해두었기때문에 서버에 이미지 폴더를 배포할 필요가 없다.
통합 테스트(Integration Testing)
스프링 부트 애플리케이션의 여러 컴포넌트와 레이어들이 제대로 상호작용하는지, 실제로 외부 시스템과의 통합도 잘 동작하는지 검증하는 테스트
애플리케이션의 모든 구성 요소를 포함하여 전체 시스템의 동작을 검증하므로 단위 테스트보다는 더 많은 시스템 자원과 시간이 소요
- 실제 컨테이너 실행
- 통합 테스트에서는 스프링 애플리케이션의 모든 레이어(컨트롤러, 서비스, 데이터 액세스 등)와 관련된 빈들이 실제로 컨테이너 내에서 실행된다.
- 이를 통해 실제 의존성과 설정을 반영한 환경에서 테스트를 수행할 수 있다.
- 실제 데이터베이스 사용
- 통합 테스트는 보통 실제 데이터베이스와 연동하여 테스트를 수행
- 테스트 데이터를 데이터베이스에 실제로 저장하고 읽어와서 동작 확인
- JUnit 테스트 프레임워크
- @SpringBootTest : 통합 테스트 클래스 정의
- @Autowired : 실제 빈들을 주입하여 테스트 수행
- @Transactional : 트랜잭션 관리
✳️ 단위 수정시 전체 시스템에 끼칠 영향을 통합 테스트를 통해 파악할 수 있다.
또한 테스트 서버에서 build시 통합 테스트를 수행하므로 배포 시 오류를 줄일 수 있다.
application-test.yml - test 용 profile 생성
server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
spring:
datasource:
url: jdbc:h2:mem:test;MODE=MySQL
driver-class-name: org.h2.Driver
username: //username 작성
password: //password 작성
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
logging:
level:
'[com.example.kakao]': DEBUG
'[org.hibernate.type]': TRACE
file:
path: ./images/
teardown.sql (resources/db/teardown.sql)
-- 모든 제약 조건 비활성화
SET REFERENTIAL_INTEGRITY FALSE;
truncate table user_tb;
truncate table product_tb;
truncate table option_tb;
truncate table cart_tb;
truncate table order_tb;
truncate table item_tb;
SET REFERENTIAL_INTEGRITY TRUE;
-- 모든 제약 조건 활성화
INSERT INTO user_tb (`id`,`email`,`password`,`username`, `roles`) VALUES ('1', 'ssarmango@nate.com', '{bcrypt}$2a$10$8H0OT8wgtALJkig6fmypi.Y7jzI5Y7W9PGgRKqnVeS2cLWGifwHF2', 'ssarmango', 'ROLE_USER');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('1', '기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전', '', '/images/1.jpg', '1000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('2', '[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율', '', '/images/2.jpg', '2000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('3', '삼성전자 JBL JR310 외 어린이용/성인용 헤드셋 3종!', '', '/images/3.jpg', '30000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('4', '바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종', '', '/images/4.jpg', '4000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('5', '[더주] 컷팅말랑장족, 숏다리 100g/300g 외 주전부리 모음 /중독성 최고/마른안주', '', '/images/5.jpg', '5000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('6', '굳지않는 앙금절편 1,050g 2팩 외 우리쌀떡 모음전', '', '/images/6.jpg', '15900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('7', 'eoe 이너딜리티 30포, 오렌지맛 고 식이섬유 보충제', '', '/images/7.jpg', '26800');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('8', '제나벨 PDRN 크림 2개. 피부보습/진정 케어', '', '/images/8.jpg', '25900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('9', '플레이스테이션 VR2 호라이즌 번들. 생생한 몰입감', '', '/images/9.jpg', '797000');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('10', '통영 홍 가리비 2kg, 2세트 구매시 1kg 추가증정', '', '/images/10.jpg', '8900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('11', '아삭한 궁채 장아찌 1kg 외 인기 반찬 모음전', '', '/images/11.jpg', '6900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('12', '깨끗한나라 순수소프트 30롤 2팩. 무형광, 도톰 3겹', '', '/images/12.jpg', '28900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('13', '생활공작소 초미세모 칫솔 12입 2개+가글 증정', '', '/images/13.jpg', '9900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('14', '경북 영천 샤인머스켓 가정용 1kg 2수 내외', '', '/images/14.jpg', '9900');
INSERT INTO product_tb (`id`,`product_name`,`description`,`image`, `price`) VALUES ('15', '[LIVE][5%쿠폰] 홈카페 Y3.3 캡슐머신 베이직 세트', '', '/images/15.jpg', '148000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('1', '1', '01. 슬라이딩 지퍼백 크리스마스에디션 4종', '10000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('2', '1', '02. 슬라이딩 지퍼백 플라워에디션 5종', '10900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('3', '1', '고무장갑 베이지 S(소형) 6팩', '9900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('4', '1', '뽑아쓰는 키친타올 130매 12팩', '16900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('5', '1', '2겹 식빵수세미 6매', '8900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('6', '2', '22년산 햇단밤 700g(한정판매)', '9900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('7', '2', '22년산 햇단밤 1kg(한정판매)', '14500');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('8', '2', '밤깎기+다회용 구이판 세트', '5500');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('9', '3', 'JR310 (유선 전용) - 블루', '29900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('10', '3', 'JR310BT (무선 전용) - 레드', '49900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('11', '3', 'JR310BT (무선 전용) - 그린', '49900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('12', '3', 'JR310BT (무선 전용) - 블루', '49900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('13', '3', 'T510BT (무선 전용) - 블랙', '52900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('15', '4', '선택01_바른곡물효소 누룽지맛 2박스', '17900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('16', '4', '선택02_바른곡물효소누룽지맛 6박스', '50000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('17', '4', '선택03_바른곡물효소3박스+유산균효소3박스', '50000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('18', '4', '선택04_바른곡물효소3박스+19종유산균3박스', '50000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('19', '5', '01. 말랑컷팅장족 100g', '4900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('20', '5', '02. 말랑컷팅장족 300g', '12800');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('21', '5', '03. 눌린장족 100g', '4900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('22', '6', '굳지않는 쑥 앙금 절편 1050g', '15900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('23', '6', '굳지않는 흑미 앙금 절편 1050g', '15900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('24', '6', '굳지않는 흰 가래떡 1050g', '15900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('14', '3', 'T510BT (무선 전용) - 화이트', '52900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('25', '7', '이너딜리티 1박스', '26800');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('26', '7', '이너딜리티 2박스+사은품 2종', '49800');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('27', '8', '제나벨 PDRN 자생크림 1+1', '25900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('28', '9', '플레이스테이션 VR2 호라이즌 번들', '839000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('29', '9', '플레이스테이션 VR2', '797000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('30', '10', '홍가리비2kg(50미이내)', '8900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('31', '11', '궁채 절임 1kg', '6900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('32', '11', '양념 깻잎 1kg', '8900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('33', '11', '된장 깻잎 1kg', '8900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('34', '11', '간장 깻잎 1kg', '7900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('35', '11', '고추 무침 1kg', '8900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('36', '11', '파래 무침 1kg', '9900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('37', '12', '01_순수소프트 27m 30롤 2팩', '28900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('38', '12', '02_벚꽃 프리미엄 27m 30롤 2팩', '32900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('39', '13', '(증정) 초미세모 칫솔 12개 x 2개', '11900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('40', '13', '(증정) 잇몸케어 치약 100G 3개 x 2개', '16900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('41', '13', '(증정) 구취케어 치약 100G 3개 x 2개', '16900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('42', '13', '(증정)화이트케어 치약 100G 3개 x 2개', '19900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('43', '13', '(증정) 어린이 칫솔 12EA', '9900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('44', '14', '[가정용] 샤인머스켓 1kg 2수내외', '9900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('45', '14', '[특품] 샤인머스켓 1kg 1-2수', '12900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('46', '14', '[특품] 샤인머스켓 2kg 2-3수', '23900');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('47', '15', '화이트', '148000');
INSERT INTO option_tb (`id`,`product_id`,`option_name`,`price`) VALUES ('48', '15', '블랙', '148000');
INSERT INTO cart_tb (`id`,`user_id`, `option_id`, `quantity`, `price`) VALUES ('1', '1', '1', '5', '50000');
INSERT INTO cart_tb (`id`,`user_id`, `option_id`, `quantity`, `price`) VALUES ('2', '1', '2', '1', '10900');
INSERT INTO cart_tb (`id`,`user_id`, `option_id`, `quantity`, `price`) VALUES ('3', '1', '16', '5', '250000');
INSERT INTO order_tb (`id`,`user_id`) VALUES ('1', '1');
INSERT INTO item_tb (`id`,`option_id`, `quantity`, `price`, `order_id`) VALUES ('1', '1', '5', '50000', '1');
INSERT INTO item_tb (`id`,`option_id`, `quantity`, `price`, `order_id`) VALUES ('2', '2', '1', '10900', '1');
INSERT INTO item_tb (`id`,`option_id`, `quantity`, `price`, `order_id`) VALUES ('3', '16', '5', '250000', '1');
dummy data 생성 - 필요한 것만
CartRestControllerTest - 통합 테스트 코드
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080)
@ActiveProfiles("test") //test profile 사용
@Sql(value = "classpath:db/teardown.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) //Test 메서드 실행 전에 Sql 실행
@AutoConfigureMockMvc //MockMvc 사용
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) //통합테스트(SF-F-DS(Handler, ExHandler)-C-S-R-PC-DB) 다 뜬다.
public class ProductRestControllerTest extends MyRestDoc {
@Test
public void findAll_test() throws Exception {
// given teardown.sql
// Integer page = 1;
// when
ResultActions resultActions = mvc.perform(
get("/products")
);
//페이징 확인
// ResultActions resultActions = mvc.perform(
// get("/products")
// .param("page", page.toString())
// );
// eye (눈으로 본다.)
// String responseBody = resultActions.andReturn().getResponse().getContentAsString();
// System.out.println("테스트 : "+responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response[0].id").value(1));
resultActions.andExpect(jsonPath("$.response[0].productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response[0].description").value(""));
resultActions.andExpect(jsonPath("$.response[0].image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response[0].price").value(1000));
resultActions.andDo(MockMvcResultHandlers.print()).andDo(document); //restDoc
}
@Test
public void findById_test() throws Exception {
// given teardown.sql
int id = 1;
// when
ResultActions resultActions = mvc.perform(
get("/products/" + id)
);
// console
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : "+responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response.id").value(1));
resultActions.andExpect(jsonPath("$.response.productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response.description").value(""));
resultActions.andExpect(jsonPath("$.response.image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response.price").value(1000));
resultActions.andDo(MockMvcResultHandlers.print()).andDo(document);
}
}
ProductControllerTest
package com.example.kakao.product;
import com.example.kakao.MyRestDoc;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080)
@ActiveProfiles("test") //test profile 사용
@Sql(value = "classpath:db/teardown.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) //Test 메서드 실행 전에 Sql 실행
@AutoConfigureMockMvc //MockMvc 사용
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) //통합테스트(SF-F-DS(Handler, ExHandler)-C-S-R-PC-DB) 다 뜬다.
public class ProductRestControllerTest extends MyRestDoc {
@Test
public void findAll_test() throws Exception {
// given teardown.sql
// Integer page = 1;
// when
ResultActions resultActions = mvc.perform(
get("/products")
);
//페이징 확인
// ResultActions resultActions = mvc.perform(
// get("/products")
// .param("page", page.toString())
// );
// eye (눈으로 본다.)
// String responseBody = resultActions.andReturn().getResponse().getContentAsString();
// System.out.println("테스트 : "+responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response[0].id").value(1));
resultActions.andExpect(jsonPath("$.response[0].productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response[0].description").value(""));
resultActions.andExpect(jsonPath("$.response[0].image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response[0].price").value(1000));
resultActions.andDo(MockMvcResultHandlers.print()).andDo(document); //restDoc
}
@Test
public void findById_test() throws Exception {
// given teardown.sql
int id = 1;
// when
ResultActions resultActions = mvc.perform(
get("/products/" + id)
);
// console
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : "+responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response.id").value(1));
resultActions.andExpect(jsonPath("$.response.productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response.description").value(""));
resultActions.andExpect(jsonPath("$.response.image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response.price").value(1000));
resultActions.andDo(MockMvcResultHandlers.print()).andDo(document);
}
}
API Docs 문서자동화
build.gradle 설정
plugins { ...생략
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
dependencies { ...생략
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
ext {
set('snippetsDir', file("build/generated-snippets"))
}
tasks.named('test') {
outputs.dir snippetsDir
systemProperty 'file.encoding', 'UTF-8'
useJUnitPlatform()
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
}
bootJar {
dependsOn asciidoctor
copy {
// src/docs/asciidoc == from 경로
from "${asciidoctor.outputDir}"
into 'src/main/resources/static/docs'
// /static/docs로 복사!
}
}
추상테스트 클래스
@ExtendWith({ SpringExtension.class, RestDocumentationExtension.class })
public class MyRestDoc {
protected MockMvc mvc;
protected RestDocumentationResultHandler document; //문서로 생성
@BeforeEach
private void setup(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.document = MockMvcRestDocumentation.document("{class-name}/{method-name}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), //요청 formating
Preprocessors.preprocessResponse(Preprocessors.prettyPrint())); //응답 formating
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)) //filter 추가
.apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)) //설정 적용
// .apply(SecurityMockMvcConfigurers.springSecurity())
.alwaysDo(document) //문서 생성
.build();
}
}
적용
@AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080) //붙이기
public class CartRestControllerTest extends MyRestDoc { //상속
..
@WithUserDetails(value = "ssarmango@nate.com") //시큐리티 인증
@Test
public void addCartList_test() throws Exception {
// given -> optionId [1,2,16]이 teardown.sql을 통해 들어가 있음
List<CartRequest.SaveDTO> requestDTOs = new ArrayList<>();
CartRequest.SaveDTO item = new CartRequest.SaveDTO();
item.setOptionId(3);
item.setQuantity(5);
requestDTOs.add(item);
String requestBody = om.writeValueAsString(requestDTOs);
System.out.println("요청 데이터 : " + requestBody);
// when
ResultActions resultActions = mvc.perform( //MyRestDoc의 mvc 사용
post("/carts/add")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON_VALUE)
);
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andDo(MockMvcResultHandlers.print()).andDo(document); //붙이기
}
✅ @AutoConfigureRestDocs(uriScheme = "http", uriHost = "localhost", uriPort = 8080) 붙이기
✅ MyRestDoc 상속받아 부모의 mockMvc와 document 사용하기
✅ verify 가장 마지막에 resultActions.andDo(MockMvcResultHandlers.print()).andDo(document); 추가하기
build/generated-snippets
test 진행시 진행한 테스트에 대한 문서 생성됨
API 문서 format 만들기
src/docs/asciidoc (default 경로)
api-docs.adoc (파일 이름은 선택가능, 확장자는 adoc)
= 카카오 쇼핑하기 RestAPI Metacoding <getinthere@naver.com>
ifndef::snippets[]
:snippets: ./build/generated-snippets
endif::[]
:product: product-rest-controller-test
:cart: cart-rest-controller-test
:toc: left
:toclevels: 2
:source-highlighter: highlightjs == 상품
=== 전체 상품 목록 조회
* param : page={number}
* param의 디폴트 값은 0이다.
==== 요청 예시 include::{snippets}/{product}/find-all_test/http-request.adoc[]
==== 응답 예시 include::{snippets}/{product}/find-all_test/response-body.adoc[]
=== 개별 상품 상세 조회 ==== 요청 예시
include::{snippets}/{product}/find-by-id_test/http-request.adoc[]
==== 응답 예시 include::{snippets}/{product}/find-by-id_test/response-body.adoc[]
== 장바구니
=== 장바구니 담기
==== 요청 예시 include::{snippets}/{cart}/add-cart-list_test/http-request.adoc[]
==== 응답 예시 include::{snippets}/{cart}/add-cart-list_test/response-body.adoc[]
=== 장바구니 조회 ==== 요청 예시
include::{snippets}/{cart}/find-all_test/http-request.adoc[]
==== 응답 예시 include::{snippets}/{cart}/find-all_test/response-body.adoc[]
=== 장바구니 수정
* 주문하기를 할 때 장바구니 데이터를 update하고 그 결과를 응답받는다.
* 결재페이지에서 이 응답을 사용해도 되고, 다시 장바구니 조회 API를 사용해도 된다.
==== 요청 예시 include::{snippets}/{cart}/update_test/http-request.adoc[]
==== 응답 예시 include::{snippets}/{cart}/update_test/response-body.adoc[]
adoc파일 -> html 파일로 변환하기 위한 코드
./gradlew clean build시 html파일이 resources/static/docs 내부에 생성됨
:product: product-rest-controller-test //test시 생성된 폴더 적기
생성된 api-docs.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.10">
<meta name="author" content="Metacoding">
<title>카카오 쇼핑하기 RestAPI</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
<style>
/* Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment @import statement to use as custom stylesheet */
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite::before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}
:not(pre)>code.nobreak{word-wrap:normal}
:not(pre)>code.nowrap{white-space:nowrap}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:100%;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details>summary:first-of-type{cursor:pointer;display:list-item;outline:none;margin-bottom:.75em}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:1px;border-color:#dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;-webkit-border-radius:4px;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class="highlight"],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos{border-right:1px solid currentColor;opacity:.35;padding-right:.5em}
pre.pygments .lineno{border-right:1px solid currentColor;opacity:.35;display:inline-block;margin-right:.75em}
pre.pygments .lineno::before{content:"";margin-right:-.125em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0}
table.tableblock{max-width:100%;border-collapse:separate}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
td.tableblock>.content>:last-child.sidebarblock{margin-bottom:0}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}
table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}
table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}
table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px}
table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0}
table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}
table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot,table.frame-ends{border-width:1px 0}
table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd),table.stripes-even tr:nth-of-type(even),table.stripes-hover tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
ul.checklist{margin-left:.625em}
ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data>table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
</head>
<body class="article">
<div id="header">
<h1>카카오 쇼핑하기 RestAPI</h1>
<div class="details">
<span id="author" class="author">Metacoding</span><br>
<span id="email" class="email"><a href="mailto:getinthere@naver.com">getinthere@naver.com</a></span><br>
<span id="revnumber">version 1.0</span>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="_상품">상품</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_전체_상품_목록_조회">전체 상품 목록 조회</h3>
<div class="ulist">
<ul>
<li>
<p>param : page={number}</p>
</li>
<li>
<p>param의 디폴트 값은 0이다.</p>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="_요청_예시">요청 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /products HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_응답_예시">응답 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code>{
"success" : true,
"response" : [ {
"id" : 1,
"productName" : "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전",
"description" : "",
"image" : "/images/1.jpg",
"price" : 1000
}, {
"id" : 2,
"productName" : "[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율",
"description" : "",
"image" : "/images/2.jpg",
"price" : 2000
}, {
"id" : 3,
"productName" : "삼성전자 JBL JR310 외 어린이용/성인용 헤드셋 3종!",
"description" : "",
"image" : "/images/3.jpg",
"price" : 30000
}, {
"id" : 4,
"productName" : "바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종",
"description" : "",
"image" : "/images/4.jpg",
"price" : 4000
}, {
"id" : 5,
"productName" : "[더주] 컷팅말랑장족, 숏다리 100g/300g 외 주전부리 모음 /중독성 최고/마른안주",
"description" : "",
"image" : "/images/5.jpg",
"price" : 5000
}, {
"id" : 6,
"productName" : "굳지않는 앙금절편 1,050g 2팩 외 우리쌀떡 모음전",
"description" : "",
"image" : "/images/6.jpg",
"price" : 15900
}, {
"id" : 7,
"productName" : "eoe 이너딜리티 30포, 오렌지맛 고 식이섬유 보충제",
"description" : "",
"image" : "/images/7.jpg",
"price" : 26800
}, {
"id" : 8,
"productName" : "제나벨 PDRN 크림 2개. 피부보습/진정 케어",
"description" : "",
"image" : "/images/8.jpg",
"price" : 25900
}, {
"id" : 9,
"productName" : "플레이스테이션 VR2 호라이즌 번들. 생생한 몰입감",
"description" : "",
"image" : "/images/9.jpg",
"price" : 797000
} ],
"error" : null
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_개별_상품_상세_조회">개별 상품 상세 조회</h3>
<div class="sect3">
<h4 id="_요청_예시_2">요청 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /products/1 HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_응답_예시_2">응답 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code>{
"success" : true,
"response" : {
"id" : 1,
"productName" : "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전",
"description" : "",
"image" : "/images/1.jpg",
"price" : 1000,
"starCount" : 5,
"options" : [ {
"id" : 1,
"optionName" : "01. 슬라이딩 지퍼백 크리스마스에디션 4종",
"price" : 10000
}, {
"id" : 2,
"optionName" : "02. 슬라이딩 지퍼백 플라워에디션 5종",
"price" : 10900
}, {
"id" : 3,
"optionName" : "고무장갑 베이지 S(소형) 6팩",
"price" : 9900
}, {
"id" : 4,
"optionName" : "뽑아쓰는 키친타올 130매 12팩",
"price" : 16900
}, {
"id" : 5,
"optionName" : "2겹 식빵수세미 6매",
"price" : 8900
} ]
},
"error" : null
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_장바구니">장바구니</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_장바구니_담기">장바구니 담기</h3>
<div class="sect3">
<h4 id="_요청_예시_3">요청 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">POST /carts/add HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 42
Host: localhost:8080
[ {
"optionId" : 3,
"quantity" : 5
} ]</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_응답_예시_3">응답 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code>{
"success" : true,
"response" : null,
"error" : null
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_장바구니_조회">장바구니 조회</h3>
<div class="sect3">
<h4 id="_요청_예시_4">요청 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">GET /carts HTTP/1.1
Host: localhost:8080</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_응답_예시_4">응답 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code>{
"success" : true,
"response" : {
"products" : [ {
"id" : 1,
"productName" : "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전",
"carts" : [ {
"id" : 1,
"option" : {
"id" : 1,
"optionName" : "01. 슬라이딩 지퍼백 크리스마스에디션 4종",
"price" : 10000
},
"quantity" : 5,
"price" : 50000
}, {
"id" : 2,
"option" : {
"id" : 2,
"optionName" : "02. 슬라이딩 지퍼백 플라워에디션 5종",
"price" : 10900
},
"quantity" : 1,
"price" : 10900
} ]
}, {
"id" : 4,
"productName" : "바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종",
"carts" : [ {
"id" : 3,
"option" : {
"id" : 16,
"optionName" : "선택02_바른곡물효소누룽지맛 6박스",
"price" : 50000
},
"quantity" : 5,
"price" : 250000
} ]
} ],
"totalPrice" : 310900
},
"error" : null
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_장바구니_수정">장바구니 수정</h3>
<div class="ulist">
<ul>
<li>
<p>주문하기를 할 때 장바구니 데이터를 update하고 그 결과를 응답받는다.</p>
</li>
<li>
<p>결재페이지에서 이 응답을 사용해도 되고, 다시 장바구니 조회 API를 사용해도 된다.</p>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="_요청_예시_5">요청 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-http" data-lang="http">POST /carts/update HTTP/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 41
Host: localhost:8080
[ {
"cartId" : 1,
"quantity" : 10
} ]</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_응답_예시_5">응답 예시</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code>{
"success" : true,
"response" : {
"carts" : [ {
"cartId" : 1,
"optionId" : 1,
"optionName" : "01. 슬라이딩 지퍼백 크리스마스에디션 4종",
"quantity" : 10,
"price" : 100000
}, {
"cartId" : 2,
"optionId" : 2,
"optionName" : "02. 슬라이딩 지퍼백 플라워에디션 5종",
"quantity" : 1,
"price" : 10900
}, {
"cartId" : 3,
"optionId" : 16,
"optionName" : "선택02_바른곡물효소누룽지맛 6박스",
"quantity" : 5,
"price" : 250000
} ],
"totalPrice" : 360900
},
"error" : null
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Version 1.0<br>
Last updated 2023-07-31 14:07:20 +0900
</div>
</div>
</body>
</html>
크롬에서 본 화면
api 문서보기
배포
커널
- 운영체제의 핵심 부분(항상 메모리에 올라가있음)
- 하드웨어와 응용 프로그램 사이에서 컴퓨터 자원을 관리하는 역할
- 인터페이스로써 응용 프로그램 수행에 필요한 여러가지 서비스 제공
- 여러가지 하드웨어(CPU, 메모리)등의 리소스 관리하는 역할
- 항상 컴퓨터 자원을 바라만 보고 있기에 사용자와의 상호작용은 쉘(Shell) 명령어 사용
커널의 목표
- 컴퓨터의 물리적(하드웨어) 자원과 추상화 자원 관리
- 추상화란 물리적으로 하나뿐인 하드웨어를 여러 사용자들이 번갈아 사용할 수 있도록 여러개처럼 보이게 하는 기술
- 커널이 관리함에 따라 각 사용자들을 하나의 하드웨어를 독점하는 것처럼 느낄 수 있도록 함
커널 수행 작업
- Task(Process) Management : 물리적 자원인 CPU를 추상적 자원인 Task로 제공
- Memory Management : 물리적 자원인 메모리를 추상적 자원인 Page 또는 Segment로 제공
- File System : 물리적 자원인 디스크를 추상적 자원인 File로 제공
- Network Management : 물리적 자원이 네트워크 장치를 추상적 자원인 Socket으로 제공
- Device Driver Management : 각종 외부 장치에 대한 접근
- Interrupt Handling : 인터럽트 핸들러
- I/O Communication : 입출력 통신 관리
✅ 커널에 다양한 확장 애플리케이션을 추가하여 운영체제를 제공하는 것들을 Ubuntu, CentOS라고 함
리눅스 컨테이너(LXC)
컨테이너(화물 컨테이너)의 개념
여러개의 다양한 물건들을 각각의 컨테이너에 담아서 운반할 수 있으며, 컨테이너들은 서로 독립적으로 작동한다.
즉, 컨테이너에 담긴 물건이 다른 컨테이너의 물건에 영향을 미치지 않는다.
리눅스 컨테이너
화물 컨테이너의 개념을 컴퓨터 환경으로 확장
운영체제의 커널을 제외한 모든 프로세스와 라이브러리 등을 하나의 독립적인 컨테이너로 포장한다.
이렇게 하면 컨테이너 안에서 실행되는 프로세스들은 서로 독립적이며, 다른 컨테이너에서 실행되는 프로세스들과는 분리된다.
도커
컨테이너라는 가상의 격리 환경을 만들기 위해 프로세스를 독립시켜주는 가상화 기술과 자원(CPU, 메모리, network bandwidth)에 대한 제어를 가능하게 해주는 리눅스 커널의 기능을 사용한다.(namespace, cgroup)
컨테이너 런타임
컨테이너가 필요한 리소스를 할당하고, 네트워크 설정, 파일 시스템등을 관리해준다.
각 컨테이너는 독립적인 환경을 가지며, 격리되어 작동되게 해준다.
✅ 각각의 프로그램간에 간섭을 일으킬 수 없는 장벽을 치고, CPU, 메모리 등의 자원 또한 독립적으로 사용할 수 있도록 할당, 관리한다.
=> 두 프로그램이 서로 다른 컴퓨터에 설치되어있다고 생각하게 됨 = OS 커널을 공유하는 가상화
실습
application.yml
spring:
profiles:
active:
- local
profile은 local로 설정
application-local.yml
server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
spring:
datasource:
url: jdbc:h2:mem:test;MODE=MySQL # 메모리로, h2 DB
driver-class-name: org.h2.Driver
username: # username 작성
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
logging:
level:
'[com.example.kakao]': DEBUG
'[org.hibernate.type]': TRACE
file:
path: ./images/ # 이미지 파일 경로
jar 파일 만들기
./gradlew clean build
기존 build 파일을 지우고 새롭게 build
test 진행 후 build 수행
실행
java -jar [jar파일]
실행시에 서버가 작동되고 Get, Post 요청이 잘 응답한다.
개발 환경
개발 환경에서 production(운영 환경)으로 배포시 여러가지를 고려해야한다.
- DB
- OS(운영체제)
- Settings(환경 설정)
개발환경과 운영환경은 다르므로, 바로 배포하지 않고 production과 같은 환경인 Test 서버에서 테스트 진행한다.
- git download
- test 진행
- build - jar 파일 생성 (production 환경으로 실행)
배포계획
nginx는 서버들 앞에 존재하는 웹서버로 클라이언트 요청을 받는다.
✅ 클라이언트가 80 포트로 요청하면, 리액트 서버의 8080 포트로 포트포워딩 되어 요청된다
React 서버는 Spring 서버(Project)로 요청하고 Spring 서버는 필요시 DB 서버(mariaDB)로 요청을 보낸다.
✅ Spring 요청시 /api를 앞에 붙인다.(규칙)
ex) 스프링 : localhost:8080/products일때 리액트: localhost:3000/api/products
배포 웹 : 도메인.com/api/products
카카오 크램폴린 IDE를 사용해서 배포 환경과 동일한 테스트 환경 사용하기
D2Hub은 IDE가 아닌 Github의 코드로 이미지를 build한다.
Kargo는 D2Hub 이미지로 DKOS에 배포한다.
mysql 실행
service mysql start
✅ 실행되는지 포트로 확인하기
spring 서버 실행
gradle clean build && java -jar -Dspring.profiles.active=ide build/libs/[jar파일이름].jar
ide profile로 실행 (local이 아닌 개발 환경과 같은 ide 환경으로 설정)
application-ide.yml
server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
spring:
datasource:
url: jdbc:mariadb://localhost:3306/kakao?allowPublicKeyRetrieval=true&useSSL=false
driver-class-name: org.mariadb.jdbc.Driver
username: //username 작성
password: //password 작성
jpa:
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: none
show-sql: true
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
logging:
level:
'[com.example.kakao]': DEBUG
'[org.hibernate.type]': TRACE
file:
path: ./images/
IDE 환경에 maria DB가 이미 설치되어있다. (사용만 하면 됨)
react 실행
chmod +x ./startFront.sh && ./startFront.sh
x(execute) 권한을 주고 react 실행
startFront.sh
rm -rf /var/lib/apt/lists/* && \
rm /etc/nginx/sites-enabled/default && \
service nginx start
(cd /goormService/frontend && npm i && npm start)
여기서 nginx는 서버 앞단에 존재하는 웹 서버, 즉 클라이언트가 접근하는 서버 이름이다.
클라이언트가 80 포트로 요청하면, 리액트 서버의 8080 포트로 포트포워딩 되어 요청된다.
IDE에서 Preview로 본 화면
테스트 환경인 IDE에 리액트 서버와 스프링 서버 모두 잘 띄워진 것을 볼 수 있다.
📌 코드 수정할 경우에 local에서 수정 후 git push, IDE에서 pull 하여 이미지 build
D2Hub - image 빌드(build)
Kargo 배포하기
실행중인 컨테이너 보기
kubectl get pods
컨테이너 로그 보기
kubectl logs backend-d497976b9-l988
DB 접속하기
kubectl exec -it mariadb-0 -- /bin/bash
mysql -h mariadb-0.mariadb -u root -p kakao //password:root
✅ Error 발생시(Could not extract resultSet) 클러스터의 Maria DB 관련 리소스를 초기화
kubectl delete statefulset mariadb
kubectl delete svc mariadb
kubectl delete pvc data-mariadb-0
그 후 재배포하기
'Spring > 카테캠 - TIL' 카테고리의 다른 글
TIL [0802] : 6주차 과제 수행 (통합테스트, API 문서 작성, 배포) (0) | 2023.08.02 |
---|---|
응답값 검증 (0) | 2023.08.02 |
TIL [0728] 카카오테크캠퍼스 5주차 과제 (기능 구현 + 예외 처리) (0) | 2023.07.28 |
카카오테크캠퍼스 4주차 코드리뷰 (0) | 2023.07.28 |
TIL [0727] : 기능 구현, AOP 개념 정리 (0) | 2023.07.27 |