<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>JUNEee</title>
    <link>https://codebox123.tistory.com/</link>
    <description>개발새발네발</description>
    <language>ko</language>
    <pubDate>Thu, 4 Jun 2026 16:09:37 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JUNEee</managingEditor>
    <image>
      <title>JUNEee</title>
      <url>https://tistory1.daumcdn.net/tistory/7342258/attach/549de02318934002bf2b4cb1e4909056</url>
      <link>https://codebox123.tistory.com</link>
    </image>
    <item>
      <title>[JavaScript] 자바스크립트 Number 자료형</title>
      <link>https://codebox123.tistory.com/13</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckUqOa/btsOm7Dyol2/uGapFrihqqIm4kaksPauFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckUqOa/btsOm7Dyol2/uGapFrihqqIm4kaksPauFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckUqOa/btsOm7Dyol2/uGapFrihqqIm4kaksPauFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckUqOa%2FbtsOm7Dyol2%2FuGapFrihqqIm4kaksPauFk%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color: red;&quot;&gt;Number 의 특징&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트에서는 숫자 자료형이 매우 넒은 부분을 포함한다.&lt;br&gt;일반적으로 java 와 같이 타입이 고정되어있는 언어의 경우 숫자형 타입으로 &lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;int&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;long&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;byte&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;short&lt;/strong&gt;&lt;br&gt;등이 존재하는 반면 자바스크립트의 경우 이러한 숫자의 형태와 상관 없이 모든 형태를 허용하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아래 예제를 보면 let 으로 선언된 result라는 변수에 정수형, 실수형, 2진수값, 16진수값 등 여러 숫자형 타입이 들어갈 수 있으며, 모두 10진수로 변환되어 출력됨을 알 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let result = 3
console.log(result); //3
//
result = 3.14
console.log(result); //3.14
//
result = 0b0011;
console.log(result); //3
//
result = 0x3
console.log(result); //3
.
.
.&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color: red;&quot;&gt;숫자형의 특수한 상태를 표현하는 Infinity / NaN&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;숫자의 형태에는 무한대이거나 또는 허용되지 않는 값도 존재할 수 있다. 이것들을 각각 Infinity, NaN 이라고 표현하게 되는데&lt;br&gt;&lt;strong&gt;[Infinity]&lt;/strong&gt; : 단어 뜻 그대로 무한대의 수를 의미하게 된다.&lt;br&gt;아래 예제를 참고해보자&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let result = 3 / 0
console.log(result); //Infinity&lt;/code&gt;&lt;/pre&gt;
예제에서는 result에 3을 0으로 나눈 값을 대입하고 있으며 3을 0으로 나눈 값은 무한대이므로 해당 변수의 값을 출력해보면 &lt;strong&gt;Infinity&lt;/strong&gt; 가 출력됨을 알 수 있다.&lt;br&gt;&lt;strong&gt;[NaN(Not a Number)]&lt;/strong&gt; : 숫자가 아닌 값을 의미한다.&lt;br&gt;아래 예제를 참고해보자&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let result = 3 / &amp;quot;십&amp;quot;;
console.log(result); //NaN&lt;/code&gt;&lt;/pre&gt;
3을 숫자 10이 아닌 문자 &amp;quot;십&amp;quot;으로 나누었다. 숫자를 문자로 나누었을 때 이는 수학적으로 정의되지 않은 연산이므로 NaN이 반환된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;음의 무한대 &lt;strong&gt;-Infinity&lt;/strong&gt;&lt;br&gt;만약 3이 아닌 -3 음수를 나누게 되면 결과값은 음의 무한대가 나온다.&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let result = -3 / 0;
console.log(result); // -Infinity&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NaN&lt;/strong&gt; 은 자기 자신과도 같지 않다.&lt;br&gt;result에 NaN 을 대입하였다 이후 result 값과 NaN을 비교 연산하였고 결과는 false 즉 NaN 은 그 자체로 유일한 값이다.&lt;br&gt;따라서 result의 값이 NaN인지를 확인해야할 경우 Number.isNaN(result) 을 사용하는것이 적절하다.&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let result = NaN;
console.log(result === NaN); // false
console.log(result == NaN);  // false
// NaN 확인하는 방법
console.log(Number.isNaN(result));  // true&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>FE/자바스크립트</category>
      <category>JavaScript</category>
      <category>Nan</category>
      <category>number</category>
      <category>자바스크립트</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/13</guid>
      <comments>https://codebox123.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 3 Jun 2025 15:42:56 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 자바스크립트 프로토타입 이해하기!</title>
      <link>https://codebox123.tistory.com/12</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GWQyU/btsOmsn2n0u/kEjbrChR5cQiv0ysAtTKy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GWQyU/btsOmsn2n0u/kEjbrChR5cQiv0ysAtTKy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GWQyU/btsOmsn2n0u/kEjbrChR5cQiv0ysAtTKy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGWQyU%2FbtsOmsn2n0u%2FkEjbrChR5cQiv0ysAtTKy0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color: red;&quot;&gt;프로토타입이란?&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트의 모든 객체는 자신의 &amp;quot;&lt;strong&gt;원형(Prototype)&lt;/strong&gt;&amp;quot; 이 되는 객체를 가지며 이를 &lt;strong&gt;프로토타입&lt;/strong&gt;이라고 한다.&lt;br&gt;뭔소린지 모르겠다. 원형이 되는 객체? 의미가 다소 추상적이지 않는가 예시 코드를 보며 이해해보자.&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;script&amp;gt;
  function 사람() {
      this.name = &amp;#39;june&amp;#39;;
      this.age = 19;
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
먼저 사람 이라는 생성자 함수를 추가해주었다. 사람은 고유의 이름과 성별 나이 등을 가지고 있을 수 있는데, 현재는 이름과 나이 뿐이다.&lt;br&gt;따라서 사람 이라는 객체에 프로토타입을 활용하여 gender 라는 값을 추가해볼 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;먼저 출력해보기&lt;br&gt;생성자를 호출하여 사람 객체 하나를 만들고 값을 출력해보았다.&lt;br&gt;이전에 정의한 대로 name과 age가 잘 들어있는 것을 볼 수 있다.&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;lt;script&amp;gt;
  function 사람() {
      this.name = &amp;#39;june&amp;#39;;
      this.age = 19;
  }
var person = new 사람();
console.log(person); // {name: &amp;#39;june&amp;#39;, age: 19}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
우리는 여기서 프로토타입을 활용하여 gender라는 필드를 추가해줄 것이다.&lt;br&gt;앞서 초반에 프로토타입에 대한 정의로 자바스크립트의 모든 객체는 자신의 &lt;strong&gt;원형&lt;/strong&gt; 이 되는 객체를 갖는다고 하였다 그렇다면 우리가 생성한 person객체 또한 원형이 되는 객체가 있다는 의미가 아닐까? 다른 객체지향형 언어 들을 다루어보았다면 이 개념이 부모-자식 관계와 유사함을 느낄 것이다.&lt;br&gt;마치 우리가 생성한 person 이라는 객체는 자식에 해당하고 person 의 원형은 부모에 해당하는 것으로 비유할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;gender 필드 추가하기&lt;br&gt;person 객체의 부모(프로토타입) 에 gender라는 필드를 추가하려면 해당 프로토타입에 접근할 필요가 있다. &lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;사람.prototype.gender = &amp;#39;male&amp;#39;&lt;/code&gt;&lt;/pre&gt;
프로토타입에 접근하는 방법은 &amp;#39;사람&amp;#39; 이라는 생성자 함수를 통해 부모 객체에 접근할 수 있다.&lt;br&gt;부모 객체에 접근한 이후 gender 라는 필드를 추가하고 해당 필드 값으로 &amp;#39;male&amp;#39; 을 추가해 주었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;추가한 gender 필드 출력해보기&lt;br&gt;코드를 한번 보자 &amp;#39;사람&amp;#39; 이라는 생성자를 호출하여 person객체를 만들었다 그리고 ( 사람.prototype.gender = &amp;#39;male&amp;#39; ) 을 통해 프로토타입에 gender라는 필드를 추가하고 male 값을 넣어주었다. 그런데 person객체를 출력해보면 어디에도 gender라는 필드는 존재하지 않는다.&lt;br&gt;사실 우리가 추가한 gender라는 필드는 person 객체에 추가된 것이 아니다.&lt;br&gt;person객체의 부모 객체인 prototype에 추가된 것이다 따라서 ( console.log(person.gender); ) person 객체를 참조하여 gender 필드의 값을 가져올 수 있는 이유는 &lt;strong&gt;프로토타입 체인&lt;/strong&gt; 덕분이다.&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function 사람() {
      this.name = &amp;#39;june&amp;#39;;
      this.age = 19;
  }
  사람.prototype.gender = &amp;#39;male&amp;#39;
  var person = new 사람();
  console.log(person); // {name: &amp;#39;june&amp;#39;, age: 19}
  console.log(person.gender); // &amp;#39;male&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color: red;&quot;&gt;프로토타입 체인&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;p&gt;자바스크립트는 특정 객체의 필드에 접근할 때 만약 존재하지 않는 필드라면 내부 슬롯의 참조를 따라 상위 프로토타입의 필드를 순차적으로 검색한다.&lt;br&gt;즉 자바스크립트는 객체에서 뭔가를 찾을 때 없으면 &amp;quot;부모한테 가서 물어보는&amp;quot; 방식으로 동작한다는 의미 이다.&lt;br&gt;&lt;strong&gt;[프로토타입 체인 흐름]&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;gender : &amp;quot;gender라는 필드가 person 객체에 없는데?&lt;/li&gt;
&lt;li&gt;사람.prototype : &amp;quot;여기에 gender 필드가 있고 값은 male이야&amp;quot;&lt;/li&gt;
&lt;li&gt;male 값 반환&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function 사람() {
     this.name = &amp;#39;june&amp;#39;;
     this.age = 19;
 }
 사람.prototype.gender = &amp;#39;male&amp;#39;
 var person = new 사람();
 console.log(person); // {name: &amp;#39;june&amp;#39;, age: 19}
 console.log(person.gender); // &amp;#39;male&amp;#39;&lt;/code&gt;&lt;/pre&gt;
