<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>zzzang_hyeon.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 14 May 2025 07:56:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>zzzang_hyeon.log</title>
            <url>https://velog.velcdn.com/images/zzzang_hyeon/profile/20fec594-48bd-41cc-95e4-b9d3aadb16a4/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. zzzang_hyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/zzzang_hyeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Jpql, 시큐리티]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-4</link>
            <guid>https://velog.io/@zzzang_hyeon/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-4</guid>
            <pubDate>Wed, 14 May 2025 07:56:20 GMT</pubDate>
            <description><![CDATA[<h2 id="auditing을-이용한-엔티티-공통-속성화">Auditing을 이용한 엔티티 공통 속성화</h2>
<ol>
<li>@MappedSuperclass</li>
<li>AuditorAware 인터페이스 </li>
<li>@EntityListeners</li>
<li>@EnableJpaAuditing</li>
</ol>
<h2 id="jpql">JPQL</h2>
<p>-설정된 주기별로 실행될 함수를 설정</p>
<p>@Scheduled</p>
<p>1) fixedDelay
: 작업 완료 후 고정 시간 지연 간격</p>
<p>2) fixedRate
: 고정 시간 간격으로 실행</p>
<p>3) initialDelay
: 작업 시작 전 시간 간격 지연</p>
<p>4) cron
: 상세한 실행 주기를 설정할 때</p>
<p>0 0 <em>/1***</em>
1시간 마다 실행 정각 실행</p>
<p>0 30 13,18***</p>
<p>오후 1시 30분
오후 6시 30분 실행</p>
<p>5) @EnableScheduling 
: 스케줄링 설정 활성화</p>
<blockquote>
<h1 id="스프링-시큐리티">스프링 시큐리티</h1>
</blockquote>
<p>-인증,인가</p>
<ol>
<li>의존성 설치</li>
<li>스프링 시큐티리 설정 </li>
<li>회원가입 구현 <h4 id="1-userdetails-인터페이스--dto">1) UserDetails 인터페이스 : DTO</h4>
</li>
</ol>
<h4 id="2-userdetailsservice-인터페이스--service">2) UserDetailsService 인터페이스 : Service</h4>
<ol start="4">
<li>시큐리티를 이용한 회원 인증(로그인) 구현 </li>
<li>로그인 정보 가져오기
1) Principal 요청메서드에 주입  : getName() : 아이디  : 요청 메서드의 주입
2) SecurityContextHolder를 통해서 가져오기
3) @AuthenticationPrincipal  : UserDetails 구현 객체 주입, 요청 메서드의 주입시 밖에 사용 가능 </li>
</ol>
<p>4) Authentication
    Object getPrincipal(...) : UserDetails의 구현 객체 
    boolean isAuthenticated() : 인증 여부</p>
<p>/error 템플릿 경로 : 응답 코드.html</p>
<ol start="6">
<li><p>thymeleaf-extras-springsecurity6
 1) xmlns:sec=&quot;<a href="http://www.thymeleaf.org/extras/spring-security&quot;">http://www.thymeleaf.org/extras/spring-security&quot;</a></p>
<p> 2) sec:authorize=&quot;hasAnyAuthority(...)&quot;, sec:authorize=&quot;hasAuthority(...)&quot;
 3) sec:authorize=&quot;isAuthenticated()&quot; : 로그인 상태 
 4) sec:authorize=&quot;isAnonymous()&quot; : 미로그인 상태 </p>
<p> 5) csrf 토큰 설정하기 </p>
<pre><code> - ${_csrf.token}
 - ${_csrf.headerName}</code></pre></li>
