<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jwjin_dev.log</title>
        <link>https://velog.io/</link>
        <description>주니어 개발자</description>
        <lastBuildDate>Mon, 24 Jul 2023 11:40:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. jwjin_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jwjin_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Spring] PostgreSQL docker container 세팅 및 에러 해결]]></title>
            <link>https://velog.io/@jwjin_dev/Spring-PostgreSQL-docker-container-%EC%84%B8%ED%8C%85-%EB%B0%8F-P</link>
            <guid>https://velog.io/@jwjin_dev/Spring-PostgreSQL-docker-container-%EC%84%B8%ED%8C%85-%EB%B0%8F-P</guid>
            <pubDate>Mon, 24 Jul 2023 11:40:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/237b05ba-5f59-498d-ad6b-8e85c901c11d/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/f3b9a41f-816d-4657-a07f-1c4120ff0e78/image.png" alt=""></p>
<h3 id="사용-이유">사용 이유</h3>
<p>프로젝트가 어느정도 진척되면 docker-compose를 이용하여
redis, postgreSQL, 스프링 어플리케이션을 모두 묶어 CI/CD를 진행하기로 했기에, 로컬 DB를 이용할 수도 있지만 이 후 호환성 고려 및 docker에 조금이라도 더 친숙해지고자 docker를 이용하여 DB를 세팅하기로 결정.</p>
<h3 id="세팅-전-확인">세팅 전 확인</h3>
<p>세팅 하기전 컨테이너에 바인딩할 포트를 확인해주세요..
혹시라도 저처럼 체크하지 않고 그냥 설정할 경우 충돌이 생길 수 있습니다..</p>
<p>윈도우의 경우 아래의 명령어를 이용하여 5432 포트가 사용중인지 확인 후 사용중이라면 호스트의 포트를 변경하여 사용하시면 됩니다.</p>
<pre><code>netstat -ano | findstr 포트번호</code></pre><h4 id="1-도커-이미지-받아오기">1. 도커 이미지 받아오기</h4>
<pre><code>docker pull postgres:{version}</code></pre><p>자신의 터미널을 실행 후 사용하고자 하는 버전의 postgres의 이미지를 받아옵니다. 
최신 버전의 경우 <strong>docker pull postgres:latest</strong> 사용</p>
<h4 id="2-컨테이너-실행">2. 컨테이너 실행</h4>
<pre><code>docker run -p {호스트 포트}:{컨테이너 포트} --name {컨테이너 이름} \ // 포트 및 컨테이너 이름 설정
-e POSTGRES_PASSWORD={db password} \ // 비밀번호
-d postgres // -d 옵션을 줘서 detached 모드로 사용</code></pre><p>컨테이너를 지워도 데이터를 계속 유지시키고자 하면 volume을 생성 후 
-v pgdata:/var/lib/postgresql/data 옵션을 추가 </p>
<pre><code>docker volume create pgtest</code></pre><p>아래와 같은 형태로 실행</p>
<pre><code>docker run -p 5432:5432 --name postgres-container -e POSTGRES_PASSWORD=1234 -e POSTGRES_DB=testdb -d -v pgtest:/var/lib/postgresql/data postgres</code></pre><p>docker ps 명령어를 통해 실행 되었는지 확인
<img src="https://velog.velcdn.com/images/jwjin_dev/post/907fc539-db12-430d-b9f3-fe401230a681/image.png" alt=""></p>
<h4 id="3-컨테이너-접속">3. 컨테이너 접속</h4>
<p>아래 명령어를 사용해 접속</p>
<pre><code>docker exec -it &lt;컨테이너 이름&gt; /bin/bash  // -it 옵션을 사용해 배시 셸을 사용
psql -U postgres // default username인 postgres로 접속</code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/72b21bd7-6420-4fe2-a8f9-0bc19ae5c1cd/image.png" alt=""></p>
<p>접속이 완료되었으면 DB의 접근 권한을 세팅한다.</p>
<pre><code>create role testuser with login password &#39;1234&#39;;
alter user testuser with superuser;
\l // 데이터베이스 상태 확인</code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/7dba76a0-8cdb-48bb-a9a4-509d6c918bc0/image.png" alt=""></p>
<pre><code>grant all privileges on database testdb to testuser;</code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/de390d43-a390-4ca4-824f-82e7e1cc9d48/image.png" alt=""></p>
<p>application.yml 설정</p>
<pre><code>spring:
  config:
    activate:
      on-profile: local

  datasource:
    url: jdbc:postgresql://0.0.0.0:5432/testdb
    username: testuser
    password: 1234
    driver-class-name: org.postgresql.Driver
  jpa:
    database: postgresql
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: create

    properties:
      hibernate:
        format_sql: true
</code></pre><p>테스트를 해보았는데..</p>
<h3 id="에러-발생">에러 발생</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/f3d39cd6-0b9b-4b38-82f3-0f370f5da9aa/image.png" alt="">
PSQLException 에러 발생, 연결 실패. 
그런데 문자열이 깨졌다?
에러 내용을 알아야 하는데, Intellij 유니코드 세팅, postgresql 컨테이너의 유니코드 세팅을 다시해도 마찬가지로 깨졌다.</p>
<p>팀원의 도움을 받아 열심히 구글링한 결과</p>
<pre><code>org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name &#39;entityManagerFactory&#39; 
defined in class path resource[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] 
Unable to build Hibernate SessionFactory; 
nested exception is org.hibernate.exception.GenericJDBCException: Unable to open JDBC Connection for DDL execution
치명적오류: 호스트 &quot;내 ip&quot;, 사용자 &quot;testuser&quot;, 데이터베이스 &quot;testdb&quot;, 
SSL 중지 연결에 대한 설정이 pg_hba.conf 파일에 없습니다.</code></pre><p>다음과 같은 에러였다. 이를 해결하기 위해 열심히 구글링하여 pg_hba.conf, postgresql.conf 파일도 수정해보고, 여러번 도커를 다시 세팅해보아도 해결되지 않았고 
한참을 고민하고 삽질한 끝에 원인을 찾았다.</p>
<p>이전에 학습할때 로컬환경에서 사용했던 postgres DB가 생각이 났고,
아래의 명령어를 사용하여 확인해본 결과 5432 포트로 서비스가 계속 실행되고 있었다.</p>
<pre><code>netstat -ano | findstr 5432</code></pre><p>이때문에 계속 로컬에 존재하지않는 db와 username로 연결하려고 하니 당연히 될리가 없었고,
문자열이 깨지는 것도 결국 로컬환경의 문제였다.
실행 전 체크해야 됐었던 부분인데 이런 부분 때문에 시간을 낭비하니 너무 아까웠음..
로컬에서 postgresql이 설치된 경로에 아래 명령어를 사용해 서비스를 중지시키니 정상적으로 작동!</p>
<pre><code>pg_ctl stop -mf -w -D ../data
</code></pre><p>pg_ctl에 대한 자세한 명령어는 이 곳 참조!
<a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=iyagi15&amp;logNo=10141652237">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=iyagi15&amp;logNo=10141652237</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] N:M, 다대다 연관 관계]]></title>
            <link>https://velog.io/@jwjin_dev/Spring-NM-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%97%B0%EA%B4%80-%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@jwjin_dev/Spring-NM-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%97%B0%EA%B4%80-%EA%B4%80%EA%B3%84</guid>
            <pubDate>Thu, 20 Jul 2023 14:09:23 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>설계 미스로 playlist 엔티티와 music 엔티티간 관계가 N:1로 설정되어 있었고,
원하는 실제 동작은 플레이리스트는 N개의 음악을 가지고, 음악 또한 M개의 플레이리스트를 가질 수 있도록 하는 것이기에 설계를 변경해야함</p>
<h3 id="해결-방안">해결 방안</h3>
<ol>
<li><p>다대다 매핑</p>
<pre><code class="language-java">@Entity
public class Playlist {

   @Id 
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = &quot;playlist_id&quot;)
   private Long id;

   @ManyToMany
   @JoinTable(name = &quot;playlist_music&quot;)
   private List&lt;Music&gt; musicList = new ArrayList&lt;&gt;();
}</code></pre>
<p>@ManyToMany 어노테이션 사용 시 @JoinTable 어노테이션을 활용해
중간 테이블을 Hibernate가 생성해주지만, 중간 테이블에 PK, FK만 매핑해주기에 
추후에 키 이외의 필요한 정보를 컬럼으로 넣어주고자 할 때 문제가 발생 </p>
</li>
<li><p><strong>직접 중간 테이블 엔티티 생성하여 매핑</strong>
 1:N, N:1 관계로 분리한 엔티티를 만들고, 이를 사용하여 매핑
 playlist(1) - playlist_music(N) - music(1) 
 이 후 스펙이 변경될 것을 고려하여 2번을 선택</p>
</li>
</ol>
<h3 id="erd">ERD</h3>
<p>아래와 같은 구조로 ER-Diagram을 변경
<img src="https://velog.velcdn.com/images/jwjin_dev/post/6dc767e8-2711-4857-a2a1-5b9da445a3a2/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<pre><code class="language-java">#Playlist entity
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Playlist {
    ...
    @OneToMany(mappedBy = &quot;playlist&quot;)
    private List&lt;Music&gt; musicList = new ArrayList&lt;Music&gt;();
    ...
}</code></pre>
<pre><code class="language-java">@Entity
#중간 테이블
public class PlaylistMusic {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;playlist_music_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;playlist_id&quot;)
    private Playlist playlist;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;music_id&quot;)
    private Music music;
}</code></pre>
<p>설정한대로 playlist(1) - playlist_music(N) - music(1) 이 되도록 함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 14일차 (팀 프로젝트 마무리)]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-13%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EB%AC%B4%EB%A6%AC</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-13%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EB%AC%B4%EB%A6%AC</guid>
            <pubDate>Thu, 13 Jul 2023 16:19:19 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-13-sw-직무역량-부트캠프-14일차-학습일지">2023-07-13 SW 직무역량 부트캠프 14일차 학습일지</h4>
<p>09:00 ~ 18:30 간 공대 6호관 610호에서 수업을 진행하였습니다.</p>
<h3 id="pbl">PBL</h3>
<ul>
<li>Mission 3
<img src="https://velog.velcdn.com/images/jwjin_dev/post/c6c3e3b4-cc76-4ffa-842f-bfa467a881f0/image.png" alt=""></li>
</ul>
<p>어제의 버전관리 시스템 구현 미션을 이어서 진행하였습니다.</p>
<h3 id="구현">구현</h3>
<h4 id="versionservice-updatecheck">VersionService updateCheck()</h4>
<p>프론트에서 실제 업데이트 동작을 위한 updateCheck 함수를 작성하였습니다.</p>
<pre><code class="language-java"># VersionService.java
 // 최신 버전 가져오기
    public Version getRecentVersion(Version version) {

        return versionRepository.findTopByOsInfoAndServiceNameOrderByServiceVersionDesc(version.getOsInfo(), version.getServiceName())
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;해당 os의 최신 버전 찾기 실패&quot;));
        // 체크
    }

    public UpdateCheckResponseDto updateCheck(String osInfo, String serviceName, String serviceVersion) {
        boolean isForcedUpdate = false;
        List&lt;Version&gt; versionList = versionRepository.findByIsDeleteFalseAndOsInfoAndServiceName(osInfo, serviceName)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;동일한 os와 서비스네임을 가진 서비스가 없음&quot;));
        for (Version version : versionList) {
            if(compareVersion(serviceVersion, version.getServiceVersion())) {
                if(version.isUpdateType() == true) {
                    isForcedUpdate = true;
                    break;
                }
            }
        }

        Version recentVersion = Version.builder().build();
        // String serviceVersion, boolean isRecentVersion, boolean isForcedUpdate
        return new UpdateCheckResponseDto(serviceVersion, compareVersion(serviceVersion, recentVersion.getServiceVersion()), isForcedUpdate);
    }
    public Long getVersionCount() {
        return versionRepository.count();
    }



    public static boolean compareVersion(String serviceVersion, String compareVersion) {
        boolean isNeedsUpdate = false;
        String[] serviceVersionArr = serviceVersion.split(&quot;.&quot;);
        String[] compareVersionArr = compareVersion.split(&quot;.&quot;);
        int maxLen = Math.max(serviceVersionArr.length, compareVersionArr.length);
        for (int i = 0; i &lt; maxLen; i++) {
            int x = Integer.parseInt(serviceVersionArr[i]);
            int y = Integer.parseInt(compareVersionArr[i]);
            if (x &lt; y) {
                isNeedsUpdate = true;
            } else {
                isNeedsUpdate = false;
            }
        }
        return isNeedsUpdate;
    }</code></pre>
