<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Stupidity Tries</title>
        <link>https://velog.io/</link>
        <description>이제 3년차 개발새발자. 제가 보려고 정리해놓는 글이기 때문에 다소 미흡한 내용이 많습니다.</description>
        <lastBuildDate>Tue, 07 Jan 2025 08:22:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Stupidity Tries</title>
            <url>https://velog.velcdn.com/images/kim_kim/profile/e322c4d6-5bee-4a1d-9b47-cf2e6eee93e5/image.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Stupidity Tries. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kim_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring MVC & JSP 환경에서 백엔드와 프론트엔드 연결이 안 될때 troubleshooting]]></title>
            <link>https://velog.io/@kim_kim/Spring-MVC-JSP-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B0%B1%EC%97%94%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%B0%EA%B2%B0%EC%9D%B4-%EC%95%88-%EB%90%A0%EB%95%8C-troubleshooting</link>
            <guid>https://velog.io/@kim_kim/Spring-MVC-JSP-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B0%B1%EC%97%94%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%B0%EA%B2%B0%EC%9D%B4-%EC%95%88-%EB%90%A0%EB%95%8C-troubleshooting</guid>
            <pubDate>Tue, 07 Jan 2025 08:22:15 GMT</pubDate>
            <description><![CDATA[<p>프론트에서 보낸 요청이 적절한 엔드포인트롤 타고 Controller의 메소드와 연결되는 과정에 문제가 발생하거나 예상했던 것과 다른 결과가 나올 경우 원인을 찾기 힘들 때가 많다. 200 응답을 받았는데도 실제로는 아예 목표 엔드포인트를 타지 않거나 오류 메시지조차 제대로 출력되지 않는 경우가 있기 때문이다. 다음은 내가 문제의 원인을 탐색하는 일반적인 순서이다.</p>
<ol start="0">
<li><p>디버그 모드로 실행, 문제의 엔드포인트 메소드 코드 첫 줄에 breakpoint를 찍어 실제로 엔드포인트를 전혀 타지 못하는 것인지, 혹인 엔드포인트를 정상적으로 타지만 그 후의 동작에서 문제가 발생하는 것인지 구분한다</p>
</li>
<li><p>프론트에서 정상적인 주소를 호출했는지 확인</p>
<ul>
<li>로컬 환경에서 실행할 경우 포트 번호 확인</li>
<li><code>Run/Debug Configurations &gt; Deployment 탭 &gt; Application context:</code> 에 <code>/</code> 외에 별도로 설정된 경로가 있으면 도메인 주소 뒤에 해당 경로를 붙여주어야 한다.</li>
</ul>
</li>
<li><p>Controller Method의 Annotation을 제대로 붙였는지 확인</p>
<ul>
<li>@RequestMapping은 POST와 GET 모두를, @PostMapping과 @GetMapping은 각각 POST와 GET 요청만을 받는다</li>
<li><a href="https://tecoble.techcourse.co.kr/post/2021-05-11-requestbody-modelattribute/">@RequestBody나 @ModelAttribute를 제대로 붙였는지 점검한다</a>. 프론트 단에서 데이터를 JSON 형식으로 보내는지, 혹은 form이나 query string으로 보내는지 확인해보자.</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>프론트에서 넘겨받은 parameter/payload가 매핑될 자바 객체 필드의 자료형 확인</p>
<ul>
<li>프론트에서 넘겨주는 parameter/payload가 문제를 일으키는 유력한 범인 후보일 경우, 넘겨주는 필드를 모두 제거한 후 하나씩 추가해보는 식으로 정확히 문제가 되는 부분을 찾을 수도 있다 </li>
<li>int, boolean, char과 같은 primitive type은 null값을 받을 수 없다. 이런 자료형의 필드로 null값이 들어갈 경우 오류 메시지도 뱉지 않고 목표 엔드포인트를 타지 않는다 <ul>
<li>null값이 들어갈 수도 있는 필드의 경우 대신 대응되는 Object type(예: int -&gt; Integer)을 사용하거나, 프론트에서 해당 값이 null로 들어가지 못하게 검사하거나, 적절한 디폴트 값을 넣어주자</li>
</ul>
</li>
<li>프론트에서 넘어온 파라미터에 매핑되는 백엔드 DTO 객체 필드 내에 일부 필드가 누락되어, 넘어온 파라미터에 매핑될 필드가 없는 것은 의외로 문제가 되지 않았다</li>
</ul>
</li>
<li><p>view name을 string 형식으로 반환할 경우 해당 view 이름과 경로를 올바르게 작성했는지 점검한다</p>
<ul>
<li>다른 곳들에서도 동일한 문제가 발생했다면 <code>spring.mvc.view.suffix</code> 나 <code>UrlBasedViewResolver</code> 등지에 제대로 view resolver 관련 설정이 되어있지 않아 문제가 발생하는 것일수도 있다.
예 01)<pre><code class="language-xml">&lt;bean class=&quot;org.springframework.web.servlet.view.UrlBasedViewResolver&quot; p:order=&quot;2&quot;
      p:prefix=&quot;/WEB-INF/jsp/&quot; p:suffix=&quot;.jsp&quot;
      p:viewClass=&quot;org.springframework.web.servlet.view.JstlView&quot;/&gt;</code></pre>
예 02)<pre><code class="language-yml">spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/
      suffix: .jsp    </code></pre>
</li>
</ul>
</li>
<li><p>프론트에서 넘겨받은 parameter/payload가 매핑될 자바 객체 필드의 Validation annotation 확인(사용할 시)</p>
<ul>
<li>Integer형 필드에 <code>org.hibernate.validator.constraints.@NotEmpty</code> 사용 시 오류 메시지 출력 없이 엔드포인트를 제대로 타지 않는 문제가 있어 <code>javax.validation.constraints.@NotNull</code>로 바꿔주니 정상적으로 동작했다다. <a href="https://priming.tistory.com/129">@NotEmpty나 @NotBlank는 값의 길이를 체크하는데, Integer와 같은 타입은 길이가 없기 때문에 사용할 수 없기 때문</a>이라고 한다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[cannot find symbol List.of() 문제]]></title>
            <link>https://velog.io/@kim_kim/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B2%84%EC%A0%84-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@kim_kim/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B2%84%EC%A0%84-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 03 Jan 2025 06:01:05 GMT</pubDate>
            <description><![CDATA[<p>스프링 프로젝트를 하는 중 간혹 어떤 작업자가 JRE/JDK 버전 9 이상에서 사용 가능한 <code>List.of()</code>를 사용하고 다른 작업자들은 9 미만 버전을 이용하여 <code>cannot find symbol List.of()</code> 컴파일 오류가 발생하는 등의, 버전이 서로 다르게 설정되어 발생하는 문제가 적지 않게 일어난다. 어떤 옵션의 JRE/JDK 버전을 프로젝트 개발 표준에 따라 9 이상으로 설정해도 다른 곳에서 9 미만으로 설정된 곳이 있어 문제가 발생할 수 있으니 정말 번거롭지만 스프링 프로젝트를 시작할 때 다같이 프로젝트 요구 사항에 따라 관련된 모든 설정값을 설정하고 가는 것이 바람직하다. </p>
<p>다음은 인텔리제이 아이디어에서 JRE/JDK 버전 설정이 필요한 항목들이다. 여기에 너무 잘 정리되어 있어서 굳이 이 글을 작성해야 했나 싶기도 하다... </p>
<ul>
<li><a href="https://dev-emmababy.tistory.com/139">[IntelliJ] JAVA 버전 바꾸는 방법(JDK버전)</a></li>
</ul>
<ol>
<li><p><code>Settings &gt; Build, Execution, Deployment &gt; Build Tools</code></p>
<ul>
<li>Maven 사용 시 importing, Runner에서 사용되는 JDK/JRE 버전 확인/변경</li>
<li>Gradle 사용 시 Gradle Projects 아래 프로젝트의 <code>Gradle &gt; Gradle JVM:</code> 항목 변경 </li>
</ul>
</li>
<li><p><code>Settings &gt; Build, Execution, Deployment &gt; Compiler &gt; Java Compiler</code></p>
<ul>
<li><code>Project bytecode version:</code> 항목 변경 </li>
</ul>
</li>
</ol>
<ol start="3">
<li><code>Project Structure &gt; Project Settings &gt; Modules</code><ul>
<li>우측 <code>Sources &gt; Language level</code> 항목 변경</li>
<li>우측 <code>Dependencies &gt; Module SDK:</code> 항목 변경</li>
</ul>
</li>
</ol>
<ol start="4">
<li><code>Project Structure &gt; Project Settings &gt; Project</code> <ul>
<li><code>SDK:</code> 항목 변경</li>
<li><code>Language level:</code> 항목 변경</li>
</ul>
</li>
</ol>
<ol start="5">
<li><code>Run/Debug Configurations&gt; config 선택 &gt; Server</code>  <ul>
<li><code>JRE:</code> 항목 변경</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_241126]]></title>
            <link>https://velog.io/@kim_kim/TIL241126</link>
            <guid>https://velog.io/@kim_kim/TIL241126</guid>
            <pubDate>Tue, 26 Nov 2024 01:19:52 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Controller에서 <code>@ModelAttribute</code>로 프론트에서 넘어온 파라메터를 매핑하는데 계속 실패했다. 오류가 나는 대신 200 응답을 받는데 실제로는 아예 해당 엔드포인트를 타고 들어가지도 않는다. 내 친구 챗GPT에게 하소연하자 parameter가 하나도 없는 상태에서 하나씩 더해보며 범인을 찾아보자는데, 범인은 DTO 내에서 데이터 타입이 <code>int</code>인 필드였다!!! 해당 변수명 앞으로 null값이 들어가는데, 데이터 타입이 <code>Integer</code>라면 해당 값으로 null값이 들어와도 매핑이 정상적으로 되지만 <code>int</code>는 값이 null로 들어오는 것을 정상 처리할 수 없어 엔드포인트를 정상적으로 타지도 않았던 것 같다. 해당 변수의 타입은 내가 수정할 수 있는게 아니어서 해당 파라미터로 null값이 넘어가지 않도록 프론트에서 처리했다. <ul>