<li><p>페이지 권한 설정하기 </p>
<ul>
<li>AuthenticationEntryPoint </li>
</ul>
</li>
<li><p>Spring Data Auditing + Spring Security</p>
</li>
</ol>
<ul>
<li>로그인 사용자가 자동 DB 추가 
1) AuditorAware 인터페이스</li>
</ul>
<p>POST 요청시 CSRF 토큰 검증 : 검증 실패시 403</p>
<ul>
<li>자바 스크립트 ajax 형태로 POST 데이터를 전송할시 CSRF 토큰 검증 </li>
</ul>
<ol start="9">
<li>@EnableMethodSecurity</li>
</ol>
<p>1) @PreAuthorize: 메서드가 실행되기 전에 인증을 거친다.
2) @PostAuthorize: 메서드가 실행되고 나서 응답을 보내기 전에 인증을 거친다.</p>
<p>3) 사용할수 있는 표현식 </p>
<ul>
<li>hasRole([role]) : 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true</li>
<li>hasAnyRole([role1,role2]) : 현재 사용자의 권한디 파라미터의 권한 중 일치하는 것이 있는 경우 true</li>
<li>principal : 사용자를 증명하는 주요객체(User)를 직접 접근할 수 있다.</li>
<li>authentication : SecurityContext에 있는 authentication 객체에 접근 할 수 있다.</li>
<li>permitAll : 모든 접근 허용</li>
<li>denyAll : 모든 접근 비허용</li>
<li>isAnonymous() : 현재 사용자가 익명(비로그인)인 상태인 경우 true</li>
<li>isRememberMe() : 현재 사용자가 RememberMe 사용자라면 true</li>
<li>isAuthenticated() : 현재 사용자가 익명이 아니라면 (로그인 상태라면) true</li>
<li>isFullyAuthenticated() : 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Q클래스(Q-Type)]]></title>
            <link>https://velog.io/@zzzang_hyeon/Q%ED%81%B4%EB%9E%98%EC%8A%A4Q-Type</link>
            <guid>https://velog.io/@zzzang_hyeon/Q%ED%81%B4%EB%9E%98%EC%8A%A4Q-Type</guid>
            <pubDate>Sat, 22 Mar 2025 08:14:25 GMT</pubDate>
            <description><![CDATA[<h2 id="q클래스">Q클래스</h2>
<p>Q클래스는 QueryDSL이 엔터티를 기반으로 자동 생성하는 클래스</p>
<p> Q클래스를 사용하면 엔터티의 필드를 <strong>타입 안정성(Type Safety)</strong>을 유지하면서 쿼리를 작성할 수 있다.</p>
<h2 id="추가-방법">추가 방법</h2>
<p> <img src="https://velog.velcdn.com/images/zzzang_hyeon/post/12635218-f8c4-40a2-9f6a-65e8a981c6d8/image.png" alt=""></p>
<p>의존성에 추가해준다.</p>
<pre><code class="language-java">public class QWishList extends EntityPathBase&lt;WishList&gt; {
    public final StringPath email = createString(&quot;email&quot;);
    public final NumberPath&lt;Long&gt; seq = createNumber(&quot;seq&quot;, Long.class);
    public final DateTimePath&lt;LocalDateTime&gt; createdAt = createDateTime(&quot;createdAt&quot;, LocalDateTime.class);
}</code></pre>
<p>위처럼 WishList 엔터티를 기반으로 QWishList 클래스가 src/main/generated 폴더에 생성된다.</p>
<h2 id="사용법">사용법</h2>
<h4 id="특정-필드로-조회-eq-사용">특정 필드로 조회 (eq 사용)</h4>
<pre><code class="language-java">QWishList wishList = QWishList.wishList;
WishList result = queryFactory
    .selectFrom(wishList)
    .where(wishList.email.eq(&quot;user@example.com&quot;))
    .fetchOne();</code></pre>
<h4 id="다중-조건-조회-booleanbuilder-활용">다중 조건 조회 (BooleanBuilder 활용)</h4>
<pre><code class="language-java">BooleanBuilder builder = new BooleanBuilder();
builder.and(wishList.email.eq(&quot;user@example.com&quot;));
builder.and(wishList.seq.eq(123L));

List&lt;WishList&gt; results = repository.findAll(builder);
</code></pre>
<h4 id="정렬해서-조회-sortby-활용">정렬해서 조회 (Sort.by 활용)</h4>
<pre><code class="language-java">List&lt;WishList&gt; results = repository.findAll(
    wishList.email.eq(&quot;user@example.com&quot;),
    Sort.by(Sort.Direction.DESC, &quot;createdAt&quot;)
);
</code></pre>
<p>또한, build.gradle 에서 QClass에 관한 설정을 할 수 있다.</p>
<pre><code class="language-java">// JUnit 5 (JUnit Platform) 사용 설정
tasks.named(&#39;test&#39;) {
    useJUnitPlatform()
}

// QueryDSL 자동 생성된 파일이 저장될 디렉토리 지정
def querydslDir = layout.buildDirectory.dir(&quot;generated/querydsl&quot;).get().asFile

sourceSets {
    // QueryDSL이 생성한 Q클래스를 소스 코드로 인식하도록 추가
    main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile) {
    // Java 컴파일 시, QueryDSL이 생성한 Q클래스도 함께 컴파일되도록 설정
    options.getGeneratedSourceOutputDirectory().set(file(querydslDir))
}

clean.doLast {
    // &#39;gradle clean&#39; 실행 시 QueryDSL이 생성한 Q클래스 삭제 (최신 상태 유지)
    file(querydslDir).deleteDir()
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 빈 컨테이너 관련]]></title>
            <link>https://velog.io/@zzzang_hyeon/j9misjkw</link>
            <guid>https://velog.io/@zzzang_hyeon/j9misjkw</guid>
            <pubDate>Wed, 19 Mar 2025 08:15:45 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">    if (anno instanceof RequestMapping) { // 모든 요청 방식 매핑
                RequestMapping mapping = (RequestMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof GetMapping &amp;&amp; method.equals(&quot;GET&quot;)) { // GET 방식 매핑
                GetMapping mapping = (GetMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PostMapping &amp;&amp; method.equals(&quot;POST&quot;)) {
                PostMapping mapping = (PostMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PutMapping &amp;&amp; method.equals(&quot;PUT&quot;)) {
                PutMapping mapping = (PutMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PatchMapping &amp;&amp; method.equals(&quot;PATCH&quot;)) {
                PatchMapping mapping = (PatchMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof DeleteMapping &amp;&amp; method.equals(&quot;DELETE&quot;)) {
                DeleteMapping mapping = (DeleteMapping) anno;
                mappings = mapping.value();
            }</code></pre>
<p>예를들어 요청이</p>
<pre><code> if (anno instanceof RequestMapping) { // 모든 요청 방식 매핑
                RequestMapping mapping = (RequestMapping) anno;
                mappings = mapping.value();
            }</code></pre><p>에 해당되는 요청이 들어왔다면 </p>
<p>//에노테이션</p>
<pre><code>@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String[] value();
}
</code></pre><p>해당 에노테이션 배열에 </p>
<p>(MemberController)</p>
<pre><code>@Controller
@RequestMapping(&quot;/member&quot;)
@RequiredArgsConstructor
public class MemberController {
    private final JoinService joinService;

    @GetMapping(&quot;/{mode}/test/{num}&quot;)
    public String join(@PathVariable(&quot;mode&quot;) String mode, @RequestParam(&quot;seq&quot;) int seq, RequestJoin form,  HttpServletResponse response, @PathVariable(&quot;num&quot;) int num) {
        System.out.printf(&quot;mode=%s, seq=%d, num=%d%n&quot;, mode, seq, num);
        System.out.println(form);
        joinService.process();
        return &quot;member/join&quot;;
    }
}</code></pre><p>[/member] 이 담기게된다.</p>
<hr>
<pre><code>package org.choongang.global.router;

import jakarta.servlet.http.HttpServletRequest;
import org.choongang.global.config.annotations.*;
import org.choongang.global.config.containers.BeanContainer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class HandlerMappingImpl implements HandlerMapping{


    private String controllerUrl;

    @Override
    public List&lt;Object&gt; search(HttpServletRequest request) {

        List&lt;Object&gt; items = getControllers();

        for (Object item : items) {         // item = req
            /* Type 애노테이션에서 체크 S */
            // @RequestMapping, @GetMapping, @PostMapping, @PatchMapping, @PutMapping, @DeleteMapping
            if (isMatch(request,item.getClass().getDeclaredAnnotations(), false, null)) {
                // 메서드 체크
                for (Method m : item.getClass().getDeclaredMethods()) {
                    if (isMatch(request, m.getDeclaredAnnotations(), true, controllerUrl)) {
                        return List.of(item, m);    // req객체와 매칭되는 컨트롤러에 정의되어 있는 요청 메서드를 리스트로 리턴
                    }
                }
            }
            /* Type 애노테이션에서 체크 E */

            /**
             * Method 애노테이션에서 체크 S
             *  - Type 애노테이션 주소 매핑이 되지 않은 경우, 메서드에서 패턴 체크
             */
            for (Method m : item.getClass().getDeclaredMethods()) {
                if (isMatch(request, m.getDeclaredAnnotations(), true, null)) {
                    return List.of(item, m);
                }
            }
            /* Method 애노테이션에서 체크 E */
        }

        return null;
    }


    /**
     *
     * @param request
     * @param annotations : 적용 애노테이션 목록
     * @param isMethod : 메서드의 에노테이션 체크인지
     * @param prefixUrl : 컨트롤러 체크인 경우 타입 애노테이션에서 적용된 경우
     * @return
     */
    private boolean isMatch(HttpServletRequest request, Annotation[] annotations, boolean isMethod, String prefixUrl) {
    //에노테이션과 요청이 일치하는지 확인하기 위함.
        String uri = request.getRequestURI();
        String method = request.getMethod().toUpperCase();  //요청 방식 가져옴 GET,POST 등등
        String[] mappings = null;
        for (Annotation anno : annotations) {

            if (anno instanceof RequestMapping) { // 모든 요청 방식 매핑
                RequestMapping mapping = (RequestMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof GetMapping &amp;&amp; method.equals(&quot;GET&quot;)) { // GET 방식 매핑
                GetMapping mapping = (GetMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PostMapping &amp;&amp; method.equals(&quot;POST&quot;)) {
                PostMapping mapping = (PostMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PutMapping &amp;&amp; method.equals(&quot;PUT&quot;)) {
                PutMapping mapping = (PutMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof PatchMapping &amp;&amp; method.equals(&quot;PATCH&quot;)) {
                PatchMapping mapping = (PatchMapping) anno;
                mappings = mapping.value();
            } else if (anno instanceof DeleteMapping &amp;&amp; method.equals(&quot;DELETE&quot;)) {
                DeleteMapping mapping = (DeleteMapping) anno;
                mappings = mapping.value();
            }

            if (mappings != null &amp;&amp; mappings.length &gt; 0) {

                String matchUrl = null;
                if (isMethod) {
                    String addUrl = prefixUrl == null ? &quot;&quot; : prefixUrl;
                    // 메서드인 경우 *와 {경로변수} 고려하여 처리
                    for(String mapping : mappings) {
                        String pattern = mapping.replace(&quot;/*&quot;, &quot;/\\w*&quot;)
                                .replaceAll(&quot;/\\{\\w+\\}&quot;, &quot;/(\\\\w*)&quot;);

                        Pattern p = Pattern.compile(&quot;^&quot; + request.getContextPath() + addUrl + pattern + &quot;$&quot;);
                        Matcher matcher = p.matcher(uri);
                        return matcher.find();
                    }
                } else {
                    List&lt;String&gt; matches = Arrays.stream(mappings)
                            .filter(s -&gt; uri.startsWith(request.getContextPath() + s)).toList();
                    if (!matches.isEmpty()) {
                        matchUrl = matches.get(0);
                        controllerUrl = matchUrl;
                    }
                }
                return matchUrl != null &amp;&amp; !matchUrl.isBlank();
            }
        }

        return false;
    }

    /**
     * 모든 컨트롤러 조회
     *
     * @return
     */
    private List&lt;Object&gt; getControllers() {
       return BeanContainer.getInstance().getBeans().entrySet()
                    .stream()
                    .map(s -&gt; s.getValue())
                .filter(b -&gt; Arrays.stream(b.getClass().getDeclaredAnnotations()).anyMatch(a -&gt; a instanceof Controller || a instanceof RestController))
                .toList();
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[예외처리 - 프로퍼티]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0</link>
            <guid>https://velog.io/@zzzang_hyeon/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0</guid>
            <pubDate>Wed, 12 Mar 2025 03:07:40 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-boot에서-예외-메시지를-프로퍼티-파일로-관리하기">Spring Boot에서 예외 메시지를 프로퍼티 파일로 관리하기</h1>
<p>Spring Boot 애플리케이션에서는 예외 메시지를 코드에서 직접 하드코딩하는 대신, <strong>프로퍼티 파일을 활용하여 중앙에서 관리</strong>할 수 있다. </p>
<h2 id="1-예외-메시지-프로퍼티-파일-설정">1. 예외 메시지 프로퍼티 파일 설정</h2>
<p>우선, <code>messages.properties</code> 파일을 <code>src/main/resources</code> 경로에 생성하고 예외 메시지를 정의</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/199ee292-f96c-421f-b4ae-101c02edb6e4/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/bb625f3a-ef9e-4328-846d-4962870a3646/image.png" alt="">
이제 특정 예외가 발생할 때, 해당 키를 사용하여 메시지를 불러올 수 있도록 설정</p>
<h3 id="21-commonexception-클래스-만들기">2.1 <code>CommonException</code> 클래스 만들기</h3>
<p>모든 예외의 부모 클래스 역할을 하는 <code>CommonException</code>을 생성
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4b364f94-63d3-4390-bdfc-e47a3451f37d/image.png" alt=""></p>
<h3 id="22-notfoundexception-클래스-구현">2.2 <code>NotFoundException</code> 클래스 구현</h3>
<p>프로퍼티에서 예외 메시지를 가져오는 <code>NotFoundException</code> 클래스를 작성.</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/35fcc4ff-950e-4b62-855f-8085f9f03ade/image.png" alt=""></p>
<p>이제 <code>NotFoundException</code>을 생성할 때, 프로퍼티에서 해당 키의 메시지를 가져와 예외 메시지로 설정</p>
<h2 id="3-예외-발생-및-메시지-출력">3. 예외 발생 및 메시지 출력</h2>
<p>예외를 발생시킬 때 프로퍼티 키를 전달하여 적절한 메시지를 반환 가능</p>
<pre><code class="language-java">throw new NotFoundException(&quot;NotFound.board&quot;);</code></pre>
<p>위 코드를 실행하면 <code>messages.properties</code>에서 <code>NotFound.board</code> 키를 찾아 <strong>&quot;게시판을 찾을 수 없습니다.&quot;</strong> 라는 메시지를 반환</p>
<h2 id="4-다국어-지원">4. 다국어 지원</h2>
<p>Spring Boot의 <code>MessageSource</code>를 활용하면 다국어 지원도 가능.
예를 들어 <code>messages_en.properties</code> 파일을 추가하여 영어 메시지를 설정 가능</p>
<pre><code class="language-properties"># messages_en.properties
NotFound.board=Board not found.
NotFound.boardData=Post not found.
NotFound.comment=Comment not found.
RequiredCheck.guestPw=Password confirmation is required.
Mismatch.password=Passwords do not match.</code></pre>
<p>그리고 <code>application.yml</code> 또는 <code>application.properties</code>에서 기본 메시지 소스를 등록.</p>
<pre><code class="language-yaml">spring:
  messages:
    basename: messages
    encoding: UTF-8</code></pre>
<p>이제 클라이언트의 요청 언어(<code>Accept-Language</code> 헤더)에 따라 한국어 또는 영어 메시지가 자동으로 적용된다.</p>
<p>위와 같이 프로퍼티 파일을 활용하면 예외 메시지를 한 곳에서 효율적으로 관리할 수 있으며, 다국어 지원까지 가능해지고, 이를 통해 코드의 유지보수성을 높이고, 보다 깔끔한 예외 처리 방식을 구현할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security JWT]]></title>
            <link>https://velog.io/@zzzang_hyeon/Spring-Security-JWT</link>
            <guid>https://velog.io/@zzzang_hyeon/Spring-Security-JWT</guid>
            <pubDate>Sat, 08 Mar 2025 10:24:46 GMT</pubDate>
            <description><![CDATA[<h3 id="jwt란-무엇인가">JWT란 무엇인가?</h3>
<p>JWT(Json Web Token)는 사용자 인증과 권한 부여를 위해 사용되는 토큰 기반 인증 방식
일반적으로 사용자가 로그인하면 서버는 JWT를 발급하고, 이후 요청마다 클라이언트는 이 토큰을 포함하여 서버에 보냄
서버는 이 토큰을 검증하여 사용자를 인증하고 권한을 확인할 수 있음.</p>
<ul>
<li>구조<pre><code>헤더(Header).페이로드(Payload).서명(Signature)</code></pre></li>
</ul>
<h4 id="header헤더-토큰-타입jwt과-해싱-알고리즘hmac-rsa-등-정보-포함">Header(헤더): 토큰 타입(JWT)과 해싱 알고리즘(HMAC, RSA 등) 정보 포함</h4>
<h4 id="payload페이로드-사용자-정보예-id-권한-만료-시간-포함">Payload(페이로드): 사용자 정보(예: ID, 권한, 만료 시간) 포함</h4>
<h4 id="signature서명-토큰-위조-방지를-위한-서명">Signature(서명): 토큰 위조 방지를 위한 서명</h4>
<pre><code>ex)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiVVNFUiIsImV4cCI6MTcwOTAwMDAwMH0.abc123xyz456
</code></pre><h2 id="spring-security-jwt">Spring Security JWT</h2>
<p>일반적으로 Spring Security에서 UsernamePasswordAuthenticationFilter를 사용하여 로그인 처리를 함. 
하지만 JWT를 사용할 경우, 서버가 사용자 세션을 관리하지 않고, 클라이언트가 인증 정보를 자체적으로 보유하게 됨.
따라서 요청이 들어올 때마다 JWT를 검증하고, 해당 정보를 기반으로 사용자를 인증하는 커스텀 필터를 구현해야함.</p>
<h3 id="필터-클래스-생성">필터 클래스 생성</h3>
<p>Spring Security의 GenericFilterBean을 상속하여 필터를 구현</p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class LoginFilter extends GenericFilterBean {

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    private final Utils utils;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        String token = getToken(request);
        if (StringUtils.hasText(token)) {
            loginProcess(token);
        }

        chain.doFilter(request, response);
    }
}</code></pre>
<h3 id="jwt-토큰-추출">JWT 토큰 추출</h3>
<p>클라이언트가 보낸 요청에서 JWT 토큰을 추출하는 메서드를 작성</p>
<pre><code class="language-java">private String getToken(ServletRequest request) {
    HttpServletRequest req = (HttpServletRequest) request;
    String bearerToken = req.getHeader(&quot;Authorization&quot;);
    if (StringUtils.hasText(bearerToken) &amp;&amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
        return bearerToken.substring(7).trim();
    }
    return req.getParameter(&quot;token&quot;);
}</code></pre>
<ul>
<li>Authorization 헤더에서 Bearer 토큰을 추출함.</li>
<li>헤더가 없는 경우, URL의 token 파라미터 값을 검사함.</li>
</ul>
<h3 id="jwt-검증-및-사용자-인증-처리">JWT 검증 및 사용자 인증 처리</h3>
<p>JWT를 검증하고, 사용자 정보를 SecurityContextHolder에 저장하는 로직을 작성</p>
<pre><code class="language-java">private void loginProcess(String token) {
    try {
        String apiUrl = utils.url(&quot;/account&quot;, &quot;memberservice&quot;);

        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token);
        HttpEntity&lt;Void&gt; entity = new HttpEntity&lt;&gt;(headers);
        ResponseEntity&lt;JSONData&gt; response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, JSONData.class);

        if (response.getStatusCode().is2xxSuccessful()) {
            JSONData data = response.getBody();
            if (data != null &amp;&amp; data.isSuccess()) {
                String json = objectMapper.writeValueAsString(data.getData());
                Member member = objectMapper.readValue(json, Member.class);

                List&lt;SimpleGrantedAuthority&gt; authorities = List.of(new SimpleGrantedAuthority(member.getAuthority().name()));

                MemberInfo memberInfo = MemberInfo.builder()
                        .email(member.getEmail())
                        .password(member.getPassword())
                        .member(member)
                        .authorities(authorities)
                        .build();

                Authentication authentication = new UsernamePasswordAuthenticationToken(memberInfo, token, memberInfo.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}</code></pre>
<ul>
<li>JWT를 이용해 회원 정보를 가져온다.</li>
<li>회원의 Authority 정보를 SimpleGrantedAuthority 리스트에 추가한다.</li>
<li>SecurityContextHolder에 인증 객체를 저장하여 로그인 처리를 완료한다.</li>
</ul>
<p>-SimpleGrantedAuthority는 Spring Security에서 제공하는 클래스,
Spring Security에서 권한을 설정하고 체크할 때 필수적으로 사용</p>
<h3 id="필터-등록-및-적용">필터 등록 및 적용</h3>
<p>Spring Security에서 LoginFilter가 실행되도록 필터 체인에 등록해야 한다.</p>
<pre><code class="language-java">@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, LoginFilter loginFilter) throws Exception {
        return http
                .addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeHttpRequests(auth -&gt; auth.anyRequest().authenticated())
                .build();
    }
}</code></pre>
<ul>
<li><p>addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class)을 사용하여 로그인 필터가 Spring Security 필터 체인에서 먼저 실행되도록 설정한다.</p>
</li>
<li><p>모든 요청을 인증된 사용자만 접근할 수 있도록 설정한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ec2]]></title>
            <link>https://velog.io/@zzzang_hyeon/a</link>
            <guid>https://velog.io/@zzzang_hyeon/a</guid>
            <pubDate>Mon, 03 Mar 2025 02:13:31 GMT</pubDate>
            <description><![CDATA[<p>EC2 -&gt; Elastic Cloud Computing</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/cec5e741-d731-4869-b324-38858b47671d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/9021973e-1f0e-49f9-ac86-c563d451fa07/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/43cf1ee8-1e8d-4733-97b3-8694e857db27/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d34396bc-ed43-45cf-9ce5-4b59c3bb17a8/image.png" alt=""></p>
<p>인증서</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/26af512d-c6b2-48e9-b7b8-ac886e051050/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c25e4bf9-662b-4ad1-9564-0e11d4302ad4/image.png" alt=""></p>
<p>도메인 변경</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/08e185fc-14f1-451a-9369-c4f07fbf35f4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c959ee22-bbde-4ad5-a3a8-5f42766a06dd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/bb70099f-e949-41b5-85e9-8cbfd3bda666/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b2f8ac40-6664-43f8-8900-ab37c41337ef/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/57a72ae7-7b5f-4952-880b-3b76f4ba0484/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/104d1dfe-bfca-4f74-9b4e-290cd911a751/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/7a817c43-64f9-45a9-a9ca-4e9fa3d0694c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DTO, DAT, 암호화]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EC%95%94%ED%98%B8%ED%99%94</link>
            <guid>https://velog.io/@zzzang_hyeon/%EC%95%94%ED%98%B8%ED%99%94</guid>
            <pubDate>Mon, 24 Feb 2025 13:24:49 GMT</pubDate>
            <description><![CDATA[<p>DTO, DAO</p>
<p>RequestJoin : DTO
MemberMapper : DAO</p>
<p>DTO(Data Transfer Object)는 계층 간 데이터 교환을 위한 객체로, 데이터베이스와 직접적인 연관 없이 사용된다. 예를 들어, RequestJoin 같은 DTO는 회원 가입 요청 정보를 담는 데 사용된다.</p>
<p>DAO(Data Access Object)는 데이터베이스와 직접 상호작용하는 객체로, CRUD(Create, Read, Update, Delete) 작업을 수행한다. MemberMapper 같은 DAO는 회원 정보를 조회하거나 저장하는 역할을 한다.</p>
<h3 id="암호화-개념">암호화 개념</h3>
<ol>
<li><p>양방향 암호화
암호화된 데이터를 다시 복호화할 수 있는 방식으로, AES, RSA 같은 알고리즘이 있다. 비밀번호보다는 신용카드 정보 같은 복호화가 필요한 데이터에 사용된다.</p>
</li>
<li><p>단방향 암호화 (해시)
암호화된 데이터를 복호화할 수 없는 방식이다. 비밀번호 저장에 적합하며, 대표적인 해시 알고리즘으로 MD5, SHA-256, SHA-512 등이 있다.</p>
</li>
</ol>
<p>고정 해시 : 같은 값이면 항상 같은 해시 값이 생성된다. (MD5, SHA 계열)
유동 해시 : 같은 값이라도 매번 다른 해시 값이 생성된다. (Salt 사용, 예: BCrypt)</p>
<h4 id="bcrypt-jbcrypt-라이브러리">BCrypt (jbcrypt 라이브러리)</h4>
<p>BCrypt는 보안성이 뛰어난 유동 해시 방식으로, 비밀번호 저장 시 주로 사용된다.</p>
<p>hashpw(...) : 비밀번호를 해시화
checkpw(...) : 해시 값을 검증</p>
<pre><code class="language-java">import org.mindrot.jbcrypt.BCrypt;

public class PasswordEncryption {
    public static void main(String[] args) {
        String password = &quot;mySecurePassword&quot;;

        // 비밀번호 해싱
        String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt());
        System.out.println(&quot;Hashed Password: &quot; + hashedPassword);

        // 비밀번호 검증
        boolean isMatch = BCrypt.checkpw(password, hashedPassword);
        System.out.println(&quot;Password Match: &quot; + isMatch);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java Bean Validation]]></title>
            <link>https://velog.io/@zzzang_hyeon/Java-Bean-Validation</link>
            <guid>https://velog.io/@zzzang_hyeon/Java-Bean-Validation</guid>
            <pubDate>Thu, 20 Feb 2025 13:29:36 GMT</pubDate>
            <description><![CDATA[<h3 id="java-bean-validation-api또는-jakarta-validation">Java Bean Validation API(또는 Jakarta Validation)</h3>
<p>Spring Boot의 DTO(데이터 전송 객체)나 엔티티 클래스에서 입력값을 검증할 때 주로 사용된다.</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/users&quot;)
public class UserController {

    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;String&gt; registerUser(@Valid @RequestBody UserRequest request) {
        return ResponseEntity.ok(&quot;회원가입 성공!&quot;);
    }
}
</code></pre>
<p>위 예시처럼 @Vaild 애노테이션을 통해 검증한다.</p>
<p>그 후 엔티티나 DTO에서</p>
<pre><code class="language-java">@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String name;

    @Size(min = 6, max = 20)
    private String password;

    @Min(18)
    private int age;
}
</code></pre>
<p>위와 같이 DB에 저장할때 유효하지 않은 값을 예외처리할 수 있다.</p>
<p>기본적인 애노테이션만 알아보자면, 
문자열
✔️ @NotBlank → 빈 값, 공백 문자열 불가
✔️ @Size(min=, max=) → 문자열 길이 제한</p>
<p>숫자
✔️ @Min(value) → 최소값 지정
✔️ @Max(value) → 최대값 지정</p>
<p>컬렉션 &amp; 객체
✔️ @NotEmpty → 리스트나 배열이 비어있으면 안 됨
✔️ @Valid → 내부 객체 필드까지 검증</p>
<p>등이있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Restful api]]></title>
            <link>https://velog.io/@zzzang_hyeon/Restful-api</link>
            <guid>https://velog.io/@zzzang_hyeon/Restful-api</guid>
            <pubDate>Fri, 14 Feb 2025 12:14:50 GMT</pubDate>
            <description><![CDATA[<h3 id="restrepresentational-state-transfer">REST(Representational State Transfer)</h3>
<p>웹에서 리소스를 효과적으로 다루는 방식이다.</p>
<ul>
<li>리소스:  웹에서 관리할 대상(데이터, 자원)</li>
</ul>
<p>✔ RESTful API는 REST 원칙을 지키는 API
✔ 리소스 중심의 설계 + HTTP 메서드 활용
✔ 일관성 있고, 확장 가능하며, 유지보수하기 쉬운 구조
✔ URI는 리소스를 표현하고, 동작은 HTTP 메서드로</p>
<h3 id="--장점">- 장점</h3>
<ol>
<li>일관된 인터페이스</li>
<li>확장성 </li>
<li>유지보수 용이 </li>
<li>다양한 클라이언트와 호환</li>
<li>캐싱 가능 </li>
</ol>
<h3 id="--원칙">- 원칙</h3>
<ul>
<li>리소스명은 복수형 (/users 대신 /user ❌ → /users ⭕)</li>
<li>행동(동사)은 URL이 아니라 HTTP 메서드로 표현 (/getUser ❌ → GET /users/1 ⭕)</li>
<li>계층적 구조 유지 (/users/1/posts → 1번 사용자 게시글)</li>
<li>필요한 경우 쿼리 스트링 사용 (/users?role=admin)</li>
<li>하위 리소스를 나타낼 때는 슬래시(/) 사용 (/posts/1/comments/2 → 1번 게시글의 2번 댓글)</li>
</ul>
<p>restful에서는 리소스 자체를 식별하는 데는 쿼리스트링을 사용하지 않는다.</p>
<h3 id="--쿼리스트링을-사용할-때">- 쿼리스트링을 사용할 때</h3>
<ul>
<li>리소스를 필터링, 정렬, 페이징</li>
</ul>
<p>예)
쿼리스트링 사용 예</p>
<pre><code>검색 (필터링)    GET /posts?skey=REST (REST 관련 게시글 검색)
정렬    GET /posts?sort=date_DESC (날짜 내림차순 정렬)
페이징    GET /posts?page=2&amp;limit=10 (2페이지, 10개씩)
복합 검색    GET /posts?skey=API&amp;sort=likes_DESC&amp;page=1&amp;limit=5</code></pre><p>사용하지 않는 예</p>
<pre><code>GET /users/1   (URI로 리소스 식별)
GET /posts/10  (URI로 특정 게시글 조회)
</code></pre><p>결론 리소스를 적절하게 효율적으로 다루는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@PathVariable]]></title>
            <link>https://velog.io/@zzzang_hyeon/PathVariable</link>
            <guid>https://velog.io/@zzzang_hyeon/PathVariable</guid>
            <pubDate>Thu, 13 Feb 2025 14:51:25 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">    @Operation(summary = &quot;게시판 설정 조회&quot;, method = &quot;GET&quot;)
    @ApiResponse(responseCode = &quot;200&quot;, description = &quot;게시판 ID(bid)로 설정 조회&quot;)
    @Parameter(name=&quot;bid&quot;, required = true, description = &quot;경로변수, 게시판 ID(bid)&quot;, example = &quot;notice&quot;)
    // 게시판 설정
    @GetMapping(&quot;/config/{bid}&quot;)
    public JSONData getConfig(@PathVariable(&quot;bid&quot;) String bid) {

        Board board = configInfoService.get(bid);

        return new JSONData(board);
    }
</code></pre>
<h2 id="--pathvariable-에노테이션의-역할">- @PathVariable 에노테이션의 역할</h2>
<ol>
<li><p>URL 경로 변수(bid)를 컨트롤러 메서드의 매개변수로 변환</p>
</li>
<li><p>클라이언트가 요청한 URL에서 {bid} 값을 추출하여 bid 변수에 저장</p>
</li>
</ol>
<p>예시) 
/list/12345 와 같이 요청이 들어온다면 
{bid} 부분이 12345로 치환된다.</p>
<p>그 후, bid 값이 infoService.getList(bid, search)에 전달된다.</p>
<p>가장 스프링부트에서 기본적인 애노테이션이면서 필수적으로 사용된다.</p>
<h3 id="생략가능">생략가능</h3>
<pre><code class="language-java">@GetMapping(&quot;/config/{bid}&quot;)
public JSONData getConfig(@PathVariable String bid) {  // &quot;bid&quot; 생략 가능
    Board board = configInfoService.get(bid);
    return new JSONData(board);
}
</code></pre>
<p>위와 같이 변수명이 같다면 생략하여 표기도 가능하다.</p>
<h2 id="vs-requestparam">vs @RequestParam</h2>
<pre><code class="language-java">@GetMapping(&quot;/posts&quot;)
    public JSONData listAllPosts(
            @RequestParam(value = &quot;page&quot;, defaultValue = &quot;1&quot;) int page, // 기본값 1
            @RequestParam(value = &quot;limit&quot;, defaultValue = &quot;10&quot;) int limit, // 기본값 10
            @ModelAttribute BoardDataSearch search) {

        search.setPage(page);
        search.setLimit(limit); // limit은 기본적으로 10으로 설정됨

        ListData&lt;BoardData&gt; data = infoService.getList(search, DeleteStatus.ALL);
        return new JSONData(data);
    }</code></pre>
<p>꽤나 비슷하게 사용될 수 있지만 </p>
<ol>
<li>클라이언트가 /posts?page=2&amp;limit=20 요청을 보냄</li>
<li>@RequestParam(value = &quot;page&quot;)는 page=2 값을 추출하여 int page에 저장</li>
<li>@RequestParam(value = &quot;limit&quot;)는 limit=20 값을 추출하여 int limit에 저장</li>
</ol>
<p>위와 같이 쿼리스트링 값을 추출하여 컨트롤러 메서드의 매개변수에 주입할때 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swagger UI 작성]]></title>
            <link>https://velog.io/@zzzang_hyeon/Swagger-UI-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@zzzang_hyeon/Swagger-UI-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Wed, 12 Feb 2025 12:58:26 GMT</pubDate>
            <description><![CDATA[<p>Swagger UI는 본인이 만든 api를 타인과 공유할때 유용한 API 문서화 도구이다. </p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/5ce5bdd7-b4c3-4f12-aaeb-9841fa1e3c00/image.png" alt=""></p>
<p>위와 같이 정렬되게 되고 클릭할 시 API 엔드포인트, HTTP 메서드(GET, POST, PUT 등), 요청 파라미터, 응답 형식 등을 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/253d9bbd-f84b-493f-868a-50d0a630c2d9/image.png" alt=""></p>
<p>작성하는 법은 </p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/ad3ebd68-16a3-4fd2-b2f8-8538f113f1c7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/57e4fd6a-7115-440d-be67-39c67e27dd45/image.png" alt=""></p>
<p>게시글 목록 api를 기준으로 </p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/35ea42b7-7944-4691-be09-c6ddc48dce92/image.png" alt=""></p>
<p>이런식으로 애노테이션을 활용하여 작성하게 된다.</p>
<p>자세하게는 </p>
<pre><code> @Parameter(name=&quot;bid&quot;, required = true, description = &quot;경로변수, 게시판 ID&quot;, example = &quot;notice&quot;)</code></pre><ul>
<li><p>required를 활성화할 경우 필수적으로 필요한 요소임을 표시하고,</p>
</li>
<li><p>description를 통해 해당 요소에 대한 설명을 덧붙인다. 예시) 게시글 번호, 작성자 번호 등등..
(HTML 태그를 사용할 수 있다.)</p>
</li>
<li><p>example를 통해 파라미터에 대한 예시 값을 제공한다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/31c7e6e5-9ae6-40ff-adf0-22ceac0d535a/image.png" alt=""></p>
<p>여기서 <img src="https://velog.velcdn.com/images/zzzang_hyeon/post/9c66cd76-4136-4f12-bb9c-5fad22e94b37/image.png" alt=""></p>
<p>이 항목에 대해서는 따로 작성한 적이 없는데 표기되는 이유는</p>
<p>swagger UI에서 </p>
<pre><code>@ModelAttribute </code></pre><p>를 사용하면 Swagger가 자동으로 해당 객체를 쿼리 파라미터로 변환하고, 이를 API 문서에 반영한다.</p>
<p>그리고 required가 표시된 이유는 쿼리 파라미터의 요소중에 한개라도 필수 항목이라고 판단하면 표시된다 .</p>
<pre><code>@Data
public class BoardSearch extends CommonSearch {
    private String bid;
    private List&lt;String&gt; bids;

    private String bname;

    private boolean active;
}
</code></pre><p>active 요소가 null일수 없기에 필수적이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[api에서 반환 타입]]></title>
            <link>https://velog.io/@zzzang_hyeon/api%EC%97%90%EC%84%9C-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@zzzang_hyeon/api%EC%97%90%EC%84%9C-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85</guid>
            <pubDate>Tue, 11 Feb 2025 14:40:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/a516d140-edb0-4647-8614-a36ae1d2d80a/image.png" alt=""></p>
<p>우선 위 상태코드를 이해 한뒤
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/8e4a7560-b5c2-404d-9e8c-2bc3f5b5c98a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/17b512db-0c47-446a-b3cd-161e53ddd9bf/image.png" alt=""></p>
<p>위 반환타입을 이해하면,</p>
<p>데이터 베이스에 새로운 값을 저장할 경우에는 상태값을 201 created로 지정하여 전송하는 것이 맞다.
하지만 삭제, 단순 조회 같은 경우는 요청이 성공적으로 이루어졌는지만 판단하면 되기에 일반적인 JSONData 형식으로 반환 타입을 설정하는게 일반적이다.</p>
<p>그리고 사실은 글 수정 같은경우에도 실패 or 성공 둘중 하나만 전송하면 되기에 200OK를 전송하는 것이 맞지만, 위 코드(생략된 부분) 에는 글 수정과 작성을 통합해서 기능을 처리하고 있기때문에 상태코드를 수정할 수 있는 </p>
<pre><code>ResponseEntity &lt;JSONData&gt;</code></pre><p>를 사용하였다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[배포]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@zzzang_hyeon/%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Thu, 23 Jan 2025 21:30:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="젠킨스">젠킨스</h1>
</blockquote>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/62f8b362-3039-4582-802a-064ab757f99a/image.png" alt=""></p>
<p><a href="http://jenkins.hidog.xyz:8080/">http://jenkins.hidog.xyz:8080/</a></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b05b5efd-ba4b-4a64-ba89-727415640e81/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/68a6b761-8ad2-4ecf-88c8-7666ee668c2a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Aws 명령어]]></title>
            <link>https://velog.io/@zzzang_hyeon/%E3%85%81%E3%85%81%E3%85%81</link>
            <guid>https://velog.io/@zzzang_hyeon/%E3%85%81%E3%85%81%E3%85%81</guid>
            <pubDate>Wed, 08 Jan 2025 02:52:58 GMT</pubDate>
            <description><![CDATA[<p>sh deploy_boardservice.sh
tail -f application_boardservice.log</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 정리]]></title>
            <link>https://velog.io/@zzzang_hyeon/React-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@zzzang_hyeon/React-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 05 Sep 2024 06:39:00 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트-리로드-기준">컴포넌트 리로드 기준</h2>
<p>1) props: 부모 props가 변경이 되면 자식 컴포넌트가 리로드
2) state: 컴포넌트 안의 상태 값이 변경이 되면(useState를 통해 만든 값) 리로드
3) 부모 컴포넌트가 리로드 -&gt; 자식 컴포넌트도 리로드
-하위 컴포넌트가 변경 사항이 없는 경우 다시 로딩하면 메모리 낭비</p>
<ul>
<li>메모제이션 기법(캐싱) : React.memo(컴포넌트)
4) 강제 (클래스형 컴포넌트에서 this.forceUpdate())</li>
</ul>
<h2 id="1-jsx">1. JSX</h2>
<p>&lt;Fragment&gt; -&gt; &lt;&gt;
변수, 간단한 식 -&gt; {...}
{/* 주석 ... */}
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b613e2f9-44b9-4146-81e8-18591fad84e0/image.png" alt="">
조건문(if), 반복문(for, while) 사용 불가</p>
<p>삼항 조건문 : 간단한 조건문 대체</p>
<p>참고) 
or 연산자(||) : 기본값 설정시
and 연산자 : 간단한 조건식을 대체 (참인 조건만 체크하는 경우) </p>
<p>참고) 코드 정리
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/05b9e51e-6b8d-41d6-950d-e95e663cf106/image.png" alt=""></p>
<h2 id="2-컴포넌트">2. 컴포넌트</h2>
<p>함수형 컴포넌트</p>
<pre><code class="language-js">function 컴포넌트 명() {
    return (
        JSX 문법
    );
}</code></pre>
<p>화살표 함수를 써도 됨</p>
<h3 id="greeting">Greeting</h3>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/aae32f02-933b-4d54-88b2-25bcde84a017/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/a7dba400-89a9-451e-a993-0a741e168559/image.png" alt=""></p>
<h3 id="속성--컴포넌트에-전달하는-값">속성 : 컴포넌트에 전달하는 값</h3>
<ul>
<li>컴포넌트의 함수 매개변수에서 확인 가능 (props)</li>
</ul>
<h2 id="3-이벤트-핸들링">3. 이벤트 핸들링</h2>
<h3 id="1-usestate">1) useState</h3>
<p>-state : 상태값
-컴포넌트 내부에서 변경될 수 있는 값
-컴포넌트의 리렌더링 기준, 값이 바뀌면 컴포넌트 리렌더링이 된다.
-const [상태값, 상태 변경 함수] = useState(기본값);</p>
<p>상태 변경 함수 (값);
상태 변경 함수 (함수 형태)</p>
<p>예)
setItems(newItems);</p>
<p>setItems((이전 상태 값) =&gt; 반환값); // useCallback, useEffect</p>
<ul>
<li>react-icons
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/46ef8b6c-ae3a-4b3d-9c19-49fc2193307b/image.png" alt=""></li>
</ul>
<p>참고) 컨테이너 컴포넌트
-이벤트 처리
-값의 가공 처리</p>
<p>프리젠테이셔널 컴포넌트
-컨테이너 컴포넌트가 가공한 값을 출력</p>
<ul>
<li><h4 id="immer를-사용하여-더-쉽게-불변성-유지하기">immer를 사용하여 더 쉽게 불변성 유지하기</h4>
</li>
<li>값만 변경해도 불변성 유지 (새로운 객체 자동 생성)
yarn add immer
npm i immer</li>
</ul>
<p>{...} -&gt; if X, for X
-&gt; 배열 객체의 map 메서드
데이터 -&gt; JSX 객체 변환</p>
<h3 id="2-useeffect">2) useEffect</h3>
<p>컴포넌트 렌더링시 호출 : 최초에 필요한 작업
변화 감지 기준 값이 변경될때 호출</p>
<h3 id="3-usereducer">3) useReducer</h3>
<p>useState와 비슷한 기능
상태값 관리
상태 변화 함수를 컴포넌트 외부에 정의
:컴포넌트가 렌더링 다시 돼도 </p>
<p>메모제이션(캐싱)</p>
<h3 id="4-usememo">4) useMemo</h3>
<p>특정 함수의 연산 값</p>
<h3 id="5-usecallback">5) useCallback</h3>
<p>변화 감지 기준의 값이 변경되지 않으면 함수가 다시 정의 X</p>
<h3 id="6-useref">6) useRef</h3>
<p>-지역변수
current 속성 값으로 접근 가능</p>
<p>-DOM 선택시</p>
<blockquote>
<h1 id="스타일링">스타일링</h1>
</blockquote>
<h2 id="sass">Sass</h2>
<p>설치 yarn add sass</p>
<p>2) Scss
-&gt; {}
3) CSS Module
-리액트에서 기본 제공</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/ab852644-19a5-4254-be13-1872f86e9789/image.png" alt=""></p>
<p>4) classnames
yarn add classnames</p>
<p>5) styled-components
yarn add styled-components</p>
<p>tagged 함수</p>
<h2 id="코드-스플리팅">코드 스플리팅</h2>
<p>게임 랜더링 느낌
보이는것만 로드</p>
<p>Loadable Component
yarn add @loadable/component</p>
<h2 id="context-api">Context API</h2>
<p>: 전역 상태 관리</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 등록 (MSA)]]></title>
            <link>https://velog.io/@zzzang_hyeon/AWS-%EB%93%B1%EB%A1%9D-MSA</link>
            <guid>https://velog.io/@zzzang_hyeon/AWS-%EB%93%B1%EB%A1%9D-MSA</guid>
            <pubDate>Thu, 05 Sep 2024 06:25:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/42edca76-1522-4bf0-9d59-233228befe44/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/6ace04bb-ad97-4e6e-808a-24ae572fc784/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d6f2c1ae-7e1a-45fa-a107-f87e97ed8839/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/bcc22720-3fe7-4255-bf32-3e22249d61a0/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/45c3cf78-ce22-4419-805a-aad7a67c5668/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/79622afb-8069-433d-afb0-7510d45bd413/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c1a7830a-507d-478a-9fff-cea58215a29b/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/2edaee12-220e-412e-ad32-2122f4e6b6b4/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/75138afe-83e8-4dd7-a85a-f903c23ed230/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/8e36aef4-141d-4d5e-b8e7-641f5cdb80a5/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4909a70c-c0b6-4aaf-a183-e6b572f50905/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/6e661acd-ab20-45f2-bdb2-ca66f6cb8638/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/7ab0babe-16ab-45a3-b9d1-eb4f00bde97a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/057434e7-8493-432e-9f54-729d72fea2ce/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/79be6dc7-7d59-4147-a8ae-300b8fdf953d/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/47866d99-ae20-4951-a55d-bceaff0ab39a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/87b1f278-6da2-4bc0-91df-3a6253ead4f8/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/37effe67-ca51-4577-8f2b-44aab7903f2a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/558367af-8dbc-4ba1-bc34-e85772443073/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/eb688dc2-b29d-437c-855b-062cea3613ee/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/a4a5956f-19b4-4896-8def-314019c6bca3/image.png" alt="">