<h4 id="versionrepository">VersionRepository</h4>
<p>명세가 변경되며 변경점이 많았고, 그 과정에서 문법적인 실수와 파라미터 매핑시 순서 실수 등으로 인해 </p>
<pre><code class="language-java">#VersionRepository.java

    Optional&lt;Version&gt; findTopByOsInfoAndServiceNameOrderByServiceVersionDesc(String osInfo, String serviceName);
    List&lt;Version&gt; findByOsInfoAndServiceName(String osInfo, String serviceName);
//    @Query(&quot;select v from Version v where v.os_info= osInfo and v.service_name= serviceName and is_delete = false&quot;)
    Optional&lt;List&lt;Version&gt;&gt; findByIsDeleteFalseAndOsInfoAndServiceName(String osInfo, String serviceName);</code></pre>
<h4 id="오류-발생">오류 발생</h4>
<p>Restful한 API 구현을 위해 기존에 PostMapping으로 구현되어 있던 Read 기능들을 GET Method를 사용하여 받아 오고자 GetMapping RequestParam을 사용하여 Dto를 매핑하였지만,
JpaRepository 기능 사용 시 값을 제대로 받아오지 못하는 현상이 발생했습니다.
같은 값이 출력되었고 추가로 분석하여 차이점을 찾으려 했으나 시간이 부족해 실패하였고,
내일 멘토님께 질문을 해봐야겠습니다..</p>
<h3 id="후기">후기</h3>
<p>처음 떠올렸던 기능에서 시간이 지나고 피드백을 받으며 스펙을 바꾸며 명세를 수 차례 변경하였습니다. 이 과정에서 제때 바뀐 명세를 반영하지 못하여 에러가 발생하며 시간이 버려지고, 결국 원래 명세하였던 기능 및 추가하고자 하는 기능들을 반영하지 못했습니다.
원래도 짧은 시간의 팀 프로젝트였지만 자바 자체의 문법에 익숙하지 않았고, 디버깅 능력 및 쿼리 작성 능력 부족 등 제 역량 부족으로 인해 더더욱 시간이 부족해져 아쉬웠던 프로젝트 였습니다. 이를 발판으로 하여 부족한 점을 보완하고자 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 13일차 (팀 프로젝트)]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-13%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-13%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Wed, 12 Jul 2023 17:53:03 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-12-sw-직무역량-부트캠프-13일차-학습일지">2023-07-12 SW 직무역량 부트캠프 13일차 학습일지</h4>
<p>09:00 ~ 17:30 간 강원대학교 AI융합 라운지에서 수업을 진행하였습니다.</p>
<h3 id="pbl">PBL</h3>
<ul>
<li>Mission 3
<img src="https://velog.velcdn.com/images/jwjin_dev/post/c6c3e3b4-cc76-4ffa-842f-bfa467a881f0/image.png" alt=""></li>
</ul>
<h3 id="명세">명세</h3>
<p>새로운 미션의 목표에 맞춰 getConfigAll API에 대한 명세를 수정하였고, 추가적인 API에 대한 명세를 아래과 같이 진행하였습니다.</p>
<ul>
<li><p>기본 리스트 화면의 API인 getConfigAll
<img src="https://velog.velcdn.com/images/jwjin_dev/post/97645d20-b36d-4024-b8f4-933228f291b9/image.png" alt=""></p>
</li>
<li><p>Add : Version 정보를 Insert 합니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/57ead3ac-4be2-43d8-bea5-126d660e34f7/image.png" alt=""></p>
</li>
<li><p>수정 : Version 정보를 Update 합니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/2aaac3b8-de3a-4cd2-9721-6d55e5113b18/image.png" alt=""></p>
</li>
<li><p>삭제 : Version 정보를 Delete 합니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/881adb16-4232-4f53-9038-7453b57a1ad4/image.png" alt=""></p>
</li>
<li><p>Test : 해당 버전의 정보를 json String으로 정상적으로 출력하는지 확인하는 Test기능
<img src="https://velog.velcdn.com/images/jwjin_dev/post/b2a874f3-b588-4a91-b510-ae00b521b7aa/image.png" alt=""></p>
</li>
</ul>
<h3 id="구현">구현</h3>
<p>이번에는 기존 GetConfigAll과 versionAdd, Test기능 API 구현 및 스펙 수정, 코드의 리팩토링을 진행하였습니다.</p>
<h4 id="getconfigalla-기본-리스트-화면">GetConfigAll(A. 기본 리스트 화면)</h4>
<p>임시로 표시하기위해 임의의 값를 파라미터로 넣어 사용하였는데, 프론트와 연동을 위해 Dto 및 컨트롤러를 수정하였습니다.</p>
<pre><code class="language-java"># VersionService

public Page&lt;Version&gt; getVersionList(Integer pageNumber, Integer pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
        return versionRepository.findAll(pageRequest);
    }</code></pre>
<pre><code class="language-java"># VersionController
 @PostMapping(&quot;/getconfigall&quot;)
    public ResponseEntity&lt;List&lt;Version&gt;&gt; getConfigAll(@RequestBody VersionPageRequestDto requestDto) {
        return ResponseEntity.ok(versionService.getVersionList(requestDto.getPageNumber(), requestDto.getPageSize()).toList());
    }</code></pre>
<h4 id="versionadd-b-add">VersionAdd (B. ADD)</h4>
<p>HTTP Status code 201를 반환하기 위해 created로 리턴하였습니다.  </p>
<pre><code class="language-java"># VersionService

    @PostMapping(&quot;/versionadd&quot;)
    public ResponseEntity&lt;Version&gt; versionAdd(@RequestBody AddVersionRequestDto requestDto,
                                             UriComponentsBuilder builder){
        // 컨텍스트 상대 경로 URI를 쉽게 만들게 해주는 UriComponentsBuilder를 컨트롤러 메서드의 인자로 지정
        Version version = Version.createVersion(requestDto);
        versionService.saveVersion(version);

        URI location = builder.path(&quot;/vercontrol/versionadd&quot;)
                .buildAndExpand(version.getId()).toUri();

        return ResponseEntity.created(location).body(version);
    }</code></pre>
<h4 id="getversion-e-test">getversion (E. Test)</h4>
<p>PathVariable로 id를 받아와 해당 정보를 리턴</p>
<pre><code class="language-java"># VersionController

    @GetMapping(&quot;/getversion/{id}&quot;)
    public ResponseEntity&lt;Version&gt; getVersion(@PathVariable Long versionId) {
        return ResponseEntity.ok(versionService.findById(versionId));
    }</code></pre>
<h4 id="리팩토링">리팩토링</h4>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/983cbef1-dbc5-4efd-aa30-688c5d2476b0/image.png" alt="">
 위 사진과 같이 기존 컨트롤러 단에서 builder를 호출하여 객체를 만드는 것이 OOP와 멀어지고 실제 유지보수에도 좋지 않다 판단하여 엔티티에  createVersion 함수를 추가하여 리팩토링 하였습니다.</p>
<pre><code class="language-java"># Version.java

    @Builder
    public static Version createVersion(VersionRequestDto versionRequestDto) {
        Version version = Version.builder()
                .osInfo(versionRequestDto.getOsInfo())
                .serviceVersion(versionRequestDto.getServiceVersion())
                .serviceName(versionRequestDto.getServiceName())
                .updateType(versionRequestDto.isUpdateType())
                .message(versionRequestDto.getMessage())
                .packageInfo(versionRequestDto.getPackageInfo())
                .build();

        return version;

    }</code></pre>
<h3 id="후기">후기</h3>
<p>프론트와 API 통신 과정에서 잘못된 명세 및 구현하며 수정된 부분으로 인해 명세와 달라져 시간이 지체된 부분이 있었는데, 정확한 명세와 명세에 따른 구현, 수정시 명세또한 수정되어야 한다는 점을 간과했던 것 같습니다. 
프론트와 백엔드, 즉 팀원간 소통 및 협업이 부족하다 느껴 더 시간을 더 투자하여 진행하고자 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 12일차 (팀 프로젝트)]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-12%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-12%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Tue, 11 Jul 2023 16:07:14 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-11-sw-직무역량-부트캠프-12일차-학습일지">2023-07-11 SW 직무역량 부트캠프 12일차 학습일지</h4>
<p>09:00 ~ 16:30 간 강원대학교 AI융합 라운지에서 수업을 진행하였습니다.</p>
<h3 id="pbl">PBL</h3>
<ul>
<li>Mission 2 
<img src="https://velog.velcdn.com/images/jwjin_dev/post/2e3c246b-9fbf-4364-843f-5d1101add51b/image.png" alt=""></li>
</ul>
<h3 id="명세">명세</h3>
<p>팀원분들과 API에 어떤 파라미터와 아웃풋 값이 필요할지 논의하여, 그 결과를 통해 getConfig, getConfigAll API에 대한 명세를 아래과 같이 진행하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/2bec6fe0-9a77-4e9e-a79e-461202bc4c78/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/606d63ae-f239-4712-bba6-591270d2739a/image.png" alt=""></p>
<h3 id="구현">구현</h3>
<p>아래와 같은 예시 화면이 안내되었습니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/fd2c9351-fa8f-47e0-b529-59fc70bae927/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/7e7aee26-7b11-47e8-9e0c-e7050fae60c3/image.png" alt=""></p>
<h4 id="getconfigall">GetConfigAll</h4>
<p>DB의 객체를 나누어 가져오는 방식에 대해 백엔드 팀원분과 토의하였고,
모두 가져와 나누자, 쿼리를 보내 가져오자, 프론트에서 처리하자(?) 등 다양한 의견이 나왔고, 그 중 가장 손쉬운 방법인 JPA Paging 방법을 발견하고 다음과 같이 구현하였다.</p>
<pre><code class="language-java"># VersionService

    public Page&lt;Version&gt; getVersionList(int count) {

        # PageRequest 임시로 0 설정 매개변수는 (page,size) 형태

        PageRequest pageRequest = PageRequest.of(0,count);
        return versionRepository.findAll(pageRequest);
    }</code></pre>
<h4 id="getconfig">GetConfig</h4>
<p>querydsl, @Query 어노테이션 사용 등 다양한 방법을 떠올리고 시도했지만, 코딩하던 중간에 배터리가 없어 방전되는 바람에 코드가 증발해서 다른 팀의 백엔드 팀원분께 도움을 요청했고,
JPA의 findTopby와 OrderBy를 이용하여 구현하였습니다.</p>
<pre><code class="language-java"># VersionService

    public Version getRecentVersion(Version version) {
        #findTop N By 형태로 사용, 생략시 default값인 1이 들어감
        return versionRepository.findTopByOsInfoOrderByServiceVersionDesc(version.getOsInfo())
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;해당 os의 최신 버전 찾기 실패&quot;));
    }</code></pre>