예시 코드를 다시 보면 우리가 만든 person객체에는 gender라는 필드가 존재하지 않는다.&lt;br&gt;따라서 ( person.gender ) 는 프로토타입 체인에 따라 상위 프로토타입을 검색하게 되고 여기서 ( 사람.prototype.gender = &amp;#39;male&amp;#39; ) 이런 코드를 통해 앞서 프로토타입에 gender라는 필드를 추가해주었기 때문에 &amp;#39;male&amp;#39; 이라는 값을 가져올 수 있게 되는 것이다!&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>FE/자바스크립트</category>
      <category>자바스크립트</category>
      <category>프로토타입</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/12</guid>
      <comments>https://codebox123.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 3 Jun 2025 13:27:34 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] rest api 스프링부트 회원관리 기능 구현하기! (CRUD)</title>
      <link>https://codebox123.tistory.com/11</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nW2A2/btsOhWnT0kT/8kW2XjgxHSQ6m5tFOHKk11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nW2A2/btsOhWnT0kT/8kW2XjgxHSQ6m5tFOHKk11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nW2A2/btsOhWnT0kT/8kW2XjgxHSQ6m5tFOHKk11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnW2A2%2FbtsOhWnT0kT%2F8kW2XjgxHSQ6m5tFOHKk11%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;CRUD 란?&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;p&gt;CRUD 란 Create, Read, Update, Delete 의 앞글자를 딴 개념으로 대부분의 시스템이 데이터를 처리할 때 기본적으로 제공하는 핵심 기능을 의미한다.&lt;br&gt;본 블로그에서는 CRUD를 각각&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C (Create)&lt;/strong&gt; : 회원가입&lt;br&gt;&lt;strong&gt;R (Read)&lt;/strong&gt; : 회원조회&lt;br&gt;&lt;strong&gt;U (Update)&lt;/strong&gt; : 회원정보 수정&lt;br&gt;&lt;strong&gt;D (Delete)&lt;/strong&gt; : 회원정보 삭제&lt;br&gt;로 구현해볼 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;시작하기 전 준비하기&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;p&gt;회원관리 기능을 구현하기 전 사전 준비작업이 필요하다.&lt;br&gt;먼저 사용할 기술스택은 다음과 같다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Spring JPA&lt;/li&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;Swagger&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;먼저 MySQL, Spring JPA, Spring Security, Swagger 사용을 위해 의존성을 추가해주어야 한다&lt;br&gt;현재 추가된 의존성은 다음과 같다&lt;pre&gt;&lt;code&gt;//build.gradle
  implementation &amp;#39;org.springframework.boot:spring-boot-starter-security&amp;#39;
  implementation &amp;#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8&amp;#39;
  implementation &amp;#39;org.springframework.boot:spring-boot-starter-data-jpa&amp;#39;
  runtimeOnly &amp;#39;com.mysql:mysql-connector-j&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;MySQL 연결&lt;pre&gt;&lt;code&gt;//application.properties
  #mysql
  spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  spring.datasource.url=jdbc:mysql://localhost:3306/crud
  spring.datasource.username=
  spring.datasource.password=
  spring.jpa.hibernate.ddl-auto=update&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Security 설정 (security를 사용하는 이유는 회원가입 시 패스워드 필드를 해싱하기 위함임)&lt;br&gt;기본적으로 모든 api 경로를 허용해주었다. 또한 패스워드 필드를 해싱하기 위해 필요한 BCryptPasswordEncoder 객체를 의존성 주입받기 위하여 bean으로 등록해주었다.&lt;pre&gt;&lt;code&gt;//SecurityConfig
@Configuration
public class SecurityConfig {
  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
      http
              .csrf(csrf -&amp;gt; csrf.disable())
              .sessionManagement(session -&amp;gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
              .authorizeHttpRequests(auth -&amp;gt; auth
                      .anyRequest().permitAll());
              return http.build();
  }
  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;프로젝트 구조&lt;pre&gt;&lt;code&gt;crud
├── CrudApplication.java           // 메인 애플리케이션 클래스
├── config
│   └── SecurityConfig.java        // 보안 설정 클래스
├── controller
│   └── Controller.java            // 요청을 처리하는 컨트롤러
├── dto
│   ├── FindDto.java               // 사용자 조회용 DTO
│   ├── LoginDto.java              // 로그인용 DTO
│   ├── SignUpDto.java             // 회원가입용 DTO
│   └── UpdateDto.java             // 사용자 정보 수정용 DTO
├── entity
│   └── UserEntity.java            // 사용자 엔티티 클래스
├── repository
│   └── Repository.java            // 데이터베이스 접근 인터페이스
└── service
  └── Service.java&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;회원 JPA 엔터티 생성&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
@Builder
@Table(name =&amp;quot;사용자&amp;quot;)
public class UserEntity {
    @Id
    @Column(length = 255)
    private String id;

    @Column(length = 255)
    private String name;

    @Column(length = 255)
    private String password;
}&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;회원 JPA 레파지토리 생성&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;public interface Repository extends JpaRepository&amp;lt;UserEntity, String&amp;gt; {
    boolean existsById(String id);
}&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[1] 회원가입 구현&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;controller&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@PostMapping(&amp;quot;/signup&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; signUp(@RequestBody SignUpDto signUpDto) {
      try {
          String signUpResponse = service.signUp(signUpDto);
          return ResponseEntity.ok().body(signUpResponse);
      } catch (RuntimeException e) {
          return ResponseEntity.badRequest().body(e.getMessage());
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dto&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class SignUpDto {
  private String id;
  private String name;
  private String password;

  public UserEntity toEntity() {
      return UserEntity.builder()
              .id(this.id)
              .name(this.name)
              .password(this.password)
              .build();
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public String signUp(SignUpDto signUpDto) {
      if(repository.existsById(signUpDto.getId())) {
          log.info(&amp;quot;이미 가입된 유저입니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;이미 가입된 유저입니다.&amp;quot;);
      }
      signUpDto.setPassword(hash.encode(signUpDto.getPassword()));
      repository.save(signUpDto.toEntity());

      return &amp;quot;회원가입 성공&amp;quot;;
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DB&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1YHvk/btsOiq34Pip/942LmFlMP6HkBTHXzZtC3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1YHvk/btsOiq34Pip/942LmFlMP6HkBTHXzZtC3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1YHvk/btsOiq34Pip/942LmFlMP6HkBTHXzZtC3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1YHvk%2FbtsOiq34Pip%2F942LmFlMP6HkBTHXzZtC3K%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[2] 회원조회 구현&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;controller&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@GetMapping(&amp;quot;/{id}&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; findById(@PathVariable String id) {
      System.out.println(id);
      try {
          FindDto user = service.findById(id);
          return ResponseEntity.ok().body(user);
      } catch (RuntimeException e) {
          return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
      }

  }

  @GetMapping(&amp;quot;/all&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; findAll() {
      List&amp;lt;FindDto&amp;gt; users = service.findAll();
      return ResponseEntity.ok().body(users);
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dto&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Getter
@Setter
@Builder
public class FindDto {
  private String id;
  private String name;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public List&amp;lt;FindDto&amp;gt; findAll() {
      List&amp;lt;UserEntity&amp;gt; users =  repository.findAll();
      return users.stream()
              .map(user -&amp;gt; FindDto.builder()
                      .id(user.getId())
                      .name(user.getName())
                      .build())
              .toList();
  }

  public FindDto findById(String id) {
      UserEntity user = repository.findById(id).orElse(null);
      if(user == null) {
          log.info(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
      } else {
          log.info(&amp;quot;유저 정보 조회 성공&amp;quot;);
          return FindDto.builder()
                  .id(user.getId())
                  .name(user.getName())
                  .build();
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[3] 회원수정 구현&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;controller&lt;pre&gt;&lt;code&gt;@PatchMapping(&amp;quot;/{id}&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; update(@PathVariable String id, @RequestBody UpdateDto updateDto) {
      try {
          String updateResponse = service.update(id, updateDto);
          return ResponseEntity.ok().body(updateResponse);
      } catch (RuntimeException e) {
          return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Dto&lt;pre&gt;&lt;code&gt;@Getter
@Setter
public class UpdateDto {
  private String name;
  private String password;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Service&lt;pre&gt;&lt;code&gt;public String update(String id, UpdateDto updateDto) {
      if(!repository.existsById(id)) {
          log.info(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
      } else {
          UserEntity user = repository.findById(id).orElse(null);
          user.setName(updateDto.getName());
          user.setPassword(hash.encode(updateDto.getPassword()));
          repository.save(user);
          return &amp;quot;유저 정보 업데이트 성공&amp;quot;;
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[4] 회원삭제 구현&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;controller&lt;pre&gt;&lt;code&gt;@DeleteMapping(&amp;quot;/{id}&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; delete(@PathVariable String id) {
      try {
          String deleteResponse = service.delete(id);
          return ResponseEntity.ok().body(deleteResponse);
      } catch (RuntimeException e) {
          return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Dto&lt;pre&gt;&lt;code&gt;@Getter
@Setter
public class UpdateDto {
  private String name;
  private String password;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Service&lt;pre&gt;&lt;code&gt;public String update(String id, UpdateDto updateDto) {
      if(!repository.existsById(id)) {
          log.info(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;해당 ID의 유저가 없습니다.&amp;quot;);
      } else {
          UserEntity user = repository.findById(id).orElse(null);
          user.setName(updateDto.getName());
          user.setPassword(hash.encode(updateDto.getPassword()));
          repository.save(user);
          return &amp;quot;유저 정보 업데이트 성공&amp;quot;;
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[5] 로그인 구현&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;controller&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@PostMapping(&amp;quot;/login&amp;quot;)
  public ResponseEntity&amp;lt;?&amp;gt; login(@RequestBody LoginDto loginDto) {
      try {
          String LoginResponse = service.login(loginDto);
          return ResponseEntity.ok().body(LoginResponse);
      } catch (RuntimeException e) {
          return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dto&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Getter
@Setter
public class LoginDto {
  private String id;
  private String password;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public String login(LoginDto loginDto) {

      if(!repository.existsById(loginDto.getId())) {
          log.info(&amp;quot;가입된 유저가 없습니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;가입된 유저가 없습니다.&amp;quot;);
      }

      UserEntity user = repository.findById(loginDto.getId()).orElse(null);
      if(hash.matches(loginDto.getPassword(), user.getPassword())) {
          log.info(&amp;quot;로그인 성공&amp;quot;);
          return &amp;quot;로그인 성공&amp;quot;;
      } else {
          log.info(&amp;quot;비밀번호가 일치하지 않습니다.&amp;quot;);
          throw new RuntimeException(&amp;quot;비밀번호가 일치하지 않습니다.&amp;quot;);
      }
  }&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;구현 결과&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;p&gt;api 테스트는 postman 으로 진행하겠다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;회원가입&lt;/strong&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwS7ky/btsOheXC4vv/89u89ffG62zxnGVAyPQHWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwS7ky/btsOheXC4vv/89u89ffG62zxnGVAyPQHWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwS7ky/btsOheXC4vv/89u89ffG62zxnGVAyPQHWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwS7ky%2FbtsOheXC4vv%2F89u89ffG62zxnGVAyPQHWK%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회원조회&lt;/strong&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boU06z/btsOjehvLXW/ac0Pvj4ff070GhU6ZmzyvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boU06z/btsOjehvLXW/ac0Pvj4ff070GhU6ZmzyvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boU06z/btsOjehvLXW/ac0Pvj4ff070GhU6ZmzyvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboU06z%2FbtsOjehvLXW%2Fac0Pvj4ff070GhU6ZmzyvK%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPEcYR/btsOhGmg8zr/HcX8ytrSXLLdwt2y91lPF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPEcYR/btsOhGmg8zr/HcX8ytrSXLLdwt2y91lPF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPEcYR/btsOhGmg8zr/HcX8ytrSXLLdwt2y91lPF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPEcYR%2FbtsOhGmg8zr%2FHcX8ytrSXLLdwt2y91lPF0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회원정보 수정&lt;/strong&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duqMDo/btsOhVpUtDf/DD9skpUGHF3MbafmAgopKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duqMDo/btsOhVpUtDf/DD9skpUGHF3MbafmAgopKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duqMDo/btsOhVpUtDf/DD9skpUGHF3MbafmAgopKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduqMDo%2FbtsOhVpUtDf%2FDD9skpUGHF3MbafmAgopKK%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회원정보 삭제&lt;/strong&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r5N6j/btsOjfAFwXB/7fAtyAFBkgke83U2oBylD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r5N6j/btsOjfAFwXB/7fAtyAFBkgke83U2oBylD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r5N6j/btsOjfAFwXB/7fAtyAFBkgke83U2oBylD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr5N6j%2FbtsOjfAFwXB%2F7fAtyAFBkgke83U2oBylD1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;로그인&lt;/strong&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rPdwB/btsOhUxNGjw/hHK20afj2OKkVTIAVLDpi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rPdwB/btsOhUxNGjw/hHK20afj2OKkVTIAVLDpi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rPdwB/btsOhUxNGjw/hHK20afj2OKkVTIAVLDpi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrPdwB%2FbtsOhUxNGjw%2FhHK20afj2OKkVTIAVLDpi0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>BE/스프링</category>
      <category>CRUD</category>
      <category>spring</category>
      <category>SpringBoot</category>
      <category>회원관리</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/11</guid>
      <comments>https://codebox123.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 29 May 2025 19:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] REST Api 란 무엇일까?</title>
      <link>https://codebox123.tistory.com/10</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfiwYy/btsN68Kj3PU/1RvTUauunHeO1sx0slJW5k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfiwYy/btsN68Kj3PU/1RvTUauunHeO1sx0slJW5k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfiwYy/btsN68Kj3PU/1RvTUauunHeO1sx0slJW5k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfiwYy%2FbtsN68Kj3PU%2F1RvTUauunHeO1sx0slJW5k%2Fimg.webp&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[REST Api 개념]&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Api 란?&lt;/strong&gt;&lt;br&gt;&lt;u&gt;Application Programming Interface&lt;/u&gt; 의 약자로 컴퓨터나 컴퓨터 프로그램이 서로 소통할 수 있도록 제공하는 인터페이스이다.&lt;br&gt;웹 환경에서의 api는 서버가 제공하는 기능들을(자원의 조회 등..) URI 의 형태로 클라이언트에게 제공하게 된다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kW9yD/btsN8YTsHNJ/QRk0J7Tpr8oikSQHKNwvSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kW9yD/btsN8YTsHNJ/QRk0J7Tpr8oikSQHKNwvSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kW9yD/btsN8YTsHNJ/QRk0J7Tpr8oikSQHKNwvSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkW9yD%2FbtsN8YTsHNJ%2FQRk0J7Tpr8oikSQHKNwvSk%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST 란?&lt;/strong&gt;&lt;br&gt;&lt;u&gt;Representational State Transfer&lt;/u&gt; 의 약자로 HTTP 프로토콜을 이용한 웹 서비스 설계에서 주로 사용하는 설계 원칙이다.&lt;br&gt;자원을 이름으로 구분하여 자원의 상태를 주고받는 모든 것들을 의미하며 네트워크 상에서 클라이언트와 서버 사이의 통신 규칙 이라 이해하면 될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST Api 란?&lt;/strong&gt;&lt;br&gt;REST Api 는 REST의 설계원칙이 적용된 Api 를 의미하며 URI 로 자원의 형태를 표현하기 때문에 표현 규칙 또한 존재한다.&lt;ol&gt;
&lt;li&gt;동사보다는 명사를, 대문자보다는 소문자를 사용한다.&lt;/li&gt;
&lt;li&gt;복합명사의 경우 URI의 가독성을 높이기 위해 하이픈(-) 을 사용하며 밑줄(_) 은 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;파일의 확장자는 URI에 포함시키지 않는다.&lt;/li&gt;
&lt;li&gt;URI에는 동사 표현이 들어가면 안된다.&lt;pre&gt;&lt;code&gt;http://localhost/user/get/id..&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;자원에 대한 행위 표현은 HTTP Method 를 사용하여 표현한다.&lt;br&gt; &lt;strong&gt;GET&lt;/strong&gt; : 자원을 조회할 때 사용한다.&lt;br&gt; &lt;strong&gt;POST&lt;/strong&gt; : 자원을 생성하거나 정보를 전달할때 사용한다.&lt;br&gt; &lt;strong&gt;PUT/PATCH&lt;/strong&gt; : 자원을 수정할 때 사용한다.&lt;br&gt; (PUT 은 자원을 모두 업데이트 해야할 경우 사용한다. PATCH 는 자원의 일부를 업데이트 해야할 경우 사용한다.)&lt;br&gt; &lt;strong&gt;DELETE&lt;/strong&gt; : 자원을 삭제할 때 사용한다.&lt;pre&gt;&lt;code&gt; POST      http://localhost/users     (생성)✅
 PUT/PATCH http://localhost/users/123 (수정)✅
 DELETE    http://localhost/users/123 (삭제)✅
 GET       http://localhost/users/123 (조회)✅
 GET       http://localhost/users     (전체조회)✅&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;경로 부분 중 변하는 부분은 유일한 값으로 대체한다.&lt;pre&gt;&lt;code&gt; PUT/PATCH http://localhost/users/{id} 
 DELETE    http://localhost/users/{id} 
 GET       http://localhost/users/{id}&lt;/code&gt;&lt;/pre&gt; URI 에서 {id} 값이 변화하므로 해당 id 값은 자원을 식별하기 위한 유일한 값이어야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[REST Api 설계원칙]&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;p&gt;앞서 REST란 클라이언트와 서버가 데이터를 주고받는 방식에 대한 설계 원칙임을 이야기 하였다.&lt;br&gt;그렇다면 RESTful 한 설계를 하기 위해서 어떤 원칙들이 존재할까&lt;br&gt;REST 설계 원칙에는 총 6가지가 존재한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client-server 구조&lt;/strong&gt; : 클라이언트와 서버는 서로 독립적이어야 하며, 클라이언트는 URI 리소스만을 알아야 한다.&lt;br&gt;이 둘은 독립적으로 개발되거나 대체될 수 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateless&lt;/strong&gt; : 클라이언트의 요청에는 요청을 이해할 수 있는 모든 정보가 포함되어있어야 한다.&lt;br&gt;서버는 요청에 대한 어떤 정보도 저장하지 않으며 세션이나 인증/인가 와 같은 정보는 클라이언트의 요청에 모두 포함되어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cacheable&lt;/strong&gt; : 서버는 응답 헤더에 해당 요청의 캐싱 가능 여부를 제공해야하며 클라이언트는 이를 캐싱하여 서버와 클라이언트간의 통신 효율을 높인다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uniform interface&lt;/strong&gt; : REST Api 에서는 일관된 인터페이스를 제공하도록 하기 위해 4가지의 설계원칙을 제공하고있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;identification of resources : 요청 시 개별 자원을 식별할 수 있어야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;manipulation of resources through representations : 어떤 자원에 대하여 작업하기 위한 적절한 표현과 데이터가 갖추어져 있다면 서버는 자원을 조작할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PUT /users/123                    ← 어떤 자원인지 명시
Content-Type: application/json    ← 메타데이터 (JSON 형식이라고 알려줌)
Authorization: Bearer token123    ← 메타데이터 (권한 정보)

{                                ← 적절한 표현 (JSON)
&amp;quot;name&amp;quot;: &amp;quot;김영희&amp;quot;,
&amp;quot;email&amp;quot;: &amp;quot;kim@example.com&amp;quot;,
&amp;quot;phone&amp;quot;: &amp;quot;010-1234-5678&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layered system&lt;/strong&gt; : REST는 다중 계층 구조를 가지도록 허용한다. (Api 서버와 DB 서버, 인증 서버를 분리하는 등..)&lt;br&gt; 각 계층은 다른 계층의 대한 정보를 얻을 수 없다. 클라이언트는 Api 서버 하고만 상호작용하며 이외의 계층과는 상호작용 하거나 알 수 없다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code on demand(필수아님)&lt;/strong&gt; : 서버가 클라이언트에서 실행시킬 수 있는 로직을 전송하여 클라이언트가 사전에 구현해야할 기능을 간소화시킬 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
&amp;quot;formConfig&amp;quot;: {
 &amp;quot;fields&amp;quot;: [
   {&amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;username&amp;quot;, &amp;quot;required&amp;quot;: true},
   {&amp;quot;type&amp;quot;: &amp;quot;email&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;email&amp;quot;, &amp;quot;required&amp;quot;: true}
 ]
},
&amp;quot;renderScript&amp;quot;: &amp;quot;function renderForm(config) { config.fields.forEach(field =&amp;gt; { /* 폼 생성 로직 */ }); }&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>BE/스프링</category>
      <category>API</category>
      <category>http</category>
      <category>Rest</category>
      <category>rest api</category>
      <category>restful</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/10</guid>
      <comments>https://codebox123.tistory.com/10#entry10comment</comments>
      <pubDate>Thu, 22 May 2025 18:28:51 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 변수의 선언 및 특징</title>
      <link>https://codebox123.tistory.com/8</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmIHXh/btsN5TS6sNX/xzBMap5evrG181sEgzlnRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmIHXh/btsN5TS6sNX/xzBMap5evrG181sEgzlnRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmIHXh/btsN5TS6sNX/xzBMap5evrG181sEgzlnRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmIHXh%2FbtsN5TS6sNX%2FxzBMap5evrG181sEgzlnRk%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[ 변수의 선언 ]&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트에서 변수를 사용하기 위해서는 반드시 사전에 개발자가 사용할 변수를 선언하여야 한다.&lt;br&gt;자바스크립트에서는 변수를 선언하기 위해 3가지 키워드를 제공한다&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;var&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;let&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;const&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;var&lt;/strong&gt; : 여러 블로그들을 살펴보면 &amp;quot;제발 var 쓰지 말고 let 쓰세요&amp;quot; 라는 글들을 심심치 않게 볼 수 있다.&lt;br&gt;그 이유는 여러가지가 있는데 대표적으로 var 라는 키워드는 &lt;strong&gt;블록 레벨 스코프&lt;/strong&gt;를 지원하지 않고 &lt;strong&gt;함수 레벨 스코프&lt;/strong&gt;를 지원한다는 점이다.&lt;br&gt;&lt;strong&gt;블록 레벨 스코프&lt;/strong&gt; : 블록 레벨 스코프란 선언된 변수가 특정 블록 내부에서 유효함을 의미한다.&lt;pre&gt;&lt;code&gt;function example() {
if (true) {
  let count = 20;
}
console.log(count); //에러
// count 라는 변수는 if 라는 블록 내부에 선어되어 있기 때문에 블록 외부에서 count로 접근할 수 없다.
}&lt;/code&gt;&lt;/pre&gt;&lt;strong&gt;함수 레벨 스코프&lt;/strong&gt; : 함수 레벨 스코프란 선언된 변수가 함수 내부에서 유효함을 의미한다.&lt;pre&gt;&lt;code&gt;function example() {
if (true) {
  var count = 20;
}
console.log(count); //에러안남
// 반면에 var 로 선언된 변수의 경우 example() 이라는 함수 내에서 유효하기 때문에 if 블록 내부에 선언되어 있어도 블록 외부에서 접근이 가능하다.
}&lt;/code&gt;&lt;/pre&gt;만약 다른 프로그래밍 언어를 사용해본 개발자라면 함수 레벨 스코프는 뭔가 이상하다.. 일반적인 상식으로 블록 내에 선언된 변수는 외부에서 접근할 수 없는게 당연한데 var 키워드는 이를 허용해버린다.&lt;br&gt;이로인해 의도치 않게 변수에 접근하거나 또는 수정되어 버릴 수 있기 때문에 디버깅의 어려움과 변수가 오염될 수 있다는 단점이 있다.&lt;br&gt;이러한 var 키워드의 단점을 보완하고자 ES6에서는 let 과 const 키워드를 도입하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;let&lt;/strong&gt; : 앞선 예시에서 let 키워드의 특징을 알 수 있었는데, let 으로 선언된 변수의 경우 &lt;strong&gt;블록 레벨 스코프&lt;/strong&gt; 를 지원한다.&lt;br&gt;따라서 if 블록 내에 let 으로 변수가 선언되었다면, if 블록 외부에서는 해당 변수에 접근할 수 없게 된다.&lt;pre&gt;&lt;code&gt;function example() {
if (true) {
  let count = 20;
}
console.log(count); //에러
// let 으로 선언되었기 때문에 if 블록 내에 선언된 count변수에는 접근할 수 없다
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;const&lt;/strong&gt; : var, let 과는 달리 const 키워드로 선언된 변수는 재할당이 금지된다. &lt;pre&gt;&lt;code&gt;function example() {
  const test = 20;
  test = 30; //에러
}&lt;/code&gt;&lt;/pre&gt;정확히는 &lt;strong&gt;원시 값&lt;/strong&gt; 을 재할당 할 수 없는 것이지 만약 const로 선언된 변수에 객체를 할당할 경우 값을 변경할 수 있다.&lt;br&gt;즉 const 는 재할당을 금지할뿐 불변하는 것은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;u&gt;&lt;span style=&quot;color:red&quot;&gt;[ 변수의 특징 ]&lt;/span&gt;&lt;/u&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;호이스팅&lt;/strong&gt; : 호이스팅이란 변수와 함수의 메모리 할당을 선언 전에 수행하는 것을 의미한다. 즉, 선언문이 스코프의 선두로 끌어 올려진 것처럼 동작하게 되는 것이다.&lt;pre&gt;&lt;code&gt;console.log(count); //출력값 : undefined
count = 123; //변수할당
console.log(count); //출력값 : 123
//var 키워드로 선언된 변수의 경우 선언과 동시에 undefined로 초기화 된다. 
var count;&lt;/code&gt;&lt;/pre&gt;위 예제에서 알 수 있듯이 var count 라는 변수 선언문이 코드의 맨 아래에 위치함에도 &lt;strong&gt;console.log(count);&lt;/strong&gt; 명령이 정상 실행된다.&lt;br&gt;&amp;quot;let, const 의 경우 &lt;strong&gt;호이스팅&lt;/strong&gt; 이 일어나지 않는다.&amp;quot; 라는 것은 잘못된 설명이다.&lt;br&gt;다음은 이전 예제에서 var 키워드를 let으로만 변경한 코드이다. 과연 어떻게 동작할까?&lt;pre&gt;&lt;code&gt;console.log(count); //에러
count = 123; 
console.log(count); 
let count;&lt;/code&gt;&lt;/pre&gt;var 과 달리 let으로 선언된 변수의 경우 선언문 이전에 참조가 불가능하다.&lt;br&gt;이는 TDZ에 의한 것인데...&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TDZ&lt;/strong&gt; : TDZ란 let 이나 const로 선언된 변수가 선언되기 전까지 사용할 수 없는 구간을 의미한다.&lt;br&gt;let과 const의 경우 선언만 호이스팅되고 초기화는 실제 선언문 위치에서 발생하기 때문에 선언문 이전의 범위(TDZ) 영역에서 변수를 참조할 경우 초기화 되지 않은 변수를 참조하려 시도하여 에러가 발생한다.&lt;br&gt;&lt;u&gt;(여기서 주의할 점은 let은 선언과 할당을 분리할 수 있지만 const는 재할당이 불가능한 상수 이므로 선언과 할당이 동시에 이루어져야 한다.)&lt;/u&gt;&lt;pre&gt;&lt;code&gt;var count;
count = 20;
let test;
test = 30;
const name; //에러
name = &amp;#39;june&amp;#39;; &lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>FE/자바스크립트</category>
      <category>모던 자바스크립트</category>
      <category>자바스크립트</category>
      <category>자바스크립트기초</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/8</guid>
      <comments>https://codebox123.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 21 May 2025 15:50:12 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Github Actions 를 활용한 CI/CD 파이프라인 구축(개념)</title>
      <link>https://codebox123.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyRRxZ/btsNpYzQSqR/gYlJFY8S8L2NHuR146UoQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyRRxZ/btsNpYzQSqR/gYlJFY8S8L2NHuR146UoQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyRRxZ/btsNpYzQSqR/gYlJFY8S8L2NHuR146UoQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyRRxZ%2FbtsNpYzQSqR%2FgYlJFY8S8L2NHuR146UoQ1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;CI/CD 개념&lt;/h2&gt;
&lt;p&gt;개발자라면 한번쯤은 이런 생각을 했을 것이다 &amp;quot;서비스를 개발하고 배포하는 과정이 너무 귀찮은데?&amp;quot; 수시로 바뀌는 코드들과 버그 수정으로 반복되는 테스트, 빌드-&amp;gt;배포 과정은 매우 비효율 적일 뿐더러 무엇보다 귀찮다. 반복되는 이 일련의 과정을 자동화 시킨다면 생산성과 효율 면에서 매우 긍정적인 효과를 가져다 줄 것이다!&lt;br&gt;&lt;br&gt;&lt;br&gt;CI/CD 란 이런 개발자들의 귀찮음을 해결해줄 수 있는 훌륭한 자동화 기술 이다! &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:red&quot;&gt;CI(Continuous Intergration)&lt;/span&gt;&lt;br&gt;CI 란 Continuous Intergration [지속적 통합] 을 의미한다.&lt;br&gt;한 프로젝트에 git을 사용하여 여러 개발자가 개발하고 있다고 가정해보자 만약 프로젝트를 오랜 기간 병합하지 않고 각자 개발했다면 후에 병합을 진행했을 때 수많은 충돌에 직면할 것이다 이렇게 되면 서비스 개발에 집중할 수 없고 코드 수정과 병합에 많은 시간을 소비하게 되니 서비스 출시 기간만 자꾸 미뤄지는 상황에 놓이게 될 것이다.&lt;br&gt;이러한 상황을 방지하기 위해서는 작업단위를 잘게 쪼개어 지속적인 빌드 과정과 테스트를 거처 병합하는 것이 중요한데 이를 자동화 시키는 것이 CI 지속적 통합 이다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caS5JX/btsNoMnFHKB/FkUkzM2GOQjMHkKGjADQD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caS5JX/btsNoMnFHKB/FkUkzM2GOQjMHkKGjADQD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caS5JX/btsNoMnFHKB/FkUkzM2GOQjMHkKGjADQD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaS5JX%2FbtsNoMnFHKB%2FFkUkzM2GOQjMHkKGjADQD1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:red&quot;&gt;CD(Continuous Delivery / Continuous Deployment)&lt;/span&gt;&lt;br&gt;CD 란 &amp;quot;지속적인 제공&amp;quot; 과 &amp;quot;지속적인 배포&amp;quot; 를 의미한다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Continuous Delivery&lt;/span&gt; 에 대해 알아보자면 CI를 통해 빌드와 테스트 단계를 거친 후 서비스 배포가 가능하다 판단되면 개발자가 수동으로 배포를 진행하는것을 의미한다.&lt;br&gt;반면 &lt;span style=&quot;color:red&quot;&gt;Continuous Deployment&lt;/span&gt; 는 배포 과정 마저 자동화 시키는 것을 의미한다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VBcBD/btsNpQowxzl/98KyakeaOsXtIUvFCwfWn1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VBcBD/btsNpQowxzl/98KyakeaOsXtIUvFCwfWn1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VBcBD/btsNpQowxzl/98KyakeaOsXtIUvFCwfWn1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVBcBD%2FbtsNpQowxzl%2F98KyakeaOsXtIUvFCwfWn1%2Fimg.webp&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Github Actions 개념&lt;/h2&gt;
&lt;p&gt;Github Actions 란 깃허브에서 제공하는 CI/CD 도구 이다.&lt;br&gt;CI/CD 를 구축하려면 개발환경과 배포 환경 사이에서 이어줄 무언가가 필요한데 이를 Github Actions가 수행할 수 있다.&lt;br&gt;이해를 쉽게 하기 위해 간단한 상황을 하나 가정해보면, 만약 당신이 기능 개발을 완료한 프로젝트를 깃허브에 push 했을때 이 프로젝트가 자동으로 빌드되고 배포 되려면 어떤 과정을 거쳐야 하겠는가? &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;먼저 프로젝트가 push되었다는 것을 감지한다.&lt;/li&gt;
&lt;li&gt;배포 환경에 접근한다.&lt;/li&gt;
&lt;li&gt;기능이 추가된 새로운 프로젝트를 가져온다.&lt;/li&gt;
&lt;li&gt;빌드 후 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;한마디로 개발환경과 배포환경 사이에서 이러한 과정들을 자동화시켜줄 무언가가 필요한데 이 역할을 Github Actions가 수행하는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:red&quot;&gt;Github Action Core&lt;/span&gt;&lt;br&gt;Github Action을 상용하기 위해서는 알아야될 기본적인 개념들이 있다&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Workflow: &lt;/span&gt;여러 job으로 구성되며 자동화하고자 하는 전체 프로세스를 의미한다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Event: &lt;/span&gt;특정 브랜치에 push 하거나 pull request 하는 이벤트를 감지한다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Job: &lt;/span&gt;Workflow 내에서 독립적으로 실행되는 작업 단위로 여러 Step 들로 구성되며 여러 job들은 기본적으로 병렬로 실행된다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Step: &lt;/span&gt;Job내에 정의된 step들은 순서대로 실행되며 원하는 특정 명령을 실행시킬 수 있다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Action: &lt;/span&gt;Workflow 내에서 재사용 가능한 코드 단위를 의미한다. Github Action에서 사용 가능한 추가기능? 라이브러리? 정도로 생각하면 이해하기 편할 것이다.&lt;br&gt;&lt;span style=&quot;color:red&quot;&gt;Runner: &lt;/span&gt;명령을 실행시켜주는 가상의 컴퓨터 라고 볼 수 있다. runs-on 명령을 통해 명령이 실행될 환경을 직접 설정해줄 수 있다.(ex: runs-on: ubuntu-latest)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;u&gt;[Spring Boot] Github Actions 를 활용한 CI/CD 파이프라인 구축(환경구축)&lt;/u&gt; 편에서는 Github Actions 사용법과 기본적인 spring 프로젝트 세팅, 그리고 aws EC2 인스턴스를 생성해보겠다&lt;/p&gt;</description>
      <category>BE/스프링</category>
      <category>CI/CD</category>
      <category>Github action</category>
      <category>spring</category>
      <category>SpringBoot</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/7</guid>
      <comments>https://codebox123.tistory.com/7#entry7comment</comments>
      <pubDate>Thu, 17 Apr 2025 20:33:15 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Git Flow 란? 브랜치 전략 세우기</title>
      <link>https://codebox123.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHx2NB/btsNhpj26pL/aB3kiiimOEuK7fa94De4v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHx2NB/btsNhpj26pL/aB3kiiimOEuK7fa94De4v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHx2NB/btsNhpj26pL/aB3kiiimOEuK7fa94De4v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHx2NB%2FbtsNhpj26pL%2FaB3kiiimOEuK7fa94De4v0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;Git Flow(깃 플로우)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;span style=&quot;color:#FF6666; font-weight:bold;&quot;&gt;들어가기 전..&lt;/span&gt;&lt;br&gt;깃 플로우 전략을 이해하기 위해서는 먼저 Git 관련 지식들이 선행되어 있어야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;branch, commit, merge 등 git에 대한 기본 개념&lt;/li&gt;
&lt;li&gt;git branch, git add, git checkout, git push 등 기본적인 깃 사용 명령어&lt;/li&gt;
&lt;li&gt;코드 리뷰 등 git을 사용한 전반적인 개발 프로세스에 대한 이해&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;span style=&quot;color:#FF6666; font-weight:bold;&quot;&gt;Git Flow 란 무엇인가?&lt;/span&gt;&lt;br&gt;git flow 는 한마디로 브랜치 관리 전략이다. 만약 여러분이 git 을 사용하여 여러사람과 프로젝트를 진행 하는데 팀원들 끼리의 약속 없이 마구잡이로 브랜치를 생성하고, 병합하고, 푸시 한다면 코드가 뒤죽박죽 섞이거나 충돌이 발생하여 이를 수정하는데에 많은 시간이 소요될 것이며 그만큼 서비스 배포도 지연될 것이다..&lt;br&gt;이러한 혼란스러운 상황을 예방하고자 git flow 라는 브랜치 관리 전략을 사용하여 일정하게 브랜치를 관리하고 병합하고 배포하는 것이다&lt;br&gt;(git flow 이외에도 여러 전략들이 존재한다 &lt;u&gt;github flow&lt;/u&gt;, &lt;u&gt;trunk based&lt;/u&gt; 등)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;span style=&quot;color:#FF6666; font-weight:bold;&quot;&gt;Git Flow 가 제시한 브랜치 전략&lt;/span&gt;&lt;br&gt;총 5가지의 브랜치가 존재하며 각각의 역할이 다르다!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;주 브랜치&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;main/master : 해당 브랜치는 실제 운영 환경에 배포될 수 있는 프로젝트 파일이 존재하여야 함을 가정한다 &lt;/li&gt;
&lt;li&gt;develop : 다음에 배포될 것을 개발하는 브랜치로 개발에 기준이 되는 브랜치 이다&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;보조 브랜치&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;feature : 새로운 기능을 추가해야 할 때 develop 브랜치에서 분기하여 개발하기 위해 사용한다&lt;/li&gt;
&lt;li&gt;release : develop 브랜치에서 완성된 프로젝트를 바로 main/master 브랜치로 병합하지 않고 release 브랜치를 만들어 테스트 후 병합한다&lt;/li&gt;
&lt;li&gt;hotfix : 실제 서비스 환경에서 급히 수정해야할 상황이 발생하였을 때 main/master 브랜치에서 분기하여 수정하기 위해 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;span style=&quot;color:#FF6666; font-weight:bold;&quot;&gt;Git Flow 의 장단점?&lt;/span&gt;&lt;br&gt;&lt;u&gt;&amp;lt;장점&amp;gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;feature 브랜치를 사용하여 여러 개발자가 서로 방해받지 않고 기능을 동시에 개발할 수 있다&lt;/li&gt;
&lt;li&gt;다양한 버전에서 프로젝트를 관리 할 수 있다. 예를 들면 실제 서비스 버전 / 테스트 버전 등..&lt;/li&gt;
&lt;li&gt;코드 병합 전 검토와 승인 과정이 포함되어있어 코드 품질이 보장된다&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;u&gt;&amp;lt;단점&amp;gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;새로운 팀이 학습하기에는 다소 복잡한 패턴 이므로 어느정도의 학습 곡선이 존재한다&lt;/li&gt;
&lt;li&gt;git flow 특성상 많은 브랜치를 사용하기 때문에 유지 관리가 부담스러울 수 있다&lt;/li&gt;
&lt;li&gt;git flow 는 엄격한 규칙을 따라야 하기 때문에 빠른 개발과 배포에는 적절하지 않다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:#FF6666; font-weight:bold;&quot;&gt;Git Flow 흐름&lt;/span&gt;&lt;br&gt;다음은 git flow의 개발 과정을 나타낸 그림 이다&lt;br&gt;시간의 흐름대로 위에서 아래로 천천히 브랜치를 따라가보면 된다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WEMRM/btsNgnOl2I7/lWizNYnNj62Jwc2eSYECy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WEMRM/btsNgnOl2I7/lWizNYnNj62Jwc2eSYECy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WEMRM/btsNgnOl2I7/lWizNYnNj62Jwc2eSYECy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWEMRM%2FbtsNgnOl2I7%2FlWizNYnNj62Jwc2eSYECy1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>기타</category>
      <category>git</category>
      <category>gitflow</category>
      <category>github</category>
      <category>깃플로우</category>
      <category>깃허브</category>
      <category>브랜치관리전략</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/6</guid>
      <comments>https://codebox123.tistory.com/6#entry6comment</comments>
      <pubDate>Thu, 10 Apr 2025 21:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] MCP(Model Context Protocol) 기본 개념 및 테스트(1)</title>
      <link>https://codebox123.tistory.com/4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duauFf/btsM9ln606d/poWaWjBsHOYf43OTtDQuvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duauFf/btsM9ln606d/poWaWjBsHOYf43OTtDQuvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duauFf/btsM9ln606d/poWaWjBsHOYf43OTtDQuvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduauFf%2FbtsM9ln606d%2FpoWaWjBsHOYf43OTtDQuvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;918&quot; height=&quot;260&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;MCP 란 무엇인가?!&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MCP는 여러 LLM 모델이 외부 세계와 통신하기 위해 엔트로픽 에서 정의한 표준 프로토콜 이라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;여러 mcp를 소개하는 글에서는 mcp를 USB-C 포트로 비유하는 경우가 많은데, 이는 mcp &lt;a href=&quot;https://modelcontextprotocol.io/introduction&quot;&gt;공식문서&lt;/a&gt;에서도 확인할 수 있는 내용이다.&lt;/p&gt;
&lt;p&gt;Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools. &lt;/p&gt;
&lt;p&gt;&amp;quot;MCP를 AI 애플리케이션의 USB-C 포트라고 생각하세요. USB-C가 다양한 주변 기기 및 액세서리에 장치를 연결하는 표준화된 방식을 제공하는 것처럼 MCP는 AI 모델을 다양한 데이터 소스 및 도구에 연결하는 표준화된 방식을 제공합니다.&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;그래서 MCP로 뭘 할 수 있는데?  &lt;/p&gt;
&lt;p&gt;필자가 mcp를 접하고 가장 먼저 떠오른 생각은 &amp;quot;LLM 모델의 종류와 관계없이 다양한 편의 기능을 구현할 수 있겠는데?&amp;quot; 라는 생각이였다. 이에 대해 매우 단순한 예를 하나 들자면 사용자가 분석을 원하는 파일을 업로드 하고 이를 분석해줄 LLM 모델을 바꿔가며 (GPT, claude 등) 이용할 수 있다.&lt;br&gt;(파일 업로드를 지원하지 않는 모델이라 하더라도..)&lt;br&gt;모델에 의존하지 않고 사용자에게 일관된 인터페이스를 제공할 수 있다는 의미이기도 하다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;한계점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MCP 의 한계점은? &lt;/p&gt;
&lt;p&gt;앞서 MCP는 USB-C 포트와 유사하다고 하였다. USB-C 포트는 스마트폰 포함 대부분의 기기에 일관된 충전 규격을 제공함으로써 생태계의 호환성과 효율성을 높였듯이 mcp 또한 LLM모델의 종류와 상관없이 MCP서버가 사용자와 LLM사이를 중계하여 사용자에게 일관된 경험을 제공할 수 있도록 서비스 개발이 가능해진다&lt;br&gt;다만 이 방식에는 한계점이 존재하는데 (개인적으로 느끼는 한계점으로 그냥 참고만 하길..)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모델의 독립성을 완전히 보장하지는 못한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;MCP 구조 부분에서 언급하겠지만, MCP는 크게 세 부분으로 나뉜다&lt;br&gt;&amp;quot;MCP 호스트&amp;quot;, &amp;quot;MCP 클라이언트&amp;quot;, &amp;quot;MCP 서버&amp;quot;&lt;br&gt;간단히만 알아보자면 사용자가 입력한 자연어는 먼저 MCP 호스트 내에서 LLM모델로 전달되게 된다 LLM모델은 이를 MCP 클라이언트가 이해할 수 있는 구조화된 데이터로 변환 후 MCP 클라이언트에게 전달하게 되는데 이때 문제가 발생한다.&lt;br&gt;만약 우리가 사용하려는 LLM 모델의 성능 이슈로 인해 사용자가 입력한 자연어를 적절하게 해석하지 못한다면 전반적인 MCP 흐름에 부정적인 영향을 주게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;데이터 주입 방식이 비효율 적이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;앞서 언급했듯이 MCP 구조는 세 부분으로 나뉜다.&lt;br&gt;만약 사용자가 &amp;quot;내 깃허브 README 파일을 분석해줘&amp;quot;라고 요청한다면, MCP 서버는 README 파일을 읽고 데이터를 MCP 클라이언트에게 전달할 것이다.&lt;br&gt;MCP 클라이언트는 받은 데이터를 LLM 모델이 이해할 수 있는 형태로 변환한 후, 사용자의 요청과 함께 프롬프트 형식으로 데이터를 주입하게 된다. 이 과정에서 문제가 발생한다.&lt;br&gt;간단한 파일 내용을 분석하는 것은 프롬프트 주입 방식이 적합하지만, 대용량 파일을 분석할 경우 이러한 방식은 과도한 토큰을 소모하게 되고, 이는 곧 비용 증가로 직결된다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3lAU/btsM676AWqT/XPXh9pipQIxQLuLcuyIdj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3lAU/btsM676AWqT/XPXh9pipQIxQLuLcuyIdj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3lAU/btsM676AWqT/XPXh9pipQIxQLuLcuyIdj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3lAU%2FbtsM676AWqT%2FXPXh9pipQIxQLuLcuyIdj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2026&quot; height=&quot;308&quot; data-origin-width=&quot;2026&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MCP 구조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MCP(Model Context Protocol) 는 크게 3가지 부분으로 나뉜다&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;MCP 호스트&lt;/li&gt;
&lt;li&gt;MCP 클라이언트&lt;/li&gt;
&lt;li&gt;MCP 서버&lt;br&gt;&lt;strong&gt;MCP 호스트&lt;/strong&gt; : MCP 호스트는 Claude Desktop, 통합 개발 환경, 또는 기타 AI 도구와 같이 MCP를 통해 데이터에 접근하고자 하는 애플리케이션 프로그램을 의미한다&lt;br&gt;&lt;strong&gt;MCP 클라이언트&lt;/strong&gt; : MCP 클라이언트는 다양한 MCP 서버와 MCP 프로토콜을 통해 연결될 수 있으며 서버에서 데이터를 가져와 LLM 모델에게 제공한다&lt;br&gt;&lt;strong&gt;MCP 서버&lt;/strong&gt; : 표준화된 Model Context Protocol을 통해 특정 기능을 제공한다.(MCP 클라이언트가 요청한 데이터를 MCP 서버는 내부의 다양한 도구들(web api, sql 등)을 활용하여 데이터를 조회하고 이를 제공한다)&lt;br&gt;전체 구조를 보면 다음과 같다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/827LW/btsM9A602d4/qCuDeIxXw8KFLvKxVmU9G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/827LW/btsM9A602d4/qCuDeIxXw8KFLvKxVmU9G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/827LW/btsM9A602d4/qCuDeIxXw8KFLvKxVmU9G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F827LW%2FbtsM9A602d4%2FqCuDeIxXw8KFLvKxVmU9G1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;768&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;이어서 &lt;a href=&quot;https://codebox123.tistory.com/5&quot; title=&quot;[Spring Boot] MCP(Model Context Protocol) 기본 개념 및 테스트(2)&quot;&gt;[Spring Boot] MCP(Model Context Protocol) 기본 개념 및 테스트(2)&lt;/a&gt; 편에서 스프링부트를 사용해 실제 MCP서버를 구현해보도록 하겠다&lt;/p&gt;</description>
      <category>BE/스프링</category>
      <category>java</category>
      <category>llm연동</category>
      <category>mcp</category>
      <category>mcpserver</category>
      <category>modelcontextprotocol</category>
      <category>SpringBoot</category>
      <category>springframework</category>
      <category>개발자튜토리얼</category>
      <category>백엔드개발</category>
      <category>스프링부트</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/4</guid>
      <comments>https://codebox123.tistory.com/4#entry4comment</comments>
      <pubDate>Fri, 4 Apr 2025 14:07:56 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] selenium 을 사용한 네이버 플레이스 리뷰 크롤링(2)</title>
      <link>https://codebox123.tistory.com/3</link>
      <description>&lt;h2&gt;selenium을 활용한 네이버 크롤링 2편&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;삽질의 시간!&lt;br&gt;자 먼저 크롤링을 수행할 페이지를 찾기 위해 리뷰가 있는 네이버 지도를 들어가 준다(Chrome)&lt;br&gt;&lt;a href=&quot;https://map.naver.com/&quot;&gt;네이버 지도&lt;/a&gt;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8B3Gv/btsM2MsW77j/mxouX71cqlgk8eLpqaKC61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8B3Gv/btsM2MsW77j/mxouX71cqlgk8eLpqaKC61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8B3Gv/btsM2MsW77j/mxouX71cqlgk8eLpqaKC61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8B3Gv%2FbtsM2MsW77j%2FmxouX71cqlgk8eLpqaKC61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;1448&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스타벅스 페이지로 이동&lt;br&gt;그 다음 리뷰가 있는 페이지로 이동해줄텐데 나는 &lt;a href=&quot;https://map.naver.com/p/search/%EA%B0%95%EB%82%A8%20%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4/place/1086067689?c=12.00,0,0,0,dh&amp;amp;placePath=%3Fentry%253Dbmp&quot;&gt;강남 스타벅스&lt;/a&gt; 페이지 에서 삽질을 해볼 생각이다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wtf5R/btsMZ5HHlrN/EhMQWxCzTT5dblm9WiWvb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wtf5R/btsMZ5HHlrN/EhMQWxCzTT5dblm9WiWvb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wtf5R/btsMZ5HHlrN/EhMQWxCzTT5dblm9WiWvb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwtf5R%2FbtsMZ5HHlrN%2FEhMQWxCzTT5dblm9WiWvb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;1448&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;개발자 도구 열기&lt;br&gt;먼저 리뷰를 누르고 크롬 브라우저 우측 상단 점 3개 클릭 -&amp;gt; 도구 더보기 -&amp;gt; 개발자 도구 를 눌러 준다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW6i8J/btsM1hnpU8j/zxueg8Ddkpz2ROrqS99iJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW6i8J/btsM1hnpU8j/zxueg8Ddkpz2ROrqS99iJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW6i8J/btsM1hnpU8j/zxueg8Ddkpz2ROrqS99iJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW6i8J%2FbtsM1hnpU8j%2Fzxueg8Ddkpz2ROrqS99iJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;1448&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;개발자 도구에서 리뷰 요소 찾기&lt;br&gt;개발자 도구 창에서 좌측 상단에 보시면 페이지 element를 선택할 수 있는 도구가 있는데&lt;br&gt;클릭한 다음 리뷰를 누르자&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJtCu4/btsM1R9EqtU/jcRprmBH8713E04ZsVEDQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJtCu4/btsM1R9EqtU/jcRprmBH8713E04ZsVEDQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJtCu4/btsM1R9EqtU/jcRprmBH8713E04ZsVEDQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJtCu4%2FbtsM1R9EqtU%2FjcRprmBH8713E04ZsVEDQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;1448&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGdtuF/btsM2bmbJev/KylFDngSK1kkLHBrowYNSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGdtuF/btsM2bmbJev/KylFDngSK1kkLHBrowYNSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGdtuF/btsM2bmbJev/KylFDngSK1kkLHBrowYNSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGdtuF%2FbtsM2bmbJev%2FKylFDngSK1kkLHBrowYNSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;1414&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;1414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;개발자 도구에서 리뷰 요소 찾기&lt;br&gt;그러면 해당 리뷰 글이 어떤 요소에 포함되어 있는지 전반적인 페이지 구조를 확인할 수 있게 된다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQI5Fo/btsM2HkQjYK/0rr1p0WE7k2oN7r5aIcrPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQI5Fo/btsM2HkQjYK/0rr1p0WE7k2oN7r5aIcrPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQI5Fo/btsM2HkQjYK/0rr1p0WE7k2oN7r5aIcrPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQI5Fo%2FbtsM2HkQjYK%2F0rr1p0WE7k2oN7r5aIcrPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2368&quot; height=&quot;1444&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;페이지 구조 분석&lt;br&gt;그렇다면 이제 리뷰를 찾았으니까 &lt;a href=&quot;https://map.naver.com/p/search/%EA%B0%95%EB%82%A8%20%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4/place/1086067689?c=12.00,0,0,0,dh&amp;amp;placePath=/review&quot;&gt;현재 페이지&lt;/a&gt;에서 크롤링을 수행하는 것이 맞을까?&lt;br&gt;가능은 하겠지만 너무 비효율적이다.. 왜냐하면 리뷰 쪽만 크롤링 할 예정인데 너무 많은 불필요한 요소들이 함께 포함되어 있어 크롤링 과정이 복잡해지기 때문&lt;br&gt;따라서 우리는 리뷰 부분만을 포함하고 있는 부분을 찾을 생각이다.&lt;br&gt;먼저 페이지를 슥 한번 훑어보면 리뷰를 포함하고 있는 상위 태그에 entryIframe 이라는 id값을 가지고 있는 iframe이 보인다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Km5y/btsM13hFIIE/EaJBZCJasWL7dmtuFt5bfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Km5y/btsM13hFIIE/EaJBZCJasWL7dmtuFt5bfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Km5y/btsM13hFIIE/EaJBZCJasWL7dmtuFt5bfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Km5y%2FbtsM13hFIIE%2FEaJBZCJasWL7dmtuFt5bfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2368&quot; height=&quot;1444&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;iframe은 웹페이지 안에 웹페이지를 불러올 수 있게 하는 기능이다&lt;br&gt;따라서 구조를 분석해보면 스타벅스 강남R점 의 리뷰가 포함된 상세 페이지는 iframe을 사용하여 페이지 내에 삽입되어 있다는 사실!&lt;br&gt;삽입되어 있는 상세 페이지의 링크를 찾아 거기서 크롤링을 수행한다면 매우 효율적으로 크롤링을 수행할 수 있게된다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r97nf/btsM2IjPeV7/uIbuYoe7VxAkMLRKKkHkC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r97nf/btsM2IjPeV7/uIbuYoe7VxAkMLRKKkHkC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r97nf/btsM2IjPeV7/uIbuYoe7VxAkMLRKKkHkC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr97nf%2FbtsM2IjPeV7%2FuIbuYoe7VxAkMLRKKkHkC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1446&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;상세 페이지로 이동&lt;br&gt;삽입된 &lt;a href=&quot;https://pcmap.place.naver.com/restaurant/1086067689/review/visitor?entry=bmp&amp;amp;from=map&amp;amp;fromPanelNum=2&amp;amp;locale=ko&amp;amp;searchText=%EA%B0%95%EB%82%A8%20%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4&amp;amp;svcName=map_pcv5&amp;amp;timestamp=202503301116&quot;&gt;상세 페이지&lt;/a&gt;로 이동해보자&lt;br&gt;뭔가 확대된(?) 느낌? 이제 여기서부터 크롤링을 시작해볼 예정이다&lt;br&gt;아까처럼 개발자 도구에서 element 선택 기능을 사용해 댓글 요소를 클릭!&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c19utx/btsM0wd1BtR/PkMKZFziK32H8GM9Z05Hl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c19utx/btsM0wd1BtR/PkMKZFziK32H8GM9Z05Hl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c19utx/btsM0wd1BtR/PkMKZFziK32H8GM9Z05Hl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc19utx%2FbtsM0wd1BtR%2FPkMKZFziK32H8GM9Z05Hl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1446&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBiimH/btsM1mPh7HR/nkOWdsEKL2C45fqQxKoin1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBiimH/btsM1mPh7HR/nkOWdsEKL2C45fqQxKoin1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBiimH/btsM1mPh7HR/nkOWdsEKL2C45fqQxKoin1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBiimH%2FbtsM1mPh7HR%2FnkOWdsEKL2C45fqQxKoin1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1446&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;크롤링 시작&lt;br&gt;구조를 보면 pui__vn15t2 라는 클래스명을 갖는 하위 요소에 a태그 내에 리뷰글이 존재하고 있다 우리는 이 부분을 공략해볼 예정이다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1AZJ/btsM2wp5WQP/zHxff6VqLAxHfKKWD8L2ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1AZJ/btsM2wp5WQP/zHxff6VqLAxHfKKWD8L2ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1AZJ/btsM2wp5WQP/zHxff6VqLAxHfKKWD8L2ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1AZJ%2FbtsM2wp5WQP%2FzHxff6VqLAxHfKKWD8L2ZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1446&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;혹시 눈치 챈 사람도 있을 것 같은데 이전 블로그에서 크롤링을 수행 할 메서드의 인자값으로 id 값이 들어간다고 했었고 이는 매장의 고유 코드번호 같은 것이라 이야기 했었다 &lt;a href=&quot;https://pcmap.place.naver.com/restaurant/1086067689/review/visitor?entry=bmp&amp;amp;from=map&amp;amp;fromPanelNum=2&amp;amp;locale=ko&amp;amp;searchText=%EA%B0%95%EB%82%A8%20%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4&amp;amp;svcName=map_pcv5&amp;amp;timestamp=202503301116&quot;&gt;페이지&lt;/a&gt;위에 주소 부분을 자세히 보면 숫자가 보인다&lt;br&gt;해당 숫자가 바로 스타벅스 강남R점 의 고유 식별자(?) 코드 같은 것이다&lt;br&gt;이제 우리는 조회를 원하는 매장의 식별자 코드만 알게 된다면 해당 매장을 특정하여 리뷰와 같은 데이터를 가지고 올 수 있게 될 것이다!&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buzIym/btsM2JJKRmq/nzq0smM2DWJWgp7PCIyLak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buzIym/btsM2JJKRmq/nzq0smM2DWJWgp7PCIyLak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buzIym/btsM2JJKRmq/nzq0smM2DWJWgp7PCIyLak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuzIym%2FbtsM2JJKRmq%2Fnzq0smM2DWJWgp7PCIyLak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1610&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;크롤링 시작:메서드 구현&lt;br&gt;다시 스프링으로 돌아가서 이전 블로그에서 만들었던 service클래스에 들어가 보자&lt;br&gt;accessNaverPlaceReview 라는 이름의 메서드를 구현해볼 것이다&lt;br&gt;코드는 아래와 같다값을 json형태로 반환해야 하기 때문에 데이터를 담을 dto클래스도 만들어 준다간단한 테스트 api도 필요하므로 controller클래스도 만들어 준다자세한 설명은 주석으로 처리했다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@RestController 
public class Controller { 
@Autowired 
Service service; 

@GetMapping(&amp;quot;/test/{id}&amp;quot;) 
public ResponseEntity&amp;lt;?&amp;gt; test(@PathVariable String id) { 
    return service.accessNaverPlaceReview(id); 
} 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@Getter 
@Setter 
public class ReviewDto { 
private String cafeName; //카페이름 
private String status; //현재 영업중인지 여부 
private List&amp;lt;?&amp;gt; reviews; //리뷰들 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@Autowired 
private WebDriver driver; //웹 브라우저를 제어하기 위한 객체 
private List&amp;lt;String&amp;gt; reviews_str; //크롤링한 리뷰들을(문자열로 변환된) 저장하는 리스트 객체 
public ResponseEntity&amp;lt;?&amp;gt; accessNaverPlaceReview(String id) { 
try { 
    ReviewDto reviewDto = new ReviewDto(); 
    String url = &amp;quot;https://pcmap.place.naver.com/restaurant/&amp;quot; + id + &amp;quot;/review/visitor?additionalHeight=76&amp;amp;from=map&amp;amp;fromPanelNum=1&amp;amp;locale=ko&amp;amp;svcName=map_pcv5&amp;amp;timestamp=202503291343&amp;quot;; //네이버 지도 리뷰 링크 
    String url2 = &amp;quot;https://pcmap.place.naver.com/restaurant/&amp;quot; + id + &amp;quot;/home?additionalHeight=76&amp;amp;from=map&amp;amp;fromPanelNum=1&amp;amp;locale=ko&amp;amp;svcName=map_pcv5&amp;amp;timestamp=202503291343&amp;quot;; //네이버 지도 홈 링크 
    driver.get(url); //네이버 지도 리뷰 페이지로 이동 
    List&amp;lt;WebElement&amp;gt; reviews = driver.findElements( //리뷰 크롤링 
        By.cssSelector(&amp;quot;div.pui__vn15t2 a&amp;quot;) 
    ); 
    reviews_str = reviews.stream() 
        .map(WebElement::getText) 
        .filter(text -&amp;gt; !text.equals(&amp;quot;더보기&amp;quot;)) 
        .collect(Collectors.toList()); //더보기 제거 

    String name = driver.findElement(By.className(&amp;quot;bh9OH&amp;quot;)).getText(); //식당 이름 크롤링 

    driver.get(url2); //네이버 지도 홈으로 이동 
    String status = driver.findElement(By.cssSelector(&amp;quot;.A_cdD em&amp;quot;)).getText(); //매장 이름 크롤링 

    reviewDto.setCafeName(name); 
    reviewDto.setStatus(status); 
    reviewDto.setReviews(reviews_str); 

    return ResponseEntity.ok().body(reviewDto); 
} catch (Exception e) {
    return ResponseEntity.badRequest().body(&amp;quot;error&amp;quot;);
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;진짜 끝&lt;br&gt;이제 프로젝트를 빌드한 뒤 postman이나 또는 기타 api테스트 프로그램을 사용해 테스트를 해보자&lt;br&gt;/test/1086067689 api로 요청을 했고 결과는?!&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVwT4/btsM1MNKKEq/qQU4lU5EsTqUlmcmDYaJsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVwT4/btsM1MNKKEq/qQU4lU5EsTqUlmcmDYaJsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVwT4/btsM1MNKKEq/qQU4lU5EsTqUlmcmDYaJsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzVwT4%2FbtsM1MNKKEq%2FqQU4lU5EsTqUlmcmDYaJsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1824&quot; height=&quot;924&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;이렇게 잘 받아오는 것을 확인할 수 있다&lt;br&gt;1편 도입부분에 한번 설명 했지만, 블로그에서 설명한 대로 진행하면 pui__vn15t2 라는 요소를 찾지 못했다고 나올 것이다&lt;br&gt;네이버는 기본적으로 크롤링을 허용하지 않고 있기 때문에 selenium 이 에러 페이지에 접근하게 되어 거기서 요소를 찾으려고 하기 때문이다&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhV6GU/btsM02xjiRQ/dADOXKs98JZlE2AUY5w22k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhV6GU/btsM02xjiRQ/dADOXKs98JZlE2AUY5w22k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhV6GU/btsM02xjiRQ/dADOXKs98JZlE2AUY5w22k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhV6GU%2FbtsM02xjiRQ%2FdADOXKs98JZlE2AUY5w22k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;788&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;따라서 selenium을 정상적으로 접속하게 하려면 약간의 우회가 필요한데, 이는 구글링 좀만 해봐도 방법이 나온다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>BE/스프링</category>
      <category>selenium</category>
      <category>spring</category>
      <category>springboot 크롤링</category>
      <category>네이버 크롤링</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/3</guid>
      <comments>https://codebox123.tistory.com/3#entry3comment</comments>
      <pubDate>Sun, 30 Mar 2025 12:29:23 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] selenium 을 사용한 네이버 플레이스 리뷰 크롤링(1)</title>
      <link>https://codebox123.tistory.com/2</link>
      <description>&lt;ul&gt;
&lt;li&gt;결과 화면&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs0l3o/btsM2xoRPZF/0AfdgtsSSdTj5aRxR5eSH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs0l3o/btsM2xoRPZF/0AfdgtsSSdTj5aRxR5eSH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs0l3o/btsM2xoRPZF/0AfdgtsSSdTj5aRxR5eSH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs0l3o%2FbtsM2xoRPZF%2F0AfdgtsSSdTj5aRxR5eSH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1836&quot; height=&quot;916&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;최종적으로 완성된 화면이다.&lt;br&gt;제목은 &amp;#39;리뷰 크롤링&amp;#39; 이라고 지었으나, 리뷰 뿐만 아니라 영업시간과 영업중인지 아닌지 등 대부분의 데이터 들을 크롤링 하여 활용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;들어가기 전&lt;br&gt;기본적으로 네이버는 크롤링이 막혀있다.. 본 블로그는 스프링 부트에서 selenium을 활용한 크롤링 방법을 &amp;#39;공부&amp;#39; 하기 위해 작성하였기 때문에 이것을 우회하기 위한 자세한 설명은 제외하였다&lt;br&gt;(네이버가 제공중인 api에서는 특정 매장의 정보를 제한적으로 공개하고 있기 때문에, 이상의 데이터를 크롤링 하는 것은 네이버 정책에 위배될 수 있음을 고려함)&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;환경 세팅(준비물)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;JAVA Spring Boot&lt;/li&gt;
&lt;li&gt;selenium&lt;/li&gt;
&lt;li&gt;크롤링 할 대상 페이지&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;selenium 의존성 추가(Gradle)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;implementation &amp;#39;org.seleniumhq.selenium:selenium-java:latest.release&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;먼저 스프링 서버에 selenium 의존성을 추가시켜 준다. 최신 버전을 사용하는 이유는 selenium 4.6.0 버전 이후 자동으로 크롤링에 필요한 브라우저 드라이버를 관리해 주므로 개발자가 드라이버를 따로 설치할 필요 없이 바로 사용 가능하다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;설정을 위한 config 클래스 생성&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ChromeDriverConfig {
  @Bean
  public ChromeDriver chromeDriver() {
      ChromeOptions options = new ChromeOptions();
      options.addArguments(&amp;quot;--headless&amp;quot;);
      return new ChromeDriver(options);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;크롬 브라우저를 사용하여 크롤링을 해볼 예정이다. 먼저 기본적인 설정들이 필요한데,&lt;br&gt;먼저 chromeDriver() 메서드를 스프링 @Bean 으로 등록해준다&lt;br&gt;ChromeOptions options = new ChromeOptions(); //설정 객체를 생성하는 부분이다&lt;br&gt;options.addArguments(&amp;quot;--headless&amp;quot;); //셀레니움이 페이지에 접근할 때 화면을 띄우지 않고 백그라운드에서 동작하도록 설정하는 부분이다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;테스트를 위한 컨트롤러 생성만든 후에 API를 직접 호출해볼 예정이므로 컨트롤러를 생성해준다&lt;br&gt;api는 get 방식으로 /test/{id} 를 호출 하여 json형식으로 데이터를 반환받을 생각이다.&lt;br&gt;{id} 부분에는 내가 조회하고자 하는 매장의 코드가 들어가게 된다&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController public class Controller 
{ 
  @Autowired Service service; 
  @GetMapping(&amp;quot;/test/{id}&amp;quot;) 
    public ResponseEntity&amp;lt;?&amp;gt; test(@PathVariable String id) 
    { 
      return service.accessNaverPlaceReview(id); 
    } 
}```

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;기능 구현을 위한 service 클래스도 생성private WebDriver driver; //셀레니움 작업을 수행하기 위한 객체다 우리는 앞으로 driver 객체를 통해 웹사이트를 크롤링 해볼 예정이다!&lt;br&gt;public ResponseEntity&amp;lt;?&amp;gt; accessNaverPlaceReview(String id) //id 값은 조회하고자 하는 매장의 번호를 의미한다&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Service 
{ 
  @Autowired 
  private WebDriver driver; 
    public ResponseEntity&amp;lt;?&amp;gt; accessNaverPlaceReview(String id) 
    { 
    } 
}```

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;구현 시작!&lt;br&gt;사실 매우매우 간단하다 service쪽을 제외하면 전부 구현이 완료되어있다! 근데 아직 안심하긴 이르다 크롤링의 꽃은 바로 &amp;#39;삽질&amp;#39;&lt;br&gt;지금부터 네이버 플레이스 페이지를 &amp;#39;크롬 개발자 도구&amp;#39; 를 활용해서 하나하나 뜯어보도록 하겠다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;내용이 길어지므로&lt;br&gt;&lt;a href=&quot;https://codebox123.tistory.com/3&quot;&gt;[Spring Boot] selenium 을 사용한 네이버 플레이스 리뷰 크롤링(2)&lt;/a&gt;&lt;br&gt;에서 계속 하도록 하겠다.&lt;/p&gt;</description>
      <category>BE/스프링</category>
      <category>spring</category>
      <category>SpringBoot</category>
      <category>springboot크롤링</category>
      <category>네이버 크롤링</category>
      <author>JUNEee</author>
      <guid isPermaLink="true">https://codebox123.tistory.com/2</guid>
      <comments>https://codebox123.tistory.com/2#entry2comment</comments>
      <pubDate>Sat, 29 Mar 2025 16:10:06 +0900</pubDate>
    </item>
  </channel>
</rss>