<li>프론트에서 넘어온 파라미터에 매핑되는 백엔드 DTO 객체 필드 내에 일부 필드가 누락되어 넘어온 파라미터 일부가 매핑될 필드가 없는 것은 의외로 문제가 되지 않았다. </li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Generative AI with Vertex AI: Text Prompt Design: Challenge Lab 간단 후기]]></title>
            <link>https://velog.io/@kim_kim/Generative-AI-with-Vertex-AI-Text-Prompt-Design-Challenge-Lab-%EA%B0%84%EB%8B%A8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim_kim/Generative-AI-with-Vertex-AI-Text-Prompt-Design-Challenge-Lab-%EA%B0%84%EB%8B%A8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 23 Oct 2024 02:03:34 GMT</pubDate>
            <description><![CDATA[<p>구글 클라우드의 <code>Text Prompt Engineering Technique</code> 코스에서 <code>Generative AI with Vertex AI: Text Prompt Design: Challenge Lab</code>을 완료한 후 느낀 점들이다. 나중에 또 이런 방식의(jypyter notebook에 Gemini 관련 코드를 작성) 시험을 칠 일을 대비해서 기록해둔다.</p>
<p>TL;DR 채점 기준이 너무 엄격하면서도 모호하다. <code>Challenge Lab</code>의 공통적인 문제점이다 </p>
<ul>
<li>(task 1)주어진 프로젝트 ID와 Region 정보를 이용해 모델을 초기화하라는 지시가 있었는데 정상적으로 지시를 수행했는데도 채점할 때 계속 오답이 있다는 메시지가 떴다. 커널을 재시작해도 해결되지 않았는데 재시험을 볼 때는 정상적으로 통과되었다. 영문을 모르겠다</li>
<li>(task 2)반드시 instruction을 잘 읽어보고 모두 따를 것. <code>프롬프트를 수정하기 전 default 프롬프트로 한번 돌려서 결과를 확인하라</code>와 같은 지시가 있을 때 프롬프트 수정 전 주어진 기본 프롬프트를 돌리지 않으면 채점에서 오답이 뜨는 것을 확인했다</li>
<li>(task 3)문제에서 요구하는 내용과 형식의 response 값을 얻었는데도 계속 오답 처리가 되었는데 프롬프트에 &#39;Make sure NOT to include surplus markdown.&#39;라는 지시를 포함시켜 response에서 title을 빼게 하니 통과되었다(정말로 title이 포함되어 오답이 뜬 것이었는지 추가적인 검증 필요함)</li>
<li>(task 4)문제에서 요구하는 내용의 response 값을 얻었는데도 계속 오답 처리가 되다가 instruction에 포함된 단어들을 prompt에 다 우겨넣으니 <code>response.text</code>가 이전과 동일한데도 통과된 일이 있었다. 결과값 뿐만이 아니라 prompt나 jupyter notebook에 작성한 코드도 채점 대상이 되는 것 같다</li>
<li>(task 5)문제에서 요구하는 내용의 response 값을 얻었고, 이보다 더 지시에 부합하는 값을 얻을 수 없을 거라 생각했는데 채점에서 오답이 떠서 분노로 계속 채점하기 버튼을 클릭하다 보니 통과되었다. 무엇이 문제였을까?</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩테스트용 파이썬 문법 및 내장함수 간단 정리]]></title>
            <link>https://velog.io/@kim_kim/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9A%A9-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EB%B2%95-%EB%B0%8F-%EB%82%B4%EC%9E%A5%ED%95%A8%EC%88%98-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kim_kim/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9A%A9-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%AC%B8%EB%B2%95-%EB%B0%8F-%EB%82%B4%EC%9E%A5%ED%95%A8%EC%88%98-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 15 Oct 2024 02:29:10 GMT</pubDate>
            <description><![CDATA[<p>오랜만에 파이썬 쓰려니 문법이 하나도 기억이 안 나서 내가 보려고 정리함
<a href="https://docs.python.org/3/">공식 documentation</a>
<a href="https://www.w3schools.com/python/default.asp">파이썬 기초 튜토리얼</a></p>
<h1 id="loops">Loops</h1>
<h2 id="for-loops">For Loops</h2>
<ul>
<li><code>break</code>, <code>continue</code> 사용 가능함</li>
<li>문자열도 iteratable함</li>
<li><code>range(start, end, [increment])</code>도 iteratable함</li>
<li><code>else:</code> 사용 가능: break 없이 모든 loop가 완료되었을 시에만 실행됨<pre><code class="language-python">fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;]
for x in fruits:
print(x)</code></pre>
</li>
</ul>
<h2 id="while-loops">While Loops</h2>
<ul>
<li><code>break</code>, <code>continue</code> 사용 가능함</li>
<li>무한루프가 되지 않도록 주의!!!</li>
<li><code>else:</code> 사용 가능: condition이 true가 아니게 되었을 때 실행<pre><code class="language-python">i = 1
while i &lt; 6:
print(i)
i += 1
else: 
print(&quot;loop ended&quot;)</code></pre>
</li>
</ul>
<hr>
<h1 id="ifelse">if...else</h1>
<ul>
<li>and, or, not 사용 가능<pre><code class="language-python">if condition1:
  do_stuff()
elif condition2:
  do_another_stuff()
else:
  do_something_else()</code></pre>
</li>
</ul>
<hr>
<h1 id="숫자-연산">숫자 연산</h1>
<h2 id="max">max</h2>
<pre><code class="language-bash">&gt;&gt;&gt; help(max)
Help on built-in function max in module builtins:

max(...)
    max(iterable, *[, default=obj, key=func]) -&gt; value
    max(arg1, arg2, *args, *[, key=func]) -&gt; value

    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the largest argument.</code></pre>
<h2 id="min">min</h2>
<pre><code class="language-bash">&gt;&gt;&gt; help(min)
Help on built-in function min in module builtins:

min(...)
    min(iterable, *[, default=obj, key=func]) -&gt; value
    min(arg1, arg2, *args, *[, key=func]) -&gt; value

    With a single iterable argument, return its smallest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the smallest argument.</code></pre>
<h2 id="증감연산자"><del>증감연산자</del></h2>
<p>파이썬에서는 증감연산자 <code>i++</code>, <code>i--</code>, <code>++i</code>, <code>--i</code>를 사용하지 않는다. 대신 <code>i += 1</code>과 같은 방식으로 표현한다.</p>
<h2 id="divmod">divmod</h2>
<p><code>divmod(a, b)</code>는 <code>(a // b, a % b)</code>를 반환</p>
<hr>
<h1 id="string">String</h1>
<h2 id="split">split</h2>
<p>문자열을 delimeter 기준으로 나누어 list로 반환.
delimeter가 주어지지 않으면 공백 문자를 기준으로 나눔.
<code>str.split([delimeter], [maxsplit])</code></p>
<h2 id="zfill">zfill</h2>
<p>string 앞에 지정된 길이가 될때까지 0을 채워줌.</p>
<pre><code class="language-python">&gt;&gt; s = &quot;50&quot;
&gt;&gt; s.zfill(4)
&#39;0050&#39;</code></pre>
<h1 id="list">List</h1>
<h2 id="sorted">sorted</h2>
<p>주어진 list의 sort된 버전을 반환함.</p>
<pre><code class="language-python">l = [7, 3, 5, 1]
l = sorted(l)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[구글 OTP Spring 구현]]></title>
            <link>https://velog.io/@kim_kim/%EA%B5%AC%EA%B8%80-OTP-Spring-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@kim_kim/%EA%B5%AC%EA%B8%80-OTP-Spring-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 09 Aug 2024 07:00:47 GMT</pubDate>
            <description><![CDATA[<h1 id="otp란">OTP란?</h1>
<ul>
<li><a href="https://en.wikipedia.org/wiki/One-time_password">위키피디아에 잘 설명되어 있다</a></li>
</ul>
<h1 id="구글-otpgoogle-authenticatior">구글 OTP(Google Authenticatior)</h1>
<p><a href="https://en.wikipedia.org/wiki/Google_Authenticator">위키피디아 문서</a></p>
<ul>
<li><p>보안 문제로 요즘은 잘 사용되지 않고 Authy, Aegis, BitWarden이 더 많이 쓰인다는 것 같다.</p>
</li>
<li><p>안드로이드 앱의 <a href="https://github.com/google/google-authenticator-android/">오픈 소스 포크</a>가 깃허브에서 공개되었으나 21년도 이후로 업데이트가 중단된 상황이다. </p>
<h1 id="구현">구현</h1>
</li>
<li><p>지정된 <code>WINDOW_SIZE</code>만큼 현재 시각 앞뒤로 코드를 추가 검증한다. 0일 경우 현재 시각만을 기준으로 검사한다.</p>
</li>
<li><p>랜덤하게 키를 생성할 때 <code>SECRET_KEY_LENGTH</code>를 참조해 키의 길이를 결정한다. 20 이상으로 하는 것이 추천됨. </p>
<ul>
<li><p>key 길이가 len(key) % 8 == 1 일 경우 (예: 17자, 33자) 코드가 비정상적으로 생성되는 문제가 있다. Google Authenticator에서는 해당 문제를 이후 수정한 것으로 보이나 오픈 소스로 공개된 코드와 이를 활용해 작성한 코드 및 라이브러리(warrenstrange GoogleAuth)에는 문제가 여전하다. 해당 길이를 피해서 키를 생성해야 한다.</p>
<h2 id="변수-설정">변수 설정</h2>
<pre><code class="language-java">// 양의 정수. 검증 실패 시 현재 시각 전후로 해당 변수로 지정된 횟수만큼 추가 검증
final int WINDOW_SIZE = 3;

// 길이 20 이상, 8의 배수인 양수로 설정하는 것을 추천함. 길이가 8의 배수 + 1이면 인증 제대로 안되는 문제 있음
final int SECRET_KEY_LENGTH = 32;</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="otp-코드-계산">OTP 코드 계산</h2>
<pre><code class="language-java">    public int calculateOtpCodeFromKey(String otpKey, long t) throws NoSuchAlgorithmException, InvalidKeyException, EncoderException {
        byte[] timeData = new byte[8];
        long value = t;
        for (int i = 8; i-- &gt; 0; value &gt;&gt;&gt;= 8){
            timeData[i] = (byte) value;
        }

        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(otpKey);
        SecretKeySpec signKey = new SecretKeySpec(decodedKey, &quot;HmacSHA1&quot;);
        Mac mac = Mac.getInstance(&quot;HmacSHA1&quot;);
        mac.init(signKey);
        byte[] hashedOtpKey = mac.doFinal(timeData);

        int offset = hashedOtpKey[20 - 1] &amp; 0xF;
        long truncatedHash = 0;
        for (int i = 0; i &lt; 4; ++i) {
            truncatedHash &lt;&lt;= 8;
            // we just keep the first byte
            truncatedHash |= (hashedOtpKey[offset + i] &amp; 0xFF);
        }

        truncatedHash &amp;= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return (int) truncatedHash;
    }</code></pre>
<h2 id="otp-코드-검증">OTP 코드 검증</h2>
<pre><code class="language-java">    public Boolean verifyOtpCode(int otpCode, String otpKey) throws NoSuchAlgorithmException, InvalidKeyException, EncoderException {
        long t = new Date().getTime() / 30000;
        // 현재 시간 기준으로 우선 검사
        if (otpCode == calculateOtpCodeFromKey(otpKey, t)){
            return true;
        }
        // 최초 검사 실패 시 현재 시간 전후로 WINDOW_SIZE 만큼 추가 검사
        for (int i = -1 * this.WINDOW_SIZE; i &lt;= this.WINDOW_SIZE; i++){
            if (i == 0){
                continue;
            }
            if (otpCode == calculateOtpCodeFromKey(otpKey, t + i)){
                return true;
            }
        }
        // 검사 실패: 코드 불일치
        return false;
    }</code></pre>
<h2 id="otp-키-생성">OTP 키 생성</h2>
<pre><code class="language-java">    public String generateRandomBase32Key(){
        // Allocating the buffer
        byte[] buffer = new byte[this.SECRET_KEY_LENGTH * 3];
        // Filling the buffer with random numbers
        new SecureRandom().nextBytes(buffer);
        // Getting the key &amp; converting it to Base32
        Base32 codec = new Base32();
        byte[] bEncodedKey = codec.encode(buffer) ;
        return new String(bEncodedKey).substring(0, this.SECRET_KEY_LENGTH);
    }</code></pre>
<h2 id="테스트">테스트</h2>
<pre><code class="language-java">        String generatedKey = otpUtil.generateRandomBase32Key();
        long t = new Date().getTime() / 30000;
        System.out.println(&quot;now: &quot; + new Date());
        int calculatedCode = otpUtil.calculateOtpCodeFromKey(generatedKey, t);
        System.out.println(&quot;key: &quot; + generatedKey);
        System.out.println(&quot;code: &quot; + calculatedCode);
        assert otpUtil.verifyOtpCode(calculatedCode, generatedKey);</code></pre>
<h1 id="참고한-문서">참고한 문서</h1>
<p><a href="https://www.javacodegeeks.com/2011/12/google-authenticator-using-it-with-your.html">Google Authenticator: Using It With Your Own Java Authentication Server</a></p>
<p><a href="https://zero-gravity.tistory.com/221">[Java] Google Authenticator(Google OTP)를 이용한 개발.</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 관리사 2급 합격 후기]]></title>
            <link>https://velog.io/@kim_kim/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B4%80%EB%A6%AC%EC%82%AC-2%EA%B8%89-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim_kim/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B4%80%EB%A6%AC%EC%82%AC-2%EA%B8%89-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 09 Jul 2024 06:41:06 GMT</pubDate>
            <description><![CDATA[<p> 회사 업무가 잠시 농한기에 접어들어 간혹 돌발적으로 발생하는 수정 사항을 처리하는 것 외에 할 일이 많지 않아 시간을 때우기 위해 자격증 공부를 하게 되었다. 네트워크 관리사 2급을 선택한 이유는 일단 국가 공인 자격증이며, 정보처리기사와 독학사 등등에서 얼기설기 쌓은 네트워크 관련 지식과 일하면서 주워들은 것들을 보다 체계적으로 정리할 필요를 느껴서이다(합격하기 비교적 쉽다고도 들었다).</p>
<h1 id="필기80점대">필기(80점대)</h1>
<h2 id="공부-방법">공부 방법</h2>
<ul>
<li>많은 합격 후기 글에서 좋은 교재가 없으니 교재 구입은 추천하지 않는다는 내용이 공통적으로 있어 교재를 별도로 구입하지 않고 <a href="https://www.comcbt.com/">전자문제집 CBT</a>에서 2019 - 2024 기출을 하루에 한두개씩 풀었다. <ul>
<li>처음 풀었을 때는 총점이 50점대가 나왔으나 필기 시험을 보기 직전에는 꾸준히 90점 이상이 나왔다. 정작 시험에서는 80점대 점수를 맞았지만...</li>
</ul>
</li>
<li>리눅스 관련 문제는 리눅스를 간단한 명령어만이라도 사용해본 적이 있다면 조금만 공부해도 수월하게 풀 수 있을 수준이다.</li>
<li>기출을 풀고 틀린 문제는 스프레드시트로 오답 노트를 만들어 정리했다. 이전에 틀린 적이 있는데 또 틀린 문제는 별도로 표시해 반복해서 풀어본다.</li>
<li>시험에 많이 나오는데 내가 잘 모르는 주요 주제들은 위키피디아, 개인 블로그, <a href="https://www.geeksforgeeks.org/">geeksforgeeks</a> 등지에서 관련 정보를 찾아 정리했다.<ul>
<li>OSI 7계층(+ TCP/IP 4계층)별 특징, 기능, 주요 프로토콜과 그 기능, 장비, PDU</li>
<li>IPv4 vs IPv6 특징, 차이</li>
<li>IPv4 주소 보고 클래스 구분, private network 범위, 서브네팅 계산&amp;할당 가능한 호스트 수 계산, 브로트캐스트 주소 찾기</li>
<li>IPv6 주소 형식, 생략법</li>
<li>IEEE 802.3(이더넷, CSMA/CD), .4(토큰 버스), .5(토큰 링), .11(CSMA/CA) </li>
<li>ICMP 메시지 타입과 그 기능</li>
<li>CSMA/CD vs CSMA/CA</li>
<li>IP vs TCP vs UDP</li>
<li>TCP flag들과 3 way handshake 절차</li>
<li>재전송 기반 오류 제어: ARQ 방식의 종류</li>
<li>리눅스 기본적인 디렉토리 구조와 그 기능</li>
<li>리눅스와 윈도우 터미널 기본적인 명령어와 그 기능</li>
<li>라우팅 프로토콜: 사용되는 알고리즘, RIP vs OSPF</li>
<li>주요 포트 번호와 그 포트를 사용하는 어플리케이션(TCP, UDP 구분 필수)</li>
<li>브로드캐스트 vs 멀티캐스트 vs 애니캐스트 vs 유니캐스트</li>
<li>RAID: 특히 0, 1 방식과 차이</li>
</ul>
</li>
</ul>
<h2 id="시험-당일-후기">시험 당일 후기</h2>
<ul>
<li>문제은행 방식이라 그런지 이전에 풀었던 기출에서 대부분의 문제가 나온 것 같다(이전에 본적이 있는데도 또 틀리는 문제도 있다)</li>
<li>만점자 배출을 억제하기 위해서인지 절대 못 풀만한 새로운 문제가 두어문제 나왔다. 공부를 한다고 풀 수 있는 문제가 아니었다. 이런 문제는 어쩔 수 없다...</li>
<li>선린인터넷고등학교에서 시험을 봤는데, 후문으로 들어가는게 고사장에 더 가까울 것 같으나 후문이 잠겨있다. 정문을 사용하자.</li>
</ul>
<h1 id="실기945">실기(94.5)</h1>
<h2 id="공부-방법-1">공부 방법</h2>
<ul>
<li><p><a href="https://www.youtube.com/@TV-jx3dr">햄릿슈 유투브</a></p>
<ul>
<li>이 분 영상만 입문 시리즈부터 최신 문제 복원/해설 시리즈까지 보며 공식 에뮬레이터로 따라해도 왠만하면 합격 가능할 것 같다. </li>
<li>수험생들이 많이 궁금해하는 것들을 모아 출제 기관에 문의를 넣어 받은 답을 공개한 영상도 있다.</li>
<li>문제 풀이 영상에서 풀이 전 문제를 띄워놓은 장면에서 유투브 영상을 잠시 정지시켜 두고, 직접 에뮬레이터로 문제를 풀어본 후 풀이를 보며 내가 제출한 것과 비교해보는 방식으로 영상을 활용했다.</li>
<li>네트워크 엔지니어링 분야 전문가가 아니라 자세한 설명은 기대할 수 없다는 단점이 있다(본인도 영상에서 그렇게 말씀하시곤 한다)</li>
<li>좀 오래된 영상에는 간간히 잘못된 내용이 나오기도 한다. 댓글에서 누가 이를 지적하거나 업로더가 직접 댓글로 정정하니 댓글을 읽어보는 것이 좋다.</li>
</ul>
</li>
<li><p><a href="https://tech-network.tistory.com">우진아빠의 네트워크 실무</a></p>
<ul>
<li>20년 넘게 네트워크 분야에서 엔지니어로 근무하신분답게 설명이 자세하고 실무에서는 실제로 어떻게 하는지 이야기를 많이 해주신다. 보충 설명이 필요하거나 자세한 원리가 궁금할때 해당 블로그에서 찾아보는 식으로 많이 활용했다.</li>
<li>실무자의 실전 노하우가 담긴 심화 문제도 출제해주신다.</li>
<li>시험 전 단답형 문제를 한번 훑어보고 갔는데 그러지 않았다면 시험 때 두어문제 더 틀렸을 것 같다... 설명이 자세하고 잘 정리되어 있어 기억에 잘 남았다.</li>
<li>유투브 채널과 카페도 운영하신다.</li>
<li>블로그 글이나 유투브 영상에서 실제 현장에 사용되는 네트워크 장비를 구경시켜 주시는데 정말 신기했다.</li>
</ul>
</li>
<li><p>케이블은 랜툴 세트와 랜선 2m를 구입해 이틀에 하나씩, 시험 시작 시간에 맞춰(아침 9시 반쯤) 타이머 6분(실제 시험에서 주어지는 시간은 10분)을 맞춰놓고 다이렉트나 크로스 케이블을 제작했다. </p>
</li>
</ul>
<h2 id="시험-당일-후기-1">시험 당일 후기</h2>
<ul>
<li>케이블 제작은 실기 1번 문제이다. 실전같은 환경에서 연습한 덕인지 시험 당일에도 좀 비몽사몽한 상태에서 5분 안에 다이렉트 케이블을 제대로 제작했다. 제작 후 케이블에 수험자 정보가 인쇄된 라벨을 부착하면 시험관이 수거해 채점한다.<ul>
<li>사제 랜툴과 피복 탈피기를 사용할 수 있다. 대부분의 수험생이 도구를 구입해왔지만, 사제 도구를 가져오지 않아 시험장 보급품을 대여한 응시자도 몇명 있었다(질이 좋지는 않다고 고지해줬다)</li>
</ul>
</li>
<li>윈도우 서버 에뮬레이터 문제와 단답형/선택형까지 모두 푼 후 답안을 전송하면 자동으로 라우터 문제를 풀 수 있는 웹페이지가 뜬다.</li>
<li>라우터 문제도 다 풀고 답안을 전송하면 시험관이 있는 교실 앞쪽 책상으로 가 본인의 케이블 제작 문제 점수와 답안 전송 상태를 확인한 후 퇴실한다.</li>
<li>또 선린인터넷고등학교에서 시험을 봤다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_240702]]></title>
            <link>https://velog.io/@kim_kim/TIL240702</link>
            <guid>https://velog.io/@kim_kim/TIL240702</guid>
            <pubDate>Tue, 02 Jul 2024 07:02:23 GMT</pubDate>
            <description><![CDATA[<h1 id="intellij-idea의-find-in-filesctrl--shift--f로-접근-내-file-mask-기능-활용하기">Intellij IDEA의 find in files(Ctrl + Shift + F로 접근) 내 File mask 기능 활용하기</h1>
<p><img src="https://velog.velcdn.com/images/kim_kim/post/2ae58890-2ee6-4d32-bd9e-e1d68d718cf2/image.png" alt="find in files 내 검색창"></p>
<ul>
<li>와일드카드 문자 <code>*</code>를 사용할 수 있다. <ul>
<li><code>*.java</code>: 모든 자바 파일에서 검색</li>
<li><code>*Controller.java</code>: 파일명이 Controller로 끝나는 모든 자바 파일에서 검색</li>
</ul>
</li>
<li>not의 의미로 <code>!</code>를 사용할 수 있다<ul>
<li><code>!*Controller.java</code>: 파일명이 Controller로 끝나는 자바 파일을 <strong>제외한</strong> 모든 파일에서 검색</li>
</ul>
</li>
<li>최신 버전에서는 어떤지 모르겠는데, 내가 사용하는 22년 버전에서는 file mask를 제거할 수 있는 기능이 IDE 내에서 제공되지 않는다. 다른 내용으로 덮어쓰기 시도 시 아예 새로운 mask가 생성된다<ul>
<li>(Windows 기준) <code>C:/Users/{user}/AppData/Roaming/JetBrains/IntelliJIdea{version}/options</code> 경로의 <code>find.xml</code> 파일 안에 있는 mask tag 중 원치 않는 것을 지우면 된다. 정말 번거롭다...
<img src="https://velog.velcdn.com/images/kim_kim/post/c3683035-81ab-46d2-aa29-c5a87b00c52d/image.png" alt="vi로 열었을 때"></li>
</ul>
</li>
</ul>
<p><a href="https://www.evan.org/ghg/java/intellij-search-with-file-mask/">참고한 글: intellij search with file mask</a>  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[String format 시 java.util.UnknownFormatConversionException: Conversion = '"' 오류]]></title>
            <link>https://velog.io/@kim_kim/String-format-%EC%8B%9C-java.util.UnknownFormatConversionException-Conversion-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@kim_kim/String-format-%EC%8B%9C-java.util.UnknownFormatConversionException-Conversion-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Tue, 11 Jun 2024 06:02:13 GMT</pubDate>
            <description><![CDATA[<h1 id="상황">상황</h1>
<p>html 형식의 텍스트를 읽어와 일부 내용(%s)을 상황에 따라 적절한 내용으로 formatting 해야 하는 상황에서 html에 포함된 % 부호 때문에 <code>java.util.UnknownFormatConversionException: Conversion = &#39;&quot;&#39;</code> 오류 발생.</p>
<pre><code class="language-java">String htmlString = &quot;&lt;div style=\&quot;width: 30%\&quot;&gt;&lt;p&gt;%s&lt;/p&gt;&lt;span&gt;%s&lt;/span&gt;&lt;/div&gt;&quot;;
String htmlString = String.format(htmlString, &quot;내용 1&quot;, &quot;내용 2&quot;);</code></pre>
<h1 id="해결">해결</h1>
<p>챗지피티의 조언으로 %를 일일히 escape처리하는 대신, <code>String.format</code> 대신 <code>MessageFormat.format</code>메소드를 사용하도록 다음과 같은 형태로 html과 자바 코드를 변경했다. 고마워요 챗지피티!</p>
<pre><code class="language-java">import java.text.MessageFormat;

String htmlString = &quot;&lt;div style=\&quot;width: 30%\&quot;&gt;&lt;p&gt;{0}&lt;/p&gt;&lt;span&gt;{1}&lt;/span&gt;&lt;/div&gt;&quot;;
String htmlString = MessageFormat.format(htmlString, &quot;내용 1&quot;, &quot;내용 2&quot;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_240527]]></title>
            <link>https://velog.io/@kim_kim/TIL240527</link>
            <guid>https://velog.io/@kim_kim/TIL240527</guid>
            <pubDate>Tue, 28 May 2024 05:47:17 GMT</pubDate>
            <description><![CDATA[<ul>
<li>작년 10월 경 나보다 더 상위 버전의 JDK를 사용하는 공동작업자가 커밋한 코드에 포함된 <code>List.of(...)</code> 메소드 때문에 컴파일 시 오류가 발생했다. 해당 메소드는 JDK 9에 추가된 것으로, 그보다 상위 버전으로 자바 컴파일러를 설정해 해결했다. <ol>
<li>Settings &gt; Build, Execution, Deployment &gt; Compiler &gt; Java Compiler</li>
<li>상단의 <code>Use &#39;--release&#39; option for cross-compilation (Java 9 and later)</code> 옵션 <strong>언체크</strong> </li>
<li>그 아래의 <code>Project bytecode version</code>을 11로 변경</li>
</ol>
</li>
<li>예전에 동일한 문제가 한번 발생해 <code>Project Structure &gt; Project Settings &gt; Project</code>에서 SDK와 Language Level을 11로 변경해 해결했으나 이번에 발생한 문제는 이 방법으로 해결이 되지 않았다.</li>
<li>프로젝트 시작 전 JDK 버전과 같은 세팅을 동일하게 맞추고 가는 것이 가장 좋을 것 같다. 근데 분명 11로 맞추고 일을 시작한 기억이 있는데 왜 다시 확인하니 1.8로 설정되어 있는지 모르겠다...</li>
<li>참조) <a href="https://dev-emmababy.tistory.com/139">[IntelliJ] JAVA 버전 바꾸는 방법(JDK버전)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySql 8.0.32 Cannot convert string from utf8mb4 to binary 버그]]></title>
            <link>https://velog.io/@kim_kim/MySql-8.0.32-Cannot-convert-string-from-utf8mb4-to-binary-%EB%B2%84%EA%B7%B8</link>
            <guid>https://velog.io/@kim_kim/MySql-8.0.32-Cannot-convert-string-from-utf8mb4-to-binary-%EB%B2%84%EA%B7%B8</guid>
            <pubDate>Tue, 02 Apr 2024 05:45:41 GMT</pubDate>
            <description><![CDATA[<h1 id="상황">상황</h1>
<p>Spring + MySQL (+ MyBatis) 환경에서 view에 있는 컬럼을 대상으로 조건문에 한글을 사용해 SELECT 문 실행 시 <code>Cannot convert string from utf8mb4 to binary</code>오류가 뜨거나, 확실히 1건 이상의 검색 결과가 있어야 함에도 불구하고 검색이 되지 않는 오류가 발생했다.</p>
<h2 id="예시-1-한글-like-검색">예시 1: 한글 LIKE 검색</h2>
<ul>
<li>실행한 쿼리<pre><code class="language-sql">SELECT 
  ARTICLE.ARTICLE_ID,
  ARTICLE.ARTICLE_TITLE
FROM
  VIEW_ARTICLE ARTICLE
WHERE
  ARTICLE.ARTICLE_TITLE LIKE CONCAT(&#39;%&#39;, &#39;테스트&#39;, &#39;%&#39;)</code></pre>
</li>
<li>결과<pre><code>Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary</code></pre><h2 id="예시-2-한글--검색">예시 2: 한글 <code>=</code> 검색</h2>
</li>
<li>실행한 쿼리<pre><code class="language-sql">SELECT 
  ARTICLE.ARTICLE_ID,
  ARTICLE.ARTICLE_TITLE,
  MEMBER.MEMBER_ID,
  MEMBER.MEMBER_NAME
FROM
  VIEW_ARTICLE ARTICLE
  LEFT OUTER JOIN VIEW_MEMBER MEMBER
      ON ARTICLE.WRITER_ID = MEMBER.MEMBER_ID
WHERE
  MEMBER.MEMBER_NAME = &#39;관리자&#39;</code></pre>
</li>
<li>결과<pre><code>Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary
Cannot convert string &#39;\xEA\xB4...&#39; from utf8mb4 to binary</code></pre><h1 id="해결">해결</h1>
MySql 8.0.32 버전에만 존재하는 버그로, <code>SHOW VARIABLES LIKE &#39;version&#39;;</code>문 실행 시 나오는 버전이 해당 버전이라면 다음과 같은 방법을 통해 조치할 수 있다. 이후 버전에서는 수정되었다고 한다.</li>
</ul>
<blockquote>
<p>SELECT문 실행 전 <code>SET optimizer_switch=&#39;derived_condition_pushdown=off&#39;;</code> 문 실행         </p>
</blockquote>
<p>해당 버그 리포트는 다음 버그와 중복되는 내용이라고 한다.
<a href="https://bugs.mysql.com/bug.php?id=109699">MySQL Bug Report</a></p>
<ul>
<li>꼭 view에 국한되는 문제는 아니며, 대충 &#39;SELECT 문을 실행하면서 파생 테이블을 생성할 때 ASCII 범위를 넘어가는 문자들이 utfmb4로 정상적으로 변환되지 않아 생긴 문제&#39;라는 것 같다. 세계 인구 중 ASCII 범위 외의 문자를 이용하는 사람들이 대다수라는 것을 영어가 모국어인 개발자들이 항상 기억해 줬음 좋겠다...</li>
</ul>
<blockquote>
<p>Documented fix as follows in the MySQL 8.0.33 changelog:<br>
    When cloning a condition to push down to a derived table,
    characters in strings representing conditions were converted to
    utfmb4 correctly only for values less than 128 (the ASCII
    subset), and code points outside the ASCII subset were converted
    to invalid characters, causing the resulting character strings
    to become invalid. For derived tables without UNIONS, this led
    to problems when a column name from the derived table used
    characters outside the ASCII subset, and was used in the WHERE
    condition. For derived tables with UNIONS it created problems
    when a character outside the ASCII subset was present in a WHERE
    condition.<br>
    We fix these issues by initializing the string used in such
    cases for representing the condition to the connection character
    set.<br>
    Closed.</p>
</blockquote>
<blockquote>
<p>Posted by developer:<br> 
As a workaround, set derived condition pushdown to off like this:<br>
set optimizer_switch=&quot;derived_condition_pushdown=off&quot;;</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[커스텀 팝업 띄우기]]></title>
            <link>https://velog.io/@kim_kim/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%96%BC%EB%9F%BF-%EB%9D%84%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@kim_kim/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%96%BC%EB%9F%BF-%EB%9D%84%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Fri, 02 Feb 2024 04:48:31 GMT</pubDate>
            <description><![CDATA[<p>커스텀 팝업(얼럿)을 만들어 띄우는 기능을 간단하게 구현해보았다. 그냥 브라우저에서 기본적으로 제공하는 alert와 confirm을 사용하면 좋으련만, 팝업/모달 창과의 디자인적 통일성과 심미성 때문인지 별도로 디자인된 커스텀 alert/confirm을 사용할 것이 요청되는 곳이 많다. 구현한 코드를 응용하여 보다 복잡한 모달창을 만들 수도 있다.</p>
<h1 id="얼럿-모달-팝업-어떻게-다른가">얼럿, 모달, 팝업: 어떻게 다른가</h1>
<p><a href="https://ux.stackexchange.com/questions/90336/whats-the-difference-between-a-modal-popup-popover-and-lightbox">해당 내용이 정리된 stackexchange 글</a>
<a href="https://illustrate.digital/wordpress/what-are-modals-popups-and-lightboxes/">참고한 다른 글</a></p>
<p>명확히 기능과 특성을 구분해 지칭하기보다는 혼용해 쓰이는 경우가 많다. 나도 부모 창 위에 떠다니는 것은 다 퉁쳐서 팝업으로 부르는 경향이 있다.. 이 글에서도 팝업이라는 용어를 그러한 방식으로 사용하고 있다</p>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Alert_dialog_box">Alert</a></p>
<ul>
<li>간단한 메시지를 노출하며 이에 대한 사용자의 즉각적인 대응을 요청함. 사용자가 할 수 있는 대응은 <code>확인</code>이나 <code>취소</code> 버튼을 클릭하는 것으로 한정되어 있다</li>
<li>대응하여 얼럿 창을 해제하기 전까지는 부모 창과 상호작용하는 것을 금지한다</li>
<li><code>확인</code>버튼만 있는 것은 alert, <code>취소</code>나 그와 유사한 버튼 또한 제공하는 것은 confirm으로 불린다. confirm을 alert의 하위 개념으로 취급하는 곳도 있고, 별개의 개념으로 설명하는 곳도 있었다. 본 예시에 등장하는 것은 엄밀히 말하면 confirm이다</li>
</ul>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Modal_window">Modal/Dialog</a> </p>
<ul>
<li>단순 메시지보다 더 많은 정보를 노출한다</li>
<li>떠 있는 동안에는 부모 창과 상호작용하는 것을 막지만 직접적으로 대응하지 않고도 창 밖의 영역을 클릭해 해제할 수 있다.</li>
</ul>
</li>
<li><p>Popup</p>
<ul>
<li>즉각적인 대응을 요구하지 않으며, 해제하지 않고도 부모 창과 상호작용할 수 있다</li>
<li>Alert와 Modal와 같이 부모 창 위에 나타나는 요소들을 Popup으로 통칭하기도 한다</li>
<li>대표적인 예시로는 인터넷 쇼핑몰에 접속하면 자동으로 뜨는 광고 및 공지들이 있다</li>
</ul>
</li>
</ul>
<h1 id="구현">구현</h1>
<p>!codepen[bestKimEver/embed/xxBWVyo?default-tab=html%2Cresult]</p>
<ol>
<li><p>띄울 팝업을 HTML로 만들고 팝업 내부 조작과 관련된 코드를 작성한 후 hidden 상태로 숨겨둔다</p>
<ul>
<li>사이트 내에서 공통적으로 사용하는 alert 및 confirm은 퍼블리셔가 footer 등지에 만들어 숨겨둘 것이다</li>
<li>띄울 팝업과 그에 동반되는 코드의 분량이 많을 경우, 혹은 다른 페이지에서도 별도의 파일로 분리하는 것도 좋다. <pre><code class="language-html">   &lt;!-- JSP의 예 --&gt;
   &lt;jsp:include page=&quot;/WEB-INF/jsp/popup/popupExample.jsp /&gt;</code></pre>
</li>
<li>실제 프로젝트에서는 퍼블리셔가 이보다는 더 보기 좋게 디자인을 적용하고, z값과 위치를 설정하고, 팝업이 떠 있는 동안 팝업과 무관한 조작을 하지 못하도록 dim 처리한 레이어를 까는 등의 작업을 해놓을 것이다</li>
</ul>
</li>
<li><p>사용자가 특정 버튼을 클릭하는 등의 행동을 할 시 팝업이 열리도록 적절히 조치한다</p>
<ul>
<li>여기서는 <code>$(&quot;#btn-showPopup&quot;).on(&quot;click&quot;, () =&gt; {
$(&quot;#confirmPopup&quot;).show();
})</code>등의 코드를 작성해 직접적으로 숨겨진 팝업을 show했지만 현업에서는 보다 편하게 팝업을 노출 및 숨김 처리하고 안에 들어갈 콘텐츠를 설정할 수 있도록 퍼블리셔 등의 프론트 관계자들이 전용 함수를 작성해줄 수도 있다. 꼭 확인해보자.</li>
</ul>
</li>
</ol>
<h1 id="코드">코드</h1>
<ul>
<li><p>javascript</p>
<pre><code class="language-js">import $ from &quot;https://esm.sh/jquery&quot;;

$(&quot;#btn-showPopup&quot;).on(&quot;click&quot;, () =&gt; {
  $(&quot;#confirmPopup&quot;).show();
})

$(&quot;#btn-close&quot;).on(&quot;click&quot;, () =&gt; {
  $(&quot;#confirmPopup&quot;).hide();
})

$(&quot;#btn-confirm&quot;).on(&quot;click&quot;, () =&gt; {
  // &quot;확인&quot; 클릭 시 실행할 코드
  document.body.style.backgroundColor = &quot;gray&quot;;
  $(&quot;#confirmPopup&quot;).hide();
})</code></pre>
</li>
<li><p>HTML</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot; id=&quot;btn-showPopup&quot;&gt;show alert&lt;/button&gt;

&lt;div id=&quot;confirmPopup&quot; class=&quot;popup-wrap&quot; aria-modal=&quot;true&quot; hidden&gt;
  &lt;div class=&quot;popup-inner&quot;&gt;
    &lt;div class=&quot;popup-header&quot;&gt;
      &lt;h2 class=&quot;popup-title&quot;&gt;저는 얼럿이예요!&lt;/h2&gt;
    &lt;/div&gt;
    &lt;div class=&quot;popup-body&quot;&gt;
      &lt;p&gt;그렇습니다. 저는 얼럿입니다. &lt;br&gt;배경 색을 회색으로 변경하시겠습니까?&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class=&quot;popup-footer&quot;&gt;
      &lt;div class=&quot;btn-area&quot;&gt;
        &lt;button type=&quot;button&quot; id=&quot;btn-close&quot;&gt;취소&lt;/button&gt;
        &lt;button type=&quot;button&quot; id=&quot;btn-confirm&quot;&gt;확인&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
</li>
<li><p>CSS</p>
<pre><code class="language-css">#confirmPopup {
  border: 1px solid;
  height: 200px;
  width: 300px;
  text-align: center;
  padding-top:20px;
  background-color: white;
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[사용자 입력값 검사: 구분자로 구분된 입력값을 사전에 정의된, 허용된 입력값과 비교해 검사]]></title>
            <link>https://velog.io/@kim_kim/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9E%85%EB%A0%A5%EA%B0%92-%EA%B2%80%EC%82%AC-%EA%B5%AC%EB%B6%84%EC%9E%90%EB%A1%9C-%EA%B5%AC%EB%B6%84%EB%90%9C-%EC%9E%85%EB%A0%A5%EA%B0%92%EC%9D%84-%EC%82%AC%EC%A0%84%EC%97%90-%EC%A0%95%EC%9D%98%EB%90%9C-%ED%97%88%EC%9A%A9%EB%90%9C-%EC%9E%85%EB%A0%A5%EA%B0%92%EA%B3%BC-%EB%B9%84%EA%B5%90%ED%95%B4-%EA%B2%80%EC%82%AC</link>
            <guid>https://velog.io/@kim_kim/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9E%85%EB%A0%A5%EA%B0%92-%EA%B2%80%EC%82%AC-%EA%B5%AC%EB%B6%84%EC%9E%90%EB%A1%9C-%EA%B5%AC%EB%B6%84%EB%90%9C-%EC%9E%85%EB%A0%A5%EA%B0%92%EC%9D%84-%EC%82%AC%EC%A0%84%EC%97%90-%EC%A0%95%EC%9D%98%EB%90%9C-%ED%97%88%EC%9A%A9%EB%90%9C-%EC%9E%85%EB%A0%A5%EA%B0%92%EA%B3%BC-%EB%B9%84%EA%B5%90%ED%95%B4-%EA%B2%80%EC%82%AC</guid>
            <pubDate>Tue, 30 Jan 2024 05:33:11 GMT</pubDate>
            <description><![CDATA[<ul>
<li>사용자에게서 특정 구분자(#)로 구분된 텍스트를 입력받아 사전에 정의된, 허용된 입력값 리스트와 비교 수행</li>
<li>허용된 입력값 리스트에 포함되지 않은 값이 텍스트에 포함되어 있을 시 사용자에게 이에 대한 피드백 제공 <ul>
<li>alert로 경고를 띄운 후 허용되지 않은 항목은 입력값에서 삭제함</li>
</ul>
</li>
</ul>
<p>게시판 설정 기능의 일부로 포함되었던 기능으로, 해당 게시판에서 게시물 작성자가 입력할 수 있는 키워드를 관리자가 설정할 수 있다. 관리자가 설정할 수 있는 키워드의 종류는 원래 별도의 설정 파일에 변수 형태로 설정되어 있다. 
입력 가능한 값의 범주가 이미 정의되어 있기 때문에 그 목록을 체크박스 형식으로 제공하여 사용자가 그 중에서 허용할 항목을 선택하게 하는 것이 가장 효과적인 방법이라고 생각하나, 이것이 기획-클라이언트 파트의 선택이라 이렇게 구현하였다... 
나에게 좀 더 재량권이 주어진다면 input 아래에 입력 예시라도 추가하고 싶다. </p>
<p>!codepen[bestKimEver/embed/RwdxgEq?default-tab=html%2Cresult]</p>
<ul>
<li><p>javascript</p>
<pre><code class="language-js">import $ from &quot;https://esm.sh/jquery&quot;;

// 허용된 키워드들: 영문 키워드는 소문자로 입력
var allowedKeywordList = &quot;#출석#인증#자랑#yolo#유머#awol#질문#리뷰#이벤트&quot;.split(&quot;#&quot;).filter(function (i){return i;});
// var allowedKeywordList = [&quot;출석&quot;,&quot;인증&quot;,&quot;자랑&quot;,&quot;YOLO&quot;,&quot;유머&quot;,&quot;AWOL&quot;,&quot;질문&quot;,&quot;리뷰&quot;,&quot;이벤트&quot;];

// allowedKeywordList에 포함되지 않은 키워드는 등록 불가
$(&quot;#keywordText&quot;).on(&quot;change keyup paste blur&quot;, function(event){
  let content = $(this).val();
  // 입력 중인 텍스트가 #나 공백으로 끝나지 않을 경우 검사 안함 
  if([&quot;change&quot;, &quot;keyup&quot;].includes(event.type) &amp;&amp; !(content.endsWith(&quot;#&quot;) || /\s$/.test(content))){
    return;
  }
  // 사용자가 입력한 키워드 text parsing
  content = content.replace(/,/g, &quot;&quot;).replace(/\s/g, &quot;&quot;);
  let inputKeywordList = content.toLowerCase().split(&quot;#&quot;).filter((keyword) =&gt; {
    keyword = keyword.trim();
    if(keyword.length &gt; 0){
      return keyword;
    }
  });
  console.log(inputKeywordList)
  // 파싱한 키워드 목록을 허용된 키워드와 그렇지 않은 것으로 분류
  let validKeywordList = inputKeywordList.filter((keyword) =&gt; allowedKeywordList.includes(keyword));
  let invalidKeywordList = inputKeywordList.filter((keyword) =&gt; !allowedKeywordList.includes(keyword));

  //허용되지 않은 키워드 하나라도 포함 시 alert 띄우고 해당 키워드 삭제
  if(invalidKeywordList.length &gt; 0){
    // alert(&quot;허용되지 않은 키워드입니다.&quot;);
    alert(invalidKeywordList[0] + &quot;은(는) 허용되지 않은 키워드입니다.&quot;);
    $(&quot;#keywordText&quot;).val(validKeywordList.join(&quot;#&quot;));
  }  
})</code></pre>
</li>
<li><p>HTML</p>
<pre><code class="language-html">&lt;input type=&quot;text&quot; id=&quot;keywordText&quot; value=&quot;&quot; placeholder=&quot;키워드는 해시태그 단위로 구분하여 입력해 주세요&quot; style=&quot;width:50em;&quot; /&gt;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA["전체" 옵션이 있는 체크박스 로직]]></title>
            <link>https://velog.io/@kim_kim/%EC%A0%84%EC%B2%B4-%EC%98%B5%EC%85%98%EC%9D%B4-%EC%9E%88%EB%8A%94-%EC%B2%B4%ED%81%AC%EB%B0%95%EC%8A%A4-%EB%A1%9C%EC%A7%81</link>
            <guid>https://velog.io/@kim_kim/%EC%A0%84%EC%B2%B4-%EC%98%B5%EC%85%98%EC%9D%B4-%EC%9E%88%EB%8A%94-%EC%B2%B4%ED%81%AC%EB%B0%95%EC%8A%A4-%EB%A1%9C%EC%A7%81</guid>
            <pubDate>Fri, 26 Jan 2024 08:14:14 GMT</pubDate>
            <description><![CDATA[<ul>
<li>jQuery 사용</li>
<li>&quot;전체&quot; 체크 시 모든 체크박스 체크</li>
<li>&quot;전체&quot;가 체크된 상태에서 체크 해제 시 모든 체크박스 체크 해제</li>
<li>&quot;전체&quot;가 체크된 상태에서 다른 체크박스 중 하나라도 체크 해제 시 &quot;전체&quot;도 체크가 해제됨</li>
<li>&quot;전체&quot;가 체크되지 않은 상태에서 다른 모든 체크박스가 체크된 상태로 변경될 시 &quot;전체&quot;도 자동으로 체크됨</li>
</ul>
<p>!codepen[bestKimEver/embed/dyrZMYX?default-tab=html%2Cresult]</p>
<ul>
<li><p>javascript code</p>
<pre><code class="language-js">function applyCheckboxGeneralLogic(){
  let isChecked = this.checked;
  let formContainer = $(this).closest(&quot;.checkbox-container&quot;);
  if($(this).hasClass(&quot;checkbox-all&quot;)){
    $(formContainer).find(&quot;input[type=checkbox]&quot;).prop(&quot;checked&quot;, isChecked)
  }else{
    if(isChecked){
      if($(formContainer).find(&quot;input[type=checkbox]:not(.checkbox-all):not(:checked)&quot;).length &lt; 1){
        $(formContainer).find(&quot;input.checkbox-all&quot;).prop(&quot;checked&quot;, true);
      }
    }else{
      $(formContainer).find(&quot;input.checkbox-all&quot;).prop(&quot;checked&quot;, false);
    }
  }
}

$(&quot;div.checkbox-container input[type=checkbox]&quot;).click(applyCheckboxGeneralLogic);</code></pre>
</li>
<li><p>HTML</p>
<pre><code class="language-html">&lt;div class=&quot;checkbox-container&quot;&gt;

  &lt;div class=&quot;checkbox-row&quot;&gt;
    &lt;input type=&quot;checkbox&quot; class=&quot;checkbox-all&quot; id=&quot;option-all&quot;/&gt;
    &lt;label for=&quot;option-all&quot;&gt;
      &lt;span&gt;전체&lt;/span&gt;
    &lt;/label&gt;    
    &lt;input type=&quot;checkbox&quot; id=&quot;option-01&quot; value=&quot;01&quot;/&gt;
    &lt;label for=&quot;option-01&quot;&gt;
      &lt;span&gt;선택 1&lt;/span&gt;
    &lt;/label&gt;
    &lt;input type=&quot;checkbox&quot; id=&quot;option-02&quot; value=&quot;02&quot;/&gt;
    &lt;label for=&quot;option-02&quot;&gt;
      &lt;span&gt;선택 2&lt;/span&gt;
    &lt;/label&gt;
        &lt;input type=&quot;checkbox&quot; id=&quot;option-03&quot; value=&quot;03&quot;/&gt;
    &lt;label for=&quot;option-03&quot;&gt;
      &lt;span&gt;선택 3&lt;/span&gt;
    &lt;/label&gt;
  &lt;/div&gt;

  &lt;div class=&quot;checkbox-row&quot;&gt;
    &lt;input type=&quot;checkbox&quot; id=&quot;option-04&quot; value=&quot;04&quot;/&gt;
    &lt;label for=&quot;option-04&quot;&gt;
      &lt;span&gt;선택 4&lt;/span&gt;
    &lt;/label&gt;
    &lt;input type=&quot;checkbox&quot; id=&quot;option-05&quot; value=&quot;05&quot;/&gt;
    &lt;label for=&quot;option-05&quot;&gt;
      &lt;span&gt;선택 5&lt;/span&gt;
    &lt;/label&gt;
    &lt;input type=&quot;checkbox&quot; id=&quot;option-06&quot; value=&quot;06&quot;/&gt;
    &lt;label for=&quot;option-06&quot;&gt;
      &lt;span&gt;선택 6&lt;/span&gt;
    &lt;/label&gt;
    &lt;input type=&quot;checkbox&quot; id=&quot;option-07&quot; value=&quot;07&quot;/&gt;
    &lt;label for=&quot;option-07&quot;&gt;
      &lt;span&gt;선택 7&lt;/span&gt;
    &lt;/label&gt;
  &lt;/div&gt;

&lt;/div&gt;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intellij Run/Debug Configurations: Depoly to Tomcat]]></title>
            <link>https://velog.io/@kim_kim/Intellij-RunDebug-Configurations-Depoly-to-Tomcat</link>
            <guid>https://velog.io/@kim_kim/Intellij-RunDebug-Configurations-Depoly-to-Tomcat</guid>
            <pubDate>Thu, 09 Mar 2023 06:01:25 GMT</pubDate>
            <description><![CDATA[<h1 id="상황">상황</h1>
<p>Intellij IDE에서 톰캣을 이용해 Run 시 서버 콘솔에 <code>Connected to server</code>라는 로그와 <code>[Catalina-utility-2]</code> 관련 로그 몇 줄 후에 추가적으로 로그가 뜨지 않고 브라우저로 접속 시 모든 엔드포인트가 404 오류를 뱉어내는 오류가 있어 해결하는 데 이틀 가량을 소비했다. xml 설정 파일을 잘못 작성하거나 web.xml(WebApplicationInitializer)에 제대로 등록하지 않아 생긴 문제 같아서(아님) 그 쪽을 만져보느라 시간을 많이 허비했다.</p>
<h1 id="해결">해결</h1>
<p><code>Run/Debug Configurations</code>에서 Deployment 탭의 <code>Deploy at the server startup</code>에 배포 설정을 해 주지 않아 발생한 문제였다. <code>Before launch</code>에 <code>Build war exploded artifact</code> task가 있어 <em>그게 그거지~</em> 라고 생각했는데 아니었다... 
<img src="https://velog.velcdn.com/images/kim_kim/post/678bd7cc-b7d7-47bb-8896-60b5ffe7e6d2/image.png" alt="">
Deployment 설정을 제대로 추가해주면 artifact가 배포된다는 로그가 뜬다.</p>
<pre><code>Artifact study:war exploded: Artifact is being deployed, please wait...</code></pre><ul>
<li><p>Application context가 요상한 걸로 바뀌는데, 이대로 실행하면 오류가 발생한다. <code>/</code>나 별도로 설정된 path로 바꿔줘야 한다.</p>
</li>
<li><p>deploy 설정 추가 후에도 (EgovWeb)ServletContextListener 관련 오류가 떠서 해결했는데(EgovWebServletContextListener에서 참조하는 필수 property인 <code>Spring.datasource.DbType</code>이 설정 .properties 파일에서 누락되어 생긴 문제였다) war exploded로 배포 시 정상적으로 실행되나, 그냥 war 배포 시에는 동일한 오류가 계속 발생했다</p>
<ul>
<li><code>Before launch</code>에 <code>Build war artifact</code>를 추가해주면 정상 작동한다.</li>
</ul>
</li>
<li><p><a href="https://www.inflearn.com/questions/23403/artifact%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94">artifact는 Maven 등에서 빌드 결과로 나오는, Delivery/Depolyment를 위해 최종적으로 관리되는 개발 산출물을 의미한다.</a></p>
</li>
</ul>
<h1 id="참조">참조</h1>
<p><a href="https://www.baeldung.com/tomcat-deploy-war">7. Deploy From IntelliJ IDEA</a>
<a href="https://computer-science-student.tistory.com/615">[IntelliJ] 인텔리제이 톰캣(Tomcat) 연동</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_230213]]></title>
            <link>https://velog.io/@kim_kim/TIL230213</link>
            <guid>https://velog.io/@kim_kim/TIL230213</guid>
            <pubDate>Mon, 13 Feb 2023 07:09:45 GMT</pubDate>
            <description><![CDATA[<h1 id="docker-compose는-편리한데-너무-어렵다">docker compose는 편리한데 너무 어렵다...</h1>
<ul>
<li><a href="https://github.com/docker/compose/issues/3966">docker-compose 사용 시 이름이 같은 서비스끼리 설정이 서로 섞이는 문제가 있다.</a><ul>
<li>예전에 <code>docker-compose.yml</code> 파일을 수정해가며 up-down을 반복했을 때 백업해둔 <code>docker-compose.yml</code> 파일들과 설정이 섞였는데, 이것이 원인인줄 몰라 애꿎은 DB 설정에만 매달리다 하루를 날린 적이 있었다. </li>
<li>오늘은 기존에 up한 것과 다른 <code>docker-compose.yml</code> 파일을 up하자 새로 컨테이너가 생성되는 것이 아니라 기존에 만든 것이 지워지고 recreate 되어 컨테이너 안의 DB가 날아가는 대참사가 일어나 멘탈이 나갔다.. 오늘 반나절이 날아갔다. up 시 <code>--no-recreate</code> flag를 추가해 이러한 참사를 예방하자<pre><code class="language-bash">docker compose up [-d] --no-recreate</code></pre>
</li>
</ul>
</li>
<li><code>docker-entrypoint-initdb.d</code> 파일 내의 dump.sql을 제대로 읽어들이지 못하는 문제도 있다...(<a href="https://velog.io/@aszxvcb/docker-entrypoint-initdb.d-%EC%9D%98-.sql-%EC%8B%A4%ED%96%89%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%8A%88">이걸</a>로는 해결 안됨)<ul>
<li>그냥 dbeaver SQL 편집기에 실행할 파일을 끌어다 놓은 후 실행시켰다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_230113]]></title>
            <link>https://velog.io/@kim_kim/TIL230113</link>
            <guid>https://velog.io/@kim_kim/TIL230113</guid>
            <pubDate>Fri, 13 Jan 2023 04:47:50 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>Nexus: (게임 모딩 커뮤니티/모드 관리 프로그램과는 무관함) 메이븐 repository 관리 툴로, 개발자들은 remote repository 대신 Nexus에서 dependency를 다운받아 프로젝트의 일관성을 유지할 수 있다.</p>
</li>
<li><p>이 친숙한 스프링 프로젝트 디렉토리 구조는 Maven에서 제공하는 것이다
<img src="https://velog.velcdn.com/images/kim_kim/post/2c862854-4d59-4757-bbfb-5d65ee785f05/image.png" alt="directory system provided by maven"></p>
<ul>
<li><code>/pom.xml</code>: 프로젝트 객체 모델. 해당 프로젝트에 대한 전반적인 정보를 갖는다.</li>
<li><code>/src/main/java</code>: Java 소스 파일 위치</li>
<li><code>/src/main/resources</code>: 배포할 리소스, XML, properties, …</li>
<li><code>/src/main/webapp</code>: 웹 어플리케이션 관련 파일 위치(WEB-INF,css 등)</li>
<li><code>/src/test/java</code>: 테스트 케이스 java 소스</li>
<li><code>/src/test/resources</code>: 테스트 케이스 리소스</li>
<li><code>/target</code>: 빌드 된 output이 위치하는 디렉터리</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[eclipse 실행 시 JVM is not suitable for this product 오류]]></title>
            <link>https://velog.io/@kim_kim/eclipse-%EC%8B%A4%ED%96%89-%EC%8B%9C-JVM-is-not-suitable-for-this-product-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@kim_kim/eclipse-%EC%8B%A4%ED%96%89-%EC%8B%9C-JVM-is-not-suitable-for-this-product-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Fri, 13 Jan 2023 02:02:40 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>전자정부프레임워크 개발자용 개발환경 <code>eGovFrameDev-4.0.0-Win-64bit.exe</code>를 다운받아 실행할 때 다음과 같은 오류가 발생했다.</p>
<blockquote>
<p>Incompatible JVM</br>
Version 1.8.0_202 of the JVM is not suitable for this product. Version: 11 or greater is required.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/kim_kim/post/3d75511f-8b91-44d0-b440-9423b514bf45/image.PNG" alt="incompatible JVM error">
JVM 버전을 11 이상으로 설정하란다.</p>
<h1 id="원인">원인</h1>
<ul>
<li>eclipse는 자바 기반으로 제작되었는데, 이를 돌릴 수 있는 가상 머신(JVM)의 경로가 설정되어 있지 않아 발생하는 문제다.</li>
<li>eclipse가 위치한 디렉토리의 eclipse.ini 파일을 열어보면 다음과 같이 필요한 버전이 명시되어 있다. 이것과 가상 머신 경로로 설정한 것의 버전이 일치하지 않아도 해당 오류가 발생할 수 있다.
```</li>
<li>Dosgi.requiredJavaVersion=11
```</li>
<li><a href="https://www.egovframe.go.kr/home/sub.do?menuNo=94">전자정부프레임워크 공식 사이트</a>에서는 다음과 같이 조치하라고 안내하고 있는데, 아무 곳에나 경로를 추가한다고 해서 되는 것이 아니더라..<blockquote>
<p>이클립스 구동 시 JVM 관련 오류 발생 시, eclipse 설정 파일인 eclipse.ini 에서 사용자 PC의 JDK 설치 경로를 다음과 같이 추가합니다. (JDK는 11 버전 사용이 필수)</p>
</blockquote>
</li>
<li><ul>
<li>eclipse.ini 에 jdk 경로 추가 (예시)</li>
</ul>
</li>
<li>vm
C:Program Files\Java\jdk-11.0.5\bin\javaw.exe</li>
</ul>
<h1 id="해결">해결</h1>
<ul>
<li>eclipse가 위치한 디렉토리의 eclipse.ini 파일에 다음과 같이(예시) <code>-Dosgi.requiredJavaVersion</code>에 명시된 버전의 가상 머신 경로 설정을 추가해준다. 
```</li>
<li>vm
C:\Program Files\Java\jdk11.0.12\bin
```</li>
<li><code>javaw.exe</code>는 자바를 실행시키는 가상머신이라고 한다. </li>
<li>맨 아랫줄에 설정을 추가하면 오류가 해결되지 않는다. 맨 윗줄이 안전하다.</li>
</ul>
<h1 id="참조">참조</h1>
<p><a href="https://wooncloud.tistory.com/48">https://wooncloud.tistory.com/48</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_230112]]></title>
            <link>https://velog.io/@kim_kim/TIL230112</link>
            <guid>https://velog.io/@kim_kim/TIL230112</guid>
            <pubDate>Thu, 12 Jan 2023 08:15:42 GMT</pubDate>
            <description><![CDATA[<ul>
<li><code>%~d0\%~p0</code>의 의미<ul>
<li>batch 파일 문법이다.</li>
<li><code>%~d0</code>: 드라이브 명</li>
<li><code>%~p0</code>: 경로</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL_230110]]></title>
            <link>https://velog.io/@kim_kim/TIL230110</link>
            <guid>https://velog.io/@kim_kim/TIL230110</guid>
            <pubDate>Tue, 10 Jan 2023 10:25:32 GMT</pubDate>
            <description><![CDATA[<ul>
<li>스프링에서 대부분의 import 관련 문제는 <code>mvn install</code>으로 해결된다<ul>
<li>안 되면 <code>mvn clean install -U</code>를 강행한다</li>
<li>그래도 안 되면 .m2 폴더의 내용물을 싹 비워본다. Windows에서는 <code>${user.home}</code> 디렉토리에 있다.</li>
<li>pon.xml의 repository 경로 url이 HTTP<strong>S</strong>가 아닌 HTTP일 경우 Central Repository에서 접속을 허가해 주지 않아 필요한 의존성을 다운받지 못하는 것일 수도 있다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>