<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>steady_aa.log</title>
        <link>https://velog.io/</link>
        <description>열심히하자</description>
        <lastBuildDate>Fri, 12 Sep 2025 01:55:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>steady_aa.log</title>
            <url>https://velog.velcdn.com/images/steady_aa/profile/5c8d81a3-6af2-4ab5-8584-a80fcef71ae8/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. steady_aa.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/steady_aa" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[바이브코딩으로 간단하게 최신영화 리스트 웹 만들기]]></title>
            <link>https://velog.io/@steady_aa/%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9%EC%9C%BC%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%B5%9C%EC%8B%A0%EC%98%81%ED%99%94-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9B%B9-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@steady_aa/%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9%EC%9C%BC%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%B5%9C%EC%8B%A0%EC%98%81%ED%99%94-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9B%B9-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 12 Sep 2025 01:55:36 GMT</pubDate>
            <description><![CDATA[<p>작년에 회사에 입사하고 난 후 에 프로젝트를 진행하면서 다른 것들을 찾아보다가 바이브 코딩 관련해서 알게되어 한번 만들어 볼까 하면서 진행한 프로젝트</p>
<h3 id="진행-내용">진행 내용</h3>
<p>어떤걸 만들까 생각해보다가 네이버에서 영화 리스트를 보여주던게 사라진게 생각나서 한번 장르별 최신영화 Top 100을 보여주는 웹 프로젝트를 진행해보았다.</p>
<p>영화관련 데이터는 TMDB api 를 이용하여 영화 데이터를 가져오고 상단에 영화 이름 검색을 통해서 영화를 가져올 수 있게 해달라고 요청을 하였다.</p>
<p>아래는 실행 화면이다.</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/03317cbc-3920-42ab-9868-a460891df31e/image.png" alt=""></p>
<p>중간중간에 장르별로 카드 형식으로 보여주는 형식이나 웹 화면 크기에 맞춰서 변경하는 자잘자잘한 요청을 계속해서 해당 화면이 만들어졌다.</p>
<p>영화 카드를 클릭하면 
<img src="https://velog.velcdn.com/images/steady_aa/post/0833d830-a783-445a-8e79-2c49ca533cb9/image.png" alt=""></p>
<p>해당 영화의 상세 내용과 예고 유튜브 영상을 가져올 수 있게 되있다.
상세 화면의 경우에도 처음에는 이미지만 가져온다던가 유튜브영상을 가져오지 못하거나 크기가 안맞는 이슈가 많았는데 이 경우에는 처음 전달을 잘 못했는지 화면을 바꿀려고 해도 제대로 못바꿔서 직접 수정을 하는 귀찮은 점이 있었다.</p>
<h2 id="후기">후기</h2>
<hr>
<p>node.js 를 써본 적이 없었는데도 ai에 맡겨서 node.js를 통한 프로젝트트를 만들 수 있는게 신기했다.</p>
<p>아직 다 만든건 아니지만 하면서 느낀 것은 간단한 프로젝트의 경우에는 ai에 상세하게 설명해주고 단계까지 이야기 해주면 좀 더 그럴싸하게 만들어준다는 장점이 있다. 물론 ai가 코딩하다보니 내가 모르는 기술을 사용하는 경우도 있기 때문에 ai가 짠 코드를 한번씩 읽어보면서 학습도 되는 거같다.</p>
<p>현재는 최신영화 기준 Top100으로 진행되지만 나중에는 AI를 추가하여서 추천영화를 넣는 것도 한번 해보고 검색 기준을 추가로 넣어보던지 여러가지 추가해볼 예정이다.</p>
<p><a href="https://github.com/YunJiW/MovieRecommand">https://github.com/YunJiW/MovieRecommand</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PeachStore - 회원가입및 로그인]]></title>
            <link>https://velog.io/@steady_aa/PeachStore-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@steady_aa/PeachStore-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Sun, 23 Jun 2024 05:47:15 GMT</pubDate>
            <description><![CDATA[<h3 id="회원가입-api-구현하기">회원가입 api 구현하기</h3>
<p>기본 v1 버전 api의 경우</p>
<p>/v1/join</p>
<ul>
<li>post 로 받아서 해당 데이터 등록진행.</li>
</ul>
<p>/v1/join</p>
<pre><code>&quot;id&quot; : &quot;testID&quot;

&quot;password&quot; : &quot;testpw&quot;

&quot;phone&quot; : &quot;01012345678&quot;

&quot;sido&quot; : &quot;seoul&quot;

&quot;roadAddress&quot; : &quot;서울 성북구 무슨무슨가 887&quot;

&quot;zonecode&quot; : &quot;45678&quot;</code></pre><p>우편번호 서비스를 사용하기 쉽게 주소 잘 넣기.</p>
<ul>
<li>나중에 프론트엔드쪽에 도입해볼 예정</li>
</ul>
<p><a href="https://postcode.map.daum.net/guide">Daum Postcode Service User Guide</a></p>
<p>회원 객체</p>
<ul>
<li>이름</li>
<li>비밀번호</li>
<li>전화번호</li>
<li>주소</li>
</ul>
<p>주소의 경우 하나의 객체로 묶어서 저장하기</p>
<p>@Embeddable 과 @Embedded 사용</p>
<ul>
<li>하이버네이트에서 제공하는 임베디드 타입으로 된 주소 객체를 관리.</li>
</ul>
<p>postMan으로 회원가입이 되는 지확인.</p>
<ul>
<li>데이터를 받을때 requestBody에 받아서 json 으로 들어오게 진행</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/74313ccf-0080-490d-b364-d878d523370f/image.png" alt=""></p>
<p>회원가입 테스트 완료</p>
<ul>
<li>중복 회원가입 테스트 완료<ul>
<li>같은 회원가입 진행시 중복회원으로 오류 발생
<img src="https://velog.velcdn.com/images/steady_aa/post/6eeec76e-5a41-4a93-a145-3cbafcbf2f1d/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="회원가입까지-완료">회원가입까지 완료</h3>
<ul>
<li>다음 확인해야할것들</li>
</ul>
<p>로그인 쪽 만들기.</p>
<h2 id="로그인">로그인</h2>
<p>로그인까지 만들고 마이페이지 이동하면 마이페이지 데이터 보여주기.</p>
<ul>
<li>이름</li>
<li>전화번호</li>
<li>주소</li>
<li>나중에 주문 정보 보여줄 예정<ul>
<li>추후 주문 객체를 추가하면서 추가 예정</li>
</ul>
</li>
</ul>
<p>로그인의 경우 springsecurity를 사용중이기 때문에 로그인 부분은 security에서 진행.</p>
<ul>
<li>FE에서 데이터를 받아올려고 하니까 기본 springSecurity는 Form 을 기준으로 데이터를 전달 받다보니 Json 형식으로 받기 위해서는 filter 및 전체적으로 전부 바꿔줘야한다.</li>
</ul>
<ul>
<li>userDetailService를 커스터마이징해서 member에 있는 데이터를 가지고 login 구현 진행.</li>
</ul>
<p>Json을 통해 로그인을 진행할거기 때문에 fomlogin은 disabled 진행</p>
<ul>
<li>기본 방식은 formlogin은 사용하지 않겠다고 설정을 추가.</li>
</ul>
<h5 id="로그인-방식-json으로-변경하기">로그인 방식 json으로 변경하기</h5>
<ul>
<li><p>rest api 형식으로 진행하려다보니까 기본적인 form 로그인 방식이 아닌 json 형식으로 받아서 진행하려고 한다.</p>
</li>
<li><p>그렇기 때문에 json으로 받기 위해서는 커스텀 인증 필터를 통해서 http 요청의 json 본문에서 자격 증명이 필요하다.</p>
</li>
</ul>
<pre><code class="language-java">public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper = new ObjectMapper();

    public CustomAuthenticationFilter(String defaultFilterProcessesUrl){
        super(defaultFilterProcessesUrl);
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        Map&lt;String,String&gt; authRequest = objectMapper.readValue(request.getInputStream(),Map.class);

        String username = authRequest.get(&quot;username&quot;);
        String password = authRequest.get(&quot;password&quot;);

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username,password);

        return getAuthenticationManager().authenticate(authToken);
    }

}</code></pre>
<ul>
<li>json으로 요청온 본문에서 username과 password를 읽어서 가져오기.</li>
</ul>
<p>로그인 및 회원가입까지 진행되었으니 이제 메인인 상품 게시판 부분부터 상품과 주문관련해서 진행해야한다.</p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<ul>
<li><p>order 명령어를 생각하지 않고 만들려고 했던 것</p>
<ul>
<li>테이블이름을 orders 로 바꿔주던가 객체 자체를 orders 로 바꾸기.</li>
</ul>
</li>
<li><p>로그인 시에 json 인증방식을 활용하는 방식을 처음 하다보니 filterchain을 잘못걸어서 설정을 아예 초기화시키고 다시하는 방식을 사용했다.</p>
<ul>
<li>로그인은 제대로 되는데 회원가입에서 access_denied 가 모든 경로를 다 열어놨을 때도 발생하여 아예 security를 초기화 시키고 다시 진행.</li>
</ul>
</li>
</ul>
<ul>
<li>springsecurity 부분은 기본적으로 form에서 지원됨<ul>
<li>그렇기 떄문에 json 인증방식을 사용하려면 필터 및 대부분을 해당 사항에 맞춰서 바꿔줘야함.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[peachstore - 상품]]></title>
            <link>https://velog.io/@steady_aa/peachstore-%EC%83%81%ED%92%88</link>
            <guid>https://velog.io/@steady_aa/peachstore-%EC%83%81%ED%92%88</guid>
            <pubDate>Fri, 21 Jun 2024 07:50:20 GMT</pubDate>
            <description><![CDATA[<h4 id="상품item">상품(item)</h4>
<p>상품 등록은 판매자만 가능(개발과정에서는 전부 가능하게 하고 제대로 작동하는게 확인되면 admin만 가능하게변경)</p>
<ul>
<li><p>admin만 상품 등록,수정, 삭제 가능</p>
</li>
<li><p>name</p>
</li>
<li><p>category</p>
<ul>
<li>종류 객체 추가.</li>
</ul>
</li>
<li><p>quantity</p>
</li>
<li><p>price</p>
</li>
<li><p>type</p>
<ul>
<li>enum</li>
</ul>
</li>
</ul>
<h4 id="상품과-카테고리의-관계">상품과 카테고리의 관계</h4>
<ul>
<li>하나의 상품은 여러개의 카테고리를 가질 수 있음</li>
<li>하나의 카테고리는 여러개의 상품을 가질 수 있음.<ul>
<li>다대다<ul>
<li>둘 사이 다대일 일대다 관계로 나타내기</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="공통적인-부분-baseentity-만들기">공통적인 부분 BaseEntity 만들기</h3>
<ul>
<li>id</li>
<li>createDate</li>
<li>modifyDate</li>
</ul>
<p>정도는 전부 포함되어있는 공통 변수기 때문에 추상클래스인 BaseEntity를 통해서 관리 진행</p>
<ul>
<li>부모클래스에 속한 모든 필드를 사용하기 위해서 @superBuilder를 사용<ul>
<li>자식 객체가 부모 객체의 필드를 builder 패턴을 사용할 수 있게 해준다.</li>
</ul>
</li>
</ul>
<p>만들어 둔후에 postman에서 해당 상품 추가가 제대로 도는 지 먼저 확인</p>
<ul>
<li>아무나 신청가능하게 먼저 구현</li>
</ul>
<p>조회시에 itemDto를 통해서 필요한 것들만 보내게</p>
<p>특정 상품 조회 기능 추가 진행</p>
<ul>
<li>파라미터를 통해서 이름을 통해서 찾기.</li>
<li>특정이름이 포함되어있는지 확인하여 찾기를 통해서 여러개의 데이터를 가져올 수 있도록.</li>
</ul>
<pre><code class="language-java">public List&lt;Item&gt; findAllByItemName(String name) {
    return itemRepository.findAllByNameContaining(name);
}</code></pre>
<hr>
<p>상품 수정</p>
<ul>
<li>item id를 이용해서 값을 수정하는걸로<ul>
<li>나중에는 admin만 가능</li>
<li>현재 생각나는 방법은 post 를 이용해서 데이터 수정방법</li>
<li>patch 와  put 을 사용하는 방식은 나중에 해보자.</li>
</ul>
</li>
</ul>
<p>카테고리의 경우 없는 카테고리인 경우 카테고리는 만들어주기</p>
<p>상품 삭제</p>
<ul>
<li>id를 통해서 해당 id 상품을 삭제</li>
<li>현재는 개발 편의를 위해 admin이 아닌 로그인을 하지 않아도 삭제가 가능하게 진행<ul>
<li>이 부분은 주문까지 전부 완료 후 권한을 통해서 제어예정</li>
</ul>
</li>
</ul>
<h2 id="트러블-슈팅">트러블 슈팅</h2>
<h3 id="jackson이-hibernate-프록시-객체를-직렬화할-수-없는-문제로-오류-발생"><code>Jackson</code>이 Hibernate 프록시 객체를 직렬화할 수 없는 문제로 오류 발생</h3>
<ul>
<li>DTO를 통해서 필요한 데이터를 전달하여 직렬화 문제 해결</li>
<li>프록시 객체의 경우 직렬화가 불가능하기 때문에 DTO로 필요한 값만 넘겨줌으로써 프록시 객체가 아닌 값으로 넘겨줌.<ul>
<li>기본적으로 lazyloding을 사용중이기 때문에 프록시 객체가 넘어가게 됨</li>
<li>DTO 를 통해서 원하는 값만 바인딩 시키면서 프록시 객체가 넘어가지 않게 조정</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[springboot에 vue 연동]]></title>
            <link>https://velog.io/@steady_aa/springboot%EC%97%90-vue-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@steady_aa/springboot%EC%97%90-vue-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Wed, 19 Jun 2024 04:34:12 GMT</pubDate>
            <description><![CDATA[<p>백엔드를 rest api 형식으로 만들고 싶다보니 프론트와 백을 분리해서 해보는게 어떨까라는 생각으로 프론트를 처음 써보는 Vue.js를 사용해볼 예정</p>
<ul>
<li>vscode tool을 이용해서 vue 프로젝트를 관리할 예정</li>
<li>설치 했지만 vue 프로젝트를 만드는데 오류가 발생</li>
</ul>
<h3 id="vue-연동하면서-발생했던-오류과-해결">vue 연동하면서 발생했던 오류과 해결</h3>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/4df901cd-873e-4036-ba32-3ac02d173995/image.png" alt=""></p>
<ul>
<li>파워쉘 보안 오류가 발생.<ul>
<li>window powershell을 관리자 권한으로 켜서 </li>
<li><strong>Set-ExecutionPolicy Unrestricted</strong> 해당 코드 실행<ul>
<li>모든 스크립트를 허용처리로 바꿔줌.</li>
<li>근데 보안상 문제가 있을거 같아서 다른 방법 이용</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="파워쉘이-아닌-git-bash-터미널-이용">파워쉘이 아닌 git bash 터미널 이용</h4>
<ul>
<li>vue create 프로젝트명<ul>
<li>프로젝트 생성 -&gt; 프로젝트 명에 따라서 해당 프로젝트가 만들어진다.</li>
</ul>
</li>
</ul>
<p>프로젝트가 만들어진 후 에디터로 해당 폴더 오픈 후 시작</p>
<ul>
<li>오픈하고나서 해당 서버가 제대로 작동하는지 확인해보고 싶은경우<ul>
<li>npm run serve 를 통해 서버를 구동 해볼 수 있음.</li>
</ul>
</li>
</ul>
<p>springboot와 연동해서 사용할 것이기 때문에 springboot가 기본적으로 사용하는 8080포트를 vue 도 사용하기 때문에 vue의 포트를 8081 포트로 변경해준다.</p>
<ul>
<li>변경 방법 package.json에서 serve에 뒷부분에 --port 8081을 넣어줘서 변경</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/71a94e4c-c100-4061-a428-95147b78951a/image.png" alt=""></p>
<h3 id="간과하고-있던점">간과하고 있던점</h3>
<ul>
<li>vue 프로젝트를 springboot 프로젝트 내부에 만들어야한다는 점<ul>
<li>아무리 생각해도 바깥에서 만들면 절대 연동이 안될거 같은 느낌을 찾아보면서 느끼게 되어 자세히 보니 전부 프로젝트 내부에 만들었다는것을 알게 됨.<ul>
<li>당연하게 생각해야했던점을 아예 분리되어생성한다고 생각했다.</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인프로젝트 1]]></title>
            <link>https://velog.io/@steady_aa/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1</link>
            <guid>https://velog.io/@steady_aa/%EA%B0%9C%EC%9D%B8%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1</guid>
            <pubDate>Thu, 13 Jun 2024 08:58:24 GMT</pubDate>
            <description><![CDATA[<p>프론트는 해당 사이트와 비슷하게 구성 예정</p>
<p><a href="https://www.chiakpeach.com/">https://www.chiakpeach.com/</a></p>
<p>맨위에 로고 만들고 누르면 홈으로 가게</p>
<ul>
<li>메뉴의 각 페이지는 누르면 이동이 가능함.<ul>
<li>상품을 구매하거나 후기 게시판이 글을 쓸려하는경우 로그인 페이지로 이동</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/5dc6868e-5d36-4326-a4ff-13d5dd4fcc58/image.png" alt=""></p>
<p>홈을 만들어서 해당 페이지가 제대로 뜨는지 확인하고</p>
<p>복숭아 보이는 종류 다넣으면서 최대한 늘려보기.</p>
<h3 id="기본적으로-rest-api-설계를-해보고-싶어서-백엔드와-연결은-rest-api로-해볼-예정이다">기본적으로 rest api 설계를 해보고 싶어서 백엔드와 연결은 rest api로 해볼 예정이다.</h3>
<h2 id="api-설계">api 설계</h2>
<table>
<thead>
<tr>
<th align="center">연결되는 부분</th>
<th align="center">요청방식</th>
<th align="center">api설계</th>
</tr>
</thead>
<tbody><tr>
<td align="center">회원가입</td>
<td align="center">post</td>
<td align="center">/member/join</td>
</tr>
<tr>
<td align="center">로그인</td>
<td align="center">post</td>
<td align="center">/member/login</td>
</tr>
<tr>
<td align="center">로그아웃</td>
<td align="center">post</td>
<td align="center">/member/logout</td>
</tr>
<tr>
<td align="center">회원정보</td>
<td align="center">get</td>
<td align="center">/member/{id}</td>
</tr>
<tr>
<td align="center">내 주문 정보</td>
<td align="center">get</td>
<td align="center">/member</td>
</tr>
<tr>
<td align="center">상품조회</td>
<td align="center">get</td>
<td align="center">/itmes</td>
</tr>
<tr>
<td align="center">특정상품조회</td>
<td align="center">get</td>
<td align="center">/itmes/{id}</td>
</tr>
<tr>
<td align="center">상품생성</td>
<td align="center">post</td>
<td align="center">/items</td>
</tr>
<tr>
<td align="center">상품주문</td>
<td align="center">post</td>
<td align="center">/orders</td>
</tr>
<tr>
<td align="center">모든 주문 정보</td>
<td align="center">get</td>
<td align="center">/orders</td>
</tr>
<tr>
<td align="center">특정 주문 정보</td>
<td align="center">get</td>
<td align="center">/orders/{id}</td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sbb - 배포(完)]]></title>
            <link>https://velog.io/@steady_aa/Sbb-%EB%B0%B0%ED%8F%AC%E5%AE%8C</link>
            <guid>https://velog.io/@steady_aa/Sbb-%EB%B0%B0%ED%8F%AC%E5%AE%8C</guid>
            <pubDate>Tue, 11 Jun 2024 11:36:22 GMT</pubDate>
            <description><![CDATA[<h3 id="https-인증-설정">Https 인증 설정</h3>
<ul>
<li>접근하는건 https 를 보안이 포함되게 진행<ul>
<li>역방향 프록시에만 SSL 인증서를 발급받아서 적용시키게되면 다음으로 연결되는 웹서버쪽은 SSL을 적용하지 않아도 된다.    <ul>
<li>이부분이 중요점.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><a href="https://npm.%EB%8F%84%EB%A9%94%EC%9D%B8">https://npm.도메인</a> ) 으로 npm 콘솔에 접근할 수있도록 추가</p>
<ul>
<li><p>구글 정책으로 기본 https를 사용한다고 해서 추가적으로 npm 콘솔에 접근하기 위해서 SSL 인증기를 추가하여 https 를 사용하여 보안 진행.</p>
<ol>
<li><p>dnszi에서 *.도메인이름 을 레코드로 등록</p>
<p>​    npm.도메인네임 이 해당 ip로 전달될 수 있도록.</p>
<ul>
<li>등록이 될때까지 delay가 한 10 분정도 있어서 그 뒤에 진행하는 게좋다.<ul>
<li>기다리지 않고 하게 되면 등록이 안되어있어서 연결이 안됨.</li>
</ul>
</li>
</ul>
</li>
<li><p>등록한 뒤에 npm 에서 proxy host를 추가</p>
<ul>
<li>npm.도메인네임으로 들어오게되면 172.17.0.1:81 로연결되게 </li>
<li>여기서 SSL 부분</li>
</ul>
</li>
</ol>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/4242a594-6a09-4552-bc30-5ba65c764626/image.png" alt=""></p>
<ul>
<li>새로운 ssl 인증을 만들어주면서 저장해준다.<ul>
<li>email은 원래 작성해야한다.<ul>
<li>해당 부분을 보여주기 위해서 잠시 지웠습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>마찬가지로 게시판 프로젝트로 연결되는 도메인에도 ssl 인증을 추가</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/11f99139-b4d4-40e5-8d92-243ff2a33852/image.png" alt=""></p>
<ul>
<li>접속을 해보면 자물쇠 표시가 들어가면서 https 적용이 되는것을 확인할 수 있다.</li>
</ul>
<h3 id="여기까지-aws-ec2-프리티어-서버를-활용하여-배포를-진행해보았다">여기까지 AWS ec2 프리티어 서버를 활용하여 배포를 진행해보았다.</h3>
<ul>
<li>물론 개인프로젝트이면서 가벼운 프로젝트를 배포를 진행하는 것이기 때문에 그렇게 까지 중요한 일은 아니였지만 배포를 아예 안해보는 것보다는 배포방식에 대해서 이해하는데 꽤 도움이 됬다.</li>
<li>docker를 활용하여 이미지를 만들어 docker 컨테이너로 돌리는 게 훨씬 편하면서 포트 받는 것도 설정이 가능하여 직접 내가 서버에서 돌리는 것보다 훨씬 나았다.</li>
<li>nginx_proxy_manager를 통해서 도메인으로 요청할때 해당 요청이 발생시에 npm에서 요청을 gui를 통해서 관리하다보니 시각적으로 설정하기도 편했고, 거기에 npm 단에서 ssl 인증 처리를 하기 때문에 뒤에 웹서버 자체에 ssl 작업을 안해도됬던것도 굉장히 좋았다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[sbb - 배포(3)]]></title>
            <link>https://velog.io/@steady_aa/sbb-%EB%B0%B0%ED%8F%AC3</link>
            <guid>https://velog.io/@steady_aa/sbb-%EB%B0%B0%ED%8F%AC3</guid>
            <pubDate>Tue, 11 Jun 2024 09:53:09 GMT</pubDate>
            <description><![CDATA[<h3 id="nginx를-사용하기전-도메인-연결하기">nginx를 사용하기전 도메인 연결하기</h3>
<ul>
<li>이전에 설명으로 적어놨듯이 도메인을 설정하지 않는다면 기본적으로 사용성이 떨어지기 때문에 도메인을 가비아에서 사서 이용해보자.</li>
</ul>
<p>프로젝트 테스트 용도로 했기 때문에 가비아에서 이벤트로 1년 계약시 싼 도메인을 이용했다.</p>
<p>가비아에서 산 도메인을 내가 현재 배포연습으로 aws ec2 서버에서 docker로 배포중인 파일에 dns 설정을 해보자.</p>
<p>도메인을 산 후에 도메인관리 시스템 이용</p>
<h3 id="dnszi">DNSZI</h3>
<ol>
<li>해당 도메인을 등록 후 받은 네임서버로 가비아에 설정된 네임서버를 변경해준다.</li>
<li>해당 ip에 도메인을 추가하기 위해서 호스트 IP관리를 통해서 내가 연결할 ip를 추가해준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/722a4cc5-1d93-4cc5-a51a-a52dc6ac31ce/image.png" alt=""></p>
<p>이런식으로 가해준다. a 레코드 부분은&#39;www.결제한도메인&#39; 과</p>
<p>&#39;결제한도메인&#39; 으로 연결을 시도하면 해당 ip로 연결되게 하게 추가해놓고 밑에는 적용됬는지 확인.</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/24d57845-1788-4a5b-8c84-cf1e0754a5b7/image.png" alt=""></p>
<ul>
<li>제대로 적용된 모습.</li>
</ul>
<p>도메인이 제대로 적용 됬으니 관리를 위해서 NPM을 설치해서 진행.</p>
<h3 id="도커-컴포즈를-통해서-왜-npm을-관리할까">도커 컴포즈를 통해서 왜 npm을 관리할까?</h3>
<ul>
<li>효율적으로 관리하기 위해서<ul>
<li>도커 컴포즈 파일을 사용하여 코드로 인프라를 정의할 수 있다.</li>
</ul>
</li>
<li>간단한 명령어<ul>
<li>컴포즈 파일에 정리하고 실행 시키는 방법은 docker-compose up -d 같이 단일 명령어로 사용이 가능하다.</li>
</ul>
</li>
<li>일관된 환경을 사용가능<ul>
<li>도커 컴포즈 파일을 사용할 시 모든 개발자가 동일한 환경을 쉽게 재현 가능.</li>
</ul>
</li>
</ul>
<h3 id="설치-과정">설치 과정</h3>
<ol>
<li>도커 컴포즈 설치</li>
</ol>
<pre><code>sudo curl -L &quot;https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep &#39;tag_name&#39; | cut -d\&quot; -f4)/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose
</code></pre><ol start="2">
<li>실행 권한 부여</li>
</ol>
<pre><code>sudo chmod +x /usr/local/bin/docker-compose</code></pre><ol start="3">
<li>버전 확인</li>
</ol>
<pre><code>docker-compose --version</code></pre><p>버전 확인을 끝으로 docker-compose가 제대로 설치되있는지 확인.</p>
<p>docker-compose.yml 작성</p>
<pre><code class="language-yml">version: &quot;3&quot;
services:
  app:
    image: &#39;jc21/nginx-proxy-manager:latest&#39;
    restart: unless-stopped
    ports:
      - &#39;80:80&#39; # Public HTTP Port
      - &#39;443:443&#39; # Public HTTPS Port
      - &#39;81:81&#39; # Admin Web Port
    environment:
      TZ: &quot;Asia/Seoul&quot;
      DB_MYSQL_HOST: &quot;172.17.0.1&quot;
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: &quot;lldjlocal&quot;
      DB_MYSQL_PASSWORD: &quot;1234&quot;
      DB_MYSQL_NAME: &quot;nginx&quot;
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt</code></pre>
<p>docker-compose 실행</p>
<ul>
<li>docker-compose up -d<ul>
<li>-d -&gt; 데몬 실행(백그라운드에서 실행)</li>
</ul>
</li>
</ul>
<p>http://공인ip:81 포트 연결</p>
<ul>
<li>npm 포트</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/699f50bd-150d-4e47-861a-a2868c2ea980/image.png" alt=""></p>
<ul>
<li>처음 로그인 하면 기본적으로 임시계정이 있다.</li>
<li>id : <a href="mailto:admin@example.com">admin@example.com</a></li>
<li>password : changeme</li>
</ul>
<p>로그인을 하게 되면 id 부터 password 까지 새로 작성 진행</p>
<p>npm에서 81,80,443 포트를 관리하기 때문에 이전에 만든 80포트를 사용하는 sbb_1 도커이미지는 삭제한 뒤에 </p>
<p>8080 포트로 들어올 경우 8080으로 연결되게 바꿔줘야한다.</p>
<p>도커 이미지 삭제 &amp; 도커 컨테이너 삭제</p>
<ul>
<li>docker rmi -f sbb_1</li>
<li>docker rm -f sbb</li>
</ul>
<p>sbb 프로젝트로 이동하고 이전에 만든 Dockerfile을 통해서 다시 빌드 하고 도커를 run 시킬때</p>
<ul>
<li>docker build -t sbb</li>
</ul>
<p>80 : 8080 으로 연결햇는데 npm에서 해당 부분을 포워딩시켜주니 8080:8080으로 변경하여 도커를 실행하기.</p>
<pre><code>docker run \
  --name=sbb \
  --restart unless-stopped \
  -p 8080:8080 \
  -e TZ=Asia/Seoul \
  -e JASYPT_ENCRYPTOR_PASSWORD=$JASYPT_ENCRYPTOR_PASSWORD \
  -d \
  sbb</code></pre><p>성공적으로 연결이 완료되었다.</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/ee44f4af-684f-4640-bbd8-2af79a7dcee1/image.png" alt=""></p>
<p>이렇게 npm 을 설치하고 도메인을 연결까지 진행완료.</p>
<hr>
<h3 id="참고-ref">참고 Ref</h3>
<p>도커 컴포즈 개념과 설치</p>
<p>​    : <a href="https://seosh817.tistory.com/387">https://seosh817.tistory.com/387</a></p>
<p>DNSZI </p>
<p>​    : <a href="https://www.codesarang.com/10">https://www.codesarang.com/10</a></p>
<p>​    : <a href="https://dnszi.com/domain_blog_tistory.html">https://dnszi.com/domain_blog_tistory.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[nginx를 사용하기전 개념]]></title>
            <link>https://velog.io/@steady_aa/nginx%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0%EC%A0%84-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@steady_aa/nginx%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0%EC%A0%84-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Tue, 11 Jun 2024 02:58:55 GMT</pubDate>
            <description><![CDATA[<h3 id="nginx-proxy-manager">nginx proxy manager</h3>
<ul>
<li><p>nginx 가 지원하는 프록시 기능을 편하게 사용할 수 있게 도와주는 솔루션</p>
</li>
<li><p>Lest&#39;s Encrypt 와 같은 인증기관에서 SSL/TLS 인증서를 발급받아서 nginx의 설정 파이을 CLI에서 직접 수정하여 Https 로 엑세스 할 수 있도록 설정</p>
</li>
</ul>
<p>npm을 사용하려고 보니까 현재 내 상황에서 따져봤을때, 고정 ip를 사용하는 것도 아니고 DNS 설정을 한 것도 아니기 때문에 이걸 왜 사용해야하지 라는 생각이 들었고, 찾아보니 실제로 DNS 설정을 하게 될 경우 SSL 인증이나 도메인 관리를 편하게 해준다는 것을 알게되어 사실상 깔아도 쓸수없는 의미가 되었다.</p>
<p>결국엔 nginx proxy manager 를 사용하는 경우는 DNS 를 내가 발급을 받은 경우에 필요한 일이 아닌가?</p>
<ul>
<li>도메인 현재 없는데 과연 이게 필요한 걸까?</li>
</ul>
<h4 id="애초에-nginx는-뭘까">애초에 Nginx는 뭘까?</h4>
<ul>
<li>그 부분부터 알아보자.</li>
</ul>
<h3 id="nginx-를-왜-사용할까">Nginx 를 왜 사용할까?</h3>
<p>Nginx 자체는 웹서버기 때문에 Html,Css,js,이미지 등의 정적파일은 직접 반환 가능</p>
<ul>
<li>동적 처리만 apache서버에 요청을 보냄으로 connection을 줄여주는 역할로 과거에 썼었다.</li>
</ul>
<h3 id="event-driven-방식">Event Driven 방식</h3>
<ul>
<li>TCP connection 연결, 유저의 http request 처리 , 커넥션의 종료가지의 모든 절차를 이벤트 개념으로 취급하고 처리<ul>
<li>워커 프로세스에게 working queue라는 이름의 처리해야할 작업이 순차적으로 담긴 큐를 처리하도록<ul>
<li>이렇게 구현하면 워커 프로세스가 놀고 있는 시간 없이 끊임없는 이벤트를 처리</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Nginx는 코어 개수만큼 워커 프로세스를 만들기 때문에 워커 프로세스가 하나의 코만 이용하도록 할당</p>
<ul>
<li>cpu에서 프로세스를 변경하는 컨텍스트 스위칭을 거치지 않아도 되기 때문에 cpu 부하가 감소.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>동시 커넥션을 양 최소 10배 증가</li>
<li>동일한 커넥션 수일 때 속도 2배 향상</li>
<li>오래걸리는 disk i/o를 해야하는 작업의 경우 쓰레드 풀에 위임</li>
<li>가볍다.</li>
<li>동적 설정 변경가능<ul>
<li>개발자가 설정 파일을 변경하면 nginx는 그에 맞춰서 work process를 생성하고 이전 process 를 제거해줌.</li>
</ul>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>개발자가 실수로 프로세스를 종료하게되면 해당 프로세스가 관리하고 있던 모든 커넥션이 끊김.</li>
<li>개발자가 직접 모듈을 만들기 까다로움.</li>
</ul>
<hr>
<p>참고 youtube</p>
<p><a href="https://www.youtube.com/watch?v=6FAwAXXj5N0&amp;t=5s">https://www.youtube.com/watch?v=6FAwAXXj5N0&amp;t=5s</a></p>
<p><a href="https://sihyung92.oopy.io/server/nginx_feat_apache">https://sihyung92.oopy.io/server/nginx_feat_apache</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SBB - 배포(2)]]></title>
            <link>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC2</link>
            <guid>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC2</guid>
            <pubDate>Mon, 10 Jun 2024 11:47:33 GMT</pubDate>
            <description><![CDATA[<h2 id="할-일">할 일</h2>
<pre><code>java 파일 docker 파일로 만들기
- dockerfile생성 후 이미 만들기.

포트 포워딩을 통해서 80포트로 연결 시도시 springboot 포트인 8080 포트로 연결되게하기
- 연결이되면 외부에서 8080 포트로 연결되는거 끊기.

nginx porxy manager 설치 및 추가
</code></pre><h3 id="포트포워딩을-하는-이유">포트포워딩을 하는 이유</h3>
<ul>
<li><p>docker 파일로 만들기 전에 포트 포워딩을 진행해서 80 포트로 들어오는 경우 8080으로 넘기게 해야한다.</p>
<ul>
<li><p>현재 이 부분을 하지 않았기 때문에 http 요청의 경우 80포트 기본이기 때문에 해당 부분이 연결되어있지 않아서 연결 거부가 발생.</p>
<ul>
<li><h3 id="헤맸던-부분">헤맸던 부분</h3>
<ul>
<li>springboot의 기본 포트가 8080 포트인것과 기본적으로 https 작업을 하지 않았기 때문에 퍼블릭 ip로 접근 시에 연결 거부가 발생하는 것을 보고 왜 안되지하면서 꽤 많이 찾아봤었다.<ul>
<li>결론은 aws ec2 인스턴스에 해당 ip로 접근하게 되면 기본적으로 https://공인 ip.com 이쪽으로 연결을 진행하기 때문에 https 작업과 443 포트로 들어올 경우 연결되는 곳이 없기 때문에 연결거부가 발생하여 문제가 발생했었다.</li>
<li>그렇기 때문에 아직까지는 http://공인ip.com:8080 으로 보내줘야 연결이 가능하다.<ul>
<li>이 부분 때문에 포트포워딩이 필요함.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>현재 사용중인 리눅스 버전 : AWS linux 2023</p>
<h3 id="검색으로-찾은-커맨드">검색으로 찾은 커맨드</h3>
<pre><code>sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080</code></pre><ul>
<li>iptables <ul>
<li>리눅스 방화벽/패킷 필터링 도구</li>
</ul>
</li>
<li>-t nat<ul>
<li>nat 테이블 사용</li>
</ul>
</li>
<li>-A PREROUTING<ul>
<li>PREROUNTING 체인에 규칙 추가 -&gt; 패킷이 라우팅되기 전에 적용</li>
</ul>
</li>
</ul>
<p>뒷 부분은 80 패킷으로 들어올 경우 8080으로 리다이렉팅 해주라는 의미</p>
<p>포트포워딩 완료후 제대로 동작하는지 확인.</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/882eda31-2fa7-4c9d-856a-427be81e0b44/image.png" alt=""></p>
<ul>
<li>80 포트로 연결 시도시 해당 8080포트로 연결되는 것으로 포트포워딩이 완료되었다.</li>
</ul>
<p>해당 프로젝트를 도커 이미지로 생성하기 위해서 Docker을 작성</p>
<pre><code class="language-vim">FROM amazoncorretto:17
ARG JAR_FILE=build/libs/sbb-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;-Dspring.profiles.active=prod&quot;,&quot;/app.jar&quot;]</code></pre>
<p>이슈 발생</p>
<ul>
<li><p>도커 파일을 기준으로 빌드를 통해서 이미지를 만들려니까 해당 문제가 발생</p>
</li>
<li><pre><code>current commit information was not captured by the build: failed to read current commit information with git rev-parse --is-inside-work-tree</code></pre></li>
<li><p>해당 부분에 대해서 1시간 이상 찾아봤는데도 정보가 나오지가 않아서 진짜 모르겠다.</p>
</li>
<li><p>혹시 몰라서 gpt에도 물어봤는데 해당 도커 빌드 컨텍스트가 git 커밋정보를 읽어올려는게 실패했다고한다.</p>
<ul>
<li>그래서 해당 git clone을 한 디렉토리에서 dockerfile을 만들고 build를 했는데도 같은 위험이 뜬다.</li>
<li>이미지는 만들어지고 실행해봤을 때</li>
</ul>
</li>
</ul>
<pre><code>docker run \
  --name=sbb \
  --restart unless-stopped \
  -p 80:8080 \
  -e TZ=Asia/Seoul \
  -e JASYPT_ENCRYPTOR_PASSWORD=$JASYPT_ENCRYPTOR_PASSWORD \
  -d \
  sbb_1</code></pre><ul>
<li>도커에서 제대로 실행되고 있는 모습<ul>
<li>ip를 지웠다보니 이전 이미지랑 차이가 별로없어서 페이지를 다르게 
<img src="https://velog.velcdn.com/images/steady_aa/post/d840017e-3df7-4c93-9abb-25a4879bf575/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li>성공적으로 도커 이미지가 실행됬다!</li>
</ul>
<hr>
<h3 id="참고-ref">참고 Ref</h3>
<p>도커 서버 배포</p>
<ul>
<li><a href="https://9keyyyy.tistory.com/39">https://9keyyyy.tistory.com/39</a></li>
</ul>
<p>포트포워딩</p>
<ul>
<li><a href="https://velog.io/@as9587/AWS-EC2-Amazon-Linux-2023-OS-%ED%8F%AC%ED%8A%B8-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8port-redirect-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%9D%B4%EC%8A%88-%EC%A0%95%EB%A6%AC">https://velog.io/@as9587/AWS-EC2-Amazon-Linux-2023-OS-%ED%8F%AC%ED%8A%B8-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8port-redirect-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%9D%B4%EC%8A%88-%EC%A0%95%EB%A6%AC</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SBB - 배포(1)]]></title>
            <link>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC1</link>
            <guid>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC1</guid>
            <pubDate>Mon, 10 Jun 2024 05:39:06 GMT</pubDate>
            <description><![CDATA[<h2 id="할-일">할 일</h2>
<pre><code>배포를 시작하기 전에 처리해야할 것들 사전 작업으로 해야할 것들을 적어두기.

기본적으로 yum update를 통해서 최신화 진행 완료

springboot 3.x 이상부터는 java 17을 사용하기 때문에 java 17 깔기
- 자바 깔기 완료

db는 mariaDB로 변경하고 mariaDB를 깔아주기
- (mariaDB 깔기 완료)

Docker 추가
- springboot 와 mariaDb 둘다 도커를 통해서 실행
- (mariaDb는 docker 추가 완료)

jasypt 환경변수 설정하는 법 알아두기
- 유출되면 안되는 정보들은 jasypt를 활용해서 암호화를 진행했기 때문에.
- (환경변수 설정 완료)

개인적으로 배포 테스트하면서 진행할거기 때문에 도메인은 사지않고 진행.

ngix proxy manager를 깔기
- (아직 안한 부분)

포트 포워딩하기.
- (아직 안한 부분)</code></pre><p>기본 적으로 서버는 aws EC2 프리티어에서 진행했다.</p>
<ul>
<li>서버를 설정하는 방식의 경우 프리티어이기 때문에 t2.micro 를 선택했고 그외에는 건들이지 않았다.</li>
<li>인바운드 규칙의 경우 서버 배포 테스트 용도로 사용하기 때문에 현재는 ipv4AnyWhere로 일단 모든 곳에서 접근이 가능하게 해놨다.<ul>
<li>나중에는 모든 ip가 아닌 특정 ip 만 접근할 수있도록 제한을 거는게 좋다.<ul>
<li>Ec2로 들어오는 악의적인 접근을 막아주기 위해서</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="1-ec2-리눅스-서버에-yum-을-최신화">1. EC2 리눅스 서버에 yum 을 최신화</h3>
<ul>
<li>sudo yum update -y </li>
</ul>
<h3 id="2-자바-17-버전-설치">2. 자바 17 버전 설치</h3>
<ul>
<li>sudo yum install java-17-amazon-corretto-devel<ul>
<li>가장 많이 사용된다는 jdk를 설치 진행</li>
<li>java 어플리케이션을 실행만 하려는 경우 devel 버전을 굳이 깔 필요없음.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-도커-설치">3. 도커 설치</h3>
<ul>
<li>sudo yum install docker -y</li>
<li>도커 버전 확인<ul>
<li>docker -v</li>
</ul>
</li>
</ul>
<p>도커를 설치하고 난 뒤에 버전까지 확인 한 뒤에 활용을하려면 도커를 실행 시켜놔야한다.</p>
<ul>
<li>sudo service docker start</li>
</ul>
<p>docker 그룹을 생성한 후 사용자를 추가해준다. -&gt; sudo 없이 docker 실행이 가능하게</p>
<ul>
<li>sudo usermod -aG docker ec2-user<ul>
<li>-a : 변경대신 정보를 추가</li>
<li>-G : 사용자계정의 그룹을 대상으로<ul>
<li>사용자 계정을 docker 계정에 포함시켜라.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>docker를 실행하고 mariaDB 이미지 실행하기</p>
<pre><code># 컨테이너 실행
docker run \
  --name mariadb_1 \
  -d \
  --restart unless-stopped \
  -e MARIADB_ROOT_PASSWORD=YOURPASSWORD \
  -e TZ=Asia/Seoul \
  -p 3306:3306 \
  -v /docker_projects/mariadb_1/conf.d:/etc/mysql/conf.d \
  -v /docker_projects/mariadb_1/mysql:/var/lib/mysql \
  -v /docker_projects/mariadb_1/run/mysqld:/run/mysqld/ \
  mariadb:latest</code></pre><p>설명</p>
<pre><code>--name : 컨테이너 이름
-d : detached 모드 -&gt; 컨테이너가 백그라운드로 실행
-- restart : 컨테이너 종료시 재시작 정책 설정
-e 컨테이너 내에서 사용할 환경변수 설정 -&gt; mariaDB의 계정 비밀번호 넘겨주기.-&gt; 현재는 설명용으로 비밀번호를 지워둠.

-p 호스트와 컨테이너의 포트를 연결
&lt;호스트포트&gt; : &lt;컨테이너 포트&gt; -&gt; 호스트포트 3306으로 들어오면 해당 컨테이너 포트 3306으로 연결해줘.

-v 도커에서 마운트 볼륨 설정 -&gt; 컨테이너와 호스트간에 데이터를 공유하거나 지속적으로 보관</code></pre><p>restart 옵션 4가지</p>
<ul>
<li>no : container를 재시작 시키지않는다 -&gt; 이게 기본 설정(옵션을 넣지않으면 이걸로 실행됨)</li>
<li>on-failure[:max-retries] : container가 정상적으로 종료되지 않은 경우에만 재시작 시킴. 뒤에 max-retires에 재시작 최대 시도 횟수를 지정할 수 있음.</li>
<li>always : container를 항상 재시작 -&gt; exit code 상관없이 항상 재시작됨.</li>
<li>unless-stopped: container를 stop 시키기전까지 항상 재시작.</li>
</ul>
<hr>
<h4 id="4-git-설치">4. git 설치</h4>
<ul>
<li>git에 올라가있는 프로젝트를 배포서버에 클론하기.<ul>
<li>sudo yum install git -y</li>
</ul>
</li>
</ul>
<p>directory를 통해서 docker에서 실행할 프로젝트를 디렉토리로 나눠두기</p>
<ul>
<li>sudo mkdir -p /docker_projects/sbb/project<ul>
<li>유저 계정이기 때문에 sudo를 통해서 디렉토리 생성</li>
</ul>
</li>
</ul>
<hr>
<h3 id="5-해당-디렉토리-이동후-프로젝트-클론">5. 해당 디렉토리 이동후 프로젝트 클론</h3>
<p>public project 클론 방식</p>
<ul>
<li>sudo git clone (git 주소) .<ul>
<li>저장소를 현재 디렉토리에 클론 진행 -&gt; 디렉토리가 비어있어야 가능.</li>
</ul>
</li>
<li>sudo git clone (git 주소)<ul>
<li>현재 디렉토리에 새로운 디렉토리를 생성하고 그 안에 저장소를 clone 진행.</li>
</ul>
</li>
</ul>
<p>private repo 클론 방식은 access token을 생성하고 해당 토큰을 넣어줘야한다.</p>
<ul>
<li>sudo git clone https://(token)@github.com/your/project/path.git</li>
</ul>
<hr>
<p>프로젝트 클론까지 진행하고 나서 해야할 것들.</p>
<ul>
<li>gitignore를 통해서 application-secret.yml을 push 가 안되게 했을 경우 해당 파일이 없어서 프로젝트를 빌드해도 오류가 발생해서 실행이 안되기 때문에 해당 파일을 추가해줘야한다.</li>
</ul>
<p>이 문제를 해결하기 위해서 규격에 맞춰서 파일을 만들어서 올려놓았었다.</p>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/10b4b275-84f6-4f1f-8e0f-c3abd37a66ec/image.png" alt=""></p>
<ul>
<li>default 내용</li>
</ul>
<pre><code>spring:
  mail:
    host: smtp.naver.com
    port: 465
    username: userid
    password: password
    properties:
      mail.smtp.auth: true
      mail.smtp.ssl.enable: true
      mail.smtp.ssl.trust: smtp.naver.com
      mail.smtp.starttls.enable: true</code></pre><ul>
<li>username 과 password 에다가 해당하는 중요 정보를 넣어준다음 secret파일로 이름을 변경해줌으로써 git에는 중요정보가 유출되지 않으면서 규격을 새로 쓸 필요도 없어서 좋은 방법인거 같다.</li>
</ul>
<h4 id="파일-이름명을-바꾸기-위해-mv-명령어를-통해서-해당-이름을-변경">파일 이름명을 바꾸기 위해 mv 명령어를 통해서 해당 이름을 변경</h4>
<ul>
<li><p>sudo mv src/main/resource/application-secret.yml.default src/main/resource/application-secret.yml</p>
<ul>
<li>이름 변경을 통해서 해당 파일을 만들어주고 vim 을 이용해서 해당 파일의 username과 password를 넣어주기.</li>
</ul>
</li>
<li><p>빌드를 하기위해서 ./gradlew 파일 chmod 744로 권한을 변경해서 파일소유자가 빌드를 할 수 있도록 </p>
</li>
<li><p>gradlew 파일은 gradle 실행 환경을 제공 -&gt; 로컬 시스템이 gradle 이 설치되어 있지 않아도 gradle을 다운로드하고 프로젝트를 지정된 버전의 Gradle 사용하여 빌드.</p>
<ul>
<li>sudo chmod 744 gradlew</li>
<li>./gradlew clean build</li>
</ul>
</li>
<li><h3 id="빌드를-통해서-만들어진-빌드-파일을-실행하기-전-해야하는게-있다">빌드를 통해서 만들어진 빌드 파일을 실행하기 전 해야하는게 있다.</h3>
<ul>
<li>jasypt를 활용하면서 환경변수로 해당 복호화시 필요한 비밀번호를 넣어줘야한다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="6-jasypt-환경변수를-넣어주는-방법">6 .jasypt 환경변수를 넣어주는 방법</h3>
<p>.bashrc 환경설정파일에 넣어주기</p>
<ul>
<li><p>bash 셸의 초기화 파일 -&gt; 사용자가 로그인할 때나 새로운 쉘을 시작할 때 실행</p>
</li>
<li><p>사용자 별로 사용자 디렉토리 에 포함되는 설정파일 </p>
<ul>
<li>지역적인 함수나 별칭을 작성할 때 사용되는 설정파일.</li>
</ul>
</li>
</ul>
<p>해당 파일에 밑의 환경 변수 추가</p>
<pre><code>export JASYPT_ENCRYPTOR_PASSWORD=YOUR_ENCRYPTION_PASSWORD</code></pre><ul>
<li>YOUR_ENCRYPTION_PASSWORD 에는 내가 설정한 jasypt 비밀번호를 넣어주면된다.</li>
</ul>
<p>해당 변수를 넣어준 뒤에 서버를 재시작 하거나 Source . bashrc 와 같이 source 명령어를 통해서  즉시 적용</p>
<hr>
<h3 id="7-도커-파일로-만들기-전-해당-프로젝트가-제대로-실행되는-지-확인">7. 도커 파일로 만들기 전 해당 프로젝트가 제대로 실행되는 지 확인</h3>
<ul>
<li>빌드 했던 파일을 실행해야하기 때문에 해당 디렉토리로 이동<ul>
<li>cd /docker_projects/sbb/project</li>
</ul>
</li>
<li>컴파일 된 클래스 파일  jar 파일 실행<ul>
<li>현재 테스트를 하는 것이고 실제 배포가 진행되지 않기 때문에 버그나 문제가 발생하는지 확인하기 위해서 SnapShot.jar 파일을 실행</li>
</ul>
</li>
</ul>
<pre><code>sudo java -jar -Dspring.profiles.active=dev -Djasypt.encryptor.password=$JASYPT_ENCRYPTOR_PASSWORD build/libs/sbb-0.0.1-SNAPSHOT.jar</code></pre><hr>
<h3 id="8-외부에서-접속확인">8. 외부에서 접속확인.</h3>
<ul>
<li>포트포워딩이 되어있지않기 때문에 해당 8080 port로 연결을 시도해야 연결이된다.<ul>
<li><a href="http://AwsEC2Pulicip:8080/">http://AwsEC2Pulicip:8080/</a> 링크를 통해서 이동가능<ul>
<li>ip노출은 하지 않기 위해서 AwsEc2Publicip에 해당 ec2 server 가 할당받은 public ip를 넣으면 접속이 된다.</li>
</ul>
</li>
</ul>
</li>
<li>접속 된 모습.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/steady_aa/post/89e9cc33-c291-4ff8-8e01-9ff0a20ce7b2/image.JPG" alt=""></p>
<p>여기까지 현재 진행된 모습이고 이제 남은 부분은 다음 게시물에서 진행하면서 기록할 예정.</p>
<hr>
<h3 id="참고-ref">참고 Ref.</h3>
<p>restart 옵션 참고 ref</p>
<p><a href="https://blog.leocat.kr/notes/2019/11/20/docker-restart-container-whenever-start-docker-desktop">https://blog.leocat.kr/notes/2019/11/20/docker-restart-container-whenever-start-docker-desktop</a></p>
<p>bashrc 참고 :</p>
<p><a href="https://goldsony.tistory.com/243">https://goldsony.tistory.com/243</a></p>
<ul>
<li>포트포워딩 진행된 이슈 찾을때 </li>
</ul>
<p><a href="https://velog.io/@as9587/AWS-EC2-Amazon-Linux-2023-OS-%ED%8F%AC%ED%8A%B8-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8port-redirect-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%9D%B4%EC%8A%88-%EC%A0%95%EB%A6%AC">https://velog.io/@as9587/AWS-EC2-Amazon-Linux-2023-OS-%ED%8F%AC%ED%8A%B8-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8port-redirect-%ED%95%98%EB%A9%B0-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%9D%B4%EC%8A%88-%EC%A0%95%EB%A6%AC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SBB - 배포 진행 사전작업 해야할 것들]]></title>
            <link>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC-%EC%A7%84%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@steady_aa/SBB-%EB%B0%B0%ED%8F%AC-%EC%A7%84%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 09 Jun 2024 10:58:14 GMT</pubDate>
            <description><![CDATA[<p>AWS ec2 서버를 이용해서 해당 프로젝트를 배포해보자.</p>
<ul>
<li>팀프로젝트를 진행할 때 배포 역할을 안해봤어서 일단 개인 프로젝트를 먼저 배포해보면서 aws 웹 서버 배포에 대해서 알아가자.</li>
</ul>
<h3 id="배포를-시작하기-전에-처리해야할-것들">배포를 시작하기 전에 처리해야할 것들</h3>
<ul>
<li>사전 작업으로 해야할 것들을 적어두기.</li>
</ul>
<p>aws 서버를 만들어 준 후에 깔아줘야할 것들</p>
<ul>
<li><p>기본적으로 yum update를 통해서 최신화 진행</p>
</li>
<li><p>springboot 3.x 이상부터는 java 17을 사용하기 때문에 java 17 깔기</p>
</li>
<li><p>db는 mariaDB로 변경하고 mariaDB를 깔아주기</p>
</li>
<li><p>Docker 추가</p>
<ul>
<li>springboot 와 mariaDb 둘다 도커를 통해서 실행</li>
</ul>
</li>
<li><p>jasypt 환경변수 설정하는 법 알아두기</p>
<ul>
<li>유출되면 안되는 정보들은 jasypt를 활용해서 암호화를 진행했기 때문에.</li>
</ul>
</li>
</ul>
<ul>
<li><p>개인적으로 배포 테스트하면서 진행할거기 때문에 도메인은 사지않고 진행.</p>
</li>
<li><p>ngix proxy manager를 깔기</p>
</li>
<li><p>포트 포워딩하기.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 -404 페이지 처리 등등 놓쳤던 부분 수정]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-404-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%B2%98%EB%A6%AC-%EB%93%B1%EB%93%B1-%EB%86%93%EC%B3%A4%EB%8D%98-%EB%B6%80%EB%B6%84-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-404-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%B2%98%EB%A6%AC-%EB%93%B1%EB%93%B1-%EB%86%93%EC%B3%A4%EB%8D%98-%EB%B6%80%EB%B6%84-%EC%88%98%EC%A0%95</guid>
            <pubDate>Sun, 02 Jun 2024 05:14:00 GMT</pubDate>
            <description><![CDATA[<h3 id="404-오류-페이지-처리">404 오류 페이지 처리</h3>
<ul>
<li>타임리프를 사용시 error/404 를 추가적으로 만들어두면 알아서 404 에러가 발생시 해당페이지를 보여준다.<ul>
<li>만약 타임리프를 사용하지 않는다면 globalerrorHandler를 통해서 오류페이지 핸들링을 해주는게 좋음.</li>
</ul>
</li>
<li>타임리프를 사용하고 있기 때문에 404 페이지만 만들면 알아서 진행됨.</li>
</ul>
<p>이전에 임시로 만들어놨던 내 정보 페이지 꾸미기.</p>
<p>내 정보에서 보여줘야 할 것들</p>
<ul>
<li>내 정보<ul>
<li>이름</li>
<li>성별</li>
</ul>
</li>
<li>성별로 인기 정보<ul>
<li>전체 인기</li>
<li>여성 인기</li>
<li>남성 인기</li>
</ul>
</li>
<li>호감사유별 인기</li>
</ul>
<p>해당 데이터는 저장되어있기 때문에 가져올때  필터링 해서 가져오면된다. </p>
<pre><code class="language-java">@PreAuthorize(&quot;isAuthenticated()&quot;)
@GetMapping(&quot;/me&quot;)
public String me(Model model) {

    if (!rq.getMember().hasConnectedInstaMember()) {
        return rq.historyBack(&quot;먼저 본인의 인스타그램 아이디를 입력해주세요.&quot;);
    }
    InstaMember instaMember = rq.getMember().getInstaMember();

    long likesByGenderWomen = instaMember.getToLikeablePeople().stream().filter(likeablePerson -&gt; likeablePerson.getToInstaMember().getGender().equals(&quot;W&quot;)).count();

    long likesByGenderMen = instaMember.getToLikeablePeople().stream().filter(likeablePerson -&gt; likeablePerson.getToInstaMember().getGender().equals(&quot;M&quot;)).count();

    long typeCode1 = instaMember.getToLikeablePeople().stream().filter(likeablePerson -&gt; likeablePerson.getAttractiveTypeCode() == 1).count();

    long typeCode2 = instaMember.getToLikeablePeople().stream().filter(likeablePerson -&gt; likeablePerson.getAttractiveTypeCode() == 2).count();
    long typeCode3 = instaMember.getToLikeablePeople().stream().filter(likeablePerson -&gt; likeablePerson.getAttractiveTypeCode() == 3).count();


    model.addAttribute(&quot;likes&quot;, likesByGenderWomen + likesByGenderMen);
    model.addAttribute(&quot;likesByGenderWomen&quot;, likesByGenderWomen);
    model.addAttribute(&quot;likesByGenderMen&quot;, likesByGenderMen);
    model.addAttribute(&quot;likesByAttractiveTypeCode1&quot;, typeCode1);
    model.addAttribute(&quot;likesByAttractiveTypeCode2&quot;, typeCode2);
    model.addAttribute(&quot;likesByAttractiveTypeCode3&quot;, typeCode3);
    return &quot;user/member/me&quot;;
}</code></pre>
<ul>
<li>각 해당하는 데이터를 model에 실어서 보내주기.<ul>
<li>이 부분은 컨트롤러단이 아닌 객체 내에서 관리해도 될 거같음.</li>
</ul>
</li>
</ul>
<h3 id="놓쳤던-부분">놓쳤던 부분</h3>
<ul>
<li>like를 표시할때 만들어지는 연결되지 않은 instamember에 관해서 회원이 회원과 연결되지 않은 이미 등록된 인스타 그램 계정과 연결되지 않았을 때 연결이 되지 않았던 점 수정<ul>
<li>이미 만들어져있는 연결되지 않은 인스타 계정에 관해서 처리를 하지 않고 진행하여 발생된 중요한 오류</li>
</ul>
</li>
</ul>
<p>connect 부분</p>
<pre><code class="language-java">@Transactional
public RsData&lt;InstaMember&gt; connect(Member member, String username, String gender) {

    Optional&lt;InstaMember&gt; opInstaMember = findByUsername(username);

    if (opInstaMember.isPresent() &amp;&amp; !opInstaMember.get().getGender().equals(&quot;U&quot;)) {
        return RsData.of(&quot;F-5&quot;, &quot;이미 다른 사용자와 연결되어있습니다.&quot;);
    }

    RsData&lt;InstaMember&gt; instaMemberRsData = findByUsernameOrCreate(username, gender);

    memberService.UpdateInstaMember(member, instaMemberRsData.getData());
    return instaMemberRsData;

}</code></pre>
<ul>
<li>이전에는 인스타 계정이 존재만 하면 다른 사용자와 연결되어있다고 했지만 성별이 U 인경우 연결이 된 것이 아니기 때문에 해당 부분을 수정</li>
</ul>
<p>만들어져있는 계정에 성별을 추가하는 부분</p>
<pre><code class="language-java">@Transactional
public RsData&lt;InstaMember&gt; findByUsernameOrCreate(String username, String gender) {
    Optional&lt;InstaMember&gt; opInstaMember = findByUsername(username);

    if (opInstaMember.isPresent()) {
        InstaMember instaMember = opInstaMember.get();
        instaMember.updateGender(gender);
        instaMemberRepository.save(instaMember);

        return RsData.of(&quot;S-2&quot;, &quot;인스타 계정이 등록되었습니다.&quot;, instaMember);
    }

    return create(username, gender);
}</code></pre>
<ul>
<li>해당 인스타 user가 존재하는 경우 성별을 업데이트해서 다시 넣어준다.<ul>
<li>존재하지 않는 경우 create 진행</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 중복호감표시 금지, 10명이상 호감 금지]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%A4%91%EB%B3%B5%ED%98%B8%EA%B0%90%ED%91%9C%EC%8B%9C-%EA%B8%88%EC%A7%80-10%EB%AA%85%EC%9D%B4%EC%83%81-%ED%98%B8%EA%B0%90-%EA%B8%88%EC%A7%80</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%A4%91%EB%B3%B5%ED%98%B8%EA%B0%90%ED%91%9C%EC%8B%9C-%EA%B8%88%EC%A7%80-10%EB%AA%85%EC%9D%B4%EC%83%81-%ED%98%B8%EA%B0%90-%EA%B8%88%EC%A7%80</guid>
            <pubDate>Sun, 02 Jun 2024 05:13:25 GMT</pubDate>
            <description><![CDATA[<ul>
<li>중복 호감 표시를 금지시키려면 해당호감표시를 했는 지 확인이 필요하다.</li>
</ul>
<h3 id="중복-호감-금지">중복 호감 금지</h3>
<ul>
<li>내가 호감을 표현한 목록에 인스타 이름이 있는지 확인하여 있는 경우 호감표시가 불가능하도록 하면 되는 간단한 요소</li>
</ul>
<pre><code class="language-java">if(fromInstaMember.getFromLikeablePeople().size() &gt;= AppConfig.getLikeablePersonFromMax()){
    return RsData.of(&quot;F-1&quot;,&quot;%d명 이상에게 호감을 표시할 수 없습니다.&quot;.formatted(AppConfig.getLikeablePersonFromMax()));

}

    private boolean canLike(InstaMember fromInstaMember, InstaMember toInstaMember) {
        List&lt;LikeablePerson&gt; fromLikeablePeople = fromInstaMember.getFromLikeablePeople();

        LikeablePerson likeablePerson = fromLikeablePeople.stream()
                .filter(e -&gt; e.getToInstaMember().getUsername().equals(toInstaMember.getUsername()))
                .findFirst()
                .orElse(null);

        if (likeablePerson != null) {
            return false;
        }

        return true;

    }</code></pre>
<p>중복 호감을 보내는데 만약 typeCode가 다른 경우</p>
<ul>
<li>호감 표시만 수정하는 방식으로 진행 <ul>
<li>만약 호감 typecode 까지 똑같은 경우에만 중복 호감을 보낸다고 처리.</li>
</ul>
</li>
<li>비교해야 할 것들<ul>
<li>인스타 이름 + 호감 코드</li>
</ul>
</li>
</ul>
<pre><code class="language-java">if (searchLikeablePerson != null) {
    if (searchLikeablePerson.getAttractiveTypeCode() == attractiveTypeCode) {
        log.info(&quot;중복 호감 표시&quot;);
        return RsData.of(&quot;F-1&quot;, &quot;이미 호감을 표시한 상대입니다.&quot;);
    }

    searchLikeablePerson.modifyAttractiveTypCode(attractiveTypeCode);
    return RsData.of(&quot;S-2&quot;, &quot;호감 수정 완료&quot;);</code></pre>
<ul>
<li>호감리스트에 있는 경우<ul>
<li>호감 표시 코드 비교후 같으면 중복 호감으로 불가능하게 진행</li>
</ul>
</li>
<li>호감표시 코드는 다른 경우<ul>
<li>호감 수정 진행.</li>
</ul>
</li>
</ul>
<p>10명 이상 호감표시 금지</p>
<ul>
<li>내가 호감을 표현한 리스트의 크기가 10이면 더이상 불가능</li>
</ul>
<pre><code class="language-java">if(fromInstaMember.getFromLikeablePeople().size() &gt;= AppConfig.getLikeablePersonFromMax()){
    return RsData.of(&quot;F-1&quot;,&quot;%d명 이상에게 호감을 표시할 수 없습니다.&quot;.formatted(AppConfig.getLikeablePersonFromMax()));

}</code></pre>
<p>추가로 할 부분</p>
<ul>
<li>호감 수정 부분</li>
</ul>
<p>현재 호감 표시를 할때 중복으로 들어온 회원에 대해서 처리를 했었는데 호감 목록에서 호감을표시한 대상의 호감 이유를 바꿀 수 있어야 하기 때문에.</p>
<h3 id="호감-수정-부분으로-이동하는-부분">호감 수정 부분으로 이동하는 부분</h3>
<pre><code class="language-java">    @PreAuthorize(&quot;isAuthenticated()&quot;)
    @GetMapping(&quot;/modify/{id}&quot;)
    public String modify(@PathVariable(&quot;id&quot;) Long id, Model model) {
        LikeablePerson likeablePerson = likeablePersonService.findById(id).orElseThrow();

        RsData modifyLikeData = likeablePersonService.canModifyLike(rq.getMember(), likeablePerson);

        if (modifyLikeData.isFail()) {
            rq.historyBack(modifyLikeData.getMsg());
        }

        model.addAttribute(&quot;likeablePerson&quot;, likeablePerson);


        return &quot;/likeablePerson/modify&quot;;
    }</code></pre>
<ul>
<li>수정이 가능한지 확인 후 가능하면 modifty 폼으로 이동</li>
</ul>
<h3 id="호감-수정-진행">호감 수정 진행</h3>
<pre><code class="language-java">@PreAuthorize(&quot;isAuthenticated()&quot;)
@GetMapping(&quot;modify/{id}&quot;)
public String modifyLike(@PathVariable(&quot;id&quot;) Long id, @Valid ModifyForm modifyForm) {

    RsData likeData = likeablePersonService.modifyLike(rq.getMember(), id, modifyForm.getAttractiveTypeCode());

    if (likeData.isFail()) {
        return rq.historyBack(likeData.getMsg());
    }

    return rq.redirectWithMsg(&quot;/likeablePerson/list&quot;, likeData.getMsg());

}</code></pre>
<ul>
<li>호감이 실패하는 경우 오류를 내보내면서 historyback 진행</li>
<li>변경이 되면 호감리스트로 리다이렉트되며 호감이 완료되었다고 알림 메시지 남기기.</li>
</ul>
<p>호감 수정 서비스단</p>
<pre><code class="language-java">@Transactional
public RsData modifyLike(Member member, Long id, int attractiveTypeCode) {

    LikeablePerson likeablePerson = likeablePersonRepository.findById(id).orElseThrow();

    RsData rsData = canModifyLike(member, likeablePerson);

    if (rsData.isFail()) {
        return rsData;
    }

    likeablePerson.modifyAttractiveTypCode(attractiveTypeCode);


    return RsData.of(&quot;S-2&quot;, &quot;호감 사유 수정이 완료되었습니다.&quot;);


}

public RsData canModifyLike(Member member, LikeablePerson likeablePerson) {
    if (!member.hasConnectedInstaMember()) {
        return RsData.of(&quot;F-1&quot;, &quot;먼저 본인의 인스타그램 아이디를 입력해주세요.&quot;);
    }

    InstaMember fromInstaMember = member.getInstaMember();

    if (!Objects.equals(fromInstaMember.getId(), likeablePerson.getFromInstaMember().getId())) {
        log.info(&quot;권한이 없습니다.&quot;);
        return RsData.of(&quot;F-1&quot;, &quot;권한이 없습니다.&quot;);
    }


    return RsData.of(&quot;S-1&quot;, &quot;수정 가능&quot;);
}</code></pre>
<ul>
<li>해당 권한이 있는지 확인하는 부분과 수정이 진행되는 부분<ul>
<li>수정이 진행되는 부분은 transaction내에서 돌아가게 진행.<ul>
<li>데이터의 변경이 생기기 때문에.</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 호감도 취소 구현]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%98%B8%EA%B0%90%EB%8F%84-%EC%B7%A8%EC%86%8C-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%98%B8%EA%B0%90%EB%8F%84-%EC%B7%A8%EC%86%8C-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 31 May 2024 00:40:58 GMT</pubDate>
            <description><![CDATA[<p>현재 호감도 취소 버튼은 만들어져 있지만 구현은 안되어 있다.</p>
<ul>
<li>호감도 취소버튼으로 생기는 변화들<ul>
<li>likeableperson의 데이터 지우기</li>
<li>호감도를 표현한 사람한테 있는 리스트에서 지워주기</li>
<li>호감도 표현을 받은 사람도 지워주기.</li>
</ul>
</li>
</ul>
<p>호감도 삭제 uri 구조</p>
<p>/likeablePerson/delete/{id}</p>
<p>호감도 삭제시에 해당 uri로 접근시에 권한이 있는지 확인을 하는 코드가 필요하다.</p>
<p>호감도 삭제 controller 부분</p>
<pre><code class="language-java">@PreAuthorize(&quot;isAuthenticated()&quot;)
@PostMapping(&quot;delete/{id}&quot;)
public String deletelike(@PathVariable(&quot;id&quot;) Long id) {

    log.info(&quot;삭제 판별&quot;);

    LikeablePerson likeablePerson = likeablePersonService.findById(id).orElse(null);

    if (likeablePerson == null) {
        log.info(&quot;취소된 호감 대상입니다.&quot;);
        return rq.historyBack(&quot;이미 취소된 호감대상입니다.&quot;);
    }

    if (!Objects.equals(rq.getMember().getInstaMember().getId(), likeablePerson.getFromInstaMember().getId())) {
        log.info(&quot;권한이 없습니다.&quot;);
        return rq.historyBack(&quot;권한 없음&quot;);
    }


    log.info(&quot;삭제 가능&quot;);
    RsData&lt;LikeablePerson&gt; rsData = likeablePersonService.deleteLikeablePerson(likeablePerson);

    if (rsData.isFail()) {
        log.info(&quot;실패&quot;);
        return rq.historyBack(rsData.getMsg());
    }

    log.info(&quot;삭제 완료&quot;);
    return rq.redirectWithMsg(&quot;/likeablePerson/list&quot;, rsData.getMsg());
}</code></pre>
<ul>
<li>id 는 고유 번호이기 때문에 id를 기준으로 해당 삭제 객체를 찾아서 가져오기.</li>
<li>현재는 컨트롤러 단에서 객체를 판별하는 작업까지 맡았기 때문에 이 부분은 서비스단으로 옮기는 게 좋아보인다.</li>
<li>log의 경우 해당 컨트롤러가 제대로 작동하는지 확인 하는 용도로 추가해놨었다.</li>
</ul>
<p>삭제 판별 서비스단으로 옮기기.</p>
<pre><code class="language-java">@Transactional
public RsData deleteLikeablePerson(InstaMember instaMember, Long id) {
    LikeablePerson likeablePerson = findById(id).orElse(null);

    if (likeablePerson == null) {
        return RsData.of(&quot;F-1&quot;, &quot;취소된 호감 대상입니다.&quot;);
    }

    if (!Objects.equals(instaMember.getId(), likeablePerson.getFromInstaMember().getId())) {
        log.info(&quot;권한이 없습니다.&quot;);
        return RsData.of(&quot;F-1&quot;, &quot;권한이 없습니다.&quot;);
    }
    String tolikeablePersonUsername = likeablePerson.getToInstaMember().getUsername();

    likeablePersonRepository.delete(likeablePerson);

    return RsData.of(&quot;S-1&quot;, &quot;%s 님을 호감 취소하엿습니다.&quot;.formatted(tolikeablePersonUsername));
}</code></pre>
<ul>
<li>삭제판별과 삭제 진행은 service단에서 진행.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 카카오 로그인 추가]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B6%94%EA%B0%80</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B6%94%EA%B0%80</guid>
            <pubDate>Thu, 30 May 2024 11:03:39 GMT</pubDate>
            <description><![CDATA[<h3 id="카카오-로그인-등록방법">카카오 로그인 등록방법</h3>
<ul>
<li>kakao developer를 이용하여 앱을 등록하고 url 연결</li>
<li>로그인 기능 활성화 -&gt; oauth 리다이렉트 경로 설정 필요</li>
<li>Rest api 키가 생성되는게 이걸 가져와서 해당 프로젝트 yml파일에 설정으로 넣어준다.<ul>
<li>cliendId에 해당 rest api키를 넣어줘야한다.<ul>
<li>단 rest api키는 노출되면 안되기 때문에 추가적으로 암호화작업을 해주던가 또는 yml 파일 분리 작업을 통해서 해당 중요한 데이터가 들어있는 내용은 안올라가게 ignore 진행 필요.</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="카카오-rest-api를-yml에-등록하기-전에-해야할-일">카카오 rest api를 yml에 등록하기 전에 해야할 일</h3>
<p>Jasyspt를 사용하여 중요한 데이터들을 암호화하기 위해서 먼저 해당 설정을 먼저 jasypt를 추가하여 설정을 진행해준다.</p>
<ul>
<li>Gradle에 추가</li>
</ul>
<pre><code class="language-gradle">implementation group: &#39;com.github.ulisesbocchio&#39;, name: &#39;jasypt-spring-boot-starter&#39;, version: &#39;3.0.5&#39;</code></pre>
<ul>
<li><p>해당 부분은 여기에서 가져올 수 있다.</p>
</li>
<li><p>등록되있는 gradle</p>
<ul>
<li><a href="https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter/3.0.5">https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter/3.0.5</a></li>
</ul>
</li>
<li><p>해당 git</p>
<ul>
<li><a href="https://github.com/ulisesbocchio/jasypt-spring-boot">https://github.com/ulisesbocchio/jasypt-spring-boot</a></li>
</ul>
</li>
</ul>
<p>jasypt config 추가</p>
<pre><code class="language-java">@Configuration
@EnableEncryptableProperties
public class JasyptConfigDES {

    @Value(&quot;${jasypt.encryptor.password}&quot;)
    private String encryptKey;


    @Bean(name = &quot;jasyptStringEncryptor&quot;)
    public StringEncryptor stringEncryptor() {

        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();

        config.setPassword(encryptKey);
        config.setAlgorithm(&quot;PBEWithMD5AndDES&quot;);
        config.setKeyObtentionIterations(&quot;1000&quot;);
        config.setPoolSize(&quot;1&quot;);
        config.setProviderName(&quot;SunJCE&quot;);
        config.setSaltGeneratorClassName(&quot;org.jasypt.salt.RandomSaltGenerator&quot;);
        config.setIvGeneratorClassName(&quot;org.jasypt.iv.RandomIvGenerator&quot;);
        config.setStringOutputType(&quot;base64&quot;);
        encryptor.setConfig(config);

        return encryptor;

    }
}</code></pre>
<ul>
<li><p>문제 발생</p>
<ul>
<li><p>jasypt 암호화 작업이 제대로 안되서 username을 인지 못하는 문제 발생.</p>
</li>
<li><p>3.0.0 버전 부터 <code>PBEWithMD5AndDES</code> → <code>PBEWITHHMACSHA512ANDAES_256</code>로 알고리즘 변경이 됨</p>
</li>
<li><p><code>PBEWithMD5AndDES</code> 를 사용하려면 해당 부분을 바꿔줘야함.</p>
</li>
<li><pre><code>config.setIvGeneratorClassName(&quot;org.jasypt.iv.NoIvGenerator&quot;);</code></pre></li>
</ul>
</li>
</ul>
<p>이부분을 2시간여 정도 헤맸는데 여러 블로그를 뒤져보니 해답이 나오긴 했다. -&gt; 꼭 문서를 제대로 확인하자.</p>
<ul>
<li>해당 암호 알고리즘을 사용할려는 이유는 해당 암호화를 해주는 사이트가 존재하다보니 이걸 활용해서 암호화작업을 하고 싶어서.</li>
</ul>
<hr>
<h3 id="oauth20-라이브러리-추가">Oauth2.0 라이브러리 추가.</h3>
<ul>
<li>리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임 하는 방식.<ul>
<li>구글,카카오,네이버,페이스북등에서 제공하는 간편로그인 기능</li>
</ul>
</li>
</ul>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-oauth2-client&#39;</code></pre>
<p>카카오 rest api 문서에 필요한 데이터를 yml 에 등록한다.</p>
<pre><code class="language-yaml">  security:
    oauth2:
      client:
        registartion:
          kakao:
            clientId: ENC(CeUq2FUb7sCJtQ/UPCVqNKcZ1OHdIAE6zi9u99kVjZ7F8p5owXBJXQy3+KG6G/9r)
            scope:
              client-name: Kakao
              authorization-grant-type: authorization_code
              redirect-uri: http://localhost:8080/login/oauth2/code/kakao
              client-authentication-method: POST
            provider:
              kakao:
                authorization-uri: https://kauth.kakao.com/oauth/authorize
                token-uri: https://kauth.kakao.com/oauth/token
                user-info-uri: https://kapi.kakao.com/v2/user/me
                user-name-attribute: id</code></pre>
<p>springSecurity에 oauthlogin 시도시 해당 위치 접근 허용</p>
<pre><code class="language-java">.oauth2Login(
        oauth2Login -&gt; oauth2Login
                .loginPage(&quot;/member/login&quot;)
).</code></pre>
<p>oauth로 처리된 데이터를 받아줄 UserServiceOauth 버전을 추가</p>
<pre><code class="language-java">@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final MemberService memberService;

    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        String oauthId = oAuth2User.getName();

        String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();

        String username = providerTypeCode + &quot;__%s&quot;.formatted(oauthId);

        Member member = memberService.whenSocialLogin(providerTypeCode, username).getData();

        return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
    }

}

class CustomOAuth2User extends User implements OAuth2User {
    public CustomOAuth2User(String username, String password, Collection&lt;? extends GrantedAuthority&gt; authorities) {
        super(username, password, authorities);
    }

    @Override
    public Map&lt;String, Object&gt; getAttributes() {
        return null;
    }

    @Override
    public String getName() {
        return getUsername();
    }
}</code></pre>
<ul>
<li>Oauth 경로로 들어오는 경우 유저가 해당 코드들로 처리되게 된다.</li>
<li>providerTypeCode<ul>
<li>Oauth 로그인시 어디서 인증을 했는지 날라오기 때문에 어디서 인증했는지 분류하기 위해서 providerTypeCode 분리.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public RsData&lt;Member&gt; whenSocialLogin(String providerTypeCode, String username) {
    Optional&lt;Member&gt; findMember = findByUsername(username);

    if(findMember.isPresent()){
        return RsData.of(&quot;S-1&quot;,&quot;로그인 되었습니다.&quot;, findMember.get());
    }

    return join(providerTypeCode,username,&quot;&quot;);
}</code></pre>
<ul>
<li>이전에 같은 사람이 같은 소셜 로그인을 했는지 확인하는 코드<ul>
<li>소셜 로그인을 했었다면 그냥 그대로 로그인 했다고 넘겨주면되고, 아닌 경우 member에 해당 유저의 데이터를 멤버에 저장되게 진행해준다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="로그인-에러-발생">로그인 에러 발생</h3>
<pre><code>There was an unexpected error (type=Internal Server Error, status=500).
This class supports `client_secret_basic`, `client_secret_post`, and `none` by default. Client [kakao] is using [POST] instead. Please use a supported client authentication method, or use `setRequestEntityConverter` to supply an instance that supports [POST].
java.lang.IllegalArgumentException: This class supports `client_secret_basic`, `client_secret_post`, and `none` by default. Client [kakao] is using [POST] instead. Please use a supported client authentication method, or use `setRequestEntityConverter` to supply an instance that supports [POST].</code></pre><ul>
<li><p>로그인 인증 과정 문제에 대해서 찾아보니 Spring Security 5.8 이상버전에서는 </p>
</li>
<li><pre><code class="language-yaml">client-authentication-method: client_secret_post</code></pre>
<ul>
<li>이렇게 바꿔줘야한다.</li>
</ul>
</li>
</ul>
<p><a href="https://docs.spring.io/spring-security/reference/5.8/migration/servlet/oauth2.html">OAuth Migrations :: Spring Security</a></p>
<ul>
<li>문서를 찾아보니 post 는 client_secret_post로 대체되어있음.</li>
</ul>
<hr>
<p>암호화 방식 참고 블로그</p>
<p>: <a href="https://velog.io/@shdrnrhd113/Jaypt-%EB%A1%9C-yml-%ED%8C%8C%EC%9D%BC-%EC%95%94%ED%98%B8%ED%99%94-%ED%95%98%EA%B8%B0">https://velog.io/@shdrnrhd113/Jaypt-%EB%A1%9C-yml-%ED%8C%8C%EC%9D%BC-%EC%95%94%ED%98%B8%ED%99%94-%ED%95%98%EA%B8%B0</a></p>
<p>oauth2 로그인 사용하기  :</p>
<p><a href="https://deeplify.dev/back-end/spring/oauth2-social-login">https://deeplify.dev/back-end/spring/oauth2-social-login</a></p>
<p>해당 api 사용 방법 설명</p>
<p>: <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api">https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 호감도 폼 추가 및 호감목록 추가하기]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%98%B8%EA%B0%90%EB%8F%84-%ED%8F%BC-%EC%B6%94%EA%B0%80-%EB%B0%8F-%ED%98%B8%EA%B0%90%EB%AA%A9%EB%A1%9D-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%98%B8%EA%B0%90%EB%8F%84-%ED%8F%BC-%EC%B6%94%EA%B0%80-%EB%B0%8F-%ED%98%B8%EA%B0%90%EB%AA%A9%EB%A1%9D-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 24 May 2024 10:17:29 GMT</pubDate>
            <description><![CDATA[<h2 id="할-일">할 일</h2>
<pre><code class="language-markdown">- 본인이 좋아하는 사람 등록 폼
  - 본인의 인스타그램 회원정보 입력을 완료한 사람만 가능
  - 인스타그램 아이디
  - 매력포인트(외모, 성격, 능력)
  - 등록되어있지않은 인스타 멤버에게도 호감표현가능하게

- 호감목록 페이지 구현</code></pre>
<h2 id="호감-표시-구현-예정">호감 표시 구현 예정</h2>
<ul>
<li>호감 이유를 세가지 선택<ul>
<li>외모</li>
<li>성격</li>
<li>능력</li>
</ul>
</li>
<li>호감 표시의 경우 3개중 하나를 무조건 선택해야함.<ul>
<li>선택을 하지 않았을 경우 호감도를 선택해야합니다 메시지 나오게</li>
</ul>
</li>
</ul>
<h2 id="인스타-정보-입력을-하지-않으면-안내메세지-표현까지-진행">인스타 정보 입력을 하지 않으면 안내메세지 표현까지 진행</h2>
<ul>
<li>연결이 되어있는지 아닌지를 판단해서 만약 연결되있지 않았다면 본인의 인스타그램 아이디 입력하기 버튼이 보이게<ul>
<li>인스타 계정을 연결한 경우에는 호감 표현 폼이 보이게</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public boolean hasConnectedInstaMember() {
    return instaMember != null;
}</code></pre>
<ul>
<li>멤버에 boolean 함수 추가<ul>
<li>instamember가 비어있다면 false 리턴</li>
</ul>
</li>
</ul>
<pre><code class="language-html">&lt;th:block th:unless=&quot;${@rq.member.hasConnectedInstaMember}&quot;&gt;
    &lt;div&gt;먼저 본인의 인스타그램 아이디를 입력해주세요.&lt;/div&gt;

    &lt;div&gt;
        &lt;a href=&quot;/instaMember/connect&quot; class=&quot;btn btn-link&quot;&gt;본인의 인스타그램 아이디 입력하기&lt;/a&gt;
    &lt;/div&gt;
&lt;/th:block&gt;</code></pre>
<ul>
<li>th:unless -&gt; 해당 조건이 false 인 경우 출력됨.</li>
</ul>
<p>호감을 표현한 객체를 저장하기 위한 likeablePerson 객체</p>
<ul>
<li>누가 표시를 했는지<ul>
<li>인스타 멤버는 여러사람에게 호감표시가 가능</li>
</ul>
</li>
<li>누가 받았는지 관계가 필요하다.<ul>
<li>인스타 멤버는 여러사람에게 호감표시를 받을 수 있다.</li>
</ul>
</li>
</ul>
<p>두개의 연관관계는 일 대 다 관계로 형성된다.</p>
<p>instamember 일대다 관계 표현 추가</p>
<pre><code class="language-java">@OneToMany(mappedBy = &quot;fromInstaMember&quot;,cascade = CascadeType.ALL)
@OrderBy(&quot;id desc&quot;)
@Builder.Default
private List&lt;LikeablePerson&gt; fromLikeablePeople = new ArrayList&lt;&gt;();

@OneToMany(mappedBy = &quot;toInstaMember&quot;,cascade = CascadeType.ALL)
@OrderBy(&quot;id desc&quot;)
@Builder.Default
private List&lt;LikeablePerson&gt; toLikeablePeople = new ArrayList&lt;&gt;();</code></pre>
<p>인스타 멤버에 일대다 연결 작업 추가작업도 필요하지만 더 생각해봐야할 점은 호감도를 표시할 대상이 인스타 멤버로 저장되어 있지 않은 경우에 이 부분을 처리할 방도를 생각해야한다.</p>
<ul>
<li>인스타 id를 기반으로 호감도를 표시하기 때문에 해당 인스타 계정이 현재 내가 만들고 있는 웹사이트에 등록되어 있지 않을 수 있기 때문에</li>
</ul>
<p>InstaMemberService 에 추가</p>
<pre><code class="language-java">@Transactional
public InstaMember findByUsernameOrCreate(String username) {
    Optional&lt;InstaMember&gt; opInstaMember = findByUsername(username);

    if (opInstaMember.isPresent()) {
        return opInstaMember.get();
    }

    return create(username, &quot;U&quot;).getData();
}</code></pre>
<ul>
<li>instaMember에 등록되어있는 경우에는 해당 인스타 반환, 없는 경우 gender를 U로 해둔상태로 만들어둠.</li>
</ul>
<p>호감 목록 페이지 추가</p>
<pre><code class="language-html">&lt;html layout:decorate=&quot;~{/layout/layout.html}&quot;&gt;

&lt;head&gt;
    &lt;title&gt;당신이 좋아하는 사람들&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;

&lt;mai layout:fragment=&quot;main&quot;&gt;
    &lt;th:block th:unless=&quot;${@rq.member.hasConnectedInstaMember}&quot;&gt;
        &lt;div&gt;먼저 본인의 인스타그램 아이디를 입력해주세요.&lt;/div&gt;

        &lt;div&gt;
            &lt;a href=&quot;/instaMember/connect&quot; class=&quot;btn btn-link&quot;&gt;본인의 인스타그램 아이디 입력하기&lt;/a&gt;
        &lt;/div&gt;
    &lt;/th:block&gt;

    &lt;th:block th:if=&quot;${@rq.member.hasConnectedInstaMember}&quot;&gt;
        &lt;ul&gt;
            &lt;li th:each=&quot;likeablePerson: ${likeablePeople}&quot;&gt;
                &lt;span class=&quot;toInstaMember_username&quot; th:text=&quot;${likeablePerson.toInstaMember.username}&quot;&gt;&lt;/span&gt;
                &lt;span class=&quot;toInstaMember_attractiveTypeDisplayName&quot;
                      th:text=&quot;${likeablePerson.attractiveTypeDisplayName}&quot;&gt;&lt;/span&gt;
                &lt;a th:href=&quot;@{|delete/${likeablePerson.id}|}&quot; onclick=&quot;return confirm(&#39;정말로 삭제하시겠습니까?&#39;);&quot;&gt;삭제&lt;/a&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/th:block&gt;
&lt;/mai&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<ul>
<li>인스타 아이디가 인증되었을때만 보이게된다.<ul>
<li>삭제 버튼은 추가했지만 현재는 삭제는 구현되지 않았다.</li>
</ul>
</li>
</ul>
<p>호감 표시 및 호감 목록이 공용 레이아웃으로 보이게 header 에 추가</p>
<pre><code class="language-html">&lt;header&gt;
    &lt;a href=&quot;/&quot; class=&quot;btn btn-link&quot;&gt;메인&lt;/a&gt;
    &lt;a href=&quot;/member/login&quot; th:if=&quot;${@rq.logout}&quot; class=&quot;btn btn-link&quot;&gt;로그인&lt;/a&gt;
    &lt;a href=&quot;/member/join&quot; th:if=&quot;${@rq.logout}&quot; class=&quot;btn btn-link&quot;&gt;회원가입&lt;/a&gt;
    &lt;a href=&quot;/member/me&quot; th:if=&quot;${@rq.login}&quot; class=&quot;btn btn-link&quot;&gt;내 정보&lt;/a&gt;
    &lt;a href=&quot;/instaMember/connect&quot; th:if=&quot;${@rq.login}&quot; class=&quot;btn btn-link&quot;&gt;본인의 인스타그램 정보 입력&lt;/a&gt;
    &lt;a href=&quot;/likeablePerson/add&quot; th:if=&quot;${@rq.login}&quot; class=&quot;btn btn-link&quot;&gt;호감표시&lt;/a&gt;
    &lt;a href=&quot;/likeablePerson/list&quot; th:if=&quot;${@rq.login}&quot; class=&quot;btn btn-link&quot;&gt;호감목록&lt;/a&gt;
    &lt;a href=&quot;javascript:;&quot; th:if=&quot;${@rq.login}&quot; onclick=&quot;$(this).next().submit();&quot; class=&quot;btn btn-link&quot;&gt;로그아웃&lt;/a&gt;
    &lt;form th:if=&quot;${@rq.login}&quot; hidden th:action=&quot;|/member/logout|&quot; method=&quot;POST&quot;&gt;&lt;/form&gt;
    &lt;span th:if=&quot;${@rq.login}&quot; th:text=&quot;|${@rq.member.username}님 환영합니다.|&quot;&gt;&lt;/span&gt;
&lt;/header&gt;</code></pre>
<p><a href="https://github.com/YunJiW/GramGramProject/commit/21856eb8498a7b1fc6103f1d0787237c37eddb76">pr 부분</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 인스타 계정 정보 입력]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%9D%B8%EC%8A%A4%ED%83%80-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%EC%9E%85%EB%A0%A5</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%EC%9D%B8%EC%8A%A4%ED%83%80-%EA%B3%84%EC%A0%95-%EC%A0%95%EB%B3%B4-%EC%9E%85%EB%A0%A5</guid>
            <pubDate>Wed, 22 May 2024 13:28:43 GMT</pubDate>
            <description><![CDATA[<h3 id="인스타-계정-정보-입력">인스타 계정 정보 입력</h3>
<ul>
<li>본격적으로 인스타 그램 계정을 추가 하는 작업을 진행할 예정이다.<ul>
<li>현재는 진짜 인스타 계정을 연결하는게 아닌 임시로 계정을 추가해서 하는 작업을 진행할 예정<ul>
<li>후에는 test계정까지는 등록이 되게 진행해볼 예정.</li>
<li>test 계정이 아닌 다른 등록을 할려면 실제로 허가인증이 나야하는데 이게 굉장히 까다롭다...</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-markdown"># 할일
-  인스타그램 회원정보 입력
  -  로그인한 사람만 가능
  - 아이디
  -  성별

- 인스타 회원정보 폼 처리</code></pre>
<p>인스타 계정 정보에서 필요한 것들</p>
<ul>
<li>인스타 id 정보</li>
<li>성별</li>
</ul>
<p>현재 필요하다고 생각하는 것들은 성별 과 인스타 계정의 id 정보 정도가 있다.</p>
<ul>
<li>인스타 계정 등록은 로그인을 한 대상만 가능하게 진행.</li>
</ul>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
@RequestMapping(&quot;/instaMember&quot;)
public class InstaMemberController {

    @PreAuthorize(&quot;isAuthenticated()&quot;)
    @GetMapping(&quot;/connect&quot;)
    public String connect(){
        return &quot;/user/instaMember/connect&quot;;
    }
}</code></pre>
<p>connect.html 추가</p>
<ul>
<li>어떻게 만들건지 에 대해서 추가만 진행.</li>
</ul>
<pre><code class="language-html">&lt;html layout:decorate=&quot;~{layout/layout.html}&quot;&gt;

&lt;head&gt;
    &lt;title&gt;본인의 인스타그램 정보입력&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;

&lt;main layout:fragment=&quot;main&quot;&gt;
    &lt;script&gt;
        function ConnectForm__submit(form) {
            // username 이(가) 올바른지 체크

            form.username.value = form.username.value.trim(); // 입력란의 입력값에 있을지 모르는 좌우공백제거

            if (form.username.value.length == 0) {
                toastWarning(&#39;인스타그램 아이디를 입력해주세요.&#39;);
                form.username.focus();
                return;
            }

            if (form.username.value.length &lt; 4) {
                toastWarning(&#39;인스타그램 아이디를 4자 이상 입력해주세요.&#39;);
                form.username.focus();
                return;
            }

            const $checkedGenderRadioButton = $(form).find(&quot;[name=gender]:checked&quot;);

            if ($checkedGenderRadioButton.length == 0) {
                toastWarning(&#39;성별을 선택해주세요.&#39;);
                $(form).find(&quot;[name=gender]:first&quot;).focus();
                return;
            }

            form.submit(); // 폼 발송
        }
    &lt;/script&gt;

    &lt;form th:action method=&quot;POST&quot; class=&quot;p-10 max-w-sm flex flex-col gap-4&quot;
          onsubmit=&quot;ConnectForm__submit(this); return false;&quot;&gt;
        &lt;div&gt;
            &lt;input type=&quot;text&quot; name=&quot;username&quot; autocomplete=&quot;off&quot; maxlength=&quot;30&quot; placeholder=&quot;인스타그램 아이디&quot;
                   class=&quot;input input-bordered&quot;&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;div class=&quot;form-control&quot;&gt;
                &lt;label class=&quot;label cursor-pointer&quot;&gt;
                    &lt;span class=&quot;label-text&quot;&gt;남자&lt;/span&gt;
                    &lt;input type=&quot;radio&quot; name=&quot;gender&quot; value=&quot;M&quot; class=&quot;radio focus:bg-red-100&quot;&gt;
                &lt;/label&gt;
            &lt;/div&gt;
            &lt;div class=&quot;form-control&quot;&gt;
                &lt;label class=&quot;label cursor-pointer&quot;&gt;
                    &lt;span class=&quot;label-text&quot;&gt;여자&lt;/span&gt;
                    &lt;input type=&quot;radio&quot; name=&quot;gender&quot; value=&quot;W&quot; class=&quot;radio focus:bg-red-100&quot;&gt;
                &lt;/label&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;input type=&quot;submit&quot; value=&quot;정보입력&quot; class=&quot;btn btn-primary&quot;&gt;
        &lt;/div&gt;
    &lt;/form&gt;
&lt;/main&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<p>성별 선택이 안된 경우</p>
<ul>
<li>성별을 선택해달라는 메시지 뜨게</li>
</ul>
<p>const 뒤에 변수 명에 $가 붙는 이유</p>
<ul>
<li>jquery에서 일반적으로 구분하기 위해서 $를 붙이는 것<ul>
<li>무조건으로 $가 붙은게 jquery를 사용했다는 의미는 아니다.</li>
</ul>
</li>
</ul>
<p>jquery를 사용하지 않을 때 &#39;$&#39; 식별자 용도 2가지</p>
<ol>
<li>document.getElementById() 함수의 바로가기</li>
<li>템플릿 스트링<ol>
<li>${변수명}</li>
</ol>
</li>
</ol>
<h4 id="connectionform-추가">connectionForm 추가</h4>
<pre><code class="language-java">@Getter
@AllArgsConstructor
public class ConnectForm {

    @NotBlank
    @Size(min = 4, max = 30)
    private final String username;

    @NotBlank
    @Size(min=1,max= 1)
    private final String gender;
}</code></pre>
<p>인스타계정과 멤버는 1대1로 묶여있기 때문에 1대1 연관관계로 추가</p>
<p>member에 추가된 부분</p>
<pre><code class="language-java">@OneToOne
private InstaMember instaMember;

public void addInstaMember(InstaMember instaMember){
        this.instaMember = instaMember;
    }</code></pre>
<ul>
<li>setter 대신에 addInstaMember를 통해서 확실하게 인스타계정을 추가하겠다고 명시</li>
</ul>
<hr>
<p>const 뒤 $ 참고 블로그 : <a href="https://despiteallthat.tistory.com/133">https://despiteallthat.tistory.com/133</a></p>
<p><a href="https://velog.io/@s_sangs/JavaScript-dollor-underScore">https://velog.io/@s_sangs/JavaScript-dollor-underScore</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] - 좋다]]></title>
            <link>https://velog.io/@steady_aa/%EB%B0%B1%EC%A4%80-%EC%A2%8B%EB%8B%A4</link>
            <guid>https://velog.io/@steady_aa/%EB%B0%B1%EC%A4%80-%EC%A2%8B%EB%8B%A4</guid>
            <pubDate>Thu, 16 May 2024 01:15:31 GMT</pubDate>
            <description><![CDATA[<h1 id="문제링크">문제링크</h1>
<p><a href="https://www.acmicpc.net/problem/1253">https://www.acmicpc.net/problem/1253</a></p>
<h1 id="구현방법">구현방법</h1>
<ul>
<li><ul>
<li><p>N개의 수</p>
<ul>
<li>N 은 최대 2000개</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>숫자의 범위는 -1000000000 ~ 1000000000



어떤 수가 다른 두 수의 합으로 나타낼 수 있다면 good

수의 위치가 다르면 값이 같아도 다른 수.



정렬이 필요한가?

- 어떤 수가 다른 두 수의 합으로 나타 낼수 있다면 이기 때문에
- 모든 수의 대한 계산이 필요하지 않을까 -&gt; 정렬을 통해서 계산이 필요할 거 같다.



각 숫자에 대한 이분탐색을 통해서 해당 숫자를 만들수 있는지 확인하는 작업을 진행

- 이분탐색의 시간복잡도는 O(logN)이기 때문에 모든 계산보다 훨씬 빠름.



각 숫자를 만들수 있는지 이분탐색을 통해서 계산하면 끝인 문제이다.</code></pre><h1 id="알고리즘">알고리즘</h1>
<ul>
<li><h4 id="이분-탐색">이분 탐색</h4>
</li>
</ul>
<h1 id="code">CODE</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;


public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());

        int number[] = new int[N];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int idx = 0; idx &lt; N; idx++) {
            number[idx] = Integer.parseInt(st.nextToken());
        }
        Arrays.sort(number);

        int res = 0;

        //전체 숫자 확인
        for (int idx = 0; idx &lt; N; idx++) {
            int choice = number[idx];
            int left = 0;
            int right = N - 1;
            while (left &lt; right) {

                //같은 수가 나오면안되기 때문에 바꿔주기
                if (left == idx) left++;
                else if (right == idx) right--;

                //만약 둘이 같은 곳을 바라보면 안되기 때문에 끝내줘야함.
                if(left == right) break;

                if(number[left] + number[right] &gt; choice){
                    right--;
                }else if(number[left] + number[right] &lt; choice){
                    left++;
                }else {
                    res+=1;
                    break;
                }

            }
        }
        System.out.println(res);

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[그램그램 - 회원가입 후 이동]]></title>
            <link>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%9B%84-%EC%9D%B4%EB%8F%99</link>
            <guid>https://velog.io/@steady_aa/%EA%B7%B8%EB%9E%A8%EA%B7%B8%EB%9E%A8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%ED%9B%84-%EC%9D%B4%EB%8F%99</guid>
            <pubDate>Wed, 15 May 2024 06:13:33 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-markdown">#할일

- 회원가입이 후 로그인 페이지로 이동
    - /member/login 으로 리다이렉트 진행
- 아이디 중복으로 실패시 다시 회원가입화면으로 돌아가게</code></pre>
<p>회원가입 후에 이동하는 곳이 로그인페이지로 이동시키는 법</p>
<ul>
<li>회원가입 post를 보내고 로그인 페이지로 보내기.<ul>
<li>여기서 메시지를 추가적으로 보내고 싶은 경우<ul>
<li>메시지를 추가적으로 삽입시 한글의 경우 깨지는 현장이 존재하기 때문에 url encoding이 필요.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-javascript">    // 타임리프 문법(파라미터, ? 뒤에 입력된 매개변수들)
    const params = /*[[ ${param} ]]*/ null;

    if (params.msg) {
        toastNotice(params.msg[0]);
    }

    if (params.errorMsg) {
        toastWarning(params.errorMsg[0]);
    }</code></pre>
<ul>
<li>타임리프 문법으로  ? 뒤에 입력된 매개변수를 가져와서 메시지로 보내는 스크립트.</li>
</ul>
<pre><code class="language-java">return &quot;redirect:/member/login?msg=&quot; + Ut.url.encode(&quot;회원가입이완료되었습니다.\n 로그인 후 이용해주세요.&quot;);</code></pre>
<ul>
<li>회원가입 성공시 login으로 리다이렉트가 진행되면서 메시지창에 오른쪽 메시지가 사용되게 진행.</li>
</ul>
<h3 id="rsdata-도입하기">RsData 도입하기</h3>
<ul>
<li>규격을 만들기 위한 코드 도입.<ul>
<li>한번 규격을 잘 만들어 놓으면 여러 부분에서 사용이 가능해진다.</li>
<li>형식의 경우 실행결과코드, 메시지, 데이터 순으로 따져보니 json 형식과 꽤 유사하다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Getter
@Setter
@AllArgsConstructor
public class RsData&lt;T&gt; {

    private String resultCode;
    private String msg;
    private T data;

    public static &lt;T&gt; RsData&lt;T&gt; of(String resultCode, String msg, T data) {
        return new RsData&lt;&gt;(resultCode, msg, data);
    }

    public static &lt;T&gt; RsData&lt;T&gt; of(String resultCode, String msg) {
        return of(resultCode, msg, null);
    }

    public static &lt;T&gt; RsData&lt;T&gt; successOf(T data) {
        return of(&quot;S-1&quot;, &quot;성공&quot;, data);
    }

    public static &lt;T&gt; RsData&lt;T&gt; failOf(T data) {
        return of(&quot;F-1&quot;, &quot;실패&quot;, data);
    }

    public boolean isSuccess() {
        return resultCode.startsWith(&quot;S-&quot;);
    }

    public boolean isFail() {
        return isSuccess() == false;
    }
}</code></pre>
<ul>
<li><p>아이디 중복으로 실패하는 경우 확인 방법.</p>
<ul>
<li><p>RsData를 도입</p>
</li>
<li><pre><code class="language-java">//만약 존재한다면 이걸 반환
return RsData.of(&quot;F-1&quot;, &quot;해당 아이디(%s)는 사용중입니다.&quot;.formatted(username));

//회원가입에 성공한다면 이걸 반환
return RsData.of(&quot;S-1&quot;, &quot;회원가입이 완료되었습니다.&quot;, member);</code></pre>
</li>
</ul>
</li>
</ul>
<p>memberController에서 만약 실패했다는 걸 받게된 경우</p>
<ul>
<li>메시지를 띄우고 다시 돌아가게 하는 스크립트 코드를 추가한다.</li>
</ul>
<pre><code class="language-java">&lt;script&gt;
    alert(&#39;해당 아이디(user1)는 이미 사용중입니다.&#39;);
    history.back();
&lt;/script&gt;</code></pre>
<ul>
<li>user1으로 임시로 적용</li>
</ul>
<p><a href="https://github.com/YunJiW/GramGramProject/pull/7">PR 내용</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] - 전화번호 목록]]></title>
            <link>https://velog.io/@steady_aa/%EB%B0%B1%EC%A4%80-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D</link>
            <guid>https://velog.io/@steady_aa/%EB%B0%B1%EC%A4%80-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%EB%AA%A9%EB%A1%9D</guid>
            <pubDate>Wed, 15 May 2024 02:14:10 GMT</pubDate>
            <description><![CDATA[<h1 id="문제링크">문제링크</h1>
<p><a href="https://www.acmicpc.net/problem/5052">https://www.acmicpc.net/problem/5052</a></p>
<h1 id="구현방법">구현방법</h1>
<ul>
<li><p>일관성이 있는지 없는지 구하는 프로그램</p>
<ul>
<li>일관성을 유지하는 방법은 한 번호가 다른 번호의 접두인 경우가 없어야한다.</li>
</ul>
</li>
</ul>
<p>  번호 길이순으로 먼저 정렬을 진행.</p>
<p>  테스트케이스는 최대 50개</p>
<p>  전화번호의 수는 10000개</p>
<p>  포함 여부를 확인</p>
<ul>
<li>누적해서 확인 필요.</li>
</ul>
<p>  곰곰히 생각해보니 자바에서 접두어를 확인하는 함수가 존재한다는 걸 생각해냈다.</p>
<p>  startWith 함수를 이용한 확인방법</p>
<p>  먼저 정렬을 통해서 길이가 작은 순부터 확인 진행</p>
<ul>
<li><p>길이가 들쑥날쑥 인거보다는 길이 정렬을 통해서 비교도 편하게된다.</p>
</li>
<li><p>정렬을 통해서 사전순정렬이 진행되기 때문에 특정 문자열 바로 뒷 문자와 접두어 관계일 것이다.</p>
<ul>
<li>따라서 바로 뒷 문자의 앞부분이 같은 지 확인을 해서 같으면 접두어를 포함하는 것.</li>
</ul>
</li>
<li><h4 id="정렬을-할때-사전순으로-정렬이-된다는-거를-까먹지-말자">정렬을 할때 사전순으로 정렬이 된다는 거를 까먹지 말자.</h4>
</li>
</ul>
<h1 id="알고리즘">알고리즘</h1>
<ul>
<li><h4 id="sort">Sort</h4>
</li>
</ul>
<h1 id="code">CODE</h1>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int T = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();

        String[] phone;
        while (T-- &gt; 0) {
            int N = Integer.parseInt(br.readLine());
            phone = new String[N];
            for (int idx = 0; idx &lt; N; idx++) {
                phone[idx] = br.readLine();
            }
            Arrays.sort(phone);

            if (isContains(N, phone)) {
                sb.append(&quot;YES\n&quot;);
            } else {
                sb.append(&quot;NO\n&quot;);
            }
        }
        System.out.println(sb);

    }

    //포함여부 확인
    //포함이 되어있다면 일관성이 없으므로 false
    //포함이 되어있지 않다면 일관성이 있으므로 true
    private static boolean isContains(int n, String[] phone) {

        for(int idx = 0; idx &lt;n-1;idx++){
            if(phone[idx+1].startsWith(phone[idx])){
                return false;
            }

        }

        return true;
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>