램부족
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/420dc54e-0489-4afd-9039-1d1c4eb08c26/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/afd9d70d-5462-42af-b996-033aff9f4479/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/12f2d4f5-47b5-4b6b-89cb-037241b537e2/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/f2a45ba5-ba5b-4799-87b2-f910b603e3bd/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d44ecda0-5039-4aab-a290-fac9d0cded51/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b702289c-bd5f-4764-a9ee-fb43fe8aac99/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/5a6cc861-ed03-4738-8048-6de22d5244b9/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/72917c59-9bde-452a-9b33-ceeefa48bc33/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/28ec5586-99af-4477-81c2-127efc55ef23/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/7c6f2e70-78ea-4330-aadf-87770dc590f6/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/dfb6b3ee-8433-45bd-8712-ae94a05839a4/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/ea6b0ce4-ccea-4797-a1de-cf53f8879082/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/7e22afc2-7046-4258-baa3-b9bb554f7c6f/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4dcd752e-f80f-450a-8b41-0fe985c4c6a4/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/5e47cfdc-4c92-44b2-a8fd-db7b6cc4cb40/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/18ebb214-db3c-4607-85ab-dbabf7244bfa/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/fafa8ddd-041b-4c2d-b10f-0361cead6ce1/image.png" alt="">
(cicd 맨아래 복사)</p>
<pre><code>#!/bin/bash
pid=$(pgrep -f deploytest)
if [ -n &quot;${pid}&quot; ]
then
        kill -15 ${pid}
        echo kill process ${pid}