<h3 id="오류-발생">오류 발생</h3>
<p>프론트엔드와 백엔드 간 데이터를 주고받지 못하는 cors 문제가 발생하여
일단 테스트 진행을 위해 컨트롤러에 @CrossOrigin 어노테이션을 붙여 급한 불은 껐지만, 이렇게 모든 origin에 대해 허용해놓으면 보안상 문제가 될 것이라고 생각되서, 다시 수정할 예정입니다.</p>
<pre><code class="language-java">@RestController
@CrossOrigin
@RequestMapping(&quot;/vercontrol&quot;)
@RequiredArgsConstructor
public class VersionApiController {</code></pre>
<h3 id="후기">후기</h3>
<p>수업중 갑작스럽게 질문들을 받았는데, 바로 떠오르지 않아 대답을 못했습니다.
제가 알고있다고 생각한 개념이 사실은 완전히 체화되지 않은 지식이였고, 기술 면접 진행 시에도 대처능력을 기르기 위해 다시 한번 개념을 정리하고 재학습의 필요성을 느꼈습니다. 그리고  코드를 작성하다보니 바로바로 메소드들이 떠오르지않아 시간이 지체되고 중간에 실수도 하였습니다. 또한 오류 발생 시에도 어디서 문제가 발생했는지 찾고 대처하는데 시간이 많이 소요되어 
스프링 프레임워크 자체에 대한 공부도 더 해야할 필요성을 느꼈습니다. </p>
<h3 id="참조">참조</h3>
<p>JPA 페이징 
<a href="https://velog.io/@conatuseus/JPA-Paging-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%82%98%EB%88%84%EA%B8%B0-o7jze1wqhj">https://velog.io/@conatuseus/JPA-Paging-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%82%98%EB%88%84%EA%B8%B0-o7jze1wqhj</a>
PageRequest
<a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html">https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html</a>
JPA Limit, order 처리하기
<a href="https://devfunny.tistory.com/571">https://devfunny.tistory.com/571</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 11일차 (팀 프로젝트)]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-11%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-11%EC%9D%BC%EC%B0%A8-%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Mon, 10 Jul 2023 15:07:15 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-10-sw-직무역량-부트캠프-11일차-학습일지">2023-07-10 SW 직무역량 부트캠프 11일차 학습일지</h4>
<p>09:00 ~ 15:30 간 강원대학교 공대 6호관 강의실에서 수업을 진행하였습니다.</p>
<h3 id="📙-pbl-project-based-learning-수업">📙 PBL (Project Based Learning) 수업</h3>
<p>프로젝트 기반의 수업</p>
<ul>
<li><p>Scrum</p>
<ul>
<li><p>스크럼 회의의 분야를 크게 세가지로 나누어 진행</p>
</li>
<li><p>Tech, Domain, Business Modeling</p>
</li>
</ul>
</li>
</ul>
<ul>
<li>Mission 1. 버전 관리 시스템
<img src="https://velog.velcdn.com/images/jwjin_dev/post/603336d2-1e82-4b47-867d-a8fd38929b51/image.png" alt=""></li>
</ul>
<h3 id="📙-활동-기록">📙 활동 기록</h3>
<ul>
<li>문장 정리
시퀀스 다이어그램 작성을 위해 먼저 카카오톡의 버전관리를 레퍼런스로 정하고 버전 관리 시스템의 기능들에 관해 팀원들과 어떤 기능이 필요한지 토론을 진행하였습니다.
토론 과정에서 버전관리에 관한 다양한 기능들을 딱 한 문장으로 정의하는 것에 어려움을 느꼈습니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/1879347e-e5f9-4868-b2a6-16011347f7dc/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/2edff5d4-75a2-48ad-8369-bbda644306bf/image.jpg" alt="">
떠오른 문장들을 다음과 같이 아젠다 -&gt; 목적 -&gt; 액션 -&gt; 데이터 형태로 아래 사진과 같이 명세하였습니다. 명세하는 과정에서도 다른 아젠다와 목적이 같거나 혹은 같은 액션을 지닌 아젠다가 많아, 명세에 어려움을 겪었습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/7e8d5fb8-d9ca-4af0-bac8-16f10ec620d2/image.png" alt="">
정리한 아젠다중 하나인 버전 관리를 한다 라는 아젠다를 도식화 하기로 정한 후, 시퀀스 다이어그램 도식화 전 재 정리를 진행 하였습니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/ac34e494-98e1-431d-a00f-f0994fe4e0ef/image.png" alt=""></p>
<ul>
<li>시퀀스 다이어그램 도식화
정리한 명세를 기반으로 시퀀스 다이어그램을 작성하였습니다.
모든 아젠다에 대해 도식화를 진행하지 못해 아쉬웠습니다.
<img src="https://velog.velcdn.com/images/jwjin_dev/post/01d86d13-5734-4d73-ad1f-327120273323/image.png" alt=""></li>
</ul>
<h3 id="후기">후기</h3>
<p>스크럼, 시퀀스 다이어그램 도식화 등 이론으로는 접해 보았지만, 기억이 잘 나지않거나 애매모호 하여, 실제로 프로젝트에 적용하는 부분에서 많은 시행착오와 어려움을 겪었습니다.
지금까지 팀 프로젝트를 진행하며 명세는 대부분 기능에 대한 로직 및 처리를 위한 단순 명세, ERD를 통한 스키마 설계에서 그쳤고, 대부분의 기능은 레퍼런스 혹은 구글링을 통해 단순 코딩으로 추가하였습니다. 이렇게 문서를 작성해보며 느낀 점은, 문서화의 중요성에 대해서는 알고 있었지만, 이론을 몸소 경험해 보니 제가 지금까지 프로젝트를 진행해 왔던 방법 보다는 실제 프로젝트 진행 시 문서를 작성하며 프로젝트를 진행하는 것이 유지보수면, 효율성 등 모든 면에서 뛰어남을 다시 한번 체감할 수 있었습니다.  </p>
<h4 id="참고">참고</h4>
<p>스크럼
<a href="https://medium.com/dtevangelist/scrum-dfc6523a3604">https://medium.com/dtevangelist/scrum-dfc6523a3604</a>
시퀀스 다이어그램 작성법
<a href="https://coding-factory.tistory.com/806">https://coding-factory.tistory.com/806</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 10일차]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-10%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-10%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 07 Jul 2023 15:59:44 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-07-sw-직무역량-부트캠프-10일차-til">2023-07-07 SW 직무역량 부트캠프 10일차 TIL</h4>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/a3feb44e-c704-403e-9268-681fd69d5788/image.png" alt=""></p>
<h3 id="📙-프레젠테이션">📙 프레젠테이션</h3>
<p>Spring Security의 Authorization에 대해 프레젠테이션을 진행
팀원분과 이론적인 부분과 실제 사용 예시로 파트를 나누었고, 
아래와 같이 antMatchers 예제, 실제 사용 예시, 그리고 OAuth에 대해 발표를 진행하였다.</p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/cfc7647e-3701-456c-8458-59fda8d48758/image.png" alt=""></p>
<h3 id="📙-특강">📙 특강</h3>
<p>현재 수준에서의 백엔드 주니어 개발자가 어떤 식으로 준비해 나가야 할지 방향성에 대한 조언과
기술 면접에서의 경험의 중요성 등에 대해 말씀해주셨다.
앞으로 취업을 위해 부족하다고 느꼈던 부분들(기술면접, 코테, 코딩 능력 등)을 채우기 위해 어떤식으로 시간을 투자할지 다시금 정리해야겠다는 생각을 가지게 해준 시간.</p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/14c3c3c6-18aa-49cd-ab18-3e3b32b40876/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/ff424cee-6d48-46f2-b921-cad8502f384d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/cbfcabdd-7e17-4357-99d9-4bd3c8b7fae7/image.png" alt=""></p>
<h3 id="이론-교육-끝">이론 교육 끝!</h3>
<p>다음주 진행될 팀 프로젝트에 대비해 배웠던 내용들 정리 및 복습!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 9일차]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-9%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-9%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 06 Jul 2023 16:21:58 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-06-sw-직무역량-부트캠프-9일차-til">2023-07-06 SW 직무역량 부트캠프 9일차 TIL</h4>
<h3 id="📙-실습">📙 실습</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/b6c3dbf2-8797-492e-b401-f0cf3886a854/image.png" alt=""></p>
<p>원래 JWT 발급시 TokenProvider에 refreshToken과 accessToken의 발급과 재발급 코드를 모두 때려박아서 사용했는데, 아래처럼 코드를 분리하니 책임이 분리되고 유지보수의 효율이 높아져 더욱 객체지향적인 코드가 되는것 같다.. 생각도 못한 코드</p>
<h4 id="tokenservicejava">TokenService.java</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@Service
public class TokenService {

    private final TokenProvider tokenProvider;
    private final RefreshTokenService refreshTokenService;
    private final UserService userService;

    public String createNewAccessToken(String refreshToken) {
        // 토큰 유효성 검증
        if(tokenProvider.validToken(refreshToken)) {
            throw new IllegalArgumentException(&quot;unexpected token&quot;);
        }

        Long userId = refreshTokenService.findByRefreshToken(refreshToken).getUserId();
        User user = userService.findById(userId);

        return tokenProvider.generateToken(user, Duration.ofHours(2));
    }

}</code></pre>
<h4 id="refreshtokenservicejava">RefreshTokenService.java</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@Service
public class RefreshTokenService {
    private final RefreshTokenRepository refreshTokenRepository;

    public RefreshToken findByRefreshToken(String refreshToken) {
        return refreshTokenRepository.findByRefreshToken(refreshToken)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;unexpected refreshtoken&quot;));
    }

}</code></pre>
<h4 id="tokenapicontrollerjava">TokenApiController.java</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@RestController
public class TokenApiController {

    private final TokenService tokenService;

    @PostMapping(&quot;/api/latest/token&quot;)
    public ResponseEntity&lt;CreateAccessTokenResponseDto&gt; createNewAccessToken(@RequestBody CreateAccessTokenRequestDto createAccessTokenRequestDto) {
        String newAccessToken = tokenService.createNewAccessToken(createAccessTokenRequestDto.getRefreshToken());
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new CreateAccessTokenResponseDto(newAccessToken));
    }


}</code></pre>
<h3 id="📙-cicd">📙 CI/CD</h3>
<ul>
<li><p>AWS EC2 인스턴스 우분투 서버 생성 및 SSH를 통한 접속을 위한 세팅</p>
<ul>
<li>인스턴스가 껐다 켜졌을때를 위해 탄력적 IP주소도 세팅</li>
</ul>
</li>
<li><p>TCP 8080 포트 보안규칙 추가 (실제 진행시 편의를 위해 모든 범위 추가함)
<img src="https://velog.velcdn.com/images/jwjin_dev/post/41149082-a1c4-420a-8854-295be5e65cd4/image.png" alt=""></p>
</li>
<li><p>EC2 도커 환경 설정</p>
</li>
<li><p>우분투 내 환경 설정</p>
<pre><code>sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add
sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo chmod 666 /var/run/docker.sock</code></pre></li>
<li><p>docker 이미지 세팅 후 키 생성
ssh-keygen -t rsa -b 4096 -C &quot;[내 메일주소]&quot; -f spring-cicd</p>
</li>
<li><p>CI/CD를 위해 나온.pub 키 내용으로 ~/.ssh/authorized_keys2 생성 </p>
</li>
<li><p>Github Action 명세서 main.yml을 디렉토리 최상단/.github/workflows 디렉토리에 작성</p>
<pre><code>name: Java CI with Gradle
</code></pre></li>
</ul>
<p>on:
  push:
    branches: [ main ]</p>
