<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>lou@blog $ _</title>
    <link>https://jizard.tistory.com/</link>
    <description>지식은 점에서 선으로</description>
    <language>ko</language>
    <pubDate>Sat, 16 May 2026 20:45:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Lou Park</managingEditor>
    <image>
      <title>lou@blog $ _</title>
      <url>https://tistory1.daumcdn.net/tistory/1991373/attach/b570918b151045a780e7133f2f50bb82</url>
      <link>https://jizard.tistory.com</link>
    </image>
    <item>
      <title>[제주 성산] 도새기 상회 - 흑돼지 구이</title>
      <link>https://jizard.tistory.com/689</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_9302.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YXUL9/dJMcaiPt6Px/2uFUPf6cKnz96EnOt6raU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YXUL9/dJMcaiPt6Px/2uFUPf6cKnz96EnOt6raU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YXUL9/dJMcaiPt6Px/2uFUPf6cKnz96EnOt6raU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYXUL9%2FdJMcaiPt6Px%2F2uFUPf6cKnz96EnOt6raU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot; data-filename=&quot;IMG_9302.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성산 일출봉 근처 흑돼지를 먹으러 방문했던 곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흑돼지 자체가 맛있고 제주도에 워낙 맛있는 흑돼지 집도 많아서 참 고민이 많이되었는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성산 일대의 흑돼지 집들중에서 고기 질에대한 리뷰가 가장 좋은 곳은 여기였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사장님이 고기를 직접 구워주시는데, 고기 맛이 정말 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고사리랑 백김치도 같이 구워주고, 찍어먹을 소스도 멜젓에 소금 와사비 쌈장, 갈치속젓, 명란고추마요... 질릴틈이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 명란고추마요는 고기랑 신선한 조합이었는데, 나는 쭉 여기 찍어먹었다. 또 먹고싶다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해물된장도 해물이 많이들어가서 아주 시원하다 (비싸긴함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 제주 여행의 1픽이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>나/루슐랭 맛집</category>
      <category>도새기상회</category>
      <category>목살</category>
      <category>오겹살</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/689</guid>
      <comments>https://jizard.tistory.com/689#entry689comment</comments>
      <pubDate>Sun, 4 Jan 2026 23:40:34 +0900</pubDate>
    </item>
    <item>
      <title>포이즌 필(Poison Pill) 패턴 : 멀티 스레드 프로세스를 안전하게 종료하는 방법</title>
      <link>https://jizard.tistory.com/688</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만들어낸 코드를 한 줄 한 줄 곱씹어 보던 중에 특이한 패턴을 발견했다.&lt;/p&gt;