else
        echo no process
fi
chmod +x ./project/deploytest-0.0.1-SNAPSHOT.jar 
nohup java -jar -Dspring.profiles.active=prod ./project/deploytest-0.0.1-SNAPSHOT.jar  &gt;&gt; application.log 2&gt; /dev/null &amp;</code></pre><p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/1a5e89bd-166a-43ba-86f5-283c1dd6031a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/0f8840f1-218a-4872-bfbd-d3f7f69cb54d/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/ed9172ae-26e7-46b4-aae9-137f356dfe6f/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/eda5e601-3f84-4de4-bc73-a6a92317a351/image.png" alt=""></p>
<h2 id="최종">최종</h2>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b80b4f0c-cd60-4995-8ac3-866f237c2f0a/image.png" alt=""></p>
<p>(다른 거 등록)
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3c1e0117-073f-4a3b-b791-56705269e132/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d99f0739-60e2-46a1-b289-063feae0c4e3/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/0f321e2c-e390-4f1e-a286-fc24674e93d3/image.png" alt=""></p>
<p>환경변수 찾아서 넣어줘야함</p>
<h2 id="여기-참고">여기 참고</h2>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4c239e2d-e28e-4d9f-bbf1-ea2f4d80813d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/5aecaf4d-5fdf-4720-8940-143da385c694/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/88e64ed1-cc47-4d25-901c-aa13663c33d1/image.png" alt=""></p>
<h2 id="생략--------웹사이트-연동">생략------- 웹사이트 연동</h2>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c4cc8b94-2bb5-4709-867d-bb7fc5b4c24d/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/645fda75-58ff-4239-9558-faeeb89ac931/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/dfaf46d8-8970-44ee-ab12-7715a48f8345/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/e90e15ec-92c8-4264-b91c-77b0ba881861/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/915d994f-3602-41e6-acc0-06cd8a6f7a69/image.png" alt=""></p>
<h2 id="------여기까지">----- 여기까지</h2>
<h2 id="-------젠킨스">------ 젠킨스</h2>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/9641ff88-cc70-4b5b-a3a6-e0a461cb4a96/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/6ec0afb2-a742-4d25-9e80-7bdd4149d78e/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/13131a38-1cc4-4baf-a1db-db7f1e6a269b/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/71a8b88b-7b89-4bcd-9667-0fd27b38625c/image.png" alt="">
pem파일 key에 넣기 (aws 에서 다운받았던 거)</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/47afd191-9af4-4480-957a-31b8b7b45764/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b485a173-5bb8-4105-94fa-fcb907d75e4f/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/cbacd693-b4fb-478b-84a8-b5daaa7eb8ed/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/76433f7b-7834-425c-81f7-b1d18525182e/image.png" alt=""></p>
<p>젠킨스 파이프라인 복붙 후 주소 바꾸기 </p>
<pre><code>pipeline {
    agent any

    stages {
        stage(&#39;Git Clone&#39;) {
            steps {
                git branch: &#39;master&#39;, url: &#39;브랜치 깃 클론 주소&#39;
            }
        }
        stage(&#39;Build&#39;) {
            steps {
                dir(&quot;.&quot;) {
                    sh &quot;chmod 744 gradlew&quot;
                    sh &quot;./gradlew clean bootJar&quot;
                }
            }
        }
        stage(&#39;Deploy&#39;) {
            steps {
                sshagent(credentials: [&#39;키이름&#39;]) {
                    sh &#39;&#39;&#39;
                        ssh -o StrictHostKeyChecking=no ubuntu@서버아이피주소 uptime
                        scp /var/jenkins_home/workspace/젠킨스 서버 이름(ex:configserver)/build/libs/젠킨스 서버 이름(ex:configserver)-0.0.1-SNAPSHOT.jar ubuntu@서버아이피주소:/home/ubuntu/젠킨스 서버 이름(ex:configserver)

                        ssh -t ubuntu@서버아이피주소 ./deploy.sh
                    &#39;&#39;&#39;
                }
            }
        }
    }
}</code></pre><p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/e9976887-909f-4d89-8b80-095271c84dbd/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4cce522f-613a-4f38-bec2-922d6137cfb5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/65b079b0-849c-4b7a-9019-4cdeefff33b6/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c333e6eb-5c9d-4948-9885-3564f4cb382a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/c885f238-29c2-47c1-a733-f102b07cb6a1/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/ce8c0a3d-7bbe-40e9-855d-d64fa9316d5a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/837cd6bc-e25a-48fa-9fac-0838d27c6bb7/image.png" alt="">
서버주소로 연결 확인</p>
<ul>
<li>데이터베이스는 리눅스 서버에서
<a href="https://github.com/yonggyo1125/lecture_cicd">https://github.com/yonggyo1125/lecture_cicd</a> 도커 설치후 깔면됨</li>
</ul>
<p>// 수동 시작</p>
<pre><code>sh deploy_boardservice.sh
tail -f application_boardservice.log</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[개발 환경 구축]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@zzzang_hyeon/%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Tue, 03 Sep 2024 01:54:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="msa">MSA</h2>
</blockquote>
<h3 id="설정-서버">설정 서버</h3>
<p>: 깃허브 레포지토리</p>
<h3 id="게이트웨이">게이트웨이</h3>
<p>회원 member-service - /api/v1/member</p>
<blockquote>
<h2 id="nextjs">Next.js</h2>
</blockquote>
<h2 id="1-설치--실행">1. 설치 &amp; 실행</h2>
<p>yarn create next-app@latest 프로젝트명</p>
<p>yarn create next-app exam01    </p>
<p>npm i -g next-app</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/63c75601-94e7-4b92-996f-08a13d43591a/image.png" alt=""></p>
<p>yarn dev : 개발 서버 실행
yarn build : 배포 파일 생성
yarn start : 실서버 실행 (빌드 -&gt; 스타트)</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/a4375bd4-e96d-4efd-a049-5aa937a09087/image.png" alt=""></p>
<h2 id="2-빌드와-배포">2. 빌드와 배포</h2>
<h2 id="3-뼈대-만들기">3. 뼈대 만들기</h2>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/03a07a30-ef01-435f-8b63-7618eb568493/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/72400fdd-1512-4576-8be8-baaa6ecc16d6/image.png" alt=""></p>
<h3 id="메타데이터---레이아웃에서-헤더쪽-데이터-치환">메타데이터 -&gt; 레이아웃에서 헤더쪽 데이터 치환</h3>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/11ee4a83-8c79-461b-bfd2-b2f7ae1e67d5/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/20193123-5daf-460a-8562-b39795e9303a/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4c8888c9-828a-4730-b938-bcf129a93bdf/image.png" alt=""></p>
<h3 id="memberlayoutjs---멤버전용-레이아웃">member/layout.js -&gt; 멤버전용 레이아웃</h3>
<h2 id="4-라우팅">4. 라우팅</h2>
<h3 id="폴더---url-주소">폴더 -&gt; url 주소</h3>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3a1716be-d604-4d83-a14b-ddfadd5e14fc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3ebc4c99-0274-4f25-a632-817f2bf1abe2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/1e222004-f50c-47c0-929f-428553c17fc1/image.png" alt=""></p>
<p>1) 경로변수 - 폴더명 [경로변수명]로 만듦
props :
params : 경로변수
searchParams : 쿼리스트링 값</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/6613da82-9e60-45d1-88ce-3211086b8e68/image.png" alt=""></p>
<p>콘솔찍으면 터미널에 찍힘</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/eb3c350f-e84f-4b13-aa1b-09c5d0f53269/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/4350036a-751d-46c0-99ed-42e745544d36/image.png" alt="">
params 종류 두개 기억</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/33481c96-a450-49e6-8958-c7a42a909538/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/23b8d25f-ff3c-48f4-8ea0-535927ef5279/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/bb3f3f0f-f0cd-41c8-a586-21ff067bcfcb/image.png" alt=""></p>
<h4 id="seq-세-개-명이-같기만-하면-됨글자-seq-필수-x">seq 세 개 명이 같기만 하면 됨(글자 seq 필수 X)</h4>
<p>params 글자는 필수</p>
<h2 id="5-single-page-application">5. Single Page Application</h2>
<h4 id="-csrclient-side-rendering---react-router">-CSR(client Side Rendering) - React Router</h4>
<p>:반응성이 좋음
:인터넷 연결이 없어도 기본 페이지가 출력
:검색 반영 X
:Link, NavLink</p>
<h4 id="-ssrserver-side-rendering-nextjs의-기본-방식">-SSR(Server Side Rendering) next.js의 기본 방식</h4>
<p>:검색 반영 O
:페이지 새로고침 - 반응성 X
:a 태그를 사용해서 링크 이동</p>
<p>:next.js에서 CSR을 사용하려면? Link 태그 사용</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3b7c9296-81fc-4883-8f07-5457ce5ab53a/image.png" alt=""></p>
<h2 id="6-환경-변수">6. 환경 변수</h2>
<p>.env ...</p>
<p>NEXT_PUBLIC_ : NEXT_PUBLIC_ 접두어가 있어야 환경 변수 접근 가능
(process.env.NEXT_PUBLIC_API_URL)
참고) 리액트
REATC_APP_ : REACT_APP_ 접두어가 있어야 환경 변수 접근 가능 (process.env.REACT_APP_API_URL...)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[줄 개행 문제]]></title>
            <link>https://velog.io/@zzzang_hyeon/%EC%A4%84-%EA%B0%9C%ED%96%89-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@zzzang_hyeon/%EC%A4%84-%EA%B0%9C%ED%96%89-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 30 Aug 2024 03:12:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/03731558-a3df-479d-b940-6d3ed73582b0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3d0fcbea-eb14-4824-a477-4c6ca50c89f6/image.png" alt=""></p>