<p>jobs:
  build:</p>
<pre><code>runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v2
  - name: Set up JDK 11
    uses: actions/setup-java@v2
    with:
      java-version: &#39;11&#39;
      distribution: &#39;&lt;&lt;&lt;OPEN JDK 이름&gt;&gt;&gt;&#39;
  - name: Grant execute permission for gradlew
    run: chmod +x gradlew
  - name: Build with Gradle
    run: ./gradlew build -x test
  - name: Docker build
    run: |
      docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
      docker build -t spring-cicd . 
      docker tag spring-cicd &lt;&lt;&lt;도커허브아이디&gt;&gt;&gt;/spring-cicd:${GITHUB_SHA::7}
      docker push &lt;&lt;&lt;도커허브아이디&gt;&gt;&gt;/spring-cicd:${GITHUB_SHA::7}
  - name: Deploy
    uses: appleboy/ssh-action@master
    with:
      host: &lt;&lt;&lt;AWS EC2 호스트&gt;&gt;&gt;
      username: ubuntu
      key: ${{ secrets.KEY }}
      envs: GITHUB_SHA
      script: |
        docker pull &lt;&lt;&lt;도커허브아이디&gt;&gt;&gt;/spring-cicd:${GITHUB_SHA::7}
        docker tag &lt;&lt;&lt;도커허브아이디&gt;&gt;&gt;/spring-cicd:${GITHUB_SHA::7} spring-cicd
        docker stop server
        docker rm -f server
        docker run -d --name server -p 8080:8080 spring-cicd</code></pre><pre><code>- 깃허브에 푸시후 Actions 탭 확인
- done !
![](https://velog.velcdn.com/images/jwjin_dev/post/fddabc3f-a907-4d62-a40a-dff41838f105/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 8일차 ]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-8%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-8%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 05 Jul 2023 14:13:50 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-05-sw-직무역량-부트캠프-8일차-til">2023-07-05 SW 직무역량 부트캠프 8일차 TIL</h4>
<h4 id="jwt-json-web-token">JWT (JSON Web Token)</h4>
<ul>
<li><p>유저를 인증하고 식별하기 위한 토큰 기반 인증</p>
</li>
<li><p>세션과 달리 서버가 아닌 클라이언트에 저장됨</p>
</li>
<li><p>인증, 인가에 필요한 모든 정보를 자체적으로 지님</p>
</li>
</ul>
<h4 id="jwt의-구조">JWT의 구조</h4>
<ul>
<li><p>.을 구분자로 헤더, 내용, 서명 3가지의 암호화된 문자열로 이루어짐
<img src="https://velog.velcdn.com/images/jwjin_dev/post/656ede7e-b3aa-414a-9bb5-b6c1b4475986/image.png" alt=""></p>
</li>
<li><p>헤더(Header)</p>
<ul>
<li>토큰의 타입과 해시 암호화 알고리즘에 대한 정보를 가짐</li>
</ul>
</li>
<li><p>내용(Payload)</p>
<ul>
<li>토큰에 담을 정보가 들어있음</li>
<li>클레임(Claims) 단위로 저장, 클레임은 키/값 쌍을 뜻함</li>
</ul>
</li>
<li><p>서명(Signature)</p>
<ul>
<li>토큰의 유효성 검증 시 사용</li>
<li>헤더와 정보의 암호화 값, 비밀 키 값을 지정한 암호화 알고리즘으로 서명하여 생성</li>
</ul>
</li>
</ul>
<h4 id="access-token-refresh-token">Access Token, Refresh Token</h4>
<ul>
<li><p>단순 액세스 토큰만으로는 토큰 탈취 시 보안이 취약해지기 때문에, 이를 보완하기 위해 두개의 토큰으로 나눠서 사용</p>
</li>
<li><p>Access Token</p>
<ul>
<li>사용자 권한 인증 및 권한 접근에 사용하는 토큰</li>
<li>2~3시간 정도의 짧은 유효시간</li>
<li>DB에 저장되지 않음</li>
</ul>
</li>
</ul>
<ul>
<li><p>Refresh Token</p>
<ul>
<li><p>액세스 토큰 재발급을 위해 사용하는 토큰</p>
</li>
<li><p>15일 정도의 긴 유효시간을 가짐</p>
</li>
<li><p>DB에 저장됨</p>
<h4 id="jwtproperties">JwtProperties</h4>
<pre><code class="language-java">@Getter
@Setter
@Component
@ConfigurationProperties(&quot;jwt&quot;) // 프로퍼티에 경로를 바인딩하여 해당 경로의 값 참조
public class JwtProperties {
private String issuer;
private String secret;
}</code></pre>
<h4 id="tokenauthenticationfilter">TokenAuthenticationFilter</h4>
<pre><code class="language-java">@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
private final static String HEADER_AUTHORIZATION = &quot;Authorization&quot;;
private final static String TOKEN_PREFIX = &quot;Bearer &quot;;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
   // 요청 헤더의 authorization 키 값 조회
   String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);

   // 가져온 값에서 접두사 제거
   String token = getAccessToken(authorizationHeader);

   // 가져온 토큰값이 유효한지 확인, 유효하다면 인증 정보를 만든다
   if(tokenProvider.validToken(token))
   {
       Authentication authentication = tokenProvider.getAuthentication(token);
       SecurityContextHolder.getContext().setAuthentication(authentication);
   }

   filterChain.doFilter(request, response);
}

private String getAccessToken(String authorizationHeader) {
   if(authorizationHeader != null &amp;&amp; authorizationHeader.startsWith(TOKEN_PREFIX))
   {
       return authorizationHeader.substring(TOKEN_PREFIX.length());
   }

   return null;
}
}</code></pre>
</li>
</ul>
</li>
</ul>
<h4 id="tokenprovider--refreshtoken-관련-리포지토리-및-서비스-생성하여-사용">TokenProvider // RefreshToken 관련 리포지토리 및 서비스 생성하여 사용</h4>
<pre><code class="language-java">@RequiredArgsConstructor
@Service // 토큰 생성 및 검증 서비스
public class TokenProvider {

    private final JwtProperties jwtProperties;
    private final RefreshTokenService refreshTokenService;

    public Tokeninfo generateToken(User user, Duration accessTokenExpiredAt, Duration refreshTokenExpiredAt) {
        Date now = new Date();
        String accessToken = makeAccessToken(new Date(now.getTime()+accessTokenExpiredAt.toMillis()), user);
        String refreshToken = makeRefreshToken(new Date(now.getTime()+refreshTokenExpiredAt.toMillis()), user);
        refreshTokenService.saveToken(user.getEmail(), refreshToken);

        return Tokeninfo.builder()
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    // 다형성 ?
    public String makeAccessToken(Date expiry, User user){
        Date now = new Date();
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE) // 헤더 타입 : JWT
                .setIssuer(jwtProperties.getIssuer())
                .setIssuedAt(now)
                .setExpiration(expiry)
                .setSubject(user.getEmail())
                .claim(&quot;id&quot;, user.getId())
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
                .compact();
    }

    public String reissueAccessToken(User user, String refreshToken, Duration accessTokenExpiredAt){
        if(validRefreshToken(user, refreshToken))
        {
            Date now = new Date();;
            return makeAccessToken(new Date(now.getTime()+accessTokenExpiredAt.toMillis()), user);
        }
        return null;
    }

    public String makeRefreshToken(Date expiry, User user){
        Date now = new Date();
        String refreshToken = Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE) // 헤더 타입 : JWT
                .setIssuer(jwtProperties.getIssuer())
                .setIssuedAt(now)
                .setExpiration(expiry)
                .setSubject(user.getEmail())
                .claim(&quot;id&quot;, user.getId())
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
                .compact();

        // 저장소에 저장