&lt;pre id=&quot;code_1766888715166&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@classmethod
def stop_processor(cls):
    &quot;&quot;&quot;백그라운드 처리 스레드 중지&quot;&quot;&quot;
    cls._is_running = False
    cls._processing_queue.put(None)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나라면 스레드를 종료하기 위해서 그냥 is_running 플래그만 False로 바꿔놨을 것 같은데, 왜 Queue에다가 None이라는 이상한 값을 집어 넣는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;포이즌 필 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 안전하게 스레드를 종료하기 위한 포이즌 필(Poison Pill) 패턴이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Generated Image December 28, 2025 - 11_47AM.jpeg&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/05RXn/dJMcahbX40z/Rs5UUWEBX3h2M2zkKQ8LB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/05RXn/dJMcahbX40z/Rs5UUWEBX3h2M2zkKQ8LB1/img.jpg&quot; data-alt=&quot;Poison Pill&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/05RXn/dJMcahbX40z/Rs5UUWEBX3h2M2zkKQ8LB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F05RXn%2FdJMcahbX40z%2FRs5UUWEBX3h2M2zkKQ8LB1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1408&quot; height=&quot;768&quot; data-filename=&quot;Generated Image December 28, 2025 - 11_47AM.jpeg&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Poison Pill&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실생활에서 보이는 포이즌 필 패턴 예시가 재미있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식당에서 &quot;CLOSED&quot; 사인을 걸어두는 것을 예로들 수 있다. 영업종료 시간이 다가오면 식당주인은 문에 &quot;CLOSED&quot; 사인을 걸어둔다. 이는 더 이상 새로운 손님이 오는 것을 막지만, 지금 가게에 있는 손님들을 내쫓지는 않는다. 그들이 음식을 다 먹고, 계산하고 나갈 수 있도록 한다. 포이즌 필 메세지는 이렇듯 새로운 태스크가 처리되는 것을 막으면서도 현재 태스크를 마무리하고 안전하게 종료될 수 있도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/74ZeS/dJMcacIvWiG/jzCLTBGlDvtrDE7ic7Mj51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/74ZeS/dJMcacIvWiG/jzCLTBGlDvtrDE7ic7Mj51/img.png&quot; data-alt=&quot;포이즌 필 시퀀스 다이어그램 (https://java-design-patterns.com/patterns/poison-pill)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/74ZeS/dJMcacIvWiG/jzCLTBGlDvtrDE7ic7Mj51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F74ZeS%2FdJMcacIvWiG%2FjzCLTBGlDvtrDE7ic7Mj51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;866&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포이즌 필 시퀀스 다이어그램 (https://java-design-patterns.com/patterns/poison-pill)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 코드에서는 Queue에 들어갈 수 있는 메세지 타입들을 따로 정의한 바가 없었기때문에, AI는 편한 방법으로 &quot;None&quot;을 넣는 것을 택했던 것이다. (이놈!!! 헷갈렸잖아~)&lt;/p&gt;
&lt;pre id=&quot;code_1766889277730&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;@classmethod
def stop_processor(cls):
    &quot;&quot;&quot;백그라운드 처리 스레드 중지&quot;&quot;&quot;
    cls._is_running = False
    cls._processing_queue.put(None)

@classmethod
def _process_queue(cls):
    &quot;&quot;&quot;
    큐에서 하나씩 꺼내서 순차 처리
    &quot;&quot;&quot;
    while cls._is_running:
        try:
            # 리플레이 요청 확인 (1초마다)
            cls.check_replay_requests()

            # 큐에서 항목 가져오기 (1초 타임아웃)
            try:
                item = cls._processing_queue.get(timeout=1)
            except:
                # 큐가 비어있으면 queue.Empty 예외 발생
                continue

            if item is None:
                break  # 종료 신호

            # 튜플이면 (type, entry_id) 형태
            if isinstance(item, tuple):
                item_type, entry_id = item
                if item_type == RouletteStatus.REPLAY.value:
                    cls._replay_roulette(entry_id)
            else:
                # 일반 entry_id
                cls._spin_roulette(item)

            time.sleep(1)
        except Exception as e:
            logger.error(f&quot;룰렛 처리 오류: {e}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_process_queue는 별도 스레드에서 돌아가는 코드다. 주석에 보이는 것 처럼 item이 None(포이즌 필)인지 체크 후 종료 신호로 판단되면 즉시 루프를 종료한다. 이 코드에서는 큐에서 item을 가져오는 1초동안 스레드가 블로킹이 되는데, 포이즌 필 메세지가 없었다면 다음 사이클에나 종료되었을 것이다. 포이즌 필 메세지로 인해 대기중이던 스레드가 즉시 깨어나서 원하는 타이밍에 안전하게 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;포이즌 필 패턴 사용시 주의할 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머가 여러개인 경우, 컨슈머의 수 만큼 포이즌 필을 넣어주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://java-design-patterns.com/patterns/poison-pill/&quot;&gt;https://java-design-patterns.com/patterns/poison-pill/&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Design Pattern</category>
      <category>poison pill</category>
      <category>Queue</category>
      <category>shutdown gracefully</category>
      <category>멀티스레딩</category>
      <category>스레드</category>
      <category>포이즌필</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/688</guid>
      <comments>https://jizard.tistory.com/688#entry688comment</comments>
      <pubDate>Sun, 28 Dec 2025 11:48:02 +0900</pubDate>
    </item>
    <item>
      <title>DASHI - 치지직 도네이션 연동 룰렛 업데이트</title>
      <link>https://jizard.tistory.com/687</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.20.26.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lUmIT/dJMcadHm4Ev/o5yp3heWBsQWKC68JStKX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lUmIT/dJMcadHm4Ev/o5yp3heWBsQWKC68JStKX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lUmIT/dJMcadHm4Ev/o5yp3heWBsQWKC68JStKX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlUmIT%2FdJMcadHm4Ev%2Fo5yp3heWBsQWKC68JStKX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;345&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.20.26.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 DASHI에서 업데이트한건 룰렛 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 트위치 시절, 투네이션에서 룰렛 기능을 제공했었다. 최소금액이상 후원하면 룰렛 후원에 참여할 수 있고, 방송에 변수를 줄 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TkNiN/dJMcagjPcgX/8pkUA1FlkNyOCioDfVx4dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TkNiN/dJMcagjPcgX/8pkUA1FlkNyOCioDfVx4dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TkNiN/dJMcagjPcgX/8pkUA1FlkNyOCioDfVx4dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTkNiN%2FdJMcagjPcgX%2F8pkUA1FlkNyOCioDfVx4dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;858&quot; height=&quot;475&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룰렛 기능을 개발하기 위해서는 생각보다 많은 것이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 채팅 메세지를 받아 룰렛을 처리하는 작업 이외에도 OBS용 오버레이, 스트리머만 접근 가능한 룰렛 관리 화면을 준비해야한다. 예전같았다면 최소 일주일은 잡고 들어갔을테지만, 오늘 나는 10시간만에 모든 작업을 끝내버렸다. 바로, AI의 서포트 덕분이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 업데이트 회고를 작성하면서 AI와 함께 어떤식으로 개발했는지 공유해보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;장애물 치우기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 개발할때 가장 중요한 점은 일을 단계별로 쪼개야한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 목표지점에 도착하는게 일이라면 먼저 신발을 점검하고, 장애물을 확인하고, 장애물을 제거하고, 방향을 설정하고 달려야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DASHI는 치지직 API를 통해 폴링하여 방송이 시작되었는지 지속적으로 체크한다. 방송이 시작되면 그때 채팅 서버에 붙어서 채팅 기록, 챗봇 서비스등을 가동시킨다. 방송이 끝나면 채팅 서비스도 마찬가지로 종료하고는 했는데, 이 부분은 방송을 녹화하는 프로세스에 이상이 생겼을때 챗봇의 작동까지도 영향을 주었고, 테스트도 어렵게 만들었다. (챗봇 수정도 직접 방송을 켜서해야했다.) 룰렛 기능이 붙는다면 채팅 메세지에 의해 작동하는 또 하나의 서비스가 생기는거니, 이번에 완전히 방송을 녹화하는 프로세스와 채팅 프로세스를 분리하는 작업이 필요했다. 그래서 룰렛 작업 전 AI를 이용해 dashi_bot이라는 챗봇용 컨테이너를 따로 파서 별도의 프로세스에서 동작하도록 변경해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 구조 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1766842687555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;새로 추가할 기능은 &quot;룰렛&quot;이야.
이 기획에 대해 충분히 이해하고, 개발 계획을 짜줘. 내가 빠뜨린 부분은 없는지도 점검해.

1. 룰렛 참여 메세지처리
도네이션할때, 메세지가 &quot;!룰렛&quot;으로 시작할 경우에 룰렛에 참여할 수 있어.
도네이션이 몰릴때 룰렛이 씹히지않도록 큐잉이 필요해.
도네이션에는 고유의 donationId가 있는데, 이를 이용해서 구분하면 될 것 같아. (
donations.json
 참고)
활성화된 룰렛이 없다면 무시하면돼.

2. 룰렛 UI 처리
룰렛은 OBS나 기타 방송프로그램에서 브라우저로 띄울 수 있도록 별도의 페이지에서 돌아가고(배경없이), 슬롯머신 같은 애니메이션이 보여. 누가 룰렛을 돌렸는지도 nickname이 떠야해.

3. 룰렛관리 페이지 처리
- 룰렛에 들어갈 아이템과 확률(%)를 설정
- 룰렛 활성화/비활성화
- InputField로도 설정할 수 있지만, 행간을 구분한 텍스트로도 조정가능한 인터페이스 제공 ex) 잠자기 10\n독서 90
- 룰렛 당첨 히스토리 제공&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허접하지만 룰렛 기능을 위한 첫 프롬프트는 이랬다.&amp;nbsp;&lt;br /&gt;처음에는 기본 구조를 잡는게 중요하다. 처음 프롬프트에서 세부적인 디테일(e. g. 룰렛이 비활성화 되었을때 유저가 !룰렛 을 외치면 오류 메세지를 보내)을 요구할 필요는 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 부분은 다음과 같다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 확률에따라 아이템이 확정된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 채팅 메세지를 받아서 룰렛 참여를 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참여한 룰렛은 큐로 관리한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 룰렛 오버레이 페이지가 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 룰렛 관리 페이지가 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 기본 구조가 개발 된 후에 하나씩 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code의 Plan Mode를 이용했는데, 실제로 구현하는 입장이라 추가로 몇가지 질문을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 룰렛 오버레이 페이지와 서버간 연결을 SSE로 할것인지, WS로 할 것인지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 익명의 후원자가 룰렛에 참여할때 어떻게 표시할지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 룰렛 참여를 위한 최소 금액 설정이 필요한지?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.50.29.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;1011&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clQvkd/dJMcaaYd0of/J8QzXiL1aoA3wgu37M7q30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clQvkd/dJMcaaYd0of/J8QzXiL1aoA3wgu37M7q30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clQvkd/dJMcaaYd0of/J8QzXiL1aoA3wgu37M7q30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclQvkd%2FdJMcaaYd0of%2FJ8QzXiL1aoA3wgu37M7q30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;736&quot; height=&quot;1011&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.50.29.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;1011&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.51.20.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFUZ6a/dJMcagRE5pX/KoiURh6tGzRkekw6TDi0wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFUZ6a/dJMcagRE5pX/KoiURh6tGzRkekw6TDi0wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFUZ6a/dJMcagRE5pX/KoiURh6tGzRkekw6TDi0wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFUZ6a%2FdJMcagRE5pX%2FKoiURh6tGzRkekw6TDi0wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;443&quot; data-filename=&quot;스크린샷 2025-12-27 오후 10.51.20.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Claude가 뽑아준 계획은 대략 이랬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 계획을 읽어보고 생각한것이 맞다면 첫번째 개발 사이클을 돌린다. 오류가 나는 것도 있고 여기저기 조금씩 고쳐서 실행해보면 기능이 되기는...된다!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;299&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1aEoa/dJMcagc4r3w/3e8ITnrRGhmy3c9uRjvLP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1aEoa/dJMcagc4r3w/3e8ITnrRGhmy3c9uRjvLP1/img.png&quot; data-alt=&quot;이것이 첫번째 사이클의 룰렛 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1aEoa/dJMcagc4r3w/3e8ITnrRGhmy3c9uRjvLP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1aEoa%2FdJMcagc4r3w%2F3e8ITnrRGhmy3c9uRjvLP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;299&quot; height=&quot;266&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;299&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이것이 첫번째 사이클의 룰렛 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 룰렛 스핀 부분에 표시된 글자와 실제로 당첨된 결과가 다르지만...어쨋든 채팅 메세지를 통해서 룰렛에 참여할 수 있고, 룰렛에 있는 항목에서 확률에 따라 결과를 결정한뒤 오버레이로 전송한다는 기본적인 기능이 된다는 점이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;나누어서 다듬기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 스텝은 각 파트별로 정복해나가는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 핵심 로직 및 데이터 모델&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관리자 화면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 룰렛 오버레이 디자인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관리자 권한 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파트별로 정복하면 좋은점은 AI를 병렬로 돌릴 수 있다는 점이있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예로 나는 Claude Code로 룰렛 오버레이 디자인을 깎으면서 Antigravity IDE에서는 DB에 룰렛 제목과 룰렛 히스토리에 당시 룰렛 상태 스냅샷을 추가하는 작업을 동시에 했다. 서로 건드리는 파일이 겹칠 일도 없으면서 일을 더 빠르게 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면-기록-2025-12-27-오후-10.21.13.gif&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKFdY/dJMcagKUbrd/obN24l6JGzc6M1be57IW0k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKFdY/dJMcagKUbrd/obN24l6JGzc6M1be57IW0k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKFdY/dJMcagKUbrd/obN24l6JGzc6M1be57IW0k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kKFdY/dJMcagKUbrd/obN24l6JGzc6M1be57IW0k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;532&quot; data-filename=&quot;화면-기록-2025-12-27-오후-10.21.13.gif&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부적으로는 AI를 고문한다... 대략 이렇게....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (치지직 도네이션 사진을 보여주며) 이런 스타일로 바꿔&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (폰트를 가져오며) 이 폰트를 적용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (효과음을 가져오며) Spin시에는 이 효과음을, Done때는 이 효과음을 재생해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Done이 되면 Bounce하면서 콘페티가 보여지도록 해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아이템의 확률에 따라 디아블로 스타일로 고유 색을 정하고, Done 상태일때 배경색을 그 색으로 바꿔. 그리고 옆에는 확률을 표기해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 해서 룰렛 기능을 무사히 개발하게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-28 오후 12.28.56.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHQYBf/dJMcagYqzDz/AG0dV5kre8Gi7mIkj0KlY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHQYBf/dJMcagYqzDz/AG0dV5kre8Gi7mIkj0KlY1/img.png&quot; data-alt=&quot;룰렛 관리자 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHQYBf/dJMcagYqzDz/AG0dV5kre8Gi7mIkj0KlY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHQYBf%2FdJMcagYqzDz%2FAG0dV5kre8Gi7mIkj0KlY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;1200&quot; data-filename=&quot;스크린샷 2025-12-28 오후 12.28.56.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;룰렛 관리자 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 덕분에 개발이 즐겁다. 예전처럼 고통스럽고 오류때문에 며칠을 골머리 앓고하던 일이 사라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즐겁지만... 동시에 조금은 두려워진다. 그런 고통을 겪으며 성장한 것 같았는데, 이제 그런건 없다. 팔이 떨어질 것 같이 아파도 연필로 꾹꾹 책 내용을 요약하며 공부하던 학생이 이젠 AI가 요약한 요약본만을 보면서 머리로 이해하고있다. 나는 내가 선택한 것이, 내가 만든 것이 남들과는 다르고 그게 훨씬 낫길 바란다. 이제는 그런 센스를 기르기가 점점 어려워지는 환경이 되는 것 같다.&lt;/p&gt;</description>
      <category>프로그래밍/회고</category>
      <category>dashi</category>
      <category>치지직 룰렛</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/687</guid>
      <comments>https://jizard.tistory.com/687#entry687comment</comments>
      <pubDate>Sat, 27 Dec 2025 23:18:03 +0900</pubDate>
    </item>
    <item>
      <title>[신대방] 경식이네알쌈주꾸미 - 주꾸미 정식</title>
      <link>https://jizard.tistory.com/686</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251220_211734785.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JmqQu/dJMb99ZgiE0/zv2SMoq7QTHq0LKDHJPUSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JmqQu/dJMb99ZgiE0/zv2SMoq7QTHq0LKDHJPUSk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JmqQu/dJMb99ZgiE0/zv2SMoq7QTHq0LKDHJPUSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJmqQu%2FdJMb99ZgiE0%2Fzv2SMoq7QTHq0LKDHJPUSk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot; data-filename=&quot;KakaoTalk_20251220_211734785.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13000원짜리 주꾸미 정식이 아주 푸짐하게 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;술을 진탕 먹고 다음에 간거라 다들 많이 못먹었는데, 여기 주변에 살았다면 정말 자주갔을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;칼칼한 시래기 된장국으로 스타트를 했는데 대박이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주꾸미는 특별하진 않지만, 다른 여느 식당들만큼 통통하고 맛있다. 계란 부침에 계란도 정말 많이 들어간것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주꾸미 먹고, 계란 부침먹고, 고사리먹고, 밥 먹고 싸이클을 돌리느라 정신없었다.&lt;/p&gt;</description>
      <category>나/루슐랭 맛집</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/686</guid>
      <comments>https://jizard.tistory.com/686#entry686comment</comments>
      <pubDate>Sat, 20 Dec 2025 22:05:24 +0900</pubDate>
    </item>
    <item>
      <title>[내방] 양산도 - 가지장어덮밥</title>
      <link>https://jizard.tistory.com/685</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251220_211734785_01.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oSmgk/dJMcaaqkWTV/Ok2UGAcYxJE3Rksnd4ZbuK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oSmgk/dJMcaaqkWTV/Ok2UGAcYxJE3Rksnd4ZbuK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oSmgk/dJMcaaqkWTV/Ok2UGAcYxJE3Rksnd4ZbuK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoSmgk%2FdJMcaaqkWTV%2FOk2UGAcYxJE3Rksnd4ZbuK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot; data-filename=&quot;KakaoTalk_20251220_211734785_01.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내방역 근처에 양산도라는 장어 덮밥집이 생겨서 방문해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 장어를 정말 싫어하고...(뼈때문에) 장어 덮밥도 제대로된건 거의 처음이지만...이집이 맛있다는걸 단번에 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 메밀소바, 차완무시, 샐러드, 미소장국 이렇게 한 상 차림의 구성도 마음에 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 장어는 빠짝 구워서 거의 뼈가 느껴지지않았다. 그러면서도 부드러운 가지가 바짝구운 장어와 밥사이에 아주 조화롭게 들어온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구말에 의하면 가지장어가 제일 맛있었다고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>나/루슐랭 맛집</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/685</guid>
      <comments>https://jizard.tistory.com/685#entry685comment</comments>
      <pubDate>Sat, 20 Dec 2025 22:01:47 +0900</pubDate>
    </item>
    <item>
      <title>직접 RAG를 구축할 필요없게 해주는 Gemini File Search API</title>
      <link>https://jizard.tistory.com/684</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Chatbot을 만들면서 가장 귀찮았던건 RAG였다. RAG를 구현하기 위해 VectorDB라는 관리 포인트가 생기며, 임베딩 정책 및 모델 설정, 임베딩할 파일들을 관리하는 컴포넌트까지...RAG 구축 하나로 프로젝트가 복잡해지는 것은 순식간이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;FileSearch-Keyword_RD2-V01.width-2200.format-webp.webp&quot; data-origin-width=&quot;2096&quot; data-origin-height=&quot;1182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm6tA0/dJMcacavy8O/vgYEsbE8y7mpLFtU3O4nBk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm6tA0/dJMcacavy8O/vgYEsbE8y7mpLFtU3O4nBk/img.webp&quot; data-alt=&quot;Gemini File Search API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm6tA0/dJMcacavy8O/vgYEsbE8y7mpLFtU3O4nBk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm6tA0%2FdJMcacavy8O%2FvgYEsbE8y7mpLFtU3O4nBk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2096&quot; height=&quot;1182&quot; data-filename=&quot;FileSearch-Keyword_RD2-V01.width-2200.format-webp.webp&quot; data-origin-width=&quot;2096&quot; data-origin-height=&quot;1182&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gemini File Search API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 11월 6일, Google이 Gemini 3, Nano Banana Pro와 함께 &lt;a href=&quot;https://blog.google/technology/developers/file-search-gemini-api/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gemini File Search API를 발표&lt;/a&gt;했다. Gemini File Search API는 Gemini API에 구축된 RAG 시스템이다. &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/file-search?hl=ko#supported-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;텍스트 파일 종류라면 대부분 지원&lt;/a&gt;하며, File Store를 생성하고 파일을 업로드하는 간단한 절차만으로 검색 기능을 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 사내 API 문서를 바탕으로 API에 대한 질의를 할 수 있는 하는 MCP서버를 만들어보기 위해 OpenAPI Spec yaml 문서를 업로드해봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1764671923505&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def upload_file(self, file_path: str):
    operation = self.client.file_search_stores.upload_to_file_search_store(
        file=file_path,
        file_search_store_name=self.file_search_store.name,  # type: ignore
        config={
            &quot;display_name&quot;: Path(file_path).name,
            &quot;mime_type&quot;: self.guess_mime_type(file_path),
        },
    )
    # 완료될때까지 operation 업데이트
    while not operation.done:
        time.sleep(2)
        operation = self.client.operations.get(operation)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 청킹하고, 오버랩은 얼마나 할것이며...모델은 뭘 쓸 것인지 구구절절 설정한 것이 하나도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764672353567&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def search(self, query: str):
    prompt = f&quot;&quot;&quot;
    You are an expert API assistant.
    Using the provided OpenAPI specification, find the APIs that are most relevant to the user's query: &quot;{query}&quot;

    List the relevant APIs with their Method, Path, and a brief summary.
    Explain *why* they are relevant.
    &quot;&quot;&quot;

    response = self.client.models.generate_content(
        model=self.model,
        contents=prompt,
        config=types.GenerateContentConfig(
            tools=[
                types.Tool(
                    file_search=types.FileSearch(
                        file_search_store_names=[self.file_search_store.name]  # type: ignore
                    )
                )
            ]
        ),
    )
    return response.text&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764672587866&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def search_apis_tool(query: str) -&amp;gt; str:
    &quot;&quot;&quot;
    Search for APIs using Gemini's semantic understanding of the full OpenAPI spec.

    Args:
        query: The search query (e.g., &quot;How do I buy items?&quot;, &quot;Find APIs related to user profile&quot;).

    Returns:
        A natural language answer with relevant API details, grounded in the spec.
    &quot;&quot;&quot;
    try:
        return search_api.search(query)
    except Exception as e:
        return f&quot;Error searching with Gemini: {str(e)[:100]}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 검색하는 도구의 코드도 매우 간단해졌다. (Gemini 의존적이지만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini API를 그대로 사용하면서, 도구로서 File Search를 넘겨주면 된다. 어떤 파일 스토어를 사용할지 설정할 수 있으며, 메타 데이터를 통한 필터링도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 사용법은 &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/file-search?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 참고하기를 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/AI,ML</category>
      <category>File Search API</category>
      <category>GEMINI</category>
      <category>Gemini File Search API</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/684</guid>
      <comments>https://jizard.tistory.com/684#entry684comment</comments>
      <pubDate>Tue, 2 Dec 2025 19:55:19 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Room이 생성한 Dao 구현체 확인방법</title>
      <link>https://jizard.tistory.com/683</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 @Upsert 어노테이션을 사용해서 Collection 데이터를 넘겼을때 과연 &quot;트랜잭션처리가 되는가?&quot;에 대해서 검증해야할 일이 있었다. Room을 당연한듯이 사용하고는 했지만 너무 추상화되어있어서 내부 동작을 알지 못했다. 당연히- 트랜잭션처리가 될거라고, 되어야만 한다고 생각했지만, 어림짐작으로 그치지 않고 코드로 확실하게 보고싶었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-01 오후 5.58.53.png&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/32t2V/dJMcafE4mq3/z2MF1YnEr15ui9P3KDrhN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/32t2V/dJMcafE4mq3/z2MF1YnEr15ui9P3KDrhN1/img.png&quot; data-alt=&quot;PackageWhitelistDao_Impl&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/32t2V/dJMcafE4mq3/z2MF1YnEr15ui9P3KDrhN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F32t2V%2FdJMcafE4mq3%2Fz2MF1YnEr15ui9P3KDrhN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;113&quot; data-filename=&quot;스크린샷 2025-12-01 오후 5.58.53.png&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PackageWhitelistDao_Impl&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 빌드를 한번 끝내면 generated/build에 내 Dao이름 + _Impl 이름으로 Dao의 구현체에대한 Java Class가 생성되어있는 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-01 오후 6.00.35.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J7Wor/dJMcabJrF6P/5twuNVi3ktnrjDPzHg1U81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J7Wor/dJMcabJrF6P/5twuNVi3ktnrjDPzHg1U81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J7Wor/dJMcabJrF6P/5twuNVi3ktnrjDPzHg1U81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ7Wor%2FdJMcabJrF6P%2F5twuNVi3ktnrjDPzHg1U81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2025-12-01 오후 6.00.35.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upsert 함수가 이렇게 트랜잭션으로 감싸져 있는것을 확인했고, Room에 대한 흐릿한 불신을 지울 수 있게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1764604633651&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
  public Object upsert(final List&amp;lt;? extends WhitelistTableEntity&amp;gt; entities,
      final Continuation&amp;lt;? super List&amp;lt;Long&amp;gt;&amp;gt; $completion) {
    return CoroutinesRoom.execute(__db, true, new Callable&amp;lt;List&amp;lt;Long&amp;gt;&amp;gt;() {
      @Override
      @NonNull
      public List&amp;lt;Long&amp;gt; call() throws Exception {
        __db.beginTransaction();
        try {
          final List&amp;lt;Long&amp;gt; _result = __upsertionAdapterOfWhitelistTableEntity.upsertAndReturnIdsList(entities);
          __db.setTransactionSuccessful();
          return _result;
        } finally {
          __db.endTransaction();
        }
      }
    }, $completion);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Android</category>
      <category>Android</category>
      <category>AndroidX</category>
      <category>Room</category>
      <category>SQLite</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/683</guid>
      <comments>https://jizard.tistory.com/683#entry683comment</comments>
      <pubDate>Tue, 2 Dec 2025 00:58:38 +0900</pubDate>
    </item>
    <item>
      <title>[일본 마쓰야마] まめ楽 - 두부 정식</title>
      <link>https://jizard.tistory.com/681</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-25-20-26-57.jpeg&quot; data-origin-width=&quot;7728&quot; data-origin-height=&quot;5152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uykyv/dJMb9VzLPn8/qmoH2G6NW3bHNsLKEuuH71/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uykyv/dJMb9VzLPn8/qmoH2G6NW3bHNsLKEuuH71/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uykyv/dJMb9VzLPn8/qmoH2G6NW3bHNsLKEuuH71/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuykyv%2FdJMb9VzLPn8%2FqmoH2G6NW3bHNsLKEuuH71%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;7728&quot; height=&quot;5152&quot; data-filename=&quot;KakaoTalk_Photo_2025-10-25-20-26-57.jpeg&quot; data-origin-width=&quot;7728&quot; data-origin-height=&quot;5152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마쓰야마에서 유명한 요리는 단연 타이메시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이메시도 정말 맛있는 음식이긴하지만, 마쓰야마에 지내는동안 그런 음식만 먹을수는 없지않은가..?!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도고 공원을 한바퀴 돌고, 독특한 두부정식을 점심으로 간단히 먹었었는데 정말 좋은 가격에 건강하고 든든한 한끼를 먹은느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 어묵이나 가지 등을 독특한 방식으로 조리한 반찬들이 맛있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 빨간 고추로 만든 유즈코쇼를 두부와 함께 먹도록하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이 조합이 너무 신선해서 빨간 유즈코쇼를 하나 사왔다. 식사공간 바로옆이 식재료를 사갈수도있는 숍이다.&lt;/p&gt;</description>
      <category>나/루슐랭 맛집</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/681</guid>
      <comments>https://jizard.tistory.com/681#entry681comment</comments>
      <pubDate>Sat, 25 Oct 2025 20:31:47 +0900</pubDate>
    </item>
    <item>
      <title>OAuth 2.0 관련 용어 공부</title>
      <link>https://jizard.tistory.com/680</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인가(Authorization)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 사용자가 특정 자원에 접근할 권한이 있는지 확인 (권한 확인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증(Authentication)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 누구인지 확인 (로그인)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적인 순서: Authentication &amp;rarr; Authorization&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인증 서버(Authorization server)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 2.0 프레임워크의 구성요소로, 클라이언트가 인증 및 인가에 성공하면 Access Token을 발급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bearer 인증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 2.0 프레임워크에서 사용하는 토큰 인증 방식. HTTP 통신시 Header에 &amp;ldquo;Authorization: Bearer &amp;rdquo;과 같은 형식으로 사용한다. Basic 인증방식만으로는 사용자의 권한을 정교하게 제어할 수 없기 때문에 등장했다. 형식은 *불투명한(Opaque) 문자열일수도 있고, JWT일수도 있다. Bearer 토큰에는 서버가 클라이언트의 권한을 확인할 수 있는 메타데이터가 포함되어 있어야한다. 이때문에 서버는 토큰을 발급만하고 보관할 필요가 없고, 빠르게 토큰 검증을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Basic 인증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 인증방식. HTTP 통신시 Header에 &amp;ldquo;Authorization: Basic base64(:)&amp;rdquo;와 같은 형식으로 사용한다. 사용자 ID와 비밀번호를 base64로 인코딩한 형식이다. base64는 쉽게 디코딩가능하기 때문에 HTTPS, SSL/TLS로 통신해야 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;불투명 토큰(Opaque token)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에게는 의미가 없지만 서버의 데이터베이스에서 권한 데이터를 조회하는 참조 키 역할을 하는 무작위의 고유한 문자열이다. 다음은 불투명 토큰의 예시이다: &lt;code&gt;M-oxIny1RfaFbmjMX54L8Pl-KQEPeQvF6awzjWFA3iq&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT와는 달리 자체적으로 정보를 포함하지 않으므로, 서버는 백엔드 데이터베이스를 조회해서 이 토큰이 가진 권한 데이터를 별도로 가져오는 절차를 거쳐야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PKCE (Profile Key for Code Exchange)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암시적 흐름(Implicit Flow)은 권한 부여서버가 바로 토큰을 반환하는 방법이다. 하지만 OAuth 2.1 부터는 공식적으로 사용이 중단되었는데, URL에 Access Token이 노출되고, Refresh Token을 사용할 수 없어서 클라이언트가 안전하지 않은 방법으로 Access Token을 저장할 수 있다는 보안 문제가 있었기 때문이다. 이제 클라이언트 전용 앱에서는 권한 부여 코드 플로우(Authorization Code Flow) + PKCE를 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PKCE는 인증 코드 가로채기 공격을 방지하여 인증 플로우를 시작한 클라이언트만이 토큰 교환에 성공할 수 있도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;*&lt;/span&gt;PKCE 인증 코드 흐름:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 43~128자의 랜덤 URL 안전 문자열로 코드 검증자(Code Verifier)를 생성한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 코드 검증자를 SHA-256과 같은 암호화 해시 함수로 해싱하고, 해시를 Base64 문자열로 인코딩한다. 이렇게 생성된 문자열을 코드 챌린지(Code Challenge)라고 한다.&lt;/li&gt;
&lt;li&gt;클라이언트가 인증요청시 코드 챌린지를 포함한다. &lt;code&gt;code_challenge_method&lt;/code&gt; 매개변수는 코드 챌린지를 생성하는데 사용된 해싱 알고리즘 (e. g., S256 for SHA-256)을 지정한 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /authorize?response_type=code
...
&amp;amp;code_challenge=YOUR_CODE_CHALLENGE
&amp;amp;code_challenge_method=S256&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 클라이언트는 코드 검증자를 나중에 사용하기 위해 어딘가 저장하고, 인증 코드(Authorization Code)를 수신하면 토큰 요청을 코드 검증자와 함께 인증 서버로 보내야한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;POST /token
...
&amp;amp;code=YOUR_AUTHORIZATION_CODE
...
&amp;amp;code_verifier=YOUR_CODE_VERIFIER&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;인증서버는 코드 검증자와 코드 챌린지를 검증하여 클라이언트가 동일한 플로우에서 진행하고 있다는 것을 확인한다. 검증이 실패하면 인증 서버는 토큰 요청을 거절하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/bearer-auth&quot;&gt;https://docs.tosspayments.com/resources/glossary/bearer-auth&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://auth-wiki.logto.io/ko&quot;&gt;https://auth-wiki.logto.io/ko&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍/General</category>
      <category>Authentication</category>
      <category>authorization</category>
      <category>OAuth</category>
      <category>pkce</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/680</guid>
      <comments>https://jizard.tistory.com/680#entry680comment</comments>
      <pubDate>Wed, 22 Oct 2025 10:53:22 +0900</pubDate>
    </item>
    <item>
      <title>[논현] 성천막국수 - 막국수</title>
      <link>https://jizard.tistory.com/679</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-09-28-12-18-00.jpeg&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;3072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8L0Zj/btsQTBukXQF/vAIJKWTD0JHkbYrkdBjyH1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8L0Zj/btsQTBukXQF/vAIJKWTD0JHkbYrkdBjyH1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8L0Zj/btsQTBukXQF/vAIJKWTD0JHkbYrkdBjyH1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8L0Zj%2FbtsQTBukXQF%2FvAIJKWTD0JHkbYrkdBjyH1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4096&quot; height=&quot;3072&quot; data-filename=&quot;KakaoTalk_Photo_2025-09-28-12-18-00.jpeg&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;3072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천 받아서 방문한 집이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호불호가 꽤 클 것같은 느낌이긴하지만, 나는 마음에 들었다! 혼자가면 제육 4점 + 막국수 세트를 먹을 수 있다. 물론 난 고기를 좋아해서 그냥 제육 반판시켰다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비빔 막국수를 먹었는데, 양념이 짜니 바로 다 풀지말고 조금씩 다대기를 풀면서 먹으라는 조언을 받았기에 그렇게 먹었다. 라멘에 아지헨을 넣는 것처럼 처음에 슴슴하게 먹었다가 점점 자극적인 맛으로 먹으니 고명하나없는 막국수를 먹으면서도 다채롭게 먹을 수 있었다. 그리고 참기름 냄새가 너무좋다...ㅋㅋㅋ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제 한번 메밀이 100%로 들어간 미슐랭 막국수집에 방문한적이 있었는데, 메밀이 너무 많이들어가서 면이 푸석하고 뚝뚝 끊겼다. 건강에 좋을지 어떨진모르지만 맛으로 놓고보면 실망스러웠다. 성천 막국수는 면이 쫀득하고 탄탄한걸보면 메밀이 덜 들어간것같은데, 난 이쪽이 더 맛있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 돈만 적당히내면 맛있는 집이 많다. 서울의 아무 백화점 8층에가서 막국수를 시켜도 난 맛있게 먹을거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 기억에 남지 않는다. 다시 그 집에 갈 이유가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상한 다대기 양념과 독특한 무짠지는 성천막국수만의 특색이고, 이런 것들이 대체할 수 없는 이집만의 매력이 된다.&lt;/p&gt;</description>
      <category>나/루슐랭 맛집</category>
      <category>막국수</category>
      <author>Lou Park</author>
      <guid isPermaLink="true">https://jizard.tistory.com/679</guid>
      <comments>https://jizard.tistory.com/679#entry679comment</comments>
      <pubDate>Sun, 28 Sep 2025 12:31:32 +0900</pubDate>
    </item>
  </channel>
</rss>