<p>줄개행을 처리하고 있던 중 한글은 줄 개행이 잘 되는데 영어는 잘 안되는 오류를 발견했다.</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/285a2637-521b-4082-8cd9-c3e3b05f10e7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d5067f65-afd4-400d-8ec3-34dcaaa56379/image.png" alt=""></p>
<p>스타일에서 해결할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/454dadb8-df5f-4cfb-aa77-84e008cb5006/image.png" alt=""></p>
<p>영어나 끊어지지 않는 단어에서 사용하도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[iframe location 이동문제 ]]></title>
            <link>https://velog.io/@zzzang_hyeon/iframe-location-%EC%9D%B4%EB%8F%99%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@zzzang_hyeon/iframe-location-%EC%9D%B4%EB%8F%99%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 29 Aug 2024 08:05:14 GMT</pubDate>
            <description><![CDATA[<p>우선 refer 을 사용하여 요청을 보낸 url 을 추출할 수 있었다.</p>
<pre><code class="language-java">        String referer = request.getHeader(&quot;Referer&quot;);  // 요청 발생시킨 주소 가져옴
</code></pre>
<p>이렇게 추출한 url을 통해 iframe창을 이동 시킬 예정이였다. 
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/3452f2bd-384d-475b-8730-50c9d44af28d/image.png" alt=""></p>
<p>(해당 사진 아래 댓글 창이 iframe)</p>
<pre><code class="language-java">   if (referer != null &amp;&amp; referer.contains(&quot;board/comment/&quot;)) {
            String redirectUrl = utils.redirectUrl(String.format(&quot;/board/comment/%d?comment_id=%d&quot;, commentData.getBoardData().getSeq(), commentData.getSeq()));

            String script = String.format(&quot;location.replace(&#39;%s&#39;);&quot;, redirectUrl);

            System.out.println(&quot;script12321&quot; + script);
            model.addAttribute(&quot;script&quot;, script);

            return &quot;common/_execute_script&quot;;
        }</code></pre>
<p>그러나 댓글은 추가 되지만 url이 이동되지 않았다. 
이유는 iframe에 target 설정이 올바르지 않기때문이다 . 
iframe 댓글의 레이아웃을 적용해야했다.
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/d21f859b-9496-4ce2-8642-8976356020ac/image.png" alt="">
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/895a0f95-6496-4912-a140-b718100d1c18/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ajax JS Th 데이터 공유]]></title>
            <link>https://velog.io/@zzzang_hyeon/ajax-JS-Th-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B3%B5%EC%9C%A0</link>
            <guid>https://velog.io/@zzzang_hyeon/ajax-JS-Th-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B3%B5%EC%9C%A0</guid>
            <pubDate>Thu, 29 Aug 2024 00:53:11 GMT</pubDate>
            <description><![CDATA[<p>JS에서 데이터를 사용해 ajax로 요청을 보낸다.
controller에서 요청 정보를 받아 정보를 가공한 후, 응답을 보내면 
다시 JS ajax에서 응답을 받아 가공한 정보(예) db 조회해서 다른 정보를 가져옴)를 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/zzzang_hyeon/post/b4f0fa09-fdd4-4c9a-9de9-5b3a1857c80f/image.png" alt=""></p>
<p>그리고 이 정보들을 HTML(th) 로 넘겨서 데이터를 출력하거나 사용할 수 있는데, 
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/67042236-7d11-4530-b446-03a1ee9accc8/image.png" alt=""></p>
<p>예외적으로 사용못하는 경우가 있다.
<img src="https://velog.velcdn.com/images/zzzang_hyeon/post/e1bc66cb-9aae-4bba-bdac-668efbc134c1/image.png" alt=""></p>
<p>위와 같이 빈에 있는 메서드를 사용한 경우 내부에서 사용할 수 없다. (위 메서드는 오류가 발생함 seq = null)</p>
<p>이 데이터를 사용하기 위해 
url에 직접 변화를 가하면 된다.</p>
<pre><code class="language-js"> const href = seqEl.href.substring(0, seqEl.href.lastIndexOf(&quot;/&quot;)) + &quot;/&quot; + this.seq;
                    seqEl.href = href;
                    ifrmBoard.location.href = href.substring(0, href.lastIndexOf(&quot;/board/view/&quot;)) + &quot;/board/comment/&quot; + this.seq;
                    console.log(ifrmBoard.location.href)</code></pre>
]]></description>
        </item>
    </channel>
</rss>