        return refreshToken;
    }

    public boolean validToken(String token) {
        try{
            Jwts.parser()
                    .setSigningKey(jwtProperties.getSecret())
                    .parseClaimsJws(token);

            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean validRefreshToken(User user, String refreshToken) {
        if(validToken(refreshToken)) {   
            String savedToken = refreshTokenService.findByEmail(user.getEmail()));
            if(savedToken == refreshToken) {
                return true;
            }
        }
        return false;
    }

    // 토큰 기반으로 인증 정보를 가져오는 작업
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);
        // Authentication -&gt; principal, credentials, authorities
        // collections 보기
        Set&lt;SimpleGrantedAuthority&gt; authorities = Collections.singleton(new SimpleGrantedAuthority(&quot;ROLE_USER&quot;))
        return new UsernamePasswordAuthenticationToken(
                new org.springframework.security.core.userdetails.User(
                        claims.getSubject(), &quot;&quot;,authorities
                ), // principal
                token, // credentials
                authorities
        );
    }

    public Long getUserId(String token) {
        Claims claims = getClaims(token);

        return claims.get(&quot;id&quot;, Long.class);
    }
    private Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecret())
                .parseClaimsJws(token)
                .getBody();
    }

}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 7일차 ]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-7%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-7%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 04 Jul 2023 19:13:39 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-04-sw-직무역량-부트캠프-7일차-til">2023-07-04 SW 직무역량 부트캠프 7일차 TIL</h4>
<h3 id="📙-이론">📙 이론</h3>
<h4 id="thymeleaf">Thymeleaf</h4>
<ul>
<li><p>템플릿 엔진의 일종</p>
</li>
<li><p>순수 HTML을 최대한 유지 (네츄럴 템플릿)</p>
</li>
<li><p>SSR 방식</p>
<ul>
<li>서버 사이드 렌더링(SSR) : 서버에서 사용자에게 보여줄 페이지를 모두 미리 구성한 뒤 페이지를 렌더링을 하는 방식</li>
<li>클라이언트 사이드 렌더링(CSR) : 서버에서 전체 페이지(빈 페이지)를 최초 렌더링하고 사용자가 요청할 때마다 클라이언트 내(브라우저)에서 렌더링 하는 것을 의미</li>
</ul>
</li>
</ul>
<h4 id="spring-security">Spring Security</h4>
<ul>
<li><p>보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크</p>
<ul>
<li><p>인증(Authentication) : 식별 가능한 정보를 통해 사용자의 신원을 확인하는 절차</p>
</li>
<li><p>인가(Authorization) : 인증된 사용자가 리소스에 접근 시 권한 부여  </p>
</li>
</ul>
</li>
<li><p>인증과 권한 처리를 필터들의 집합인 FilterChain 형태로 처리</p>
</li>
<li><p>다음과 같은 Flow로 동작
<img src="https://velog.velcdn.com/images/jwjin_dev/post/ea3d63ff-8277-4751-bcbf-18a632518f32/image.png" alt=""></p>
</li>
<li><p>UsernamePasswordAuthenticationFilter을 이용한 인증 과정</p>
<ul>
<li><p>인증 요청시 UsernamePasswordAuthenticationFilter 에서 Request의 username, password를 통해 UsernamePasswordAuthenticationToken 생성</p>
</li>
<li><p>AuthenticationManager의 구현체인 ProviderManager에게 인증 책임을 위임</p>
</li>
<li><p>ProviderManager는 해당 인증을 위한 Provider에게 인증 책임을 위임</p>
</li>
<li><p>해당 Provider는 인증 절차를 수행하며, 빈으로 등록한 UserDetailsService로 부터 해당 정보를 가진 사용자의 정보를 받아와 인증 절차 수행.</p>
</li>
<li><p>인증 성공 후 Principal(인증된 사용자 객체), Credentials(자격 증명), Authorities(접근 권한)등 을 담은 Authentication 객체를 생성</p>
</li>
<li><p>SecurityContextHolder 객체 안의 SecurityContext에 저장하여 전역적으로 사용
<img src="https://velog.velcdn.com/images/jwjin_dev/post/108b5e15-26ac-43ff-948d-e5c159bc4c12/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h3 id="📙-실습">📙 실습</h3>
<h4 id="spring-security-실습-1">Spring Security 실습 (1)</h4>
<h5 id="websecurityconfigjava">WebSecurityConfig.java</h5>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final UserDetailsServiceImpl userDetailsServiceImpl;

    // 스프링 시큐리티 기능 비활성화 부분 설정
    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -&gt; web.ignoring()
                //.requestMatchers(toH2Console())
                .antMatchers(&quot;/static/**&quot;);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeRequests()
                .antMatchers(&quot;/login&quot;, &quot;/signup&quot;, &quot;/user&quot;).permitAll()
                .anyRequest().authenticated()

                .and()
                .formLogin() // 폼 로그인 방식 사용
                .loginPage(&quot;/login&quot;)
                .defaultSuccessUrl(&quot;/articles&quot;)

                .and()
                .logout() // 로그아웃 설정
                .logoutSuccessUrl(&quot;/login&quot;)
                .invalidateHttpSession(true) // 찾아보기

                .and()
                .csrf().disable()
                .build();
    }

    // 인증 관리자 관련 설정
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
        return http
                .getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailService)
                .passwordEncoder(bCryptPasswordEncoder)
                .and()
                .build();

    }

    // 패스워드 인코더로 사용할 빈 등록
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}</code></pre>
<h5 id="userdetailsserviceimpljava">UserDetailsServiceImpl.java</h5>
<ul>
<li>UserDetailsService의 구현체</li>
</ul>
<pre><code class="language-java">@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        System.out.println(&quot;email = &quot; + email);
        System.out.println(userRepository.findByEmail(email).get().getPassword());

        return userRepository.findByEmail(email)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;not found email&quot;));
    }
}</code></pre>
<p>-- 진행 중</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 6일차 ]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-6%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-6%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 03 Jul 2023 14:41:06 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-07-03-sw-직무역량-부트캠프-6일차-til">2023-07-03 SW 직무역량 부트캠프 6일차 TIL</h4>
<h3 id="📙-이론">📙 이론</h3>
<h4 id="jpa-java-persistence-api">JPA (Java Persistence API)</h4>
<ul>
<li><p>Java에서의 ORM 기술 표준으로 사용하는 인터페이스 모음</p>
<ul>
<li>ORM (Obeject-Realational Mapping) 이란?<ul>
<li>객체와 RDB(Realational DataBase)의 데이터를 자동으로 매핑해주는 도구</li>
<li>즉, 객체를 RDB의 테이블에 자동으로 영속화 해주는 것</li>
</ul>
</li>
</ul>
</li>
<li><p>인터페이스이기 때문에 구현체가 필요, 대표적으로 Hibernate가 있음</p>
</li>
<li><h4 id="영속성-컨텍스트-persistence-context">영속성 컨텍스트 (Persistence Context)</h4>
<ul>
<li>엔티티를 영구 저장하는 환경 </li>
<li>entitiyManagerFactory에서 생성된 EntityManager를 통해 엔티티를 조회하거나 엔티티를 보관하고 관리</li>
<li>EntityManager 생성시 논리적인 개념의 영속성 컨텍스트가 하나 만들어짐</li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="영속성-컨텍스트-특징">영속성 컨텍스트 특징</h4>
<ol>
<li><p>1차 캐시</p>
<ul>
<li>persist로 엔티티 영속시에 영속성 컨텍스트의 1차 캐시에 저장됨. 엔티티 접근 시 우선 1차 캐시에서 조회 후 없을 시 DB 조회를 진행함. 
<img src="https://velog.velcdn.com/images/jwjin_dev/post/8ca94b3e-6da6-45bc-9dab-a9449e01920c/image.png" alt=""></li>
<li>특징<ul>
<li>같은 엔티티의 동일성 보장</li>
</ul>
</li>
</ul>
</li>
<li><p>쓰기 지연</p>
<ul>
<li>persist로 엔티티를 영속시킬 시 바로 DB에 반영하지 않고 내부 저장소에 SQL를 저장해 두었다가 트랜잭션 커밋 시점에 한번에 처리 
<img src="https://velog.velcdn.com/images/jwjin_dev/post/b41344db-36a2-4028-bbca-8bf234a84a5c/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/ba9fa9ba-a5e0-47a2-b377-02fa11060e5b/image.png" alt=""></li>
</ul>
</li>
<li><p>변경 감지(Dirty checking)</p>
<ul>
<li>flush() 함수 호출시 저장된 엔티티의 스냅샷과 현재 엔티티를 비교하여 변경 사항이 있을 시 내부 저장소에 SQL을 저장해 두었다가 DB에 반영 후 커밋
<img src="https://velog.velcdn.com/images/jwjin_dev/post/55e89319-abfb-49ea-9aa1-9239a2f233ef/image.png" alt=""></li>
</ul>
</li>
<li><p>지연 로딩, 즉시 로딩</p>
<ul>
<li><p>지연 로딩 : 연관관계가 매핑된 엔티티 조회시 해당 엔티티의 참조 객체를 사용하는 시점에 조회하는 방법</p>
</li>
<li><p>즉시 로딩 : 연관관계가 매핑된 엔티티 조회시 해당 엔티티의 모든 객체를 조회하는 방법</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<p>JDBC (Java DataBase Connectivity)</p>
<ul>
<li>DB 접근을 위해 Java에서 제공하는 API</li>
</ul>
<h4 id="spring-data-jpa">Spring Data JPA</h4>
<ul>
<li><p>스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트</p>
</li>
<li><p>SQL이 아닌 객체 중심으로 개발 -&gt; 생산성 증대, 유지보수 난이도 낮음</p>
</li>
<li><p>기본적인 CRUD SQL를 작성하지 않아도 된다. </p>
</li>
<li><p>JpaRepository&lt;Entity, ID 자료형&gt; 와 같은 형태로 레포지토리에 상속받아 사용</p>
</li>
<li><p>Spring Data JPA의 작동구조는 다음과 같다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/4e13651a-ef60-4af7-b7c4-d39926538805/image.png" alt=""></p>
<h3 id="📙-실습">📙 실습</h3>
<p>Blog API 구축
<img src="https://velog.velcdn.com/images/jwjin_dev/post/d2a80a88-5b4c-4408-87ae-825b735ac4fd/image.png" alt=""></p>
<p>Lombok 어노테이션 사용 및 JPA Auditing을 통한 자동 시간 매핑</p>
<pre><code class="language-java"># Article.java

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 추가
@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, updatable = false)
    private Long id;

    @Column(name = &quot;title&quot;, nullable = false)
    private String title;

    @Column(name = &quot;content&quot;, nullable = false)
    private String content;

    @CreatedDate
    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createAt;

    @LastModifiedDate
    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;

    @Builder
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public void update(String title, String content){
        this.title = title;
        this.content = content;
    }

}</code></pre>
<p>main 함수에서 @EnableJpaAuditing 어노테이션을 붙여서 활성화를 시켜 주어야함.</p>
<pre><code class="language-java"># BeDay6Application.java

@EnableJpaAuditing
@SpringBootApplication
public class BeDay6Application {

    public static void main(String[] args) {
        SpringApplication.run(BeDay6Application.class, args);
    }

}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 5일차 06-30]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-5%EC%9D%BC%EC%B0%A8-06-30</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-5%EC%9D%BC%EC%B0%A8-06-30</guid>
            <pubDate>Fri, 30 Jun 2023 17:27:20 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-06-29-sw-직무역량-부트캠프-5일차-til">2023-06-29 SW 직무역량 부트캠프 5일차 TIL</h4>
<h3 id="📙-이론">📙 이론</h3>
<ul>
<li><p>AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)</p>
<ul>
<li><p>코드에서 핵심적인 로직과 부가 기능을 분리하여 모듈화 하는 것</p>
</li>
<li><p>이렇게 공통적으로 필요한 부가기능들을 사용 ex) 에러 핸들링, 로깅, 성능 측정 등</p>
</li>
<li><p>OOP(Object Oriented Programming, 객체 지향 프로그래밍) 을 더욱 발전시키기 위한 개념</p>
</li>
</ul>
</li>
</ul>
<h3 id="📙-실습">📙 실습</h3>
<ul>
<li><p>a. Member 도메인에 대한 CRUD 개발</p>
<ul>
<li><p>조건1. DB가 아닌 List 사용</p>
</li>
<li><p>조건2. Grade enum 대신 String을 사용</p>
</li>
<li><p>Member -&gt; Long id, String name, String grade</p>
<p>  <img src="https://velog.velcdn.com/images/jwjin_dev/post/09f8c92e-8818-4e0f-8e97-83f038645a60/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<p>MemberController 코드는 다음과 같다</p>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/api&quot;)
public class MemberController {

    private final MemberService memberService;

    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @PostMapping(&quot;/join&quot;)
    public ResponseEntity&lt;String&gt; join(@RequestBody Member member) {
        memberService.join(member);
        System.out.println(&quot;member.getId() = &quot; + member.getId());
        return ResponseEntity.ok(&quot;가입 완료&quot;);
    }

    @GetMapping(&quot;/info/{id}&quot;)
    public ResponseEntity&lt;Member&gt; memberInfo(@PathVariable Long id) {
        Member member1 = memberService.findMember(id);
        System.out.println(&quot;member1 = &quot; + member1);
        return ResponseEntity.ok(member1);
    }

    @PutMapping(&quot;/update/{id}&quot;)
    public ResponseEntity&lt;String&gt; update(@PathVariable Long id, @RequestBody Member member) {
        memberService.updateMember(id, member);
        return ResponseEntity.ok(&quot;수정 완료&quot;);
    }

    @DeleteMapping(&quot;/withdraw/{id}&quot;)
    public ResponseEntity&lt;String&gt; withdraw(@PathVariable Long id) {
        memberService.deleteMember(id);
        return ResponseEntity.ok(&quot;탈퇴 완료&quot;);
    }
}</code></pre>
<ul>
<li><p>b. Postman으로 동작 확인</p>
<p>위 컨트롤러에 매핑된 주소로 api 테스트</p>
<p>회원 가입 @PostMapping(&quot;/join&quot;)</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/e0c76300-c946-4ead-aff8-332f65cb6c94/image.png" alt=""></p>
<p> 회원 정보 조회 @GetMapping(&quot;/info/{id}&quot;)
 <img src="https://velog.velcdn.com/images/jwjin_dev/post/b9946152-eb35-4ec1-811c-e371ce808029/image.png" alt=""></p>
<p> 회원 정보 수정 @PutMapping(&quot;/update/{id}&quot;)
 <img src="https://velog.velcdn.com/images/jwjin_dev/post/314e8fbb-ec7c-4033-afb3-b9ead19ba76c/image.png" alt="">
 수정 후 조회
<img src="https://velog.velcdn.com/images/jwjin_dev/post/3eae70cc-73dc-4ab0-b101-82a3d76213b4/image.png" alt=""></p>
<p> 회원 정보 삭제 @DeleteMapping(&quot;/withdraw/{id}&quot;)
 <img src="https://velog.velcdn.com/images/jwjin_dev/post/7e28fafe-2f8f-4c38-bf73-730a83536a04/image.png" alt=""></p>
<ul>
<li><p>c. bootJar로 빌드하고 수동 실행하여 정상적인 서비스 동작 확인</p>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/0e8f5a5d-2f90-4986-84f5-1126b6d44780/image.png" alt=""></p>
</li>
</ul>
<p>이하 위와 동일하게 Postman으로 테스트 진행 후 정상 동작 확인</p>
<ul>
<li>d. Docker 이미지로 만들고 dockerhub 저장소에 0.0.2 버전으로 push
<img src="https://velog.velcdn.com/images/jwjin_dev/post/33993b84-dca5-4605-98b8-0c6aa7988fa2/image.png" alt=""></li>
</ul>
<ul>
<li>e. push한 0.0.2 버전을 pull하여 도커에 내려받고 컨테이너 방식으로 동작시킨 후 정상 서비스 동작 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/0eb0648f-1a10-42eb-a1ef-4bf0039aa385/image.png" alt=""><img src="https://velog.velcdn.com/images/jwjin_dev/post/b54660f5-a766-4d21-a547-79552bddcfd2/image.png" alt=""></p>
<p>이하 위와 동일하게 Postman으로 테스트 진행 후 정상 동작 확인</p>
<ul>
<li>f. 개인 github에 커밋, 푸시</li>
</ul>
<p><a href="https://github.com/jw-jin/codestates_BE">https://github.com/jw-jin/codestates_BE</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 4일차 06-29]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-4%EC%9D%BC%EC%B0%A8-06-29</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-4%EC%9D%BC%EC%B0%A8-06-29</guid>
            <pubDate>Thu, 29 Jun 2023 13:12:48 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-06-29-sw-직무역량-부트캠프-4일차-til">2023-06-29 SW 직무역량 부트캠프 4일차 TIL</h4>
<h3 id="📙-이론">📙 이론</h3>
<ul>
<li>싱글톤 <ul>
<li>객체의 인스턴스를 하나만 생성하여 사용하는 패턴</li>
<li>메모리 낭비 방지 및 데이터 공유의 용이성</li>
<li>@Configuration, 스프링 빈은 싱글톤 스코프로 생성 (빈은 스코프 변경 가능)</li>
</ul>
</li>
<li>HTTP의 4가지 메소드<ul>
<li>DispatcherServlet을 통해 컨트롤러에 매핑</li>
<li>GET -&gt; Create</li>
<li>POST -&gt; Read</li>
<li>PUT -&gt; Update</li>
<li>DELETE -&gt; Delete</li>
</ul>
</li>
</ul>
<ul>
<li>Docker</li>
</ul>
<h3 id="📙-실습">📙 실습</h3>
<ul>
<li>CRUD 매핑만 진행한 RestController 구현</li>
</ul>
<pre><code class="language-java">@RestController
@RequestMapping(&quot;/api/latest&quot;) // api 주소 매핑
public class TestController {
      // Create
      @PostMapping(&quot;/test&quot;)
      public ResponseEntity&lt;String&gt; create(@RequestBody Map&lt;String, String&gt; map) {
          // val1
          // val2
          System.out.println(&quot;map.val1 = &quot; + map.get(&quot;val1&quot;));
          System.out.println(&quot;map.val2 = &quot; + map.get(&quot;val2&quot;));
          return ResponseEntity.ok(&quot;CREATED&quot;);
      }

      // Read 
      @GetMapping(&quot;/test/{id}&quot;)
      public ResponseEntity&lt;String&gt; read(@PathVariable Long id) {
          System.out.println(&quot;id = &quot; + id);

          return ResponseEntity.ok(&quot;Hello World KNU&quot;);
      }

      // Update
      @PutMapping(&quot;/test/{id}&quot;)
      public ResponseEntity&lt;String&gt; update(@PathVariable Long id, @RequestBody Map&lt;String, String&gt; map) {
          System.out.println(&quot;id = &quot; + id);
          System.out.println(&quot;map.val1 = &quot; + map.get(&quot;val1&quot;));
          System.out.println(&quot;map.val2 = &quot; + map.get(&quot;val2&quot;));

          return ResponseEntity.ok(&quot;UPDATED&quot;);
      }

      // Delete
      @DeleteMapping(&quot;/test/{id}&quot;)
      public ResponseEntity&lt;String&gt; delete(@PathVariable Long id) {
          System.out.println(&quot;id = &quot; + id);
          return ResponseEntity.ok(&quot;DELETED&quot;);
      }</code></pre>
<ul>
<li>Docker를 이용한 스프링 프로젝트 배포</li>
</ul>
<ol>
<li>스프링 프로젝트 빌드 - Gradle bootJar 를 통해 jar 파일 생성 
<img src="https://velog.velcdn.com/images/jwjin_dev/post/f755d5bc-fdca-4690-9ce5-cf62c0274f1d/image.png" alt=""></li>
</ol>
<ol start="2">
<li><p>최상단 디렉토리에 아래와 같은 내용으로 Dockerfile 생성</p>
<pre><code># base-image
FROM openjdk:11
# COPY에서 사용될 경로 변수
ARG JAR_FILE=build/libs/*.jar
# JAR 빌드 파일을 도커 컨테이너로 복사
COPY ${JAR_FILE} app.jar
# JAR 파일 실행
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]</code></pre></li>
<li><p>아래 명령어로 빌드하여 도커 이미지 생성, 명령어 끝에 . 에 주의할 것</p>
<pre><code>$ docker build -t &lt;이미지 이름:태그&gt; . </code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/373b5d84-6fb1-4c70-8fbd-4addfc2f3f80/image.png" alt=""></p>
</li>
<li><p>docker images, docker run 명령어를 통해 생성된 이미지 확인 및 컨테이너 생성</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/5d56cf56-e897-4560-b01f-a3dee298193e/image.png" alt=""></p>
<pre><code>$ docker run &lt;이미지 이름:태그&gt;</code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/ac4ceff2-58d0-4590-9836-215c93cade2f/image.png" alt=""></p>
<ol start="5">
<li>dockerhub에 이미지 푸시를 위해 아래 명령어를 이용해 이미지 네이밍 변경<pre><code>$ docker tag &lt;이미지 이름&gt;:&lt;태그&gt; &lt;dockerhub 사용자 이름&gt;/&lt;새 이미지 이름&gt;:&lt;새 태그&gt;
// </code></pre></li>
<li>도커허브에 푸시</li>
</ol>
<pre><code>$ docker push &lt;dockerhub 사용자 이름&gt;/&lt;새 이미지 이름&gt;:&lt;새 태그&gt;</code></pre><p><img src="https://velog.velcdn.com/images/jwjin_dev/post/90494ecb-b9a8-4602-94cd-ceccfcf254fb/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/3f941ee1-302b-40a1-990f-b8c7a4a194b1/image.png" alt=""></p>
<p>정상적으로 푸시 되었고, pull 명령어를 사용하여 이미지를 가져오면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 3일차 06-28]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-3%EC%9D%BC%EC%B0%A8-06-28</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-3%EC%9D%BC%EC%B0%A8-06-28</guid>
            <pubDate>Wed, 28 Jun 2023 16:05:50 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-06-28-sw-직무역량-부트캠프-3일차-til">2023-06-28 SW 직무역량 부트캠프 3일차 TIL</h4>
<h3 id="📙-이론">📙 이론</h3>
<ul>
<li><p>클래스 -&gt; 클래스, 인터페이스 -&gt; 인터페이스 단일 상속 시 extends 키워드 사용</p>
</li>
<li><p>인터페이스 -&gt; 클래스, 상속 시 implements 키워드 사용 (구현 필요)</p>
</li>
<li><p>관심사의 분리</p>
<ul>
<li>인터페이스와 로직의 분리</li>
<li>역할을 명확하게 하여 낮은 결합도 달성</li>
</ul>
</li>
</ul>
<ul>
<li>관심사의 분리 예제 ) Layered Architecture <ul>
<li>프레젠테이션 레이어 : Controller, Request 및 Response 처리</li>
<li>서비스 레이어 : Service, 두 계층 사이 연결 역할, 비즈니스 로직 구현, 연결을 위한 인터페이스 제공</li>
<li>퍼시스턴스 레이어: Repository, DB 연결 역할, 데이터 처리 및 객체화
<img src="https://velog.velcdn.com/images/jwjin_dev/post/9bb0cc8f-bbb4-4f20-97e8-b8d4017358a3/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="📙-실습">📙 실습</h3>
<ul>
<li><p>서비스와 리파지토리의 연결성을 끊어낼 수 있는 방법 (관심사의 분리)</p>
<p>아래 예제는 DB등 다른 저장소로 변경 시, 즉 기능 확장 시에 코드가 변경되므로 OCP를 위반,
또한 구현체를 인터페이스가 아닌 서비스 클래스가 직접 설정하므로, DIP를 위반</p>
<pre><code class="language-java">  public class MemberServiceImpl implements MemberService {
      private final MemberRepository memberRepository = new TempMemberRepository();
  }</code></pre>
</li>
</ul>
<p> 외부에서 구현체의 의존관계 주입(DI)에 집중하는 AppConfig 클래스 </p>
<pre><code class="language-java">    @Configuration
    public class AppConfig {
      @Bean
      public MemberService memberService() {
          return new MemberServiceImpl(memberRepository());
      }

      @Bean
      public MemberRepository memberRepository() {
          return new TempMemberRepository();
      }</code></pre>
<p>구현체의 실행에 집중하는 변경된 MemberServiceImpl 클래스 </p>
<pre><code class="language-java">  public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}</code></pre>
<ul>
<li>위의 변경 사항을 통해 Repository 변경 시 MemberServiceImpl의 코드를 수정 할 필요가 없으므로 OCP를 만족</li>
<li>TempMemberRepository 와 같은 구현체를 의존하는 것이 아닌 MemberRepository 인터페이스를 의존하게 되므로 DIP를 만족</li>
</ul>
<p>스프링 컨테이너(ApplicationContext)에 등록된 Bean을 가져와 다음과 같이 사용 (IoC) </p>
<pre><code class="language-java">    //AppConfig appConfig = new AppConfig();

    ApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext(AppConfig.class);

    // DI, IoC
    MemberService memberService = 
            applicationContext.getBean(&quot;memberService&quot;, MemberService.class);

    Member member = new Member(1L,&quot;A&quot;, Grade.MEMBER);
    memberService.join(member);</code></pre>
<h3 id="📙-extra">📙 Extra</h3>
<ul>
<li><p>SOLID 원칙중 DIP, OCP </p>
</li>
<li><p>DIP(Dependency Inversion Principle) : 의존 역전 원칙</p>
<ul>
<li><p>상위 모듈은 하위 모듈의 구현에 의존해서는 안 되며, 하위의 모듈이 상위 모듈에 정의한 추상화에 의존 해야한다는 원칙 </p>
</li>
<li><p>구현과 역할의 분리, 클래스(구현)에 의존하지 않고, 인터페이스(역할)에 의존</p>
</li>
<li><p>인터페이스에 의존함으로서, 확장이 가능하고 변화에는 폐쇄적인 OCP도 만족</p>
</li>
</ul>
</li>
<li><p>OCP(Open Closed Principle) : 개방 폐쇄 원칙</p>
<ul>
<li><p>자신의 확장에는 열려있어야 하고, 주변의 변화에는 닫혀있어야 한다는 원칙</p>
</li>
<li><p>요구사항 변경시 기존 요소는 수정이 일어나면 안되며, 확장을 통한 재사용이 가능해야함</p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SW 직무역량 부트캠프] 1, 2일차 06-26, 06-27]]></title>
            <link>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-2%EC%9D%BC%EC%B0%A8-06-27</link>
            <guid>https://velog.io/@jwjin_dev/SW-%EC%A7%81%EB%AC%B4%EC%97%AD%EB%9F%89-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-2%EC%9D%BC%EC%B0%A8-06-27</guid>
            <pubDate>Tue, 27 Jun 2023 13:43:43 GMT</pubDate>
            <description><![CDATA[<h4 id="2023-06-26-sw-직무역량-부트캠프-1일차-til">2023-06-26 SW 직무역량 부트캠프 1일차 TIL</h4>
<p>지난 1일차는 OT 및 전반적인 웹 개발 및 웹 백엔드의 개념, 앞으로의 커리큘럼에 대해 말씀해주셨다.</p>
<h4 id="2023-06-27-sw-직무역량-부트캠프-2일차-til">2023-06-27 SW 직무역량 부트캠프 2일차 TIL</h4>
<h3 id="🧭-지난-과제">🧭 지난 과제</h3>
<ul>
<li>클래스, 인터페이스의 문법 구조와 쓰임새</li>
<li>다형성이란?</li>
</ul>
<h3 id="📙-이론">📙 이론</h3>
<ul>
<li><p>클래스, 인터페이스, 다형성</p>
<ul>
<li>클래스 :  객체를 추상화하여 객체를 생성할 수 있게해주는 설계</li>
<li>인터페이스 : 다형성을 가진 추상 클래스</li>
<li>다형성 : 하나의 객체가 여러 타입을 가질 수 있는 성질 
ex) 인터페이스, 오버로딩, 오버라이딩 등등</li>
</ul>
</li>
<li><p>웹 애플리케이션 동작 개념</p>
<ul>
<li>회원가입을 예로든 전반적인 HTTP 동작 과정의 flow와 CRUD</li>
<li>Java Servlet<ul>
<li>HTTP 요청과 응답시 필요한 과정을 간소화 시켜주는 기술</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📙-실습">📙 실습</h3>
<ul>
<li><p>순수 자바만으로 CRUD 구현</p>
</li>
<li><p>명세
<img src="https://velog.velcdn.com/images/jwjin_dev/post/b4a3b157-aeb6-4db3-8642-e9b597f0eb94/image.png" alt=""></p>
</li>
<li><p>제시해주신 명세에 따라 페어 프로그래밍으로 아래 구조로 구현을 진행</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/c92bdcac-8e49-491c-b89f-62a5179a19ba/image.png" alt=""></p>
<p>라이브 코딩을 진행하시면서, 인터페이스를 이용한 클래스 구현시의 예시를 드시며 다형성과 객체지향 프로그래밍, 스프링부트에서의 DIP, OCP, DI, IOC에 대해 엄청 강조하셨는데, 강의 전에 궁금해서 미리 찾아보았다.</p>
<ul>
<li><p>DIP(Dependency Inversion Principle) : 의존 역전 원칙</p>
<ul>
<li><p>상위 모듈은 하위 모듈의 구현에 의존해서는 안 된다. 하위의 모듈이 상위 모듈에 정의한 추상 타입에 의존 해야한다.</p>
<p>Spring 예제로 보는 SOLID DIP 예시 : <a href="https://cheese10yun.github.io/spring-solid-dip/">https://cheese10yun.github.io/spring-solid-dip/</a></p>
</li>
</ul>
</li>
<li><p>OCP(Open Closed Principle) : 개방 폐쇄 원칙</p>
<ul>
<li><p>자신의 확장에는 열려있어야 하고, 주변의 변화에는 닫혀있어야 한다.</p>
<p>Spring 예제로 보는 SOLID OCP : <a href="https://cheese10yun.github.io/spring-solid-ocp/">https://cheese10yun.github.io/spring-solid-ocp/</a></p>
</li>
</ul>
</li>
<li><p>DI(Dependency Injection) : 의존성 주입</p>
<ul>
<li>객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식.</li>
</ul>
</li>
<li><p>IoC((Inversion of Control) : 제어의 역전</p>
<ul>
<li>메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미.
DI, IoC 정리 : <a href="https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0">https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</a></li>
</ul>
</li>
</ul>
<h3 id="🤔-후기">🤔 후기</h3>
<p>지금까지 스프링부트에 대해 나름대로 공부하고 개발도 해보았지만, 내가 해온 코딩은 단순 코더에 가까웠구나 하는 생각이 들었고, 이론적인 부분에서 부족함을 느꼈다. 
10일간의 짧은 과정이지만, 기술 면접에서도 잘할 수 있도록 이론적인 지식을 완성시키고자 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 연속 부분 수열 합의 개수(파이썬)]]></title>
            <link>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Fri, 23 Jun 2023 04:25:23 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/131701">연속 부분 수열 합의 개수</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/43a67365-eca3-4fba-9a00-ec4195379d96/image.png" alt=""><img src="https://velog.velcdn.com/images/jwjin_dev/post/d4c5367c-6cb1-41da-83d6-8f84a34c3e3c/image.png" alt=""></p>
<h3 id="제한사항">제한사항</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/b2f84481-ebfb-40ff-bf5f-4c6fa641fff8/image.png" alt=""></p>
<h3 id="🧭-접근">🧭 접근</h3>
<p>단순 반복으로 진행시 시간초과가 발생합니다.
따라서 이전에 구했던 연속 부분 수열의 값에 다음 인덱스의 값을 더해 중복된 계산을 하지 않도록 하였습니다. </p>
<h3 id="✔-풀이">✔ 풀이</h3>
<ol>
<li>처음 길이 1의 연속 부분 수열을 반복문을 통해 초기화</li>
<li>이전에 구했던 결과 배열의 마지막 5개에 다음 인덱스의 값 더하기</li>
<li>elements의 길이만큼 반복</li>
<li>set을 통해 배열의 중복 제거후 길이 리턴 <h3 id="📙-소스-코드">📙 소스 코드</h3>
</li>
</ol>
<pre><code class="language-python">def solution(elements):
    res = []
    for e in elements: # 길이가 1인 수열으로 배열 초기화
        res.append(e)

    for i in range(1,len(elements)): # 길이
        sum_list = []
        for j in range(len(elements),0,-1): # 인덱스
            sums = res[-j] + elements[-j+i]
            sum_list.append(sums)
        for e in sum_list:
            res.append(e)

    return len(set(res))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 순위 검색 (파이썬)]]></title>
            <link>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%9C%EC%9C%84-%EA%B2%80%EC%83%89-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%9C%EC%9C%84-%EA%B2%80%EC%83%89-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Mon, 17 Apr 2023 15:33:50 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/72412">코딩테스트 연습 - 2021 KAKAO BLIND RECRUITMENT - 순위 검색</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/5f92c84d-44e1-4274-a434-496759c7d054/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/6d0a6eec-3c42-4727-92ef-5a0750f4ce26/image.png" alt=""></p>
<h3 id="제한사항">제한사항</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/978990ef-0424-4818-9f4a-befd92aefef7/image.png" alt=""></p>
<h3 id="🧭-접근">🧭 접근</h3>
<p>처음에는 단순 파싱을 통한 비교로 접근했지만,
info 배열의 크기는 최대 5만, query 배열의 크기는 최대 10만이므로
query를 반복하여 검사할경우 50억번이 넘어가므로 효율성 테스트를 통과할 수 없었습니다.
따라서 info의 지원 정보를 파싱하고, 지원자가 포함될 수 있는 쿼리에 대한 조합을 만든 후, 지원점수를 제외한 나머지를 key, 점수를 value로 하는 딕셔너리를 만든 후, 
쿼리에 해당하는 딕셔너리의 점수 리스트를 받아와 조건에 따라 카운트 하여 리턴합니다. 
이 경우 단순 카운트시에도 시간 초과가 발생하므로, 이분 탐색 라이브러리인 bisect를 이용하여 카운팅을 진행하였습니다.</p>
<h3 id="✔-풀이">✔ 풀이</h3>
<ol>
<li>info 배열 파싱후, 점수를 제외한 조건을 item, 점수를 score로하여
포함될 수 있는 쿼리의 조합을 생성.</li>
<li>생성된 쿼리를 join을 통해 문자열로 만들고, 이를 key로 하여 딕셔너리에 추가.</li>
<li>이분 탐색을 위해 점수 리스트 정렬</li>
<li>query 에서 &#39;and &#39;, &#39;-&#39; 인 부분은 제거 후 공백을 기준으로 파싱하고,
딕셔너리에 생성된 키를 이용하여 점수 리스트를 가져오고, 이분 탐색을 통해 쿼리의 점수 기준 인덱스를 구한 후, 점수 리스트의 전체 길이에서 기준 인덱스를 빼면 기준 이상의 점수들의 갯수가 나온다.  <h3 id="📙-소스-코드">📙 소스 코드</h3>
</li>
</ol>
<pre><code class="language-python">from itertools import combinations
from bisect import bisect_left

def solution(info, query):

    ans = []
    info_dict = {} 

    for info_str in info:
        arr = info_str.split() # 공백 기준 정렬
        item = arr[:-1] # 점수 제외 나머지 조건
        score = int(arr[-1]) # 점수
        for i in range(0,5):
            for cb in combinations(item,i): # item 중 i개를 뽑아 조합 생성
                key = &quot;&quot;.join(cb)
                if key in info_dict:
                    info_dict[key].append(score)
                else:
                    info_dict[key] = [score]

    for v in info_dict.values(): # 이진탐색을 위한 점수 정렬
        v.sort()

    ans = []
    for q in query:
        qarr = q.replace(&#39;and &#39;,&#39;&#39;).replace(&#39;-&#39;,&#39;&#39;).split() # &#39;-&#39; 제거
        qitem = qarr[:-1]
        qscore = int(qarr[-1])
        qkey = &#39;&#39;.join(qitem)
        cnt = 0
        if qkey in info_dict:
            qres = info_dict[qkey]
            cnt = len(qres) - bisect_left(qres,qscore) 
        ans.append(cnt)

    return ans</code></pre>
<h3 id="🤔-느낀-점">🤔 느낀 점</h3>
<p>프로그래머스 기준 레벨 2의 문제임에도 불구하고 생각보다 복잡했습니다. 접근 방법을 떠올리는게 쉽지 않았는데 만약 코테에 이런 문제 나왔으면 광탈했을거 같아요..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 주차 요금 계산]]></title>
            <link>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0</link>
            <guid>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0</guid>
            <pubDate>Sun, 16 Apr 2023 10:31:53 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/92341">코딩테스트 연습 - 2022 KAKAO BLIND RECRUITMENT - 주차요금 계산</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/5abf07a2-a334-49b7-bba6-1a2eb61d1f78/image.png" alt="">
<img src="https://velog.velcdn.com/images/jwjin_dev/post/ad2bddab-0cee-4bde-b1f9-baee3b703956/image.png" alt=""></p>
<h3 id="제한사항">제한사항</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/a5620837-0262-4d3e-b7c8-af5c0fbacef7/image.png" alt=""></p>
<h3 id="🧭-접근">🧭 접근</h3>
<p>제한사항에 따라 동일한 차량이 또 입차하거나 없는 차량이 출차되는 경우, 입력 시각이 잘못된 경우 와 같이 잘못된 입력이 없으므로, 
단순히 차량의 입차시 시간, 출차시 시간만을 계산하였다.</p>
<h3 id="✔-풀이">✔ 풀이</h3>
<ol>
<li>records 의 문자열을 시각, 차량번호, 내역 으로 파싱, 시각은 분 단위로 변환</li>
<li>내역이 IN인경우 딕셔너리에 차량번호를 key로 하여 입차시간, 누적시간, 입차여부 저장, 내역이 OUT인 경우 현재시간과 입차시간을 비교하여 누적시간 증가 시킴</li>
<li>종료시각까지 출차하지 않은 경우 종료시각과 입차시간 비교하여 누적시간 증가</li>
<li>차량번호가 작은 순서대로 요금을 배열에 담아야 하므로 차량번호 기준으로 정렬 후,
누적시간이 기본시간보다 작거나 같은경우 기본요금, 초과인 경우 요금 계산식에 따라 계산 후 배열에 추가 후에 return<h3 id="📙-소스-코드">📙 소스 코드</h3>
</li>
</ol>
<pre><code class="language-python">import math

def solution(fees, records):
    def_time = fees[0]; def_fee = fees[1]; over_time = fees[2]; over_fee = fees[3]
    table = {}

    for r in records:
        time, car_num, order = r.split() # 시간, 차량번호, 내역
        h, m = map(int,time.split(&#39;:&#39;))
        t = (h*60) + m 
        if order == &#39;IN&#39;: 
            if car_num not in table:
                table[car_num] = [t,0,True] # 입차시간,누적시간,입차여부
            else:
                table[car_num][0] = t # 입차시간
                table[car_num][2] = True # 입차
        elif order == &#39;OUT&#39;:
            prev_t = table[car_num][0];
            use_time = t - prev_t # 현재시간 - 입차시간
            table[car_num][1] += use_time 
            table[car_num][2] = False # 출차


    last_time = (23*60) + 59
    # table 입차시간,누적시간,입차여부
    for key, t in table.items():
        if t[2] == True: # 종료까지 출차 안했을때
            table[key][1] += last_time-t[0]

    s_table = dict(sorted(table.items(),key=lambda x:x[0])) # 차량번호 기준 정렬
    res = []

    for t in s_table.values():
        if t[1] &lt;= def_time:
            res.append(def_fee)
        else:
            fee = def_fee + math.ceil((t[1] - def_time) / over_time) * over_fee # 기본 요금 + 주차시간에 따른 요금 
            res.append(fee)


    return res</code></pre>
<h3 id="🤔-느낀-점">🤔 느낀 점</h3>
<p>이러한 문자열 처리 문제들이 코딩테스트 1번으로 자주 나오는데, 이런 문제의 경우 제한사항을 다른 문제보다 꼼꼼히 확인한 후 풀어야겠다 느꼈고, 코드가 가독성이 많이 떨어짐.. 해결 방법을 찾아봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 부대복귀 (파이썬)]]></title>
            <link>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%B6%80%EB%8C%80%EB%B3%B5%EA%B7%80-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%B6%80%EB%8C%80%EB%B3%B5%EA%B7%80-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Wed, 29 Mar 2023 13:00:19 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/132266">코딩테스트 연습 - 부대복귀</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>강철부대의 각 부대원이 여러 지역에 뿔뿔이 흩어져 특수 임무를 수행 중입니다. 지도에서 강철부대가 위치한 지역을 포함한 각 지역은 유일한 번호로 구분되며, 두 지역 간의 길을 통과하는 데 걸리는 시간은 모두 1로 동일합니다. 임무를 수행한 각 부대원은 지도 정보를 이용하여 최단시간에 부대로 복귀하고자 합니다. 다만 적군의 방해로 인해, 임무의 시작 때와 다르게 되돌아오는 경로가 없어져 복귀가 불가능한 부대원도 있을 수 있습니다.</p>
<p>강철부대가 위치한 지역을 포함한 총지역의 수 n, 두 지역을 왕복할 수 있는 길 정보를 담은 2차원 정수 배열 roads, 각 부대원이 위치한 서로 다른 지역들을 나타내는 정수 배열 sources, 강철부대의 지역 destination이 주어졌을 때, 주어진 sources의 원소 순서대로 강철부대로 복귀할 수 있는 최단시간을 담은 배열을 return하는 solution 함수를 완성해주세요. 복귀가 불가능한 경우 해당 부대원의 최단시간은 -1입니다.</p>
<h3 id="제한사항">제한사항</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/87bd47e4-3a6c-4f5d-bed9-5dd33ad02fca/image.png" alt=""></p>
<h3 id="🧭-접근">🧭 접근</h3>
<p>탐색류 문제이므로 BFS를 사용하면 되겠다고 생각했는데,
일부 케이스에서 시간 초과가 발생했다. 시간복잡도를 줄이기위해 다각도로 접근해 보았는데, 도착지가 모두 같으므로 도착지를 출발지로 정해 한번의 탐색을 통해 모든 경로에 대한 소요 시간을 탐색하였다. </p>
<h3 id="✔-풀이">✔ 풀이</h3>
<ol>
<li>양방향 탐색을 위한 그래프 만들기</li>
<li>BFS를 이용하여 탐색 및 방문 노드까지의 소요시간 추가</li>
<li>BFS를 이용하여 시작 -&gt; 레버, 레버 -&gt; 출구 까지의 거리 구하기</li>
</ol>
<h3 id="📙-소스-코드">📙 소스 코드</h3>
<pre><code class="language-python">from collections import deque

def bfs(graph, st):
    q = deque()
    visited = [0] * (ncnt+1)
    time = [0] * (ncnt+1)
    q.append(st)
    visited[st] = 1
    cnt = 0
    while q:
        nd = q.popleft()
        if nd in graph:
            for n in graph[nd]:
                if visited[n] == 0:
                    q.append(n)
                    visited[n] = 1
                    time[n] = time[nd] + 1

    return time

def solution(n, roads, sources, destination):

    global ncnt; ncnt = n # 전역변수 선언
    graph = {} # 양방향 그래프 추가
    for x,y in roads:
        if x in graph:
            graph[x].append(y)
        else:
            graph[x] = [y]
        if y in graph:
            graph[y].append(x)
        else:
            graph[y] = [x]
    # 도착점 부터 모든 경로에 대한 소요시간 배열
    time_arr = bfs(graph,destination) 


    res = []
    for s in sources:
        ti = time_arr[s] # 부대원 위치까지 걸린 시간
        # 출발, 도착지가 다른데 0인경우 방문 불가한곳
        if s != destination and ti == 0: 
            res.append(-1)
        else:
            res.append(ti)

    return res</code></pre>
<h3 id="🤔-느낀-점">🤔 느낀 점</h3>
<p>시간복잡도가 초과될 시, 단순 코드 연산을 줄이는 것이 아니라 알고리즘을 다시 검토하는 것이 좋을 것 같다. 
발상의 전환의 중요성 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 미로 탈출 (파이썬)]]></title>
            <link>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AF%B8%EB%A1%9C-%ED%83%88%EC%B6%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@jwjin_dev/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AF%B8%EB%A1%9C-%ED%83%88%EC%B6%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Mon, 27 Mar 2023 12:23:45 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/159993">코딩테스트 연습 - 미로탈출</a></p>
<h3 id="문제-설명">문제 설명</h3>
<p>1 x 1 크기의 칸들로 이루어진 직사각형 격자 형태의 미로에서 탈출하려고 합니다. 각 칸은 통로 또는 벽으로 구성되어 있으며, 벽으로 된 칸은 지나갈 수 없고 통로로 된 칸으로만 이동할 수 있습니다. 통로들 중 한 칸에는 미로를 빠져나가는 문이 있는데, 이 문은 레버를 당겨서만 열 수 있습니다. 레버 또한 통로들 중 한 칸에 있습니다. 따라서, 출발 지점에서 먼저 레버가 있는 칸으로 이동하여 레버를 당긴 후 미로를 빠져나가는 문이 있는 칸으로 이동하면 됩니다. 이때 아직 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있습니다. 미로에서 한 칸을 이동하는데 1초가 걸린다고 할 때, 최대한 빠르게 미로를 빠져나가는데 걸리는 시간을 구하려 합니다.</p>
<p>미로를 나타낸 문자열 배열 maps가 매개변수로 주어질 때, 미로를 탈출하는데 필요한 최소 시간을 return 하는 solution 함수를 완성해주세요. 만약, 탈출할 수 없다면 -1을 return 해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<p><img src="https://velog.velcdn.com/images/jwjin_dev/post/d387ba2d-cf8e-494d-8d1f-e832f2a86026/image.png" alt=""></p>
<h3 id="🧭-접근">🧭 접근</h3>
<p>탐색류 문제이므로, BFS를 사용하면 되겠다고 생각했다.</p>
<h3 id="✔-풀이">✔ 풀이</h3>
<ol>
<li>maps의 x,y 크기 및 필요한 인덱스 구하기</li>
<li>BFS 함수 작성</li>
<li>BFS를 이용하여 시작 -&gt; 레버, 레버 -&gt; 출구 까지의 거리 구하기</li>
</ol>
<h3 id="📙-소스-코드">📙 소스 코드</h3>
<pre><code class="language-python">from collections import deque

def bfs(maps,y,x,dy,dx):
    q = deque()
    q.append([y,x])
    cnt = 1
    time = 0
    movex = [0,0,-1,1] # 상 하 좌 우 
    movey = [-1,1,0,0]
    dis = [[0] * maxx for _ in range(maxy)] # 거리 배열
    visited = [[0] * maxx for _ in range(maxy)] 
    visited[y][x] = 1
    b_flag = 0
    while q:
        if b_flag == 1:
            break
        ny,nx = q.popleft()
        for i in range(4): # 4방향 이동
            mx = nx+movex[i]; my = ny+movey[i]
            # 인덱스 벗어날경우
            if mx &lt; 0 or my &lt; 0 or mx &gt;= maxx or my &gt;= maxy: 
                continue
            # 벽이나 이미 방문한 곳 일경우
            if maps[my][mx] == &#39;X&#39; or visited[my][mx] == 1:
                continue
            q.append([my,mx])
            visited[my][mx] = 1
            dis[my][mx] = dis[ny][nx] + 1 # 시작점부터의 거리 계산
            if my == dy and mx == dx: # 목표 도달한경우 break
                b_flag = 1
                break


    if b_flag != 1: # 목표 도달 못한경우
        return -1
    return dis[dy][dx] # 목표까지의 거리 반환

def solution(maps):
   # S : 시작 지점 E : 출구 L : 레버 O : 통로 X : 벽
    global maxx
    global maxy
    maxx = len(maps[0]) # x 최고 인덱스
    maxy = len(maps) # y 최고 인덱스

    for i in range(maxy): # 시작점, 출구, 레버 인덱스 구하기
        for j in range(maxx):
            val = maps[i][j]
            if val == &#39;S&#39;:
                start = [i,j]
            elif val == &#39;E&#39;:
                exit = [i,j]
            elif val == &#39;L&#39;:
                lever = [i,j]
    # 시작 -&gt; 레버
    res = bfs(maps,start[0],start[1],lever[0],lever[1]) 
    if res == -1:
        return -1
    # 레버 -&gt; 출구
    res2 = bfs(maps,lever[0],lever[1],exit[0],exit[1])
    if res2 == -1:
        return -1

    return res + res2 # 거리 반환</code></pre>
<h3 id="🤔-느낀-점">🤔 느낀 점</h3>
<p>탐색 문제류는 많이 풀어봐야 알고리즘을 빠르게 찾아 적용시킬 수 있고, 코드 작성 시간도 빨라지는 것 같다. 
예전보다는 많이 빨라졌음 😁 </p>
]]></description>
        </item>
    </channel>
